Skip to content

Commit 8c8d80d

Browse files
committed
add doc and make sure session manager can only be run once
1 parent 97fe920 commit 8c8d80d

File tree

1 file changed

+22
-3
lines changed

1 file changed

+22
-3
lines changed

src/mcp/server/streamable_http_manager.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import contextlib
66
import logging
7+
import threading
78
from collections.abc import AsyncIterator
89
from http import HTTPStatus
910
from typing import Any
@@ -37,6 +38,10 @@ class StreamableHTTPSessionManager:
3738
3. Connection management and lifecycle
3839
4. Request handling and transport setup
3940
41+
Important: Only one StreamableHTTPSessionManager instance should be created
42+
per application. The instance cannot be reused after its run() context has
43+
completed. If you need to restart the manager, create a new instance.
44+
4045
Args:
4146
app: The MCP server instance
4247
event_store: Optional event store for resumability support.
@@ -67,6 +72,9 @@ def __init__(
6772

6873
# The task group will be set during lifespan
6974
self._task_group = None
75+
# Thread-safe tracking of run() calls
76+
self._run_lock = threading.Lock()
77+
self._has_started = False
7078

7179
@contextlib.asynccontextmanager
7280
async def run(self) -> AsyncIterator[None]:
@@ -75,13 +83,26 @@ async def run(self) -> AsyncIterator[None]:
7583
7684
This creates and manages the task group for all session operations.
7785
86+
Important: This method can only be called once per instance. The same
87+
StreamableHTTPSessionManager instance cannot be reused after this
88+
context manager exits. Create a new instance if you need to restart.
89+
7890
Use this in the lifespan context manager of your Starlette app:
7991
8092
@contextlib.asynccontextmanager
8193
async def lifespan(app: Starlette) -> AsyncIterator[None]:
8294
async with session_manager.run():
8395
yield
8496
"""
97+
# Thread-safe check to ensure run() is only called once
98+
with self._run_lock:
99+
if self._has_started:
100+
raise RuntimeError(
101+
"StreamableHTTPSessionManager .run() can only be called "
102+
"once per instance. Create a new instance if you need to run again."
103+
)
104+
self._has_started = True
105+
85106
async with anyio.create_task_group() as tg:
86107
# Store the task group for later use
87108
self._task_group = tg
@@ -113,9 +134,7 @@ async def handle_request(
113134
send: ASGI send function
114135
"""
115136
if self._task_group is None:
116-
raise RuntimeError(
117-
"Task group is not initialized. Make sure to use the run()."
118-
)
137+
raise RuntimeError("Task group is not initialized. Make sure to use run().")
119138

120139
# Dispatch to the appropriate handler
121140
if self.stateless:

0 commit comments

Comments
 (0)