Skip to content

fix(transport): match reqwest response media types exactly - #1390

Open
theamodhshetty wants to merge 1 commit into
modelcontextprotocol:mainfrom
theamodhshetty:fix/reqwest-response-media-types
Open

theamodhshetty wants to merge 1 commit into
modelcontextprotocol:mainfrom
theamodhshetty:fix/reqwest-response-media-types

Conversation

@theamodhshetty

@theamodhshetty theamodhshetty commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Motivation and Context

The reqwest Streamable HTTP client rejects valid Application/JSON and Text/Event-Stream responses, while admitting different types such as application/jsonx and text/event-streaming. On a JSON 503 response, the case mismatch also loses the original JSON-RPC error.

Fixes #1389. This is separate from #1382, which changes server request validation.

Use one private matcher for the complete response media-type essence across GET admission, POST JSON/SSE dispatch and JSON error classification. Compare type/subtype case-insensitively, preserve parameter tolerance, and reject suffixes and comma-joined essences. Parameter bytes are left opaque. The existing accepted media types and body-free 429 policy are unchanged. No API or dependency changes.

How Has This Been Tested?

On macOS arm64 with Rust 1.96.1:

  • 22 real HTTP cases against axum listeners: 12 fail on unchanged upstream 2a401c4, with 10 passing controls. All pass with this fix.
  • Two additional byte-level cases preserve opaque parameter bytes and reject a non-ASCII essence.
  • All 411 rmcp library tests and 61 tests across eight related HTTP integration targets pass.
  • Strict Clippy for the library and tests passes with the relevant HTTP and stdio/child-process features enabled. Nightly workspace formatting and diff checks pass.
  • Full cross-platform and feature-matrix CI were not run locally.

The wire tests exercise GET, POST JSON/SSE, parameters, absent headers, case variants, suffix/list rejection, JSON 503 preservation and 429 status handling. No live external service or credentials are used.

Breaking Changes

Previously misclassified response types such as application/jsonx now return the existing unexpected-content-type error on success responses. They are not JSON/SSE media types. Valid response types remain accepted, with added case-insensitive support.

Types of changes

  • Bug fix
  • New feature
  • Breaking API change
  • Documentation update

Additional context

Ready for review following the merge of #1387. Issue #1389 records the scope discussion. Hosted CI passes at ab6e5df, including tests, Clippy, MSRV, conformance and public API checks. Maintainer review is pending. This patch is scoped to the reqwest backend; Unix-socket response handling is unchanged. Developed with Codex assistance. Local checks and hosted CI are reported separately.

@github-actions github-actions Bot added T-core Core library changes T-transport Transport layer changes labels Oct 23, 2026
@github-actions
github-actions Bot requested a review from DaleSeo October 23, 2026 06:39
@github-actions

Copy link
Copy Markdown
Contributor

@DaleSeo (assignment failed) is on triage rotation this week and is the reviewer for this one.

Labels come from the PR labeler. Please review the change.

Automated triage. Labels are a first pass and the assigned maintainer has the final say.

@theamodhshetty
theamodhshetty marked this pull request as ready for review November 9, 2026 03:50
@theamodhshetty
theamodhshetty requested a review from a team as a code owner November 9, 2026 03:50
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-09T03:52:38.335955Z ab6e5df Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@jokemanfire jokemanfire left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Verified at ab6e5df instead of reading the diff:

  • cargo test -p rmcp --lib --features "client reqwest transport-streamable-http-client-reqwest" -- media_types → 24 passed (8 get_response_media_types, 14 post_response_media_types, 2 byte-level essence cases).
  • Red check: restoring the three old call sites (ct.as_bytes().starts_with(...)) while keeping the new tests → 12 failed, 10 passed. That reproduces the body's "12 fail on 2a401c4, 10 passing controls" exactly.
  • cargo fmt --check clean.

The matcher looks right to me: splitting on the first ; before decoding is safe (a quoted parameter containing ; can only occur after the essence), suffix and list forms are rejected, and trim_matches([' ', '\t']) matches the OWS cases in the test matrix. I did not run the full lib suite or the feature-matrix CI locally.

Two follow-ups this PR does not have to take, but which should be recorded:

  1. The same prefix comparison survives in the Unix-socket backend — crates/rmcp/src/transport/common/unix_socket.rs:312, :317, :497-498, with identical semantics, so Application/JSON is still rejected there. The body scopes the patch to reqwest, which is honest, but media_type_is is private to the reqwest module (crates/rmcp/src/transport/common/reqwest/streamable_http_client.rs:53), so the follow-up has to duplicate it. Moving it next to JSON_MIME_TYPE in common/http_header.rs would make each remaining call site a one-line change; a TODO pointing at an issue is enough if shared code is out of scope here.

  2. Server-side request admission is still a prefix match — crates/rmcp/src/transport/streamable_http_server/tower.rs:2008 uses header.starts_with(JSON_MIME_TYPE) on the incoming Content-Type. That is what #1382 covers, so out of scope, but the two sides of the same header now disagree about Application/JSON; worth a note on #1382 rather than a change here.

Nothing blocks the fix as scoped.

Copy link
Copy Markdown
Contributor Author

Thanks for running the green/red checks and checking the scope. Recorded the Unix-socket follow-up in #1443 after confirming those prefix comparisons remain on current main. That issue tracks transport regressions and moving the matcher to common/http_header.rs for reuse.

The server admission fix in #1382 merged on October 30 at c60e824, so that item already has an upstream fix. This PR remains scoped to reqwest.

This branch has not been deployed

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

Labels

T-core Core library changes T-transport Transport layer changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

reqwest Streamable HTTP client matches response Content-Type by case-sensitive prefix

2 participants