Skip to content

Fix SSE timeout hang by propagating transport exceptions to pending r…#2430

Open
widgetwalker wants to merge 3 commits intomodelcontextprotocol:mainfrom
widgetwalker:fix-sse-timeout-hang
Open

Fix SSE timeout hang by propagating transport exceptions to pending r…#2430
widgetwalker wants to merge 3 commits intomodelcontextprotocol:mainfrom
widgetwalker:fix-sse-timeout-hang

Conversation

@widgetwalker
Copy link
Copy Markdown

Fixed SSE timeout hang in BaseSession._receive_loop. This fix propagates transport-level exceptions (like SSE read timeouts) to all pending request streams, ensuring RPC calls don't hang if the underlying connection fails. This specifically addresses issue #1401.

…equests

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.
Copilot AI review requested due to automatic review settings April 13, 2026 08:00
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR targets issue #1401 by ensuring that transport-level exceptions (e.g., SSE read timeouts) don’t leave in-flight RPC calls hanging: when the read stream surfaces an Exception, the session should wake/fail all pending request waiters.

Changes:

  • Extend BaseSession._receive_loop to broadcast transport exceptions to all pending _response_streams.
  • Convert exceptions into JSONRPCError objects and send them to per-request waiters so send_request() unblocks.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +431 to +445

# 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))

Comment on lines +431 to +445

# 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))

Comment on lines +435 to +445
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))

Comment on lines +431 to 447

# 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

widgetwalker and others added 2 commits April 13, 2026 13:42
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants