44
55import contextlib
66import logging
7+ import threading
78from collections .abc import AsyncIterator
89from http import HTTPStatus
910from 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