1010
1111from fastapi_mcp .openapi .convert import convert_openapi_to_mcp_tools
1212from fastapi_mcp .transport .sse import FastApiSseTransport
13+ from fastapi_mcp .transport .http import FastApiStreamableHttpTransport
1314from fastapi_mcp .types import HTTPRequestInfo , AuthConfig
1415
1516import logging
@@ -35,7 +36,7 @@ def decorator(
3536
3637 async def handler (req : types .CallToolRequest ):
3738 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
3940 # `FastApiSseTransport.handle_fastapi_post_message()`
4041 if hasattr (req .params , "_http_request_info" ) and req .params ._http_request_info is not None :
4142 http_request_info = HTTPRequestInfo .model_validate (req .params ._http_request_info )
@@ -241,6 +242,32 @@ def _register_mcp_endpoints_sse(
241242 self ._register_mcp_connection_endpoint_sse (router , transport , mount_path , dependencies )
242243 self ._register_mcp_messages_endpoint_sse (router , transport , mount_path , dependencies )
243244
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+
244271 def _setup_auth_2025_03_26 (self ):
245272 from fastapi_mcp .auth .proxy import (
246273 setup_oauth_custom_metadata ,
@@ -296,7 +323,7 @@ def _setup_auth(self):
296323 else :
297324 logger .info ("No auth config provided, skipping auth setup" )
298325
299- def mount (
326+ def mount_http (
300327 self ,
301328 router : Annotated [
302329 Optional [FastAPI | APIRouter ],
@@ -317,17 +344,64 @@ def mount(
317344 """
318345 ),
319346 ] = "/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 ],
322385 Doc (
323386 """
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.
325389 """
326390 ),
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" ,
328402 ) -> None :
329403 """
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.
331405
332406 There is no requirement that the FastAPI app or APIRouter is the same as the one that the MCP
333407 server was created from.
@@ -347,14 +421,9 @@ def mount(
347421 messages_path = f"{ base_path } /messages/"
348422
349423 sse_transport = FastApiSseTransport (messages_path )
350-
351424 dependencies = self ._auth_config .dependencies if self ._auth_config else None
352425
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 )
358427 self ._setup_auth ()
359428
360429 # 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(
365434 if isinstance (router , APIRouter ):
366435 self .fastapi .include_router (router )
367436
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+ )
369496
370497 async def _execute_api_tool (
371498 self ,
0 commit comments