rmcp (official Rust MCP SDK): default Streamable HTTP client never follows spec-permitted redirects - redirecting MCP servers hard-fail with Err(UnexpectedServerResponse)
Maintainers usually reply within 3 days
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 45/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- rust
- Domain
- api, networking
Research direction
Start in crates/rmcp/src/transport/common/reqwest/streamable_http_client.rs at StreamableHttpClientTransport::default_http_client(), then inspect the existing unit test covering custom-header leakage on redirects. Reproduce the 307 case from the issue and determine whether a safe redirect policy or configurable policy is intended. Done means redirecting MCP servers work without replaying custom headers unsafely, with the existing behavior and relevant tests updated accordingly.
Written by the indexing model from the issue text.
Description
rmcp (official Rust MCP SDK): default Streamable HTTP client never follows spec-permitted redirects
Type: functional / MCP conformance
Component: crates/rmcp/src/transport/common/reqwest/streamable_http_client.rs — StreamableHttpClientTransport::default_http_client()
Summary
The official Rust SDK's default Streamable HTTP client is built with:
reqwest::Client::builder()
.pool_max_idle_per_host(0)
.redirect(reqwest::redirect::Policy::none())
.build()
so it never follows HTTP redirects. The MCP spec explicitly permits servers to respond to transport requests with 301/302/307/308 (load balancing, canonical URL / trailing-slash normalization — see e.g. https://github.com/IBM/mcp-context-forge#4441 for a real 307 on /mcp/). Against any redirecting server, every request degrades to:
Err(StreamableHttpError::UnexpectedServerResponse("HTTP 307: ..."))
and the client cannot talk to that server at all.
The truncation is deliberate (there is a unit test asserting no custom-header leak to a redirect target — sensible given reqwest's default policy would replay custom headers), but the consequence is that a spec-compliant redirecting MCP server is unsupported out of the box.
Repro (no build needed)
Point any rmcp StreamableHttpClientTransport (default client) at a server that answers POST /mcp with 307 Location: <same-origin>/v2/mcp. Every post_message returns UnexpectedServerResponse("HTTP 307: ...") instead of issuing a follow-up request to the new Location.
A self-contained crate reproducing this (and the goose-side contrast) is at https://github.com/trickyfalcon/cve-hunt/blob/main/poc-goose-rust/ — scenario B prints the Err(UnexpectedServerResponse("HTTP 307: ...")) directly.
Suggested direction
Follow redirects safely rather than never: only follow cross-host/cross-lo*...* leftovers to be trimmed — or, at minimum, make redirect policy configurable on StreamableHttpClientTransportConfig while keeping the default strict (no-follow) for backwards compatibility. An explicit redirect policy override (as an associated constructor option) would let security-conscious users opt in to same-origin redirect following.
Environment
- rmcp via modelcontextprotocol/rust-sdk (checked out 2026-08-22, rmcp crate 3.x)
- Reproducible with the PoC referenced above
Happy to be credited in the advisory/release notes as: Mo (@trickyfalcon, https://trickyfalcon.com)
- Dominant language
- Rust
- Stars
- 4k
- Forks
- 648
- Avg merge
- 4d 2h
- Merged PRs (30d)
- 40
Getting set up
Starts the project's dev container in your browser, under your own GitHub account.
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from modelcontextprotocol/rust-sdk
-
P3 question T-documentation T-enhancement
Difficulty 1/5 Under an hour Newbie friendliness 86/100
modelcontextprotocol/rust-sdk#1155 ·
Maintainers usually reply within 3 days
-
ProgressDispatcher: a slow progress subscriber blocks subscribe() and delivery for other tokensOpenbug P1 ready for work T-handler
Difficulty 4/5 3-5 days Newbie friendliness 25/100
modelcontextprotocol/rust-sdk#1312 ·
Maintainers usually reply within 3 days
-
transport::stdio() runs every read and write on tokio's blocking pool, which caps stdio throughputOpenenhancement P2 T-transport
Difficulty 4/5 3-5 days Newbie friendliness 58/100
modelcontextprotocol/rust-sdk#1301 · 1 comment ·
Maintainers usually reply within 3 days
-
bug P1 ready for work T-security T-transport
Difficulty 3/5 1-2 days Newbie friendliness 74/100
modelcontextprotocol/rust-sdk#1298 · 1 comment · 1 reaction ·
Maintainers usually reply within 3 days
-
bug P1 ready for work T-model T-service
Difficulty 3/5 1-2 days Newbie friendliness 72/100
modelcontextprotocol/rust-sdk#1283 · 1 comment ·
Maintainers usually reply within 3 days
All issues in modelcontextprotocol/rust-sdk
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
canonical/opentelemetry-collector-operator#409 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Maintainers usually reply within 1 day
-
bug CLI exec tool-calls
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
Maintainers usually reply within 1 day