modelcontextprotocol / modelcontextprotocol/rust-sdk
rmcp (official Rust MCP SDK): default Streamable HTTP client never follows spec-permitted redirects - redirecting MCP servers hard-fail with Err(UnexpectedServerResponse)
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 3.9k
- Forks
- 645
- Avg merge
- 4d 13h
- Merged PRs (30d)
- 36
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)
Contributor 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.
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.
Assessment
- Tech stack
- rust
- Domain
- api, networking
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100