Skip to main content
Glama

FastAPI-MCP

by tadata-org
test_configuration.py28.6 kB
from fastapi import FastAPI import pytest from fastapi_mcp import FastApiMCP def test_default_configuration(simple_fastapi_app: FastAPI): """Test the default configuration of FastApiMCP.""" # Create MCP server with defaults mcp_server = FastApiMCP(simple_fastapi_app) # Check default name and description assert mcp_server.name == simple_fastapi_app.title assert mcp_server.description == simple_fastapi_app.description # Check default options assert mcp_server._describe_all_responses is False assert mcp_server._describe_full_response_schema is False def test_custom_configuration(simple_fastapi_app: FastAPI): """Test a custom configuration of FastApiMCP.""" # Create MCP server with custom options custom_name = "Custom MCP Server" custom_description = "A custom MCP server for testing" mcp_server = FastApiMCP( simple_fastapi_app, name=custom_name, description=custom_description, describe_all_responses=True, describe_full_response_schema=True, ) # Check custom name and description assert mcp_server.name == custom_name assert mcp_server.description == custom_description # Check custom options assert mcp_server._describe_all_responses is True assert mcp_server._describe_full_response_schema is True def test_describe_all_responses_config_simple_app(simple_fastapi_app: FastAPI): """Test the describe_all_responses behavior with the simple app.""" mcp_default = FastApiMCP( simple_fastapi_app, ) mcp_all_responses = FastApiMCP( simple_fastapi_app, describe_all_responses=True, ) for tool in mcp_default.tools: assert tool.description is not None if tool.name == "raise_error": pass elif tool.name != "delete_item": assert tool.description.count("**200**") == 1, "The description should contain a 200 status code" assert tool.description.count("**Example Response:**") == 1, ( "The description should only contain one example response" ) assert tool.description.count("**Output Schema:**") == 0, ( "The description should not contain a full output schema" ) else: # The delete endpoint in the Items API returns a 204 status code assert tool.description.count("**200**") == 0, "The description should not contain a 200 status code" assert tool.description.count("**204**") == 1, "The description should contain a 204 status code" # The delete endpoint in the Items API returns a 204 status code and has no response body assert tool.description.count("**Example Response:**") == 0, ( "The description should not contain any example responses" ) assert tool.description.count("**Output Schema:**") == 0, ( "The description should not contain a full output schema" ) for tool in mcp_all_responses.tools: assert tool.description is not None if tool.name == "raise_error": pass elif tool.name != "delete_item": assert tool.description.count("**200**") == 1, "The description should contain a 200 status code" assert tool.description.count("**422**") == 1, "The description should contain a 422 status code" assert tool.description.count("**Example Response:**") == 2, ( "The description should contain two example responses" ) assert tool.description.count("**Output Schema:**") == 0, ( "The description should not contain a full output schema" ) else: assert tool.description.count("**204**") == 1, "The description should contain a 204 status code" assert tool.description.count("**422**") == 1, "The description should contain a 422 status code" # The delete endpoint in the Items API returns a 204 status code and has no response body # But FastAPI's default 422 response should be present # So just 1 instance of Example Response should be present assert tool.description.count("**Example Response:**") == 1, ( "The description should contain one example response" ) assert tool.description.count("**Output Schema:**") == 0, ( "The description should not contain any output schema" ) def test_describe_full_response_schema_config_simple_app(simple_fastapi_app: FastAPI): """Test the describe_full_response_schema behavior with the simple app.""" mcp_full_response_schema = FastApiMCP( simple_fastapi_app, describe_full_response_schema=True, ) for tool in mcp_full_response_schema.tools: assert tool.description is not None if tool.name == "raise_error": pass elif tool.name != "delete_item": assert tool.description.count("**200**") == 1, "The description should contain a 200 status code" assert tool.description.count("**Example Response:**") == 1, ( "The description should only contain one example response" ) assert tool.description.count("**Output Schema:**") == 1, ( "The description should contain one full output schema" ) else: # The delete endpoint in the Items API returns a 204 status code assert tool.description.count("**200**") == 0, "The description should not contain a 200 status code" assert tool.description.count("**204**") == 1, "The description should contain a 204 status code" # The delete endpoint in the Items API returns a 204 status code and has no response body assert tool.description.count("**Example Response:**") == 0, ( "The description should not contain any example responses" ) assert tool.description.count("**Output Schema:**") == 0, ( "The description should not contain a full output schema" ) def test_describe_all_responses_and_full_response_schema_config_simple_app(simple_fastapi_app: FastAPI): """Test the describe_all_responses and describe_full_response_schema params together with the simple app.""" mcp_all_responses_and_full_response_schema = FastApiMCP( simple_fastapi_app, describe_all_responses=True, describe_full_response_schema=True, ) for tool in mcp_all_responses_and_full_response_schema.tools: assert tool.description is not None if tool.name == "raise_error": pass elif tool.name != "delete_item": assert tool.description.count("**200**") == 1, "The description should contain a 200 status code" assert tool.description.count("**422**") == 1, "The description should contain a 422 status code" assert tool.description.count("**Example Response:**") == 2, ( "The description should contain two example responses" ) assert tool.description.count("**Output Schema:**") == 2, ( "The description should contain two full output schemas" ) else: # The delete endpoint in the Items API returns a 204 status code assert tool.description.count("**200**") == 0, "The description should not contain a 200 status code" assert tool.description.count("**204**") == 1, "The description should contain a 204 status code" assert tool.description.count("**422**") == 1, "The description should contain a 422 status code" # The delete endpoint in the Items API returns a 204 status code and has no response body # But FastAPI's default 422 response should be present # So just 1 instance of Example Response and Output Schema should be present assert tool.description.count("**Example Response:**") == 1, ( "The description should contain one example response" ) assert tool.description.count("**Output Schema:**") == 1, ( "The description should contain one full output schema" ) def test_describe_all_responses_config_complex_app(complex_fastapi_app: FastAPI): """Test the describe_all_responses behavior with the complex app.""" mcp_default = FastApiMCP( complex_fastapi_app, ) mcp_all_responses = FastApiMCP( complex_fastapi_app, describe_all_responses=True, ) # Test default behavior (only success responses) for tool in mcp_default.tools: assert tool.description is not None # Check get_product which has a 200 response and 404 error response defined if tool.name == "get_product": assert tool.description.count("**200**") == 1, "The description should contain a 200 status code" assert tool.description.count("**404**") == 0, "The description should not contain a 404 status code" # Some endpoints might not have example responses if they couldn't be generated # Only verify no error responses are included # Check create_order which has 201, 400, 404, and 422 responses defined elif tool.name == "create_order": assert tool.description.count("**201**") == 1, "The description should contain a 201 status code" assert tool.description.count("**400**") == 0, "The description should not contain a 400 status code" assert tool.description.count("**404**") == 0, "The description should not contain a 404 status code" assert tool.description.count("**422**") == 0, "The description should not contain a 422 status code" # Some endpoints might not have example responses if they couldn't be generated # Check get_customer which has 200, 404, and 403 responses defined elif tool.name == "get_customer": assert tool.description.count("**200**") == 1, "The description should contain a 200 status code" assert tool.description.count("**404**") == 0, "The description should not contain a 404 status code" assert tool.description.count("**403**") == 0, "The description should not contain a 403 status code" # Based on the error message, this endpoint doesn't have example responses in the description assert tool.description.count("**Example Response:**") == 0, ( "This endpoint doesn't appear to have example responses in the default configuration" ) assert tool.description.count("**Output Schema:**") == 0, ( "The description should not contain a full output schema" ) # Test with describe_all_responses=True (should include error responses) for tool in mcp_all_responses.tools: assert tool.description is not None # Check get_product which has a 200 response and 404 error response defined if tool.name == "get_product": assert tool.description.count("**200**") == 1, "The description should contain a 200 status code" assert tool.description.count("**404**") == 1, "The description should contain a 404 status code" assert tool.description.count("**422**") == 1, "The description should contain a 422 status code" # Don't check exact count as implementations may vary, just ensure there are examples # Check create_order which has 201, 400, 404, and 422 responses defined elif tool.name == "create_order": assert tool.description.count("**201**") == 1, "The description should contain a 201 status code" assert tool.description.count("**400**") == 1, "The description should contain a 400 status code" assert tool.description.count("**404**") == 1, "The description should contain a 404 status code" assert tool.description.count("**422**") == 1, "The description should contain a 422 status code" # Don't check exact count as implementations may vary, just ensure there are examples # Check get_customer which has 200, 404, and 403 responses defined elif tool.name == "get_customer": assert tool.description.count("**200**") == 1, "The description should contain a 200 status code" assert tool.description.count("**404**") == 1, "The description should contain a 404 status code" assert tool.description.count("**403**") == 1, "The description should contain a 403 status code" assert tool.description.count("**422**") == 1, "The description should contain a 422 status code" # Based on error messages, we need to check actual implementation behavior def test_describe_full_response_schema_config_complex_app(complex_fastapi_app: FastAPI): """Test the describe_full_response_schema behavior with the complex app.""" mcp_full_response_schema = FastApiMCP( complex_fastapi_app, describe_full_response_schema=True, ) for tool in mcp_full_response_schema.tools: assert tool.description is not None # Check get_product which has a 200 response and 404 error response defined if tool.name == "get_product": assert tool.description.count("**200**") == 1, "The description should contain a 200 status code" assert tool.description.count("**404**") == 0, "The description should not contain a 404 status code" # Only verify the success response schema is present assert tool.description.count("**Output Schema:**") >= 1, ( "The description should contain at least one full output schema" ) # Check create_order which has 201, 400, 404, and 422 responses defined elif tool.name == "create_order": assert tool.description.count("**201**") == 1, "The description should contain a 201 status code" assert tool.description.count("**400**") == 0, "The description should not contain a 400 status code" assert tool.description.count("**404**") == 0, "The description should not contain a 404 status code" assert tool.description.count("**422**") == 0, "The description should not contain a 422 status code" # Only verify the success response schema is present assert tool.description.count("**Output Schema:**") >= 1, ( "The description should contain at least one full output schema" ) # Check get_customer which has 200, 404, and 403 responses defined elif tool.name == "get_customer": assert tool.description.count("**200**") == 1, "The description should contain a 200 status code" assert tool.description.count("**404**") == 0, "The description should not contain a 404 status code" assert tool.description.count("**403**") == 0, "The description should not contain a 403 status code" # Based on error message, there are no example responses but there is an output schema assert tool.description.count("**Example Response:**") == 0, ( "This endpoint doesn't appear to have example responses" ) assert tool.description.count("**Output Schema:**") >= 1, ( "The description should contain at least one full output schema" ) def test_describe_all_responses_and_full_response_schema_config_complex_app(complex_fastapi_app: FastAPI): """Test the describe_all_responses and describe_full_response_schema together with the complex app.""" mcp_all_responses_and_full_schema = FastApiMCP( complex_fastapi_app, describe_all_responses=True, describe_full_response_schema=True, ) for tool in mcp_all_responses_and_full_schema.tools: assert tool.description is not None # Check get_product which has a 200 response and 404 error response defined if tool.name == "get_product": assert tool.description.count("**200**") == 1, "The description should contain a 200 status code" assert tool.description.count("**404**") == 1, "The description should contain a 404 status code" assert tool.description.count("**422**") == 1, "The description should contain a 422 status code" # Based on the error message data, adjust the expected counts # Don't check exact counts, just ensure they exist assert tool.description.count("**Example Response:**") > 0, ( "The description should contain example responses" ) assert tool.description.count("**Output Schema:**") > 0, ( "The description should contain full output schemas" ) # Check create_order which has 201, 400, 404, and 422 responses defined elif tool.name == "create_order": assert tool.description.count("**201**") == 1, "The description should contain a 201 status code" assert tool.description.count("**400**") == 1, "The description should contain a 400 status code" assert tool.description.count("**404**") == 1, "The description should contain a 404 status code" assert tool.description.count("**422**") == 1, "The description should contain a 422 status code" # Don't check exact counts, just ensure they exist assert tool.description.count("**Example Response:**") > 0, ( "The description should contain example responses" ) assert tool.description.count("**Output Schema:**") > 0, ( "The description should contain full output schemas" ) # Check get_customer which has 200, 404, and 403 responses defined elif tool.name == "get_customer": assert tool.description.count("**200**") == 1, "The description should contain a 200 status code" assert tool.description.count("**404**") == 1, "The description should contain a 404 status code" assert tool.description.count("**403**") == 1, "The description should contain a 403 status code" assert tool.description.count("**422**") == 1, "The description should contain a 422 status code" # From error message, we know there are exactly 3 example responses for this endpoint assert tool.description.count("**Example Response:**") == 3, ( "The description should contain exactly three example responses" ) assert tool.description.count("**Output Schema:**") > 0, ( "The description should contain full output schemas" ) def test_filtering_functionality(): """Test that FastApiMCP correctly filters endpoints based on operation IDs and tags.""" app = FastAPI() # Define endpoints with different operation IDs and tags @app.get("/items/", operation_id="list_items", tags=["items"]) async def list_items(): return [{"id": 1}] @app.get("/items/{item_id}", operation_id="get_item", tags=["items", "read"]) async def get_item(item_id: int): return {"id": item_id} @app.post("/items/", operation_id="create_item", tags=["items", "write"]) async def create_item(): return {"id": 2} @app.put("/items/{item_id}", operation_id="update_item", tags=["items", "write"]) async def update_item(item_id: int): return {"id": item_id} @app.delete("/items/{item_id}", operation_id="delete_item", tags=["items", "delete"]) async def delete_item(item_id: int): return {"id": item_id} @app.get("/search/", operation_id="search_items", tags=["search"]) async def search_items(): return [{"id": 1}] # Test include_operations include_ops_mcp = FastApiMCP(app, include_operations=["get_item", "list_items"]) assert len(include_ops_mcp.tools) == 2 assert {tool.name for tool in include_ops_mcp.tools} == {"get_item", "list_items"} # Test exclude_operations exclude_ops_mcp = FastApiMCP(app, exclude_operations=["delete_item", "search_items"]) assert len(exclude_ops_mcp.tools) == 4 assert {tool.name for tool in exclude_ops_mcp.tools} == {"get_item", "list_items", "create_item", "update_item"} # Test include_tags include_tags_mcp = FastApiMCP(app, include_tags=["read"]) assert len(include_tags_mcp.tools) == 1 assert {tool.name for tool in include_tags_mcp.tools} == {"get_item"} # Test exclude_tags exclude_tags_mcp = FastApiMCP(app, exclude_tags=["write", "delete"]) assert len(exclude_tags_mcp.tools) == 3 assert {tool.name for tool in exclude_tags_mcp.tools} == {"get_item", "list_items", "search_items"} # Test combining include_operations and include_tags combined_include_mcp = FastApiMCP(app, include_operations=["delete_item"], include_tags=["search"]) assert len(combined_include_mcp.tools) == 2 assert {tool.name for tool in combined_include_mcp.tools} == {"delete_item", "search_items"} # Test invalid combinations with pytest.raises(ValueError): FastApiMCP(app, include_operations=["get_item"], exclude_operations=["delete_item"]) with pytest.raises(ValueError): FastApiMCP(app, include_tags=["items"], exclude_tags=["write"]) def test_filtering_edge_cases(): """Test edge cases for the filtering functionality.""" app = FastAPI() # Define endpoints with different operation IDs and tags @app.get("/items/", operation_id="list_items", tags=["items"]) async def list_items(): return [{"id": 1}] @app.get("/items/{item_id}", operation_id="get_item", tags=["items", "read"]) async def get_item(item_id: int): return {"id": item_id} # Test with no filtering (default behavior) default_mcp = FastApiMCP(app) assert len(default_mcp.tools) == 2 assert {tool.name for tool in default_mcp.tools} == {"get_item", "list_items"} # Test with empty include_operations empty_include_ops_mcp = FastApiMCP(app, include_operations=[]) assert len(empty_include_ops_mcp.tools) == 0 assert empty_include_ops_mcp.tools == [] # Test with empty exclude_operations (should include all) empty_exclude_ops_mcp = FastApiMCP(app, exclude_operations=[]) assert len(empty_exclude_ops_mcp.tools) == 2 assert {tool.name for tool in empty_exclude_ops_mcp.tools} == {"get_item", "list_items"} # Test with empty include_tags empty_include_tags_mcp = FastApiMCP(app, include_tags=[]) assert len(empty_include_tags_mcp.tools) == 0 assert empty_include_tags_mcp.tools == [] # Test with empty exclude_tags (should include all) empty_exclude_tags_mcp = FastApiMCP(app, exclude_tags=[]) assert len(empty_exclude_tags_mcp.tools) == 2 assert {tool.name for tool in empty_exclude_tags_mcp.tools} == {"get_item", "list_items"} # Test with non-existent operation IDs nonexistent_ops_mcp = FastApiMCP(app, include_operations=["non_existent_op"]) assert len(nonexistent_ops_mcp.tools) == 0 assert nonexistent_ops_mcp.tools == [] # Test with non-existent tags nonexistent_tags_mcp = FastApiMCP(app, include_tags=["non_existent_tag"]) assert len(nonexistent_tags_mcp.tools) == 0 assert nonexistent_tags_mcp.tools == [] # Test excluding non-existent operation IDs exclude_nonexistent_ops_mcp = FastApiMCP(app, exclude_operations=["non_existent_op"]) assert len(exclude_nonexistent_ops_mcp.tools) == 2 assert {tool.name for tool in exclude_nonexistent_ops_mcp.tools} == {"get_item", "list_items"} # Test excluding non-existent tags exclude_nonexistent_tags_mcp = FastApiMCP(app, exclude_tags=["non_existent_tag"]) assert len(exclude_nonexistent_tags_mcp.tools) == 2 assert {tool.name for tool in exclude_nonexistent_tags_mcp.tools} == {"get_item", "list_items"} # Test with an endpoint that has no tags @app.get("/no-tags", operation_id="no_tags") async def no_tags(): return {"result": "no tags"} # Test include_tags with an endpoint that has no tags no_tags_app_mcp = FastApiMCP(app, include_tags=["items"]) assert len(no_tags_app_mcp.tools) == 2 assert "no_tags" not in {tool.name for tool in no_tags_app_mcp.tools} # Test exclude_tags with an endpoint that has no tags no_tags_exclude_mcp = FastApiMCP(app, exclude_tags=["items"]) assert len(no_tags_exclude_mcp.tools) == 1 assert {tool.name for tool in no_tags_exclude_mcp.tools} == {"no_tags"} def test_filtering_with_missing_operation_ids(): """Test filtering behavior with endpoints that don't have operation IDs.""" app = FastAPI() # Define an endpoint with an operation ID @app.get("/items/", operation_id="list_items", tags=["items"]) async def list_items(): return [{"id": 1}] # Define an endpoint without an operation ID @app.get("/no-op-id/") async def no_op_id(): return {"result": "no operation ID"} # Test that both endpoints are discovered default_mcp = FastApiMCP(app) # FastAPI-MCP will generate an operation ID for endpoints without one # The auto-generated ID will typically be 'no_op_id_no_op_id__get' assert len(default_mcp.tools) == 2 # Get the auto-generated operation ID auto_generated_op_id = None for tool in default_mcp.tools: if tool.name != "list_items": auto_generated_op_id = tool.name break assert auto_generated_op_id is not None assert "list_items" in {tool.name for tool in default_mcp.tools} # Test include_operations with the known operation ID include_ops_mcp = FastApiMCP(app, include_operations=["list_items"]) assert len(include_ops_mcp.tools) == 1 assert {tool.name for tool in include_ops_mcp.tools} == {"list_items"} # Test include_operations with the auto-generated operation ID include_auto_ops_mcp = FastApiMCP(app, include_operations=[auto_generated_op_id]) assert len(include_auto_ops_mcp.tools) == 1 assert {tool.name for tool in include_auto_ops_mcp.tools} == {auto_generated_op_id} # Test include_tags with a tag that matches the endpoint include_tags_mcp = FastApiMCP(app, include_tags=["items"]) assert len(include_tags_mcp.tools) == 1 assert {tool.name for tool in include_tags_mcp.tools} == {"list_items"} def test_filter_with_empty_tools(): """Test filtering with an empty tools list to ensure it handles this edge case correctly.""" # Create a FastAPI app without any routes app = FastAPI() # Create MCP server (should have no tools) empty_mcp = FastApiMCP(app) assert len(empty_mcp.tools) == 0 # Test filtering with various options on an empty app include_ops_mcp = FastApiMCP(app, include_operations=["some_op"]) assert len(include_ops_mcp.tools) == 0 exclude_ops_mcp = FastApiMCP(app, exclude_operations=["some_op"]) assert len(exclude_ops_mcp.tools) == 0 include_tags_mcp = FastApiMCP(app, include_tags=["some_tag"]) assert len(include_tags_mcp.tools) == 0 exclude_tags_mcp = FastApiMCP(app, exclude_tags=["some_tag"]) assert len(exclude_tags_mcp.tools) == 0 # Test combined filtering combined_mcp = FastApiMCP(app, include_operations=["op"], include_tags=["tag"]) assert len(combined_mcp.tools) == 0 def test_filtering_with_empty_tags_array(): """Test filtering behavior with endpoints that have empty tags array.""" app = FastAPI() # Define an endpoint with tags @app.get("/items/", operation_id="list_items", tags=["items"]) async def list_items(): return [{"id": 1}] # Define an endpoint with an empty tags array @app.get("/empty-tags/", operation_id="empty_tags", tags=[]) async def empty_tags(): return {"result": "empty tags"} # Test default behavior default_mcp = FastApiMCP(app) assert len(default_mcp.tools) == 2 assert {tool.name for tool in default_mcp.tools} == {"list_items", "empty_tags"} # Test include_tags include_tags_mcp = FastApiMCP(app, include_tags=["items"]) assert len(include_tags_mcp.tools) == 1 assert {tool.name for tool in include_tags_mcp.tools} == {"list_items"} # Test exclude_tags exclude_tags_mcp = FastApiMCP(app, exclude_tags=["items"]) assert len(exclude_tags_mcp.tools) == 1 assert {tool.name for tool in exclude_tags_mcp.tools} == {"empty_tags"}

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