10
10
11
11
from fastapi_mcp .openapi .convert import convert_openapi_to_mcp_tools
12
12
from fastapi_mcp .transport .sse import FastApiSseTransport
13
+ from fastapi_mcp .transport .http import FastApiStreamableHttpTransport
13
14
from fastapi_mcp .types import HTTPRequestInfo , AuthConfig
14
15
15
16
import logging
@@ -35,7 +36,7 @@ def decorator(
35
36
36
37
async def handler (req : types .CallToolRequest ):
37
38
try :
38
- # Pull the original HTTP request info from the MCP message. It was injected in
39
+ # HACK: Pull the original HTTP request info from the MCP message. It was injected in
39
40
# `FastApiSseTransport.handle_fastapi_post_message()`
40
41
if hasattr (req .params , "_http_request_info" ) and req .params ._http_request_info is not None :
41
42
http_request_info = HTTPRequestInfo .model_validate (req .params ._http_request_info )
@@ -241,6 +242,32 @@ def _register_mcp_endpoints_sse(
241
242
self ._register_mcp_connection_endpoint_sse (router , transport , mount_path , dependencies )
242
243
self ._register_mcp_messages_endpoint_sse (router , transport , mount_path , dependencies )
243
244
245
+ def _register_mcp_http_endpoint (
246
+ self ,
247
+ router : FastAPI | APIRouter ,
248
+ transport : FastApiStreamableHttpTransport ,
249
+ mount_path : str ,
250
+ dependencies : Optional [Sequence [params .Depends ]],
251
+ ):
252
+ @router .api_route (
253
+ mount_path ,
254
+ methods = ["GET" , "POST" , "DELETE" ],
255
+ include_in_schema = False ,
256
+ operation_id = "mcp_http" ,
257
+ dependencies = dependencies ,
258
+ )
259
+ async def handle_mcp_streamable_http (request : Request ):
260
+ return await transport .handle_fastapi_request (request )
261
+
262
+ def _register_mcp_endpoints_http (
263
+ self ,
264
+ router : FastAPI | APIRouter ,
265
+ transport : FastApiStreamableHttpTransport ,
266
+ mount_path : str ,
267
+ dependencies : Optional [Sequence [params .Depends ]],
268
+ ):
269
+ self ._register_mcp_http_endpoint (router , transport , mount_path , dependencies )
270
+
244
271
def _setup_auth_2025_03_26 (self ):
245
272
from fastapi_mcp .auth .proxy import (
246
273
setup_oauth_custom_metadata ,
@@ -296,7 +323,7 @@ def _setup_auth(self):
296
323
else :
297
324
logger .info ("No auth config provided, skipping auth setup" )
298
325
299
- def mount (
326
+ def mount_http (
300
327
self ,
301
328
router : Annotated [
302
329
Optional [FastAPI | APIRouter ],
@@ -317,17 +344,64 @@ def mount(
317
344
"""
318
345
),
319
346
] = "/mcp" ,
320
- transport : Annotated [
321
- Literal ["sse" ],
347
+ ) -> None :
348
+ """
349
+ Mount the MCP server with HTTP transport to **any** FastAPI app or APIRouter.
350
+
351
+ There is no requirement that the FastAPI app or APIRouter is the same as the one that the MCP
352
+ server was created from.
353
+ """
354
+ # Normalize mount path
355
+ if not mount_path .startswith ("/" ):
356
+ mount_path = f"/{ mount_path } "
357
+ if mount_path .endswith ("/" ):
358
+ mount_path = mount_path [:- 1 ]
359
+
360
+ if not router :
361
+ router = self .fastapi
362
+
363
+ assert isinstance (router , (FastAPI , APIRouter )), f"Invalid router type: { type (router )} "
364
+
365
+ http_transport = FastApiStreamableHttpTransport ()
366
+ dependencies = self ._auth_config .dependencies if self ._auth_config else None
367
+
368
+ self ._register_mcp_endpoints_http (router , http_transport , mount_path , dependencies )
369
+ self ._setup_auth ()
370
+
371
+ # HACK: If we got a router and not a FastAPI instance, we need to re-include the router so that
372
+ # FastAPI will pick up the new routes we added. The problem with this approach is that we assume
373
+ # that the router is a sub-router of self.fastapi, which may not always be the case.
374
+ #
375
+ # TODO: Find a better way to do this.
376
+ if isinstance (router , APIRouter ):
377
+ self .fastapi .include_router (router )
378
+
379
+ logger .info (f"MCP HTTP server listening at { mount_path } " )
380
+
381
+ def mount_sse (
382
+ self ,
383
+ router : Annotated [
384
+ Optional [FastAPI | APIRouter ],
322
385
Doc (
323
386
"""
324
- The transport type for the MCP server. Currently only 'sse' is supported.
387
+ The FastAPI app or APIRouter to mount the MCP server to. If not provided, the MCP
388
+ server will be mounted to the FastAPI app.
325
389
"""
326
390
),
327
- ] = "sse" ,
391
+ ] = None ,
392
+ mount_path : Annotated [
393
+ str ,
394
+ Doc (
395
+ """
396
+ Path where the MCP server will be mounted.
397
+ Mount path is appended to the root path of FastAPI router, or to the prefix of APIRouter.
398
+ Defaults to '/sse'.
399
+ """
400
+ ),
401
+ ] = "/sse" ,
328
402
) -> None :
329
403
"""
330
- Mount the MCP server to **any** FastAPI app or APIRouter.
404
+ Mount the MCP server with SSE transport to **any** FastAPI app or APIRouter.
331
405
332
406
There is no requirement that the FastAPI app or APIRouter is the same as the one that the MCP
333
407
server was created from.
@@ -347,14 +421,9 @@ def mount(
347
421
messages_path = f"{ base_path } /messages/"
348
422
349
423
sse_transport = FastApiSseTransport (messages_path )
350
-
351
424
dependencies = self ._auth_config .dependencies if self ._auth_config else None
352
425
353
- if transport == "sse" :
354
- self ._register_mcp_endpoints_sse (router , sse_transport , mount_path , dependencies )
355
- else : # pragma: no cover
356
- raise ValueError (f"Invalid transport: { transport } " ) # pragma: no cover
357
-
426
+ self ._register_mcp_endpoints_sse (router , sse_transport , mount_path , dependencies )
358
427
self ._setup_auth ()
359
428
360
429
# HACK: If we got a router and not a FastAPI instance, we need to re-include the router so that
@@ -365,7 +434,65 @@ def mount(
365
434
if isinstance (router , APIRouter ):
366
435
self .fastapi .include_router (router )
367
436
368
- logger .info (f"MCP server listening at { mount_path } " )
437
+ logger .info (f"MCP SSE server listening at { mount_path } " )
438
+
439
+ def mount (
440
+ self ,
441
+ router : Annotated [
442
+ Optional [FastAPI | APIRouter ],
443
+ Doc (
444
+ """
445
+ The FastAPI app or APIRouter to mount the MCP server to. If not provided, the MCP
446
+ server will be mounted to the FastAPI app.
447
+ """
448
+ ),
449
+ ] = None ,
450
+ mount_path : Annotated [
451
+ str ,
452
+ Doc (
453
+ """
454
+ Path where the MCP server will be mounted.
455
+ Mount path is appended to the root path of FastAPI router, or to the prefix of APIRouter.
456
+ Defaults to '/mcp'.
457
+ """
458
+ ),
459
+ ] = "/mcp" ,
460
+ transport : Annotated [
461
+ Literal ["sse" ],
462
+ Doc (
463
+ """
464
+ The transport type for the MCP server. Currently only 'sse' is supported.
465
+ This parameter is deprecated.
466
+ """
467
+ ),
468
+ ] = "sse" ,
469
+ ) -> None :
470
+ """
471
+ [DEPRECATED] Mount the MCP server to **any** FastAPI app or APIRouter.
472
+
473
+ This method is deprecated and will be removed in a future version.
474
+ Use mount_http() for HTTP transport (recommended) or mount_sse() for SSE transport instead.
475
+
476
+ For backwards compatibility, this method defaults to SSE transport.
477
+
478
+ There is no requirement that the FastAPI app or APIRouter is the same as the one that the MCP
479
+ server was created from.
480
+ """
481
+ import warnings
482
+
483
+ warnings .warn (
484
+ "mount() is deprecated and will be removed in a future version. "
485
+ "Use mount_http() for HTTP transport (recommended) or mount_sse() for SSE transport instead." ,
486
+ DeprecationWarning ,
487
+ stacklevel = 2 ,
488
+ )
489
+
490
+ if transport == "sse" :
491
+ self .mount_sse (router , mount_path )
492
+ else : # pragma: no cover
493
+ raise ValueError ( # pragma: no cover
494
+ f"Unsupported transport: { transport } . Use mount_sse() or mount_http() instead."
495
+ )
369
496
370
497
async def _execute_api_tool (
371
498
self ,
0 commit comments