Scope HTTP client redirect following to the request's origin#3075
Draft
maxisbey wants to merge 1 commit into
Draft
Scope HTTP client redirect following to the request's origin#3075maxisbey wants to merge 1 commit into
maxisbey wants to merge 1 commit into
Conversation
create_mcp_http_client followed every redirect, so everything configured on a client (headers, auth, request bodies) was re-sent to whatever host a Location header named. Clients built by the factory now follow redirects within the same origin (scheme, host, and port), plus http-to-https upgrades of the same host on default ports, and raise the new RedirectError for anything else - before the next request is sent. - transports resolve a refused redirect in-band: requests get a JSON-RPC error naming the target and the remedy, notifications are delivered to the session's message handler; the standalone GET stream stops retrying an endpoint that keeps redirecting - caller-supplied clients that follow no redirects get the same clear error on POST, GET stream, and SSE connect instead of an opaque content-type error - OAuth discovery, registration, token, refresh, and the identity-assertion token exchange fail loudly on redirect responses instead of silently trying the next URL or abandoning the discovery chain - RedirectError and create_mcp_http_client are exported from the top-level mcp package; migration.md documents the behavior change; docs and examples configure clients through the factory, and the general-purpose fetch example uses a browser-like client of its own
Contributor
📚 Documentation preview
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Clients created by the SDK's httpx factory always enabled
follow_redirects=True, so everything configured on a client — headers, auth, request bodies — was re-sent to whatever host aLocationheader named, and a misconfigured URL failed with an unhelpfulUnexpected content type:error. This PR scopes redirect following to the request's origin and makes every refused redirect fail loudly with the target URL and the remedy.Motivation and Context
follow_redirects=Truewas added for operational convenience: trailing-slash 307s from Starlette-mounted servers (#105, #732) and hosted-server redirects (#283). The dominant case — the same-origin slash fix — keeps working exactly as before; the server-side cause was also fixed long ago in #1115. What the blanket default additionally did was silently re-send client configuration to arbitrary redirect targets and silently mask misconfigured URLs. httpx's own default isfollow_redirects=Falsefor the same reason (encode/httpx#2533 keeps its cross-origin strip list deliberately minimal and leaves per-application policy to the application).Clients built by
create_mcp_http_clientnow:RedirectErrorfor any other redirect target, before the next request is sent, with a message naming the resolved target and how to proceed (connect to the final URL, or supply your own pre-configuredhttpx.AsyncClientafter verifying the destination).Transports resolve a refused redirect in-band — a request gets a JSON-RPC error rather than a hung caller, notifications are delivered to the session's message handler, and the standalone GET stream stops retrying an endpoint that redirects on every attempt. Caller-supplied clients that follow no redirects (httpx's default) get the same clear error on POST, GET-stream, and SSE-connect paths. OAuth discovery, registration, token, refresh, and the identity-assertion token exchange fail loudly on redirect responses instead of silently trying the next URL or abandoning the discovery chain.
create_mcp_http_clientandRedirectErrorare now exported from the top-levelmcppackage (a deliberate API addition): the docs previously told users to build their ownhttpx.AsyncClientfor headers/auth, which quietly lost the SDK's defaults. Docs and examples now configure clients through the factory; the general-purposesimple-toolfetch example gets a browser-like client of its own, since it fetches arbitrary web pages rather than a configured MCP endpoint.How Has This Been Tested?
MCPServer.streamable_http_app()under uvicorn and drove it with the real client. A trailing-slash/mcp/URL initializes and completes a full session lifecycle (every request rides a genuine Starlette 307); a redirect to a different origin resolves initialization with a clear error naming the target; a typo'd path still yields the usual not-found error.follow_redirects=Trueclient is untouched (opt-in preserved).response.next_request.urlfrom httpx itself across relative, protocol-relative, absolute-form-without-host, and cross-origin shapes, so an httpx behavior change surfaces as a test failure rather than a silent policy divergence.strict-no-cover, pyright and ruff clean.Breaking Changes
Yes, deliberate and documented in
docs/migration.md: a redirect to a different origin now raisesmcp.RedirectErrorinstead of being followed. Same-origin redirects and same-host https upgrades are unaffected. Deployments that genuinely need cross-origin redirect following can pass their ownhttpx.AsyncClient(follow_redirects=True)tostreamable_http_client/sse_client.Types of changes
Checklist
Additional context
# pragma: no branch/# pragma: no covercomments are added in tests: three for the coverage.py nested-async withphantom-arc misreport documented in AGENTS.md, one marking an unreachable context-manager body in a connect-failure test.RedirectPolicyproposal in fix: add SSRF redirect protection to httpx client factory #2180 addressed the scheme-downgrade slice of the same default; with redirect targets now vetted at the factory for every consumer, that policy knob is superseded by this change.AI Disclaimer