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', bounded at 3 hops): GET redirects are followed, carrying
only request-descriptive headers (Accept, MCP-Protocol-Version,
Last-Event-ID) once a hop leaves the endpoint's origin; POST/DELETE
follow a same-origin 307/308 by re-sending the identical request
(keeps trailing-slash-redirecting framework mounts working), while a
same-origin 301/302/303 (platforms re-send those as GET) or any
cross-origin redirect surfaces as SdkHttpError with the new code
ClientHttpRedirectNotFollowed; a redirect response can no longer
supply the session id the transport adopts. 'follow' delegates to
the fetch implementation; 'error' makes every redirect terminal.
- 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, bounded at 3 hops: `GET` redirects are followed — 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` follow a same-origin `307`/`308` by re-sending the identical request (method, body and headers preserved — this keeps deployments behind trailing-slash-redirecting framework mounts working); a same-origin `301`/`302`/`303` (platforms re-send those as `GET`, corrupting the request) or any cross-origin redirect — including an `http://` transport URL whose server upgrades to `https://`; origin is an exact scheme+host+port match — surfaces as an `SdkHttpError` with the new code `SdkErrorCode.ClientHttpRedirectNotFollowed`, and a redirect response can no longer supply the `mcp-session-id` the transport adopts. The new transport option `redirectPolicy: 'manual' | 'follow' | 'error'` (default `'manual'`) provides the escape hatches: `'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 instead of an unexplained status-0 failure. `'error'` treats every redirect answer as terminal. 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