From acd0121e8d451517820aa1ec7de19efef841e3c3 Mon Sep 17 00:00:00 2001 From: Felix Weinberger Date: Fri, 18 Jul 2025 17:13:57 +0100 Subject: [PATCH 1/2] docs: clarify streamable_http_path configuration when mounting servers - Add StreamableHTTP servers section mirroring SSE servers structure - Start with simple mounting examples, then explain path composition - Document how to configure streamable_http_path for different scenarios - Add inline comments in Starlette mounting example This addresses confusion around endpoint paths when mounting FastMCP servers as sub-applications in ASGI frameworks. --- README.md | 88 +++++++++++++++++++ .../servers/streamable_http_mounting.py | 72 +++++++++++++++ .../servers/streamable_starlette_mount.py | 5 ++ 3 files changed, 165 insertions(+) create mode 100644 examples/snippets/servers/streamable_http_mounting.py diff --git a/README.md b/README.md index 993b6006b..e07a752a5 100644 --- a/README.md +++ b/README.md @@ -979,6 +979,11 @@ app = Starlette( ], lifespan=lifespan, ) + +# Note: Clients connect to http://localhost:8000/echo/mcp and http://localhost:8000/math/mcp +# To mount at the root of each path (e.g., /echo instead of /echo/mcp): +# echo_mcp.settings.streamable_http_path = "/" +# math_mcp.settings.streamable_http_path = "/" ``` _Full example: [examples/snippets/servers/streamable_starlette_mount.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/streamable_starlette_mount.py)_ @@ -1002,6 +1007,89 @@ By default, SSE servers are mounted at `/sse` and Streamable HTTP servers are mo For more information on mounting applications in Starlette, see the [Starlette documentation](https://www.starlette.io/routing/#submounting-routes). +#### StreamableHTTP servers + +You can mount the StreamableHTTP server to an existing ASGI server using the `streamable_http_app` method. This allows you to integrate the StreamableHTTP server with other ASGI applications. + + +```python +""" +Example showing how to mount StreamableHTTP servers in Starlette applications. + +Run from the repository root: + uvicorn examples.snippets.servers.streamable_http_mounting:app --reload +""" + +from starlette.applications import Starlette +from starlette.routing import Host, Mount + +from mcp.server.fastmcp import FastMCP + +# Basic example - mounting at root +mcp = FastMCP("My App") + + +@mcp.tool() +def hello() -> str: + """A simple hello tool""" + return "Hello from MCP!" + + +# Mount the StreamableHTTP server to the existing ASGI server +app = Starlette( + routes=[ + Mount("/", app=mcp.streamable_http_app()), + ] +) + +# or dynamically mount as host +app.router.routes.append(Host("mcp.acme.corp", app=mcp.streamable_http_app())) + +# Advanced example - multiple servers with path configuration +# Create multiple MCP servers +api_mcp = FastMCP("API Server") +chat_mcp = FastMCP("Chat Server") + + +@api_mcp.tool() +def api_status() -> str: + """Get API status""" + return "API is running" + + +@chat_mcp.tool() +def send_message(message: str) -> str: + """Send a chat message""" + return f"Message sent: {message}" + + +# Default behavior: endpoints will be at /api/mcp and /chat/mcp +default_app = Starlette( + routes=[ + Mount("/api", app=api_mcp.streamable_http_app()), + Mount("/chat", app=chat_mcp.streamable_http_app()), + ] +) + +# To mount at the root of each path (e.g., /api instead of /api/mcp): +# Configure streamable_http_path before mounting +api_mcp.settings.streamable_http_path = "/" +chat_mcp.settings.streamable_http_path = "/" + +configured_app = Starlette( + routes=[ + Mount("/api", app=api_mcp.streamable_http_app()), + Mount("/chat", app=chat_mcp.streamable_http_app()), + ] +) + +# Or configure during initialization +mcp_at_root = FastMCP("My Server", streamable_http_path="/") +``` + +_Full example: [examples/snippets/servers/streamable_http_mounting.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/streamable_http_mounting.py)_ + + #### SSE servers > **Note**: SSE transport is being superseded by [Streamable HTTP transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http). diff --git a/examples/snippets/servers/streamable_http_mounting.py b/examples/snippets/servers/streamable_http_mounting.py new file mode 100644 index 000000000..945bdb4c7 --- /dev/null +++ b/examples/snippets/servers/streamable_http_mounting.py @@ -0,0 +1,72 @@ +""" +Example showing how to mount StreamableHTTP servers in Starlette applications. + +Run from the repository root: + uvicorn examples.snippets.servers.streamable_http_mounting:app --reload +""" + +from starlette.applications import Starlette +from starlette.routing import Host, Mount + +from mcp.server.fastmcp import FastMCP + +# Basic example - mounting at root +mcp = FastMCP("My App") + + +@mcp.tool() +def hello() -> str: + """A simple hello tool""" + return "Hello from MCP!" + + +# Mount the StreamableHTTP server to the existing ASGI server +app = Starlette( + routes=[ + Mount("/", app=mcp.streamable_http_app()), + ] +) + +# or dynamically mount as host +app.router.routes.append(Host("mcp.acme.corp", app=mcp.streamable_http_app())) + +# Advanced example - multiple servers with path configuration +# Create multiple MCP servers +api_mcp = FastMCP("API Server") +chat_mcp = FastMCP("Chat Server") + + +@api_mcp.tool() +def api_status() -> str: + """Get API status""" + return "API is running" + + +@chat_mcp.tool() +def send_message(message: str) -> str: + """Send a chat message""" + return f"Message sent: {message}" + + +# Default behavior: endpoints will be at /api/mcp and /chat/mcp +default_app = Starlette( + routes=[ + Mount("/api", app=api_mcp.streamable_http_app()), + Mount("/chat", app=chat_mcp.streamable_http_app()), + ] +) + +# To mount at the root of each path (e.g., /api instead of /api/mcp): +# Configure streamable_http_path before mounting +api_mcp.settings.streamable_http_path = "/" +chat_mcp.settings.streamable_http_path = "/" + +configured_app = Starlette( + routes=[ + Mount("/api", app=api_mcp.streamable_http_app()), + Mount("/chat", app=chat_mcp.streamable_http_app()), + ] +) + +# Or configure during initialization +mcp_at_root = FastMCP("My Server", streamable_http_path="/") diff --git a/examples/snippets/servers/streamable_starlette_mount.py b/examples/snippets/servers/streamable_starlette_mount.py index 19e41294b..57d2d2ea5 100644 --- a/examples/snippets/servers/streamable_starlette_mount.py +++ b/examples/snippets/servers/streamable_starlette_mount.py @@ -47,3 +47,8 @@ async def lifespan(app: Starlette): ], lifespan=lifespan, ) + +# Note: Clients connect to http://localhost:8000/echo/mcp and http://localhost:8000/math/mcp +# To mount at the root of each path (e.g., /echo instead of /echo/mcp): +# echo_mcp.settings.streamable_http_path = "/" +# math_mcp.settings.streamable_http_path = "/" From fb323a32030d556285992e88357e47ae5ceb094a Mon Sep 17 00:00:00 2001 From: Felix Weinberger Date: Mon, 21 Jul 2025 13:45:35 +0100 Subject: [PATCH 2/2] refactor: split StreamableHTTP mounting examples into separate files - Split single streamable_http_mounting.py into four focused examples: - Basic mounting example - Host-based routing example - Multiple servers with path configuration - Path configuration at initialization - Removed manual route append approach, now using proper Host() in routes - Updated README with clearer structure and subsections for each example This addresses review feedback to improve clarity and organization of the StreamableHTTP mounting documentation. Reported-by:Kludex --- README.md | 125 +++++++++++++++--- .../servers/streamable_http_basic_mounting.py | 28 ++++ .../servers/streamable_http_host_mounting.py | 28 ++++ .../servers/streamable_http_mounting.py | 72 ---------- .../streamable_http_multiple_servers.py | 41 ++++++ .../servers/streamable_http_path_config.py | 29 ++++ 6 files changed, 230 insertions(+), 93 deletions(-) create mode 100644 examples/snippets/servers/streamable_http_basic_mounting.py create mode 100644 examples/snippets/servers/streamable_http_host_mounting.py delete mode 100644 examples/snippets/servers/streamable_http_mounting.py create mode 100644 examples/snippets/servers/streamable_http_multiple_servers.py create mode 100644 examples/snippets/servers/streamable_http_path_config.py diff --git a/README.md b/README.md index e07a752a5..e738c94e3 100644 --- a/README.md +++ b/README.md @@ -1011,21 +1011,23 @@ For more information on mounting applications in Starlette, see the [Starlette d You can mount the StreamableHTTP server to an existing ASGI server using the `streamable_http_app` method. This allows you to integrate the StreamableHTTP server with other ASGI applications. - +##### Basic mounting + + ```python """ -Example showing how to mount StreamableHTTP servers in Starlette applications. +Basic example showing how to mount StreamableHTTP server in Starlette. Run from the repository root: - uvicorn examples.snippets.servers.streamable_http_mounting:app --reload + uvicorn examples.snippets.servers.streamable_http_basic_mounting:app --reload """ from starlette.applications import Starlette -from starlette.routing import Host, Mount +from starlette.routing import Mount from mcp.server.fastmcp import FastMCP -# Basic example - mounting at root +# Create MCP server mcp = FastMCP("My App") @@ -1041,11 +1043,64 @@ app = Starlette( Mount("/", app=mcp.streamable_http_app()), ] ) +``` -# or dynamically mount as host -app.router.routes.append(Host("mcp.acme.corp", app=mcp.streamable_http_app())) +_Full example: [examples/snippets/servers/streamable_http_basic_mounting.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/streamable_http_basic_mounting.py)_ + + +##### Host-based routing + + +```python +""" +Example showing how to mount StreamableHTTP server using Host-based routing. + +Run from the repository root: + uvicorn examples.snippets.servers.streamable_http_host_mounting:app --reload +""" + +from starlette.applications import Starlette +from starlette.routing import Host + +from mcp.server.fastmcp import FastMCP + +# Create MCP server +mcp = FastMCP("MCP Host App") + + +@mcp.tool() +def domain_info() -> str: + """Get domain-specific information""" + return "This is served from mcp.acme.corp" + + +# Mount using Host-based routing +app = Starlette( + routes=[ + Host("mcp.acme.corp", app=mcp.streamable_http_app()), + ] +) +``` + +_Full example: [examples/snippets/servers/streamable_http_host_mounting.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/streamable_http_host_mounting.py)_ + + +##### Multiple servers with path configuration + + +```python +""" +Example showing how to mount multiple StreamableHTTP servers with path configuration. + +Run from the repository root: + uvicorn examples.snippets.servers.streamable_http_multiple_servers:app --reload +""" + +from starlette.applications import Starlette +from starlette.routing import Mount + +from mcp.server.fastmcp import FastMCP -# Advanced example - multiple servers with path configuration # Create multiple MCP servers api_mcp = FastMCP("API Server") chat_mcp = FastMCP("Chat Server") @@ -1063,31 +1118,59 @@ def send_message(message: str) -> str: return f"Message sent: {message}" -# Default behavior: endpoints will be at /api/mcp and /chat/mcp -default_app = Starlette( - routes=[ - Mount("/api", app=api_mcp.streamable_http_app()), - Mount("/chat", app=chat_mcp.streamable_http_app()), - ] -) - -# To mount at the root of each path (e.g., /api instead of /api/mcp): -# Configure streamable_http_path before mounting +# Configure servers to mount at the root of each path +# This means endpoints will be at /api and /chat instead of /api/mcp and /chat/mcp api_mcp.settings.streamable_http_path = "/" chat_mcp.settings.streamable_http_path = "/" -configured_app = Starlette( +# Mount the servers +app = Starlette( routes=[ Mount("/api", app=api_mcp.streamable_http_app()), Mount("/chat", app=chat_mcp.streamable_http_app()), ] ) +``` + +_Full example: [examples/snippets/servers/streamable_http_multiple_servers.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/streamable_http_multiple_servers.py)_ + + +##### Path configuration at initialization + + +```python +""" +Example showing path configuration during FastMCP initialization. + +Run from the repository root: + uvicorn examples.snippets.servers.streamable_http_path_config:app --reload +""" + +from starlette.applications import Starlette +from starlette.routing import Mount + +from mcp.server.fastmcp import FastMCP -# Or configure during initialization +# Configure streamable_http_path during initialization +# This server will mount at the root of wherever it's mounted mcp_at_root = FastMCP("My Server", streamable_http_path="/") + + +@mcp_at_root.tool() +def process_data(data: str) -> str: + """Process some data""" + return f"Processed: {data}" + + +# Mount at /process - endpoints will be at /process instead of /process/mcp +app = Starlette( + routes=[ + Mount("/process", app=mcp_at_root.streamable_http_app()), + ] +) ``` -_Full example: [examples/snippets/servers/streamable_http_mounting.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/streamable_http_mounting.py)_ +_Full example: [examples/snippets/servers/streamable_http_path_config.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/streamable_http_path_config.py)_ #### SSE servers diff --git a/examples/snippets/servers/streamable_http_basic_mounting.py b/examples/snippets/servers/streamable_http_basic_mounting.py new file mode 100644 index 000000000..abcc0e572 --- /dev/null +++ b/examples/snippets/servers/streamable_http_basic_mounting.py @@ -0,0 +1,28 @@ +""" +Basic example showing how to mount StreamableHTTP server in Starlette. + +Run from the repository root: + uvicorn examples.snippets.servers.streamable_http_basic_mounting:app --reload +""" + +from starlette.applications import Starlette +from starlette.routing import Mount + +from mcp.server.fastmcp import FastMCP + +# Create MCP server +mcp = FastMCP("My App") + + +@mcp.tool() +def hello() -> str: + """A simple hello tool""" + return "Hello from MCP!" + + +# Mount the StreamableHTTP server to the existing ASGI server +app = Starlette( + routes=[ + Mount("/", app=mcp.streamable_http_app()), + ] +) diff --git a/examples/snippets/servers/streamable_http_host_mounting.py b/examples/snippets/servers/streamable_http_host_mounting.py new file mode 100644 index 000000000..d48558cc8 --- /dev/null +++ b/examples/snippets/servers/streamable_http_host_mounting.py @@ -0,0 +1,28 @@ +""" +Example showing how to mount StreamableHTTP server using Host-based routing. + +Run from the repository root: + uvicorn examples.snippets.servers.streamable_http_host_mounting:app --reload +""" + +from starlette.applications import Starlette +from starlette.routing import Host + +from mcp.server.fastmcp import FastMCP + +# Create MCP server +mcp = FastMCP("MCP Host App") + + +@mcp.tool() +def domain_info() -> str: + """Get domain-specific information""" + return "This is served from mcp.acme.corp" + + +# Mount using Host-based routing +app = Starlette( + routes=[ + Host("mcp.acme.corp", app=mcp.streamable_http_app()), + ] +) diff --git a/examples/snippets/servers/streamable_http_mounting.py b/examples/snippets/servers/streamable_http_mounting.py deleted file mode 100644 index 945bdb4c7..000000000 --- a/examples/snippets/servers/streamable_http_mounting.py +++ /dev/null @@ -1,72 +0,0 @@ -""" -Example showing how to mount StreamableHTTP servers in Starlette applications. - -Run from the repository root: - uvicorn examples.snippets.servers.streamable_http_mounting:app --reload -""" - -from starlette.applications import Starlette -from starlette.routing import Host, Mount - -from mcp.server.fastmcp import FastMCP - -# Basic example - mounting at root -mcp = FastMCP("My App") - - -@mcp.tool() -def hello() -> str: - """A simple hello tool""" - return "Hello from MCP!" - - -# Mount the StreamableHTTP server to the existing ASGI server -app = Starlette( - routes=[ - Mount("/", app=mcp.streamable_http_app()), - ] -) - -# or dynamically mount as host -app.router.routes.append(Host("mcp.acme.corp", app=mcp.streamable_http_app())) - -# Advanced example - multiple servers with path configuration -# Create multiple MCP servers -api_mcp = FastMCP("API Server") -chat_mcp = FastMCP("Chat Server") - - -@api_mcp.tool() -def api_status() -> str: - """Get API status""" - return "API is running" - - -@chat_mcp.tool() -def send_message(message: str) -> str: - """Send a chat message""" - return f"Message sent: {message}" - - -# Default behavior: endpoints will be at /api/mcp and /chat/mcp -default_app = Starlette( - routes=[ - Mount("/api", app=api_mcp.streamable_http_app()), - Mount("/chat", app=chat_mcp.streamable_http_app()), - ] -) - -# To mount at the root of each path (e.g., /api instead of /api/mcp): -# Configure streamable_http_path before mounting -api_mcp.settings.streamable_http_path = "/" -chat_mcp.settings.streamable_http_path = "/" - -configured_app = Starlette( - routes=[ - Mount("/api", app=api_mcp.streamable_http_app()), - Mount("/chat", app=chat_mcp.streamable_http_app()), - ] -) - -# Or configure during initialization -mcp_at_root = FastMCP("My Server", streamable_http_path="/") diff --git a/examples/snippets/servers/streamable_http_multiple_servers.py b/examples/snippets/servers/streamable_http_multiple_servers.py new file mode 100644 index 000000000..df347b7b3 --- /dev/null +++ b/examples/snippets/servers/streamable_http_multiple_servers.py @@ -0,0 +1,41 @@ +""" +Example showing how to mount multiple StreamableHTTP servers with path configuration. + +Run from the repository root: + uvicorn examples.snippets.servers.streamable_http_multiple_servers:app --reload +""" + +from starlette.applications import Starlette +from starlette.routing import Mount + +from mcp.server.fastmcp import FastMCP + +# Create multiple MCP servers +api_mcp = FastMCP("API Server") +chat_mcp = FastMCP("Chat Server") + + +@api_mcp.tool() +def api_status() -> str: + """Get API status""" + return "API is running" + + +@chat_mcp.tool() +def send_message(message: str) -> str: + """Send a chat message""" + return f"Message sent: {message}" + + +# Configure servers to mount at the root of each path +# This means endpoints will be at /api and /chat instead of /api/mcp and /chat/mcp +api_mcp.settings.streamable_http_path = "/" +chat_mcp.settings.streamable_http_path = "/" + +# Mount the servers +app = Starlette( + routes=[ + Mount("/api", app=api_mcp.streamable_http_app()), + Mount("/chat", app=chat_mcp.streamable_http_app()), + ] +) diff --git a/examples/snippets/servers/streamable_http_path_config.py b/examples/snippets/servers/streamable_http_path_config.py new file mode 100644 index 000000000..71228423e --- /dev/null +++ b/examples/snippets/servers/streamable_http_path_config.py @@ -0,0 +1,29 @@ +""" +Example showing path configuration during FastMCP initialization. + +Run from the repository root: + uvicorn examples.snippets.servers.streamable_http_path_config:app --reload +""" + +from starlette.applications import Starlette +from starlette.routing import Mount + +from mcp.server.fastmcp import FastMCP + +# Configure streamable_http_path during initialization +# This server will mount at the root of wherever it's mounted +mcp_at_root = FastMCP("My Server", streamable_http_path="/") + + +@mcp_at_root.tool() +def process_data(data: str) -> str: + """Process some data""" + return f"Processed: {data}" + + +# Mount at /process - endpoints will be at /process instead of /process/mcp +app = Starlette( + routes=[ + Mount("/process", app=mcp_at_root.streamable_http_app()), + ] +)