Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions fastapi_mcp/http_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def create_mcp_tools_from_openapi(
base_url: Optional[str] = None,
describe_all_responses: bool = False,
describe_full_response_schema: bool = False,
timeout: Optional[float] = None,
) -> None:
"""
Create MCP tools from a FastAPI app's OpenAPI schema.
Expand All @@ -41,6 +42,7 @@ def create_mcp_tools_from_openapi(
base_url: Base URL for API requests (defaults to http://localhost:$PORT)
describe_all_responses: Whether to include all possible response schemas in tool descriptions
describe_full_response_schema: Whether to include full response schema in tool descriptions
timeout: Optional timeout for the HTTP request
"""
# Get OpenAPI schema from FastAPI app
openapi_schema = get_openapi(
Expand Down Expand Up @@ -98,9 +100,13 @@ def create_mcp_tools_from_openapi(
openapi_schema=resolved_openapi_schema,
describe_all_responses=describe_all_responses,
describe_full_response_schema=describe_full_response_schema,
timeout=timeout,
)

def _create_http_tool_function(function_template: Callable, properties: Dict[str, Any], additional_variables: Dict[str, Any]) -> Callable:

def _create_http_tool_function(
function_template: Callable, properties: Dict[str, Any], additional_variables: Dict[str, Any]
) -> Callable:
# Build parameter string with type hints
parsed_parameters = {}
parsed_parameters_with_defaults = {}
Expand All @@ -114,24 +120,21 @@ def _create_http_tool_function(function_template: Callable, properties: Dict[str
parsed_parameters_keys = list(parsed_parameters.keys()) + list(parsed_parameters_with_defaults.keys())
parsed_parameters_values = list(parsed_parameters.values()) + list(parsed_parameters_with_defaults.values())
parameters_str = ", ".join(parsed_parameters_values)
kwargs_str = ', '.join([f"'{k}': {k}" for k in parsed_parameters_keys])
kwargs_str = ", ".join([f"'{k}': {k}" for k in parsed_parameters_keys])

dynamic_function_body = f"""async def dynamic_http_tool_function({parameters_str}):
kwargs = {{{kwargs_str}}}
return await http_tool_function_template(**kwargs)
"""

# Create function namespace with required imports
namespace = {
"http_tool_function_template": function_template,
**PYTHON_TYPE_IMPORTS,
**additional_variables
}

namespace = {"http_tool_function_template": function_template, **PYTHON_TYPE_IMPORTS, **additional_variables}

# Execute the dynamic function definition
exec(dynamic_function_body, namespace)
return namespace["dynamic_http_tool_function"]


def create_http_tool(
mcp_server: FastMCP,
base_url: str,
Expand All @@ -146,6 +149,7 @@ def create_http_tool(
openapi_schema: Dict[str, Any],
describe_all_responses: bool,
describe_full_response_schema: bool,
timeout: Optional[float] = None,
) -> None:
"""
Create an MCP tool that makes an HTTP request to a FastAPI endpoint.
Expand All @@ -164,6 +168,7 @@ def create_http_tool(
openapi_schema: The full OpenAPI schema
describe_all_responses: Whether to include all possible response schemas in tool descriptions
describe_full_response_schema: Whether to include full response schema in tool descriptions
timeout: Optional timeout for the HTTP request
"""
# Build tool description
tool_description = f"{summary}" if summary else f"{method.upper()} {path}"
Expand Down Expand Up @@ -421,7 +426,7 @@ async def http_tool_function_template(**kwargs):

# Make the request
logger.debug(f"Making {method.upper()} request to {url}")
async with httpx.AsyncClient() as client:
async with httpx.AsyncClient(timeout=timeout) as client:
if method.lower() == "get":
response = await client.get(url, params=query, headers=headers)
elif method.lower() == "post":
Expand All @@ -443,7 +448,7 @@ async def http_tool_function_template(**kwargs):

# Create the http_tool_function (with name and docstring)
additional_variables = {"path_params": path_params, "query_params": query_params, "header_params": header_params}
http_tool_function = _create_http_tool_function(http_tool_function_template, properties, additional_variables) # type: ignore
http_tool_function = _create_http_tool_function(http_tool_function_template, properties, additional_variables) # type: ignore
http_tool_function.__name__ = operation_id
http_tool_function.__doc__ = tool_description

Expand Down
9 changes: 7 additions & 2 deletions fastapi_mcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ def mount_mcp_server(
base_url: Optional[str] = None,
describe_all_responses: bool = False,
describe_full_response_schema: bool = False,
timeout: Optional[int] = None,
) -> None:
"""
Mount an MCP server to a FastAPI app.
Expand All @@ -68,6 +69,7 @@ def mount_mcp_server(
base_url: Base URL for API requests
describe_all_responses: Whether to include all possible response schemas in tool descriptions. Recommended to keep False, as the LLM will probably derive if there is an error.
describe_full_response_schema: Whether to include full json schema for responses in tool descriptions. Recommended to keep False, as examples are more LLM friendly, and save tokens.
timeout: Optional timeout for the server
"""
# Normalize mount path
if not mount_path.startswith("/"):
Expand Down Expand Up @@ -95,14 +97,14 @@ async def handle_mcp_connection(request: Request):
base_url,
describe_all_responses=describe_all_responses,
describe_full_response_schema=describe_full_response_schema,
timeout=timeout,
)

# Mount the MCP connection handler
app.get(mount_path)(handle_mcp_connection)
app.mount(f"{mount_path}/messages/", app=sse_transport.handle_post_message)



def add_mcp_server(
app: FastAPI,
mount_path: str = "/mcp",
Expand All @@ -113,6 +115,7 @@ def add_mcp_server(
base_url: Optional[str] = None,
describe_all_responses: bool = False,
describe_full_response_schema: bool = False,
timeout: Optional[int] = None,
) -> FastMCP:
"""
Add an MCP server to a FastAPI app.
Expand All @@ -127,6 +130,7 @@ def add_mcp_server(
base_url: Base URL for API requests (defaults to http://localhost:$PORT)
describe_all_responses: Whether to include all possible response schemas in tool descriptions. Recommended to keep False, as the LLM will probably derive if there is an error.
describe_full_response_schema: Whether to include full json schema for responses in tool descriptions. Recommended to keep False, as examples are more LLM friendly, and save tokens.
timeout: Optional timeout for the server

Returns:
The FastMCP instance that was created and mounted
Expand All @@ -143,6 +147,7 @@ def add_mcp_server(
base_url,
describe_all_responses=describe_all_responses,
describe_full_response_schema=describe_full_response_schema,
timeout=timeout,
)

return mcp_server