Skip to content

fix(client): contain streamable HTTP POST transport errors - #3584

Closed
RaulMermans wants to merge 1 commit into
modelcontextprotocol:mainfrom
RaulMermans:fix/1401-streamable-http-post-errors
Closed

RaulMermans wants to merge 1 commit into
modelcontextprotocol:mainfrom
RaulMermans:fix/1401-streamable-http-post-errors

Conversation

@RaulMermans

@RaulMermans RaulMermans commented Sep 25, 2026 •

Copy link
Copy Markdown

Fixes #1401

After connection, an httpx2.TransportError on one POST now fails only that message: a request gets MCPError(CONNECTION_CLOSED), a notification is dropped, and the session keeps working. Connect-time initialize / server/discover still raise the raw error.

Motivation and Context

The SSE-response hang from #1401 is already fixed on main (#2005, #3047). A related failure remained in StreamableHTTPTransport._handle_post_request:

Scenario on main Result
ReadTimeout on the POST itself, or while reading a JSON response body uncaught httpx2.TransportError crashes the transport task group; the whole session and every in-flight call is torn down
Same failure on a notification POST post_writer logs and exits; every later call fails with CONNECTION_CLOSED

_handle_post_request now catches only httpx2.TransportError:

  • Request: resolved with CONNECTION_CLOSED via _resolve_abandoned_request, naming the original exception.
  • Notification: dropped; post_writer keeps running.
  • Diagnostics: the exception is logged and forwarded on the read stream, so message_handler receives it.
  • initialize / server/discover: re-raised unchanged, so lifecycle:discover:network-error-raises holds.

No dispatcher, public API or error-type changes.

How Has This Been Tested?

New tests in tests/client/test_streamable_http.py cover:

  • a failed call gets CONNECTION_CLOSED and the next call succeeds;
  • a failed notification is dropped;
  • a concurrent call is unaffected;
  • connect-time errors still propagate raw.

The requirement test passes unmodified. The full suite passes except test_permission_error, which fails the same way on clean main when run as root. pyright and Ruff are clean.

Breaking Changes

None intended. Connect-time behaviour is unchanged.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • Documentation update

Checklist

  • I am assigned to the linked issue (or it is labeled help wanted, or I'm a maintainer)
  • I have disclosed any AI assistance and can explain the change in my own words
  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Open design point: whether the forwarded exception should reach message_handler or only be logged.

AI disclosure: I used AI assistance during the investigation and implementation. I reviewed the behavior, tests and design decisions and can explain and own the change.

An httpx2.TransportError raised while sending a POST or reading its
JSON body escaped _handle_post_request. For a request it crashed the
transport task group and tore down the whole session; for a
notification it ended post_writer, so every later call failed with
CONNECTION_CLOSED.

Contain it to the message that caused it: a request is resolved with
CONNECTION_CLOSED via _resolve_abandoned_request (the message names the
original exception), a notification is dropped, and the exception is
logged and forwarded on the read stream so message_handler sees it.

Connect-time initialize and server/discover still re-raise the raw
error: Client(mode="auto") relies on it to tell an outage from a legacy
server (lifecycle:discover:network-error-raises).

Refs modelcontextprotocol#1401

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MZ7D3Be8YGBE4jLDo4PFXJ
@github-actions github-actions Bot added the missing-issue-link Auto-closed: PR needs a linked issue assigned to its author (see CONTRIBUTING.md) label Sep 25, 2026
@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

This PR has been closed automatically. This repo only keeps pull requests open when they come from a maintainer, or from a contributor a maintainer has assigned to the linked issue, and you aren't currently assigned to #1401.

If a maintainer assigns you to #1401, this PR reopens on its own and there's nothing more you need to do here. Assignment is a maintainer call based on capacity; comments that only ask to be assigned don't factor in. What does help is engaging on the issue itself by confirming the repro, explaining why it matters for your use case, or describing the approach you'd take.

You're welcome to keep pushing commits here (just avoid force-pushing, since GitHub can't reopen a rewritten branch), but that on its own won't get the PR reviewed or the issue assigned, and realistically most auto-closed PRs stay closed. There's no need to open a new PR either way.

CONTRIBUTING.md has the full reasoning, but in short:

  • We're a small team with very little capacity to review community PRs right now.
  • Many recent PRs are AI-generated with little human review, and reviewing one carefully still costs a maintainer as much time as it ever did. A well-described issue is usually more useful to us than the code.

Maintainers: reopen, remove missing-issue-link, or add bypass-issue-check to override.

@github-actions github-actions Bot closed this Sep 25, 2026
@RaulMermans

Copy link
Copy Markdown
Author

I reproduced this against current main (f1b6589) using ClientSession over streamable_http_client.

The original SSE hang appears to be fixed by changes including #2005 and #3047, but a related POST-level transport failure remains:

Scenario Result on main
ReadTimeout while reading the SSE response to a request MCPError(CONNECTION_CLOSED), no hang
Same, resumable stream with event IDs reconnection attempts exhaust, then MCPError(CONNECTION_CLOSED), no hang
ReadTimeout on the POST itself, or while reading a JSON response body uncaught httpx2.TransportError tears down the transport/session
Same failure on a notification POST post_writer logs and exits; later requests fail with CONNECTION_CLOSED

Proposed fix, tested in #3584 (auto-closed pending assignment):

  • keep raw network errors during initialize and server/discover, so lifecycle:discover:network-error-raises is unchanged;
  • after connection, catch POST-level httpx2.TransportError in _handle_post_request;
  • settle only the affected request with CONNECTION_CLOSED via _resolve_abandoned_request, naming the original exception in the message;
  • drop failed notifications without ending post_writer;
  • leave concurrent and later requests usable;
  • log the original exception and forward it on the read stream so message_handler receives it.

Does this match the intended v2 behavior, in particular keeping the session alive after a per-POST failure and forwarding the original exception to message_handler?

Disclosure: I used AI assistance during the investigation and implementation. I reviewed the behavior, tests, and design decisions and can explain and own the change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

missing-issue-link Auto-closed: PR needs a linked issue assigned to its author (see CONTRIBUTING.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ClientSession Error Handling

2 participants