-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Description
Summary
The official README example for basic mounting the StreamableHTTP server to an existing Starlette app does not work as written on recent Starlette versions (e.g. 0.48).
When running the code exactly as in the README:
from starlette.applications import Starlette
from starlette.routing import Mount
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("My App")
@mcp.tool()
def hello() -> str:
return "Hello from MCP!"
app = Starlette(
routes=[
Mount("/", app=mcp.streamable_http_app()),
]
)and launching via:
uvicorn server:appthe following runtime error occurs when the MCP client connects:
RuntimeError: Task group is not initialized. Make sure to use run().
Environment
- Starlette: 0.48.0
- Uvicorn: 0.37.0
- Python: 3.13
- mcp-sdk (python-sdk): 1.17.0
Expected Behavior
The example should run without errors, serving the StreamableHTTP transport correctly under /.
Actual Behavior
The mounted streamable_http_app() never executes its lifespan startup context (session_manager.run()), so the internal task group remains uninitialized.
As a result, any request to / raises:
RuntimeError: Task group is not initialized. Make sure to use run().
Root Cause
FastMCP.streamable_http_app() embeds its session manager inside the app’s lifespan, but when mounted with Mount("/", app=...), Starlette (0.48) does not automatically execute sub-app lifespans.
Therefore, the session manager never enters its run() context.
Workarounds
-
Run directly without mounting:
app = mcp.streamable_http_app()
-
Or explicitly run the session manager in the parent lifespan:
child = mcp.streamable_http_app() async def lifespan(app): async with mcp.session_manager.run(): yield app = Starlette(lifespan=lifespan, routes=[Mount("/", app=child)])