1
1
from contextlib import asynccontextmanager
2
2
from typing import Dict , Optional , Any , List , Union , AsyncIterator
3
3
4
- from fastapi import FastAPI , Request
4
+ from fastapi import FastAPI , Request , APIRouter
5
5
from fastapi .openapi .utils import get_openapi
6
6
from mcp .server .lowlevel .server import Server
7
- from mcp .server .sse import SseServerTransport
8
7
import mcp .types as types
9
8
10
9
from fastapi_mcp .openapi .convert import convert_openapi_to_mcp_tools
11
10
from fastapi_mcp .execute import execute_api_tool
11
+ from fastapi_mcp .transport .sse import FastApiSseTransport
12
+
13
+ from logging import getLogger
14
+
15
+
16
+ logger = getLogger (__name__ )
12
17
13
18
14
19
class FastApiMCP :
15
20
def __init__ (
16
21
self ,
17
22
fastapi : FastAPI ,
18
- mount_path : str = "/mcp" ,
19
23
name : Optional [str ] = None ,
20
24
description : Optional [str ] = None ,
21
25
base_url : Optional [str ] = None ,
@@ -29,7 +33,6 @@ def __init__(
29
33
self .name = name
30
34
self .description = description
31
35
32
- self ._mount_path = mount_path
33
36
self ._base_url = base_url
34
37
self ._describe_all_responses = describe_all_responses
35
38
self ._describe_full_response_schema = describe_full_response_schema
@@ -41,10 +44,12 @@ def create_server(self) -> Server:
41
44
Create an MCP server from the FastAPI app.
42
45
43
46
Args:
44
- app : The FastAPI application
47
+ fastapi : The FastAPI application
45
48
name: Name for the MCP server (defaults to app.title)
46
49
description: Description for the MCP server (defaults to app.description)
47
- base_url: Base URL for API requests (defaults to http://localhost:$PORT)
50
+ base_url: Base URL for API requests. If not provided, the base URL will be determined from the
51
+ FastAPI app's root path. Although optional, it is highly recommended to provide a base URL,
52
+ as the root path would be different when the app is deployed.
48
53
describe_all_responses: Whether to include all possible response schemas in tool descriptions
49
54
describe_full_response_schema: Whether to include full json schema for responses in tool descriptions
50
55
@@ -126,37 +131,42 @@ async def handle_call_tool(
126
131
127
132
return mcp_server
128
133
129
- def mount (self ) -> None :
134
+ def mount (self , router : Optional [ FastAPI | APIRouter ] = None , mount_path : str = "/mcp" ) -> None :
130
135
"""
131
136
Mount the MCP server to the FastAPI app.
132
137
133
138
Args:
134
- app: The FastAPI application
135
- mcp_server: The MCP server to mount
136
- operation_map: A mapping of operation IDs to operation details
139
+ router: The FastAPI app or APIRouter to mount the MCP server to. If not provided, the MCP
140
+ server will be mounted to the FastAPI app.
137
141
mount_path: Path where the MCP server will be mounted
138
- base_url: Base URL for API requests
139
142
"""
140
143
# Normalize mount path
141
- if not self ._mount_path .startswith ("/" ):
142
- self ._mount_path = f"/{ self ._mount_path } "
143
- if self ._mount_path .endswith ("/" ):
144
- self ._mount_path = self ._mount_path [:- 1 ]
144
+ if not mount_path .startswith ("/" ):
145
+ mount_path = f"/{ mount_path } "
146
+ if mount_path .endswith ("/" ):
147
+ mount_path = mount_path [:- 1 ]
148
+
149
+ if not router :
150
+ router = self .fastapi
145
151
146
152
# Create SSE transport for MCP messages
147
- sse_transport = SseServerTransport (f"{ self . _mount_path } /messages/" )
153
+ sse_transport = FastApiSseTransport (f"{ mount_path } /messages/" )
148
154
149
- # Define MCP connection handler
155
+ # Route for MCP connection
156
+ @router .get (mount_path )
150
157
async def handle_mcp_connection (request : Request ):
151
- async with sse_transport .connect_sse (request .scope , request .receive , request ._send ) as streams :
158
+ async with sse_transport .connect_sse (request .scope , request .receive , request ._send ) as ( reader , writer ) :
152
159
await self .mcp_server .run (
153
- streams [ 0 ] ,
154
- streams [ 1 ] ,
160
+ reader ,
161
+ writer ,
155
162
self .mcp_server .create_initialization_options (
156
163
notification_options = None , experimental_capabilities = {}
157
164
),
158
165
)
159
166
160
- # Mount the MCP connection handler
161
- self .fastapi .get (self ._mount_path )(handle_mcp_connection )
162
- self .fastapi .mount (f"{ self ._mount_path } /messages/" , app = sse_transport .handle_post_message )
167
+ # Route for MCP messages
168
+ @router .post (f"{ mount_path } /messages/" )
169
+ async def handle_post_message (request : Request ):
170
+ await sse_transport .handle_fastapi_post_message (request )
171
+
172
+ logger .info (f"MCP server listening at { mount_path } " )
0 commit comments