From 6b85e59774fab37ee4a97e1f52cc2745968788a8 Mon Sep 17 00:00:00 2001 From: widgetwalker-username Date: Mon, 13 Apr 2026 13:29:09 +0530 Subject: [PATCH 1/3] Fix SSE timeout hang by propagating transport exceptions to pending requests This fix ensures that ClientSession.request() does not hang indefinitely when the underlying SSE transport encounters a timeout or other fatal exception before the RPC response is received. It propagates the exception to all in-flight request streams, waking up waiters immediately. --- src/mcp/shared/session.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/mcp/shared/session.py b/src/mcp/shared/session.py index 243eef5ae..096fa4933 100644 --- a/src/mcp/shared/session.py +++ b/src/mcp/shared/session.py @@ -428,6 +428,21 @@ async def _handle_session_message(message: SessionMessage) -> None: async for message in self._read_stream: if isinstance(message, Exception): await self._handle_incoming(message) + + # Fix #1401: Propagate exception to all pending requests + # This prevents waiters from hanging when the transport fails + error_data = ( + message.to_error_data() + if isinstance(message, MCPError) + else ErrorData(code=0, message=str(message)) + ) + jsonrpc_error = JSONRPCError(jsonrpc="2.0", id=None, error=error_data) # id=None because it applies to all + + # We must send an error to every individual waiter + for req_id, stream in list(self._response_streams.items()): + # Send a response with the correct ID + await stream.send(JSONRPCError(jsonrpc="2.0", id=req_id, error=error_data)) + continue await _handle_session_message(message) From 58aca0449d4937b6c85bee0aae19f5769ade8d9e Mon Sep 17 00:00:00 2001 From: widgetwalaker <102951070+widgetwalker@users.noreply.github.com> Date: Mon, 13 Apr 2026 13:42:09 +0530 Subject: [PATCH 2/3] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/mcp/shared/session.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/mcp/shared/session.py b/src/mcp/shared/session.py index 096fa4933..3a056ba2a 100644 --- a/src/mcp/shared/session.py +++ b/src/mcp/shared/session.py @@ -432,17 +432,16 @@ async def _handle_session_message(message: SessionMessage) -> None: # Fix #1401: Propagate exception to all pending requests # This prevents waiters from hanging when the transport fails error_data = ( - message.to_error_data() - if isinstance(message, MCPError) + message.to_error_data() + if isinstance(message, MCPError) else ErrorData(code=0, message=str(message)) ) - jsonrpc_error = JSONRPCError(jsonrpc="2.0", id=None, error=error_data) # id=None because it applies to all - + # We must send an error to every individual waiter for req_id, stream in list(self._response_streams.items()): # Send a response with the correct ID await stream.send(JSONRPCError(jsonrpc="2.0", id=req_id, error=error_data)) - + continue await _handle_session_message(message) From 711aecdfffb1bcf016fc192959730330406de7d3 Mon Sep 17 00:00:00 2001 From: widgetwalker-username Date: Mon, 13 Apr 2026 13:44:17 +0530 Subject: [PATCH 3/3] Refine fix: explicitly pop and close response streams to avoid double-processing --- src/mcp/shared/session.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/mcp/shared/session.py b/src/mcp/shared/session.py index 3a056ba2a..c89626750 100644 --- a/src/mcp/shared/session.py +++ b/src/mcp/shared/session.py @@ -439,9 +439,13 @@ async def _handle_session_message(message: SessionMessage) -> None: # We must send an error to every individual waiter for req_id, stream in list(self._response_streams.items()): - # Send a response with the correct ID - await stream.send(JSONRPCError(jsonrpc="2.0", id=req_id, error=error_data)) - + try: + # Send a response with the correct ID + await stream.send(JSONRPCError(jsonrpc="2.0", id=req_id, error=error_data)) + finally: + # Ensure we clean up the stream so finally block doesn't double-handle + self._response_streams.pop(req_id, None) + await stream.aclose() continue await _handle_session_message(message)