From 7d48f8da42d259265f31505db0b60272732ad2c8 Mon Sep 17 00:00:00 2001 From: osushinekotan Date: Thu, 10 Apr 2025 17:44:59 +0900 Subject: [PATCH] Add timeout configuration for HTTP requests --- fastapi_mcp/http_tools.py | 25 +++++++++++++++---------- fastapi_mcp/server.py | 9 +++++++-- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/fastapi_mcp/http_tools.py b/fastapi_mcp/http_tools.py index 233ae96..a9ee1ab 100644 --- a/fastapi_mcp/http_tools.py +++ b/fastapi_mcp/http_tools.py @@ -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. @@ -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( @@ -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 = {} @@ -114,7 +120,7 @@ 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}}} @@ -122,16 +128,13 @@ def _create_http_tool_function(function_template: Callable, properties: Dict[str """ # 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, @@ -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. @@ -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}" @@ -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": @@ -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 diff --git a/fastapi_mcp/server.py b/fastapi_mcp/server.py index 1bf62b0..1114421 100644 --- a/fastapi_mcp/server.py +++ b/fastapi_mcp/server.py @@ -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. @@ -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("/"): @@ -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", @@ -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. @@ -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 @@ -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