Skip to content

fix(client): surface non-2xx HTTP responses to the waiting caller#3092

Open
himanshu-commits wants to merge 2 commits into
modelcontextprotocol:v1.xfrom
himanshu-commits:fix/streamable-http-status-error-3091-v1x
Open

fix(client): surface non-2xx HTTP responses to the waiting caller#3092
himanshu-commits wants to merge 2 commits into
modelcontextprotocol:v1.xfrom
himanshu-commits:fix/streamable-http-status-error-3091-v1x

Conversation

@himanshu-commits

Copy link
Copy Markdown

A 4xx/5xx response to a message POST hit a bare response.raise_for_status() inside the POST's background task. Nothing caught the exception and nothing resolved the request, so the caller sat until its read timeout fired: a 401 was indistinguishable from a slow server, and the escaping error tore down the transport's task group with it.

Non-2xx statuses are now answered rather than raised, generalising the handling 404 already had: a JSONRPCError stamped with the original request's id, sent as a SessionMessage over the read stream, so it resolves through the normal response-correlation path. A spec-compliant JSON-RPC error in the body is preferred over the status-derived stand-in. The connection stays usable.

This matches the behaviour already shipped on the 2.0 line, with one deliberate difference: 404 is answered ahead of the body check. A terminated session 404s with a JSON-RPC error body, so preferring it would replace this line's Session terminated / code 32600 — which callers match on for session expiry — with the server's own wording.

Callers still cannot tell a terminal 401 from a retryable 503; both remain -32603 Server returned an error response, as on 2.0. Carrying the status would be a follow-up on the 2.0 line.

Github-Issue: #3091
Reported-by: afterrburn

Motivation and Context

How Has This Been Tested?

Breaking Changes

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
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

A 4xx/5xx response to a message POST hit a bare `response.raise_for_status()`
inside the POST's background task. Nothing caught the exception and nothing
resolved the request, so the caller sat until its read timeout fired: a 401 was
indistinguishable from a slow server, and the escaping error tore down the
transport's task group with it.

Non-2xx statuses are now answered rather than raised, generalising the handling
404 already had: a `JSONRPCError` stamped with the original request's id, sent
as a `SessionMessage` over the read stream, so it resolves through the normal
response-correlation path. A spec-compliant JSON-RPC error in the body is
preferred over the status-derived stand-in. The connection stays usable.

This matches the behaviour already shipped on the 2.0 line, with one deliberate
difference: 404 is answered ahead of the body check. A terminated session 404s
*with* a JSON-RPC error body, so preferring it would replace this line's
`Session terminated` / code 32600 — which callers match on for session
expiry — with the server's own wording.

Callers still cannot tell a terminal 401 from a retryable 503; both remain
`-32603 Server returned an error response`, as on 2.0. Carrying the status
would be a follow-up on the 2.0 line.

Github-Issue: modelcontextprotocol#3091
Reported-by: afterrburn

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All reported issues were addressed across 2 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/mcp/client/streamable_http.py Outdated
Comment thread src/mcp/client/streamable_http.py
`httpx.StreamError` derives from `RuntimeError`, not `httpx.HTTPError`, so it
does not cover a body that stalls or truncates while being read: `aread()`
raises `ReadTimeout`/`RemoteProtocolError` there. Those escaped into the POST's
background task and stranded the caller — the bug this path exists to prevent.

Github-Issue: modelcontextprotocol#3091

@himanshu-commits himanshu-commits left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

done changes

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.

1 participant