From a9aca2020508a1f4cf08d64c447a9eb58d95921a Mon Sep 17 00:00:00 2001 From: comfuture Date: Wed, 19 Mar 2025 13:26:49 +0900 Subject: [PATCH] Option to mount SSE server to existing ASGI server (#312) * Option to mount SSE server to existing ASGI server Fixes #311 Add option to mount SSE server to an existing ASGI server. * Add a new method `sse_app` in `src/mcp/server/fastmcp/server.py` to return an instance of the SSE server app. * Update the `run_sse_async` method in `src/mcp/server/fastmcp/server.py` to use the new `sse_app` method. * Update the documentation in `README.md` to include instructions on how to mount the SSE server to an existing ASGI server. * Fix the example in `README.md` to use `app.mount('/', mcp.sse_app())` instead. --- For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/modelcontextprotocol/python-sdk/issues/311?shareId=XXXX-XXXX-XXXX-XXXX). * Add `sse_app` method and update `run_sse_async` method in `server.py` * Add `sse_app` method to return an instance of the SSE server app * Update `run_sse_async` method to use the new `sse_app` method Update `README.md` to include instructions for mounting SSE server * Add section on mounting the SSE server to an existing ASGI server * fix: Move import statements to the top of the file/ * docs: Update README to reflect changes in mounting SSE server with Starlette * docs: Formatting of SSE server mounting example in README --- README.md | 26 ++++++++++++++++++++++++++ src/mcp/server/fastmcp/server.py | 27 +++++++++++++++------------ 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index c062f3a..5075308 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ - [Development Mode](#development-mode) - [Claude Desktop Integration](#claude-desktop-integration) - [Direct Execution](#direct-execution) + - [Mounting to an Existing ASGI Server](#mounting-to-an-existing-asgi-server) - [Examples](#examples) - [Echo Server](#echo-server) - [SQLite Explorer](#sqlite-explorer) @@ -346,6 +347,31 @@ python server.py mcp run server.py ``` +### Mounting to an Existing ASGI Server + +You can mount the SSE server to an existing ASGI server using the `sse_app` method. This allows you to integrate the SSE server with other ASGI applications. + +```python +from starlette.applications import Starlette +from starlette.routes import Mount, Host +from mcp.server.fastmcp import FastMCP + + +mcp = FastMCP("My App") + +# Mount the SSE server to the existing ASGI server +app = Starlette( + routes=[ + Mount('/', app=mcp.sse_app()), + ] +) + +# or dynamically mount as host +app.router.routes.append(Host('mcp.acme.corp', app=mcp.sse_app())) +``` + +For more information on mounting applications in Starlette, see the [Starlette documentation](https://www.starlette.io/routing/#submounting-routes). + ## Examples ### Echo Server diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index 1e219fc..9affd9b 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -19,6 +19,8 @@ import uvicorn from pydantic import BaseModel, Field from pydantic.networks import AnyUrl from pydantic_settings import BaseSettings, SettingsConfigDict +from starlette.applications import Starlette +from starlette.routing import Mount, Route from mcp.server.fastmcp.exceptions import ResourceError from mcp.server.fastmcp.prompts import Prompt, PromptManager @@ -461,9 +463,19 @@ class FastMCP: async def run_sse_async(self) -> None: """Run the server using SSE transport.""" - from starlette.applications import Starlette - from starlette.routing import Mount, Route + starlette_app = self.sse_app() + config = uvicorn.Config( + starlette_app, + host=self.settings.host, + port=self.settings.port, + log_level=self.settings.log_level.lower(), + ) + server = uvicorn.Server(config) + await server.serve() + + def sse_app(self) -> Starlette: + """Return an instance of the SSE server app.""" sse = SseServerTransport("/messages/") async def handle_sse(request): @@ -476,7 +488,7 @@ class FastMCP: self._mcp_server.create_initialization_options(), ) - starlette_app = Starlette( + return Starlette( debug=self.settings.debug, routes=[ Route("/sse", endpoint=handle_sse), @@ -484,15 +496,6 @@ class FastMCP: ], ) - config = uvicorn.Config( - starlette_app, - host=self.settings.host, - port=self.settings.port, - log_level=self.settings.log_level.lower(), - ) - server = uvicorn.Server(config) - await server.serve() - async def list_prompts(self) -> list[MCPPrompt]: """List all available prompts.""" prompts = self._prompt_manager.list_prompts()