Skip to content

Commit 69f2ab9

Browse files
committed
Fix: Add missing lifespan parameter to StreamableHTTP mounting examples
Fixes #1484 - Add contextlib import and lifespan function to all mounting examples - Add stateless_http=True parameter to FastMCP constructors - Add lifespan=lifespan parameter to Starlette apps - Update README.md documentation with correct examples - Fixes RuntimeError: Task group is not initialized. Make sure to use run() Resolves the issue where mounting StreamableHTTP servers to existing ASGI applications would fail with RuntimeError due to missing session manager initialization.
1 parent 40acbc5 commit 69f2ab9

File tree

5 files changed

+101
-18
lines changed

5 files changed

+101
-18
lines changed

README.md

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1261,27 +1261,35 @@ Basic example showing how to mount StreamableHTTP server in Starlette.
12611261
Run from the repository root:
12621262
uvicorn examples.snippets.servers.streamable_http_basic_mounting:app --reload
12631263
"""
1264+
import contextlib
12641265

12651266
from starlette.applications import Starlette
12661267
from starlette.routing import Mount
12671268

12681269
from mcp.server.fastmcp import FastMCP
12691270

12701271
# Create MCP server
1271-
mcp = FastMCP("My App")
1272+
mcp = FastMCP("My App", stateless_http=True)
12721273

12731274

12741275
@mcp.tool()
12751276
def hello() -> str:
12761277
"""A simple hello tool"""
12771278
return "Hello from MCP!"
12781279

1280+
# Create lifespan context manager to initialize the session manager
1281+
@contextlib.asynccontextmanager
1282+
async def lifespan(app: Starlette):
1283+
"""Context manager for managing MCP session manager lifecycle."""
1284+
async with mcp.session_manager.run():
1285+
yield
12791286

12801287
# Mount the StreamableHTTP server to the existing ASGI server
12811288
app = Starlette(
12821289
routes=[
12831290
Mount("/", app=mcp.streamable_http_app()),
1284-
]
1291+
],
1292+
lifespan=lifespan,
12851293
)
12861294
```
12871295

@@ -1298,27 +1306,35 @@ Example showing how to mount StreamableHTTP server using Host-based routing.
12981306
Run from the repository root:
12991307
uvicorn examples.snippets.servers.streamable_http_host_mounting:app --reload
13001308
"""
1309+
import contextlib
13011310

13021311
from starlette.applications import Starlette
13031312
from starlette.routing import Host
13041313

13051314
from mcp.server.fastmcp import FastMCP
13061315

13071316
# Create MCP server
1308-
mcp = FastMCP("MCP Host App")
1317+
mcp = FastMCP("MCP Host App", stateless_http=True)
13091318

13101319

13111320
@mcp.tool()
13121321
def domain_info() -> str:
13131322
"""Get domain-specific information"""
13141323
return "This is served from mcp.acme.corp"
13151324

1325+
# Create lifespan context manager to initialize the session manager
1326+
@contextlib.asynccontextmanager
1327+
async def lifespan(app: Starlette):
1328+
"""Context manager for managing MCP session manager lifecycle."""
1329+
async with mcp.session_manager.run():
1330+
yield
13161331

13171332
# Mount using Host-based routing
13181333
app = Starlette(
13191334
routes=[
13201335
Host("mcp.acme.corp", app=mcp.streamable_http_app()),
1321-
]
1336+
],
1337+
lifespan=lifespan,
13221338
)
13231339
```
13241340

@@ -1335,15 +1351,16 @@ Example showing how to mount multiple StreamableHTTP servers with path configura
13351351
Run from the repository root:
13361352
uvicorn examples.snippets.servers.streamable_http_multiple_servers:app --reload
13371353
"""
1354+
import contextlib
13381355

13391356
from starlette.applications import Starlette
13401357
from starlette.routing import Mount
13411358

13421359
from mcp.server.fastmcp import FastMCP
13431360

13441361
# Create multiple MCP servers
1345-
api_mcp = FastMCP("API Server")
1346-
chat_mcp = FastMCP("Chat Server")
1362+
api_mcp = FastMCP("API Server", stateless_http=True)
1363+
chat_mcp = FastMCP("Chat Server", stateless_http=True)
13471364

13481365

13491366
@api_mcp.tool()
@@ -1363,12 +1380,22 @@ def send_message(message: str) -> str:
13631380
api_mcp.settings.streamable_http_path = "/"
13641381
chat_mcp.settings.streamable_http_path = "/"
13651382

1383+
# Create lifespan context manager to initialize both session managers
1384+
@contextlib.asynccontextmanager
1385+
async def lifespan(app: Starlette):
1386+
"""Context manager for managing multiple MCP session managers."""
1387+
async with contextlib.AsyncExitStack() as stack:
1388+
await stack.enter_async_context(api_mcp.session_manager.run())
1389+
await stack.enter_async_context(chat_mcp.session_manager.run())
1390+
yield
1391+
13661392
# Mount the servers
13671393
app = Starlette(
13681394
routes=[
13691395
Mount("/api", app=api_mcp.streamable_http_app()),
13701396
Mount("/chat", app=chat_mcp.streamable_http_app()),
1371-
]
1397+
],
1398+
lifespan=lifespan,
13721399
)
13731400
```
13741401

@@ -1385,6 +1412,7 @@ Example showing path configuration during FastMCP initialization.
13851412
Run from the repository root:
13861413
uvicorn examples.snippets.servers.streamable_http_path_config:app --reload
13871414
"""
1415+
import contextlib
13881416

13891417
from starlette.applications import Starlette
13901418
from starlette.routing import Mount
@@ -1393,7 +1421,7 @@ from mcp.server.fastmcp import FastMCP
13931421

13941422
# Configure streamable_http_path during initialization
13951423
# This server will mount at the root of wherever it's mounted
1396-
mcp_at_root = FastMCP("My Server", streamable_http_path="/")
1424+
mcp_at_root = FastMCP("My Server", streamable_http_path="/", stateless_http=True)
13971425

13981426

13991427
@mcp_at_root.tool()
@@ -1402,11 +1430,19 @@ def process_data(data: str) -> str:
14021430
return f"Processed: {data}"
14031431

14041432

1433+
# Create lifespan context manager to initialize the session manager
1434+
@contextlib.asynccontextmanager
1435+
async def lifespan(app: Starlette):
1436+
"""Context manager for managing MCP session manager lifecycle."""
1437+
async with mcp_at_root.session_manager.run():
1438+
yield
1439+
14051440
# Mount at /process - endpoints will be at /process instead of /process/mcp
14061441
app = Starlette(
14071442
routes=[
14081443
Mount("/process", app=mcp_at_root.streamable_http_app()),
1409-
]
1444+
],
1445+
lifespan=lifespan,
14101446
)
14111447
```
14121448

examples/snippets/servers/streamable_http_basic_mounting.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
uvicorn examples.snippets.servers.streamable_http_basic_mounting:app --reload
66
"""
77

8+
import contextlib
9+
810
from starlette.applications import Starlette
911
from starlette.routing import Mount
1012

1113
from mcp.server.fastmcp import FastMCP
1214

1315
# Create MCP server
14-
mcp = FastMCP("My App")
16+
mcp = FastMCP("My App", stateless_http=True)
1517

1618

1719
@mcp.tool()
@@ -20,9 +22,18 @@ def hello() -> str:
2022
return "Hello from MCP!"
2123

2224

25+
# Create lifespan context manager to initialize the session manager
26+
@contextlib.asynccontextmanager
27+
async def lifespan(app: Starlette):
28+
"""Context manager for managing MCP session manager lifecycle."""
29+
async with mcp.session_manager.run():
30+
yield
31+
32+
2333
# Mount the StreamableHTTP server to the existing ASGI server
2434
app = Starlette(
2535
routes=[
2636
Mount("/", app=mcp.streamable_http_app()),
27-
]
37+
],
38+
lifespan=lifespan,
2839
)

examples/snippets/servers/streamable_http_host_mounting.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
uvicorn examples.snippets.servers.streamable_http_host_mounting:app --reload
66
"""
77

8+
import contextlib
9+
810
from starlette.applications import Starlette
911
from starlette.routing import Host
1012

1113
from mcp.server.fastmcp import FastMCP
1214

1315
# Create MCP server
14-
mcp = FastMCP("MCP Host App")
16+
mcp = FastMCP("MCP Host App", stateless_http=True)
1517

1618

1719
@mcp.tool()
@@ -20,9 +22,18 @@ def domain_info() -> str:
2022
return "This is served from mcp.acme.corp"
2123

2224

25+
# Create lifespan context manager to initialize the session manager
26+
@contextlib.asynccontextmanager
27+
async def lifespan(app: Starlette):
28+
"""Context manager for managing MCP session manager lifecycle."""
29+
async with mcp.session_manager.run():
30+
yield
31+
32+
2333
# Mount using Host-based routing
2434
app = Starlette(
2535
routes=[
2636
Host("mcp.acme.corp", app=mcp.streamable_http_app()),
27-
]
37+
],
38+
lifespan=lifespan,
2839
)

examples/snippets/servers/streamable_http_multiple_servers.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@
55
uvicorn examples.snippets.servers.streamable_http_multiple_servers:app --reload
66
"""
77

8+
import contextlib
9+
810
from starlette.applications import Starlette
911
from starlette.routing import Mount
1012

1113
from mcp.server.fastmcp import FastMCP
1214

1315
# Create multiple MCP servers
14-
api_mcp = FastMCP("API Server")
15-
chat_mcp = FastMCP("Chat Server")
16+
api_mcp = FastMCP("API Server", stateless_http=True)
17+
chat_mcp = FastMCP("Chat Server", stateless_http=True)
1618

1719

1820
@api_mcp.tool()
@@ -32,10 +34,22 @@ def send_message(message: str) -> str:
3234
api_mcp.settings.streamable_http_path = "/"
3335
chat_mcp.settings.streamable_http_path = "/"
3436

37+
38+
# Create lifespan context manager to initialize both session managers
39+
@contextlib.asynccontextmanager
40+
async def lifespan(app: Starlette):
41+
"""Context manager for managing multiple MCP session managers."""
42+
async with contextlib.AsyncExitStack() as stack:
43+
await stack.enter_async_context(api_mcp.session_manager.run())
44+
await stack.enter_async_context(chat_mcp.session_manager.run())
45+
yield
46+
47+
3548
# Mount the servers
3649
app = Starlette(
3750
routes=[
3851
Mount("/api", app=api_mcp.streamable_http_app()),
3952
Mount("/chat", app=chat_mcp.streamable_http_app()),
40-
]
53+
],
54+
lifespan=lifespan,
4155
)

examples/snippets/servers/streamable_http_path_config.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@
55
uvicorn examples.snippets.servers.streamable_http_path_config:app --reload
66
"""
77

8+
import contextlib
9+
810
from starlette.applications import Starlette
911
from starlette.routing import Mount
1012

1113
from mcp.server.fastmcp import FastMCP
1214

1315
# Configure streamable_http_path during initialization
1416
# This server will mount at the root of wherever it's mounted
15-
mcp_at_root = FastMCP("My Server", streamable_http_path="/")
17+
mcp_at_root = FastMCP("My Server", streamable_http_path="/", stateless_http=True)
1618

1719

1820
@mcp_at_root.tool()
@@ -21,9 +23,18 @@ def process_data(data: str) -> str:
2123
return f"Processed: {data}"
2224

2325

26+
# Create lifespan context manager to initialize the session manager
27+
@contextlib.asynccontextmanager
28+
async def lifespan(app: Starlette):
29+
"""Context manager for managing MCP session manager lifecycle."""
30+
async with mcp_at_root.session_manager.run():
31+
yield
32+
33+
2434
# Mount at /process - endpoints will be at /process instead of /process/mcp
2535
app = Starlette(
2636
routes=[
2737
Mount("/process", app=mcp_at_root.streamable_http_app()),
28-
]
38+
],
39+
lifespan=lifespan,
2940
)

0 commit comments

Comments
 (0)