Skip to content

fix(client): contain HTTP transport errors on request POSTs - #3525

Closed
macrogui wants to merge 1 commit into
modelcontextprotocol:mainfrom
macrogui:fix/streamable-http-post-transport-error
Closed

macrogui wants to merge 1 commit into
modelcontextprotocol:mainfrom
macrogui:fix/streamable-http-post-transport-error

Conversation

@macrogui

Copy link
Copy Markdown

Fixes #3522

Per CONTRIBUTING, I've engaged on the issue first (reproduction + root-cause comment: #3522 (comment)) and am offering the branch described there. If a maintainer would rather shape the fix differently, feel free to close — no offense taken.

What was wrong

A request POST whose HTTP exchange itself failed — server drops the connection before sending a response, connect/read errors, timeouts — escaped _handle_post_request unhandled. The exception propagated out of the per-request task (_run_request_post has no guard), cancelling the transport's task group:

  1. the write stream closed, so every subsequent request on the session failed with MCPError: Connection closed;
  2. exiting streamable_http_client(...) re-raised the raw error as an unhandled ExceptionGroup.

The TypeScript client recovers from the same server behavior (request fails, next request succeeds), and the Python SDK already contains the sibling failure modes — 202 Accepted, non-resumable SSE drops, exhausted reconnection attempts — by resolving just the in-flight request. This was the one POST failure path that bypassed that pattern.

The change

Catch httpx2.TransportError around the POST exchange in _handle_post_request and resolve the in-flight request with a synthesized CONNECTION_CLOSED error through the existing _resolve_abandoned_request helper (which already tolerates a tearing-down read stream). The session and its transport survive; the next request goes out on a fresh connection. Notifications with a transport error are logged, not fatal to the task group.

Verification

  • Reproduced the reported behavior on main against mcp-failure-lab@0.9.0 (HTTP transport): interrupted call → Connection closed on the next pingExceptionGroup on cleanup. With the fix: the interrupted call fails with MCPError: HTTP transport error: …, the following ping succeeds on the same client, cleanup is clean.
  • New regression test test_a_post_transport_error_fails_only_that_request_and_keeps_the_session (MockTransport-based, following the existing pattern); verified it fails on unmodified main and passes with the fix.
  • pytest tests/client/ — 780 passed, 1 skipped, 1 xfailed (no regressions).
  • ruff check / ruff format clean; pyright clean on both touched files.

A POST whose HTTP exchange itself fails (server drops the connection
before sending a response, connect/read errors, timeouts) escaped
_handle_post_request unhandled. The exception propagated out of the
per-request task, cancelling the transport's task group: the write
stream closed, so every subsequent request on the session failed with
'Connection closed' and transport teardown surfaced the raw error again
as an unhandled ExceptionGroup.

Resolve only the in-flight request with a synthesized CONNECTION_CLOSED
error via the same _resolve_abandoned_request path already used for
202-accepted and non-resumable SSE-drop outcomes, so the session and
its transport survive and later requests go out on a fresh connection,
matching the TypeScript client's recovery behavior.

Fixes modelcontextprotocol#3522
@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 17, 2026
@github-actions

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 #3522.

If a maintainer assigns you to #3522, 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 17, 2026
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.

Streamable HTTP client stays closed after an interrupted response and raises ExceptionGroup on cleanup

1 participant