From 0a1aab2a49e7cb4febdce534af7a30c1e9a3412a Mon Sep 17 00:00:00 2001 From: TheMailmans Date: Tue, 25 Nov 2025 12:54:35 -0500 Subject: [PATCH 1/2] fix: add lifespan context manager to StreamableHTTP mounting examples The ASGI mounting examples were missing the lifespan context manager needed to properly initialize the session manager. Without this, requests fail with "RuntimeError: Task group is not initialized". This adds the proper lifespan pattern to: - streamable_http_basic_mounting.py - streamable_http_host_mounting.py - streamable_http_multiple_servers.py Github-Issue:#1484 --- .../servers/streamable_http_basic_mounting.py | 12 +++++++++++- .../servers/streamable_http_host_mounting.py | 12 +++++++++++- .../servers/streamable_http_multiple_servers.py | 15 ++++++++++++++- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/examples/snippets/servers/streamable_http_basic_mounting.py b/examples/snippets/servers/streamable_http_basic_mounting.py index d4813ade57..74aa36ed4f 100644 --- a/examples/snippets/servers/streamable_http_basic_mounting.py +++ b/examples/snippets/servers/streamable_http_basic_mounting.py @@ -5,6 +5,8 @@ uvicorn examples.snippets.servers.streamable_http_basic_mounting:app --reload """ +import contextlib + from starlette.applications import Starlette from starlette.routing import Mount @@ -20,9 +22,17 @@ def hello() -> str: return "Hello from MCP!" +# Create a lifespan context manager to run the session manager +@contextlib.asynccontextmanager +async def lifespan(app: Starlette): + async with mcp.session_manager.run(): + yield + + # Mount the StreamableHTTP server to the existing ASGI server app = Starlette( routes=[ Mount("/", app=mcp.streamable_http_app()), - ] + ], + lifespan=lifespan, ) diff --git a/examples/snippets/servers/streamable_http_host_mounting.py b/examples/snippets/servers/streamable_http_host_mounting.py index ee8fd1d4a0..3ae9d341e1 100644 --- a/examples/snippets/servers/streamable_http_host_mounting.py +++ b/examples/snippets/servers/streamable_http_host_mounting.py @@ -5,6 +5,8 @@ uvicorn examples.snippets.servers.streamable_http_host_mounting:app --reload """ +import contextlib + from starlette.applications import Starlette from starlette.routing import Host @@ -20,9 +22,17 @@ def domain_info() -> str: return "This is served from mcp.acme.corp" +# Create a lifespan context manager to run the session manager +@contextlib.asynccontextmanager +async def lifespan(app: Starlette): + async with mcp.session_manager.run(): + yield + + # Mount using Host-based routing app = Starlette( routes=[ Host("mcp.acme.corp", app=mcp.streamable_http_app()), - ] + ], + lifespan=lifespan, ) diff --git a/examples/snippets/servers/streamable_http_multiple_servers.py b/examples/snippets/servers/streamable_http_multiple_servers.py index 7a099acc94..8d0a1018d2 100644 --- a/examples/snippets/servers/streamable_http_multiple_servers.py +++ b/examples/snippets/servers/streamable_http_multiple_servers.py @@ -5,6 +5,8 @@ uvicorn examples.snippets.servers.streamable_http_multiple_servers:app --reload """ +import contextlib + from starlette.applications import Starlette from starlette.routing import Mount @@ -32,10 +34,21 @@ def send_message(message: str) -> str: api_mcp.settings.streamable_http_path = "/" chat_mcp.settings.streamable_http_path = "/" + +# Create a combined lifespan to manage both session managers +@contextlib.asynccontextmanager +async def lifespan(app: Starlette): + async with contextlib.AsyncExitStack() as stack: + await stack.enter_async_context(api_mcp.session_manager.run()) + await stack.enter_async_context(chat_mcp.session_manager.run()) + yield + + # Mount the servers app = Starlette( routes=[ Mount("/api", app=api_mcp.streamable_http_app()), Mount("/chat", app=chat_mcp.streamable_http_app()), - ] + ], + lifespan=lifespan, ) From c605a70f67d2d5b510561cb1fb2807fc02c54f62 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Wed, 3 Dec 2025 22:50:42 +0100 Subject: [PATCH 2/2] Update README --- README.md | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 166fc52fa7..a439184640 100644 --- a/README.md +++ b/README.md @@ -1394,6 +1394,8 @@ Run from the repository root: uvicorn examples.snippets.servers.streamable_http_basic_mounting:app --reload """ +import contextlib + from starlette.applications import Starlette from starlette.routing import Mount @@ -1409,11 +1411,19 @@ def hello() -> str: return "Hello from MCP!" +# Create a lifespan context manager to run the session manager +@contextlib.asynccontextmanager +async def lifespan(app: Starlette): + async with mcp.session_manager.run(): + yield + + # Mount the StreamableHTTP server to the existing ASGI server app = Starlette( routes=[ Mount("/", app=mcp.streamable_http_app()), - ] + ], + lifespan=lifespan, ) ``` @@ -1431,6 +1441,8 @@ Run from the repository root: uvicorn examples.snippets.servers.streamable_http_host_mounting:app --reload """ +import contextlib + from starlette.applications import Starlette from starlette.routing import Host @@ -1446,11 +1458,19 @@ def domain_info() -> str: return "This is served from mcp.acme.corp" +# Create a lifespan context manager to run the session manager +@contextlib.asynccontextmanager +async def lifespan(app: Starlette): + async with mcp.session_manager.run(): + yield + + # Mount using Host-based routing app = Starlette( routes=[ Host("mcp.acme.corp", app=mcp.streamable_http_app()), - ] + ], + lifespan=lifespan, ) ``` @@ -1468,6 +1488,8 @@ Run from the repository root: uvicorn examples.snippets.servers.streamable_http_multiple_servers:app --reload """ +import contextlib + from starlette.applications import Starlette from starlette.routing import Mount @@ -1495,12 +1517,23 @@ def send_message(message: str) -> str: api_mcp.settings.streamable_http_path = "/" chat_mcp.settings.streamable_http_path = "/" + +# Create a combined lifespan to manage both session managers +@contextlib.asynccontextmanager +async def lifespan(app: Starlette): + async with contextlib.AsyncExitStack() as stack: + await stack.enter_async_context(api_mcp.session_manager.run()) + await stack.enter_async_context(chat_mcp.session_manager.run()) + yield + + # Mount the servers app = Starlette( routes=[ Mount("/api", app=api_mcp.streamable_http_app()), Mount("/chat", app=chat_mcp.streamable_http_app()), - ] + ], + lifespan=lifespan, ) ```