You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(client): scope transport headers to MCP requests and handle redirects explicitly
The client transports applied their configured requestInit headers to
every request they issue and relied on the platform's implicit redirect
handling, so connection-scoped headers rode along to OAuth endpoints
and to redirect targets on other origins.
- requestInit now applies only to MCP requests. The OAuth requests the
authorization flow issues (metadata discovery, token, registration)
are configured separately via the new oauthRequestInit option, and
the CORS preflight-sidestep retry during discovery stays a bare
simple request instead of re-acquiring merged headers.
- MCP requests are issued under the new redirectPolicy option (default
'manual'): GET redirects are followed up to 3 hops, carrying only
request-descriptive headers (Accept, MCP-Protocol-Version,
Last-Event-ID) once a hop leaves the endpoint's origin; POST/DELETE
redirects surface as SdkHttpError with the new code
ClientHttpRedirectNotFollowed instead of being re-sent; a redirect
response can no longer supply the session id the transport adopts.
'follow' restores delegation to the fetch implementation.
- Token and client registration responses are terminal (RFC 6749 §5,
RFC 7591 §3.2): a 3xx answer rejects instead of re-sending the
request, including the cross-app access token exchanges.
- Terminal redirect errors stop SSE retry loops in both transports,
and the version negotiation probe surfaces them instead of
misreading a redirect answer as legacy-era evidence.
Token and client registration requests no longer follow HTTP redirects — including the cross-app access token exchanges. Token responses are terminal (RFC 6749 §5), so a 3xx answer from a token or registration endpoint now rejects with an error instead of being re-sent to the redirect target.
Transport `requestInit` headers now apply only to MCP requests, not to the OAuth requests issued by the transport's authorization flow (protected-resource metadata, authorization-server metadata, token, and client registration requests) — those may target a different origin than the MCP server, so connection-level headers do not carry over. A new `oauthRequestInit` transport option configures those requests explicitly.
Handle redirect (3xx) responses on the client transports' MCP requests explicitly instead of delegating them to the `fetch` implementation. `StreamableHTTPClientTransport` and `SSEClientTransport` now issue their data-plane requests (`POST` message sends, the `GET` that opens or resumes an SSE stream, the session-terminating `DELETE`) with `redirect: 'manual'` and apply RFC 9110 redirect semantics themselves: `GET` redirects are followed for up to 3 hops — a same-origin `Location` target is requested with the original headers, while a hop that leaves the endpoint's origin is requested with only the headers that describe the request itself (`Accept`, `MCP-Protocol-Version`, and — on `StreamableHTTPClientTransport` stream resumption — `Last-Event-ID`); headers configured for the connection (`requestInit` headers, the auth provider's `Authorization`, the server-issued `Mcp-Session-Id`) are not applied across origins. `POST` and `DELETE` requests are never re-sent to a `Location` target: a redirect answer to those surfaces as an `SdkHttpError` with the new code `SdkErrorCode.ClientHttpRedirectNotFollowed` (MCP endpoints that answer `POST` with a redirect are not followed — point the transport at the new endpoint instead), and a redirect response can no longer supply the `mcp-session-id` the transport adopts. The new transport option `redirectPolicy: 'manual' | 'follow'` (default `'manual'`) is the escape hatch: `'follow'` restores the previous behavior — no `redirect` field is set, so `requestInit.redirect` or the platform default applies — for deployments behind redirecting infrastructure that requires the configured headers on the redirect target, and for browser runtimes: there `redirect: 'manual'` yields an opaque redirect (Fetch `opaqueredirect`: status 0, no readable `Location` header) whose target the transport cannot observe, so under the default `'manual'` policy any redirect answer — initial or on a followed hop — fails with `ClientHttpRedirectNotFollowed` and a message naming the remedies (set `redirectPolicy: 'follow'`, or serve the MCP endpoint without redirects) instead of an unexplained status-0 failure. See the "HTTP & headers" section of `docs/migration/upgrade-to-v2.md`.
`${endpoint} endpoint responded with a redirect (HTTP ${response.status||'filtered by the runtime'}); ${endpoint.toLowerCase()} responses are terminal`
2063
+
);
2064
+
}
2065
+
}
2066
+
2049
2067
/**
2050
2068
* Internal helper to execute a token request with the given parameters.
2051
2069
* Used by {@linkcode exchangeAuthorization}, {@linkcode refreshAuthorization}, and {@linkcode fetchToken}.
@@ -2090,8 +2108,10 @@ export async function executeTokenRequest(
2090
2108
constresponse=await(fetchFn??fetch)(tokenUrl,{
2091
2109
method: 'POST',
2092
2110
headers,
2093
-
body: tokenRequestParams
2111
+
body: tokenRequestParams,
2112
+
redirect: 'manual'
2094
2113
});
2114
+
awaitassertNotRedirected(response,'Token');
2095
2115
2096
2116
if(!response.ok){
2097
2117
throwawaitparseErrorResponse(response);
@@ -2365,8 +2385,10 @@ export async function registerClient(
0 commit comments