Skip to content

Commit d99d1b5

Browse files
weiguangli-ioclaude
andcommitted
fix: add exception handling in StreamableHTTP shutdown
Update the shutdown logic to wrap terminate() calls in try-except blocks, preventing one transport's termination error from affecting others. Changes: - Use logger.exception() instead of logger.debug() for better error visibility - Simplify SSE writer closing by iterating values() directly instead of pop() - Improve code comments to explain why graceful termination is needed This follows the same approach as PR #2259 with minor improvements in error handling. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 20f8acb commit d99d1b5

File tree

2 files changed

+12
-8
lines changed

2 files changed

+12
-8
lines changed

src/mcp/server/streamable_http.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -784,12 +784,11 @@ async def terminate(self) -> None:
784784
self._terminated = True
785785
logger.info(f"Terminating session: {self.mcp_session_id}")
786786

787-
# Close all SSE stream writers
788-
sse_stream_writer_keys = list(self._sse_stream_writers.keys())
789-
for key in sse_stream_writer_keys: # pragma: no cover
790-
writer = self._sse_stream_writers.pop(key, None)
791-
if writer:
792-
writer.close()
787+
# Close all SSE stream writers so that active EventSourceResponse
788+
# coroutines complete gracefully instead of being cancelled mid-stream.
789+
for writer in list(self._sse_stream_writers.values()):
790+
writer.close()
791+
self._sse_stream_writers.clear()
793792

794793
# We need a copy of the keys to avoid modification during iteration
795794
request_stream_keys = list(self._request_streams.keys())

src/mcp/server/streamable_http_manager.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,14 @@ async def lifespan(app: Starlette) -> AsyncIterator[None]:
130130
yield # Let the application run
131131
finally:
132132
logger.info("StreamableHTTP session manager shutting down")
133-
# Terminate all active server instances before cancelling tasks
133+
# Gracefully terminate all active sessions before cancelling
134+
# tasks so that EventSourceResponse coroutines can complete
135+
# and Uvicorn does not log ASGI-incomplete-response errors.
134136
for transport in list(self._server_instances.values()):
135-
await transport.terminate()
137+
try:
138+
await transport.terminate()
139+
except Exception:
140+
logger.exception("Error terminating transport during shutdown")
136141
# Cancel task group to stop all spawned tasks
137142
tg.cancel_scope.cancel()
138143
self._task_group = None

0 commit comments

Comments
 (0)