Skip to content

fix: propagate SSE stream errors to waiting requests#2628

Closed
internet-dot wants to merge 1 commit into
modelcontextprotocol:mainfrom
internet-dot:fix/sse-error-propagation-1401
Closed

fix: propagate SSE stream errors to waiting requests#2628
internet-dot wants to merge 1 commit into
modelcontextprotocol:mainfrom
internet-dot:fix/sse-error-propagation-1401

Conversation

@internet-dot
Copy link
Copy Markdown

Summary

Fixes #1401. Also fixes #1789 (closed as duplicate).

When an SSE read timeout occurs during a StreamableHTTP POST request, the pending send_request call hangs indefinitely. The transport catches the exception but never sends an error back through the read stream, leaving the caller blocked on response_stream_reader.receive() with nothing to receive.

This PR fixes error propagation at the transport level so that SSE stream failures produce a JSONRPCError keyed to the original request ID. BaseSession._handle_response routes it to the correct per-request response stream, and send_request surfaces it as MCPError to the caller. This approach keeps failures isolated to the affected request rather than tearing down the entire session.

What changed

  • _handle_sse_response now sends a JSONRPCError(INTERNAL_ERROR, "SSE stream ended without a response") when the SSE stream ends without delivering a complete response, whether due to a read timeout, network error, or unexpected server close. If a last_event_id was received, reconnection is attempted first; the error is only sent after reconnection is exhausted.

  • _handle_reconnection returns bool instead of None so callers can distinguish success (response delivered) from failure (attempts exhausted). The method also fixes an infinite recursion bug: the attempt counter was reset to 0 on every stream end (even when no complete response was delivered), which combined with httpx read timeouts causing graceful stream termination meant the reconnection loop could run forever.

  • handle_get_stream applies the same fix to the GET stream's reconnection loop: the attempt counter only resets when events were actually received during the connection. Empty connections that close immediately count toward MAX_RECONNECTION_ATTEMPTS.

  • _default_message_handler now logs a warning for exceptions instead of silently discarding them, providing observability for transport errors not tied to a specific request.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • I have added appropriate error handling

Fixes modelcontextprotocol#1401. Also fixes modelcontextprotocol#1789 (closed as duplicate).

When an SSE read timeout occurs during a StreamableHTTP POST request, the
pending send_request call hangs indefinitely. The transport catches the
exception but never sends an error back through the read stream, leaving
the caller blocked on response_stream_reader.receive() with nothing to
receive.

This fix propagates SSE stream failures as JSONRPCError to the waiting
request, keeping failures isolated to the affected request rather than
tearing down the entire session.

Changes:
- _handle_sse_response now sends JSONRPCError when SSE stream ends without
  a complete response
- _handle_reconnection returns bool to indicate success/failure
- handle_get_stream tracks received_events to properly count reconnection
  attempts
- _default_message_handler logs warnings for unhandled exceptions
@maxisbey
Copy link
Copy Markdown
Contributor

Closing as a duplicate of #2122, which has the same change plus tests.

AI Disclaimer

@maxisbey maxisbey closed this May 18, 2026
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.

ClientSession Error Handling Client hangs forever

2 participants