Skip to main content
Glama

FastAPI-MCP

by tadata-org
test_openapi_conversion.py17.1 kB
from fastapi import FastAPI from fastapi.openapi.utils import get_openapi import mcp.types as types from fastapi_mcp.openapi.convert import convert_openapi_to_mcp_tools from fastapi_mcp.openapi.utils import ( clean_schema_for_display, generate_example_from_schema, get_single_param_type_from_schema, ) def test_simple_app_conversion(simple_fastapi_app: FastAPI): openapi_schema = get_openapi( title=simple_fastapi_app.title, version=simple_fastapi_app.version, openapi_version=simple_fastapi_app.openapi_version, description=simple_fastapi_app.description, routes=simple_fastapi_app.routes, ) tools, operation_map = convert_openapi_to_mcp_tools(openapi_schema) assert len(tools) == 6 assert len(operation_map) == 6 expected_operations = ["list_items", "get_item", "create_item", "update_item", "delete_item", "raise_error"] for op in expected_operations: assert op in operation_map for tool in tools: assert isinstance(tool, types.Tool) assert tool.name in expected_operations assert tool.description is not None assert tool.inputSchema is not None def test_complex_app_conversion(complex_fastapi_app: FastAPI): openapi_schema = get_openapi( title=complex_fastapi_app.title, version=complex_fastapi_app.version, openapi_version=complex_fastapi_app.openapi_version, description=complex_fastapi_app.description, routes=complex_fastapi_app.routes, ) tools, operation_map = convert_openapi_to_mcp_tools(openapi_schema) expected_operations = ["list_products", "get_product", "create_order", "get_customer"] assert len(tools) == len(expected_operations) assert len(operation_map) == len(expected_operations) for op in expected_operations: assert op in operation_map for tool in tools: assert isinstance(tool, types.Tool) assert tool.name in expected_operations assert tool.description is not None assert tool.inputSchema is not None def test_describe_full_response_schema(simple_fastapi_app: FastAPI): openapi_schema = get_openapi( title=simple_fastapi_app.title, version=simple_fastapi_app.version, openapi_version=simple_fastapi_app.openapi_version, description=simple_fastapi_app.description, routes=simple_fastapi_app.routes, ) tools_full, _ = convert_openapi_to_mcp_tools(openapi_schema, describe_full_response_schema=True) tools_simple, _ = convert_openapi_to_mcp_tools(openapi_schema, describe_full_response_schema=False) for i, tool in enumerate(tools_full): assert tool.description is not None assert tools_simple[i].description is not None tool_desc = tool.description or "" simple_desc = tools_simple[i].description or "" assert len(tool_desc) >= len(simple_desc) if tool.name == "delete_item": continue assert "**Output Schema:**" in tool_desc if "**Output Schema:**" in simple_desc: assert len(tool_desc) > len(simple_desc) def test_describe_all_responses(complex_fastapi_app: FastAPI): openapi_schema = get_openapi( title=complex_fastapi_app.title, version=complex_fastapi_app.version, openapi_version=complex_fastapi_app.openapi_version, description=complex_fastapi_app.description, routes=complex_fastapi_app.routes, ) tools_all, _ = convert_openapi_to_mcp_tools(openapi_schema, describe_all_responses=True) tools_success, _ = convert_openapi_to_mcp_tools(openapi_schema, describe_all_responses=False) create_order_all = next(t for t in tools_all if t.name == "create_order") create_order_success = next(t for t in tools_success if t.name == "create_order") assert create_order_all.description is not None assert create_order_success.description is not None all_desc = create_order_all.description or "" success_desc = create_order_success.description or "" assert "400" in all_desc assert "404" in all_desc assert "422" in all_desc assert all_desc.count("400") >= success_desc.count("400") def test_schema_utils(): schema = { "type": "object", "properties": { "id": {"type": "integer"}, "name": {"type": "string"}, "tags": {"type": "array", "items": {"type": "string"}}, }, "required": ["id", "name"], "additionalProperties": False, "x-internal": "Some internal data", } cleaned = clean_schema_for_display(schema) assert "required" in cleaned assert "properties" in cleaned assert "type" in cleaned example = generate_example_from_schema(schema) assert "id" in example assert "name" in example assert "tags" in example assert isinstance(example["id"], int) assert isinstance(example["name"], str) assert isinstance(example["tags"], list) assert get_single_param_type_from_schema({"type": "string"}) == "string" assert get_single_param_type_from_schema({"type": "array", "items": {"type": "string"}}) == "array" array_schema = {"type": "array", "items": {"type": "string", "enum": ["red", "green", "blue"]}} array_example = generate_example_from_schema(array_schema) assert isinstance(array_example, list) assert len(array_example) > 0 assert isinstance(array_example[0], str) def test_parameter_handling(complex_fastapi_app: FastAPI): openapi_schema = get_openapi( title=complex_fastapi_app.title, version=complex_fastapi_app.version, openapi_version=complex_fastapi_app.openapi_version, description=complex_fastapi_app.description, routes=complex_fastapi_app.routes, ) tools, operation_map = convert_openapi_to_mcp_tools(openapi_schema) list_products_tool = next(tool for tool in tools if tool.name == "list_products") properties = list_products_tool.inputSchema["properties"] assert "product_id" not in properties # This is from get_product, not list_products assert "category" in properties assert properties["category"].get("type") == "string" # Enum converted to string assert "description" in properties["category"] assert "Filter by product category" in properties["category"]["description"] assert "min_price" in properties assert properties["min_price"].get("type") == "number" assert "description" in properties["min_price"] assert "Minimum price filter" in properties["min_price"]["description"] if "minimum" in properties["min_price"]: assert properties["min_price"]["minimum"] > 0 # gt=0 in Query param assert "in_stock_only" in properties assert properties["in_stock_only"].get("type") == "boolean" assert properties["in_stock_only"].get("default") is False # Default value preserved assert "page" in properties assert properties["page"].get("type") == "integer" assert properties["page"].get("default") == 1 # Default value preserved if "minimum" in properties["page"]: assert properties["page"]["minimum"] >= 1 # ge=1 in Query param assert "size" in properties assert properties["size"].get("type") == "integer" if "minimum" in properties["size"] and "maximum" in properties["size"]: assert properties["size"]["minimum"] >= 1 # ge=1 in Query param assert properties["size"]["maximum"] <= 100 # le=100 in Query param assert "tag" in properties assert properties["tag"].get("type") == "array" required = list_products_tool.inputSchema.get("required", []) assert "page" not in required # Has default value assert "category" not in required # Optional parameter assert "list_products" in operation_map assert operation_map["list_products"]["path"] == "/products" assert operation_map["list_products"]["method"] == "get" get_product_tool = next(tool for tool in tools if tool.name == "get_product") get_product_props = get_product_tool.inputSchema["properties"] assert "product_id" in get_product_props assert get_product_props["product_id"].get("type") == "string" # UUID converted to string assert "description" in get_product_props["product_id"] get_customer_tool = next(tool for tool in tools if tool.name == "get_customer") get_customer_props = get_customer_tool.inputSchema["properties"] assert "fields" in get_customer_props assert get_customer_props["fields"].get("type") == "array" if "items" in get_customer_props["fields"]: assert get_customer_props["fields"]["items"].get("type") == "string" def test_request_body_handling(complex_fastapi_app: FastAPI): openapi_schema = get_openapi( title=complex_fastapi_app.title, version=complex_fastapi_app.version, openapi_version=complex_fastapi_app.openapi_version, description=complex_fastapi_app.description, routes=complex_fastapi_app.routes, ) create_order_route = openapi_schema["paths"]["/orders"]["post"] original_request_body = create_order_route["requestBody"]["content"]["application/json"]["schema"] original_properties = original_request_body.get("properties", {}) tools, operation_map = convert_openapi_to_mcp_tools(openapi_schema) create_order_tool = next(tool for tool in tools if tool.name == "create_order") properties = create_order_tool.inputSchema["properties"] assert "customer_id" in properties assert "items" in properties assert "shipping_address_id" in properties assert "payment_method" in properties assert "notes" in properties for param_name in ["customer_id", "items", "shipping_address_id", "payment_method", "notes"]: if "description" in original_properties.get(param_name, {}): assert "description" in properties[param_name] assert properties[param_name]["description"] == original_properties[param_name]["description"] for param_name in ["customer_id", "items", "shipping_address_id", "payment_method", "notes"]: assert properties[param_name]["title"] == param_name for param_name in ["customer_id", "items", "shipping_address_id", "payment_method", "notes"]: if "default" in original_properties.get(param_name, {}): assert "default" in properties[param_name] assert properties[param_name]["default"] == original_properties[param_name]["default"] required = create_order_tool.inputSchema.get("required", []) assert "customer_id" in required assert "items" in required assert "shipping_address_id" in required assert "payment_method" in required assert "notes" not in required # Optional in OrderRequest assert properties["items"].get("type") == "array" if "items" in properties["items"]: item_props = properties["items"]["items"] assert item_props.get("type") == "object" if "properties" in item_props: assert "product_id" in item_props["properties"] assert "quantity" in item_props["properties"] assert "unit_price" in item_props["properties"] assert "total" in item_props["properties"] for nested_param in ["product_id", "quantity", "unit_price", "total"]: assert "title" in item_props["properties"][nested_param] # Check if the original nested schema had descriptions original_item_schema = original_properties.get("items", {}).get("items", {}).get("properties", {}) if "description" in original_item_schema.get(nested_param, {}): assert "description" in item_props["properties"][nested_param] assert ( item_props["properties"][nested_param]["description"] == original_item_schema[nested_param]["description"] ) assert "create_order" in operation_map assert operation_map["create_order"]["path"] == "/orders" assert operation_map["create_order"]["method"] == "post" def test_missing_type_handling(complex_fastapi_app: FastAPI): openapi_schema = get_openapi( title=complex_fastapi_app.title, version=complex_fastapi_app.version, openapi_version=complex_fastapi_app.openapi_version, description=complex_fastapi_app.description, routes=complex_fastapi_app.routes, ) # Remove the type field from the product_id schema params = openapi_schema["paths"]["/products/{product_id}"]["get"]["parameters"] for param in params: if param.get("name") == "product_id" and "schema" in param: param["schema"].pop("type", None) break tools, operation_map = convert_openapi_to_mcp_tools(openapi_schema) get_product_tool = next(tool for tool in tools if tool.name == "get_product") get_product_props = get_product_tool.inputSchema["properties"] assert "product_id" in get_product_props assert get_product_props["product_id"].get("type") == "string" # Default type applied def test_body_params_descriptions_and_defaults(complex_fastapi_app: FastAPI): """ Test that descriptions and defaults from request body parameters are properly transferred to the MCP tool schema properties. """ openapi_schema = get_openapi( title=complex_fastapi_app.title, version=complex_fastapi_app.version, openapi_version=complex_fastapi_app.openapi_version, description=complex_fastapi_app.description, routes=complex_fastapi_app.routes, ) order_request_schema = openapi_schema["components"]["schemas"]["OrderRequest"] order_request_schema["properties"]["customer_id"]["description"] = "Test customer ID description" order_request_schema["properties"]["payment_method"]["description"] = "Test payment method description" order_request_schema["properties"]["notes"]["default"] = "Default order notes" item_schema = openapi_schema["components"]["schemas"]["OrderItem"] item_schema["properties"]["product_id"]["description"] = "Test product ID description" item_schema["properties"]["quantity"]["default"] = 1 tools, _ = convert_openapi_to_mcp_tools(openapi_schema) create_order_tool = next(tool for tool in tools if tool.name == "create_order") properties = create_order_tool.inputSchema["properties"] assert "description" in properties["customer_id"] assert properties["customer_id"]["description"] == "Test customer ID description" assert "description" in properties["payment_method"] assert properties["payment_method"]["description"] == "Test payment method description" assert "default" in properties["notes"] assert properties["notes"]["default"] == "Default order notes" if "items" in properties: assert properties["items"]["type"] == "array" assert "items" in properties["items"] item_props = properties["items"]["items"]["properties"] assert "description" in item_props["product_id"] assert item_props["product_id"]["description"] == "Test product ID description" assert "default" in item_props["quantity"] assert item_props["quantity"]["default"] == 1 def test_body_params_edge_cases(complex_fastapi_app: FastAPI): """ Test handling of edge cases for body parameters, such as: - Empty or missing descriptions - Missing type information - Empty properties object - Schema without properties """ openapi_schema = get_openapi( title=complex_fastapi_app.title, version=complex_fastapi_app.version, openapi_version=complex_fastapi_app.openapi_version, description=complex_fastapi_app.description, routes=complex_fastapi_app.routes, ) order_request_schema = openapi_schema["components"]["schemas"]["OrderRequest"] if "description" in order_request_schema["properties"]["customer_id"]: del order_request_schema["properties"]["customer_id"]["description"] if "type" in order_request_schema["properties"]["notes"]: del order_request_schema["properties"]["notes"]["type"] item_schema = openapi_schema["components"]["schemas"]["OrderItem"] if "properties" in item_schema["properties"]["total"]: del item_schema["properties"]["total"]["properties"] tools, _ = convert_openapi_to_mcp_tools(openapi_schema) create_order_tool = next(tool for tool in tools if tool.name == "create_order") properties = create_order_tool.inputSchema["properties"] assert "customer_id" in properties assert "title" in properties["customer_id"] assert properties["customer_id"]["title"] == "customer_id" assert "notes" in properties assert "type" in properties["notes"] assert properties["notes"]["type"] in ["string", "object"] # Default should be either string or object if "items" in properties: item_props = properties["items"]["items"]["properties"] assert "total" in item_props

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tadata-org/fastapi_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server