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)
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:
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 real307on/mcp/). Against any redirecting server, every request degrades to: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 answersPOST /mcpwith307 Location: <same-origin>/v2/mcp. Everypost_messagereturnsUnexpectedServerResponse("HTTP 307: ...")instead of issuing a follow-up request to the newLocation.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
StreamableHttpClientTransportConfigwhile keeping the default strict (no-follow) for backwards compatibility. An explicitredirectpolicy override (as an associated constructor option) would let security-conscious users opt in to same-origin redirect following.Environment
Happy to be credited in the advisory/release notes as: Mo (@trickyfalcon, https://trickyfalcon.com)