Skip to content

Commit aa4bc93

Browse files
committed
fix: resolve memory leak in stateless StreamableHTTP sessions
Each stateless request was spawning a long-lived task in the global task group without proper cleanup, causing memory accumulation. This fix creates request-scoped task groups that are properly cleaned up after each request. Fixes:#1221
1 parent 34e3664 commit aa4bc93

File tree

1 file changed

+28
-25
lines changed

1 file changed

+28
-25
lines changed

src/mcp/server/streamable_http_manager.py

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -166,31 +166,34 @@ async def _handle_stateless_request(
166166
security_settings=self.security_settings,
167167
)
168168

169-
# Start server in a new task
170-
async def run_stateless_server(*, task_status: TaskStatus[None] = anyio.TASK_STATUS_IGNORED):
171-
async with http_transport.connect() as streams:
172-
read_stream, write_stream = streams
173-
task_status.started()
174-
try:
175-
await self.app.run(
176-
read_stream,
177-
write_stream,
178-
self.app.create_initialization_options(),
179-
stateless=True,
180-
)
181-
except Exception:
182-
logger.exception("Stateless session crashed")
183-
184-
# Assert task group is not None for type checking
185-
assert self._task_group is not None
186-
# Start the server task
187-
await self._task_group.start(run_stateless_server)
188-
189-
# Handle the HTTP request and return the response
190-
await http_transport.handle_request(scope, receive, send)
191-
192-
# Terminate the transport after the request is handled
193-
await http_transport.terminate()
169+
# Use a separate task group
170+
async with anyio.create_task_group() as request_tg:
171+
172+
async def run_stateless_server(*, task_status: TaskStatus[None] = anyio.TASK_STATUS_IGNORED):
173+
async with http_transport.connect() as streams:
174+
read_stream, write_stream = streams
175+
task_status.started()
176+
try:
177+
await self.app.run(
178+
read_stream,
179+
write_stream,
180+
self.app.create_initialization_options(),
181+
stateless=True,
182+
)
183+
except Exception:
184+
logger.exception("Stateless session crashed")
185+
186+
# Start the server task in the request-scoped task group
187+
await request_tg.start(run_stateless_server)
188+
189+
try:
190+
# Handle the HTTP request and return the response
191+
await http_transport.handle_request(scope, receive, send)
192+
finally:
193+
# Terminate the transport after the request is handled
194+
await http_transport.terminate()
195+
# Cancel the task group to ensure the server task is cleaned up
196+
request_tg.cancel_scope.cancel()
194197

195198
async def _handle_stateful_request(
196199
self,

0 commit comments

Comments
 (0)