fix: prevent malformed dual Content-Type on direct-transport token requests#1433
Open
buptliuhs wants to merge 1 commit into
Open
fix: prevent malformed dual Content-Type on direct-transport token requests#1433buptliuhs wants to merge 1 commit into
buptliuhs wants to merge 1 commit into
Conversation
…quests Direct SSE / Streamable HTTP connections pass a `requestInit` to the transport, which the SDK wraps with `createFetchWithInit`. That wrapper merges the base `requestInit.headers` with each request's own headers using a case-sensitive object spread. The Inspector injected a capital `Content-Type` (and `Accept`) into `requestInit`. On the OAuth token request the SDK builds its headers with `new Headers(...)`, which normalizes the key to lowercase `content-type`. The two case-variants both survived the merge and `fetch` comma-joined them into `Content-Type: application/json, application/x-www-form-urlencoded`, which strict authorization servers cannot body-parse — breaking token exchange and refresh. Stop contributing `content-type`/`accept` via `requestInit` and simplify the custom fetch to a pass-through. The SDK already sets these headers itself per request, and auth/custom headers still reach every request through the SDK's `_commonHeaders()` base. This also removes a pre-existing inconsistency between the two branches' fetch wrappers. Add tests that drive the real SDK merge to assert a single Content-Type on the token request. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
a586291 to
e4285ae
Compare
Author
|
@cliffhall can you please review? thanks! |
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.
Summary
Direct (non-proxy) SSE and Streamable HTTP connections send a malformed dual
Content-Typeon the OAuth token request:Strict authorization servers can't body-parse this, so token exchange and refresh fail (e.g. Keycloak: "Failed to parse media type..."). It surfaces on token refresh once an access token expires.
Root cause
For direct connections the Inspector passes a
requestInitto the transport. The SDK wraps it withcreateFetchWithInit, which merges the baserequestInit.headerswith each request's own headers using a case-sensitive object spread.The Inspector was injecting a capital
Content-Type(andAccept) intorequestInit. On the token request the SDK builds its headers withnew Headers(...), which normalizes the key to lowercasecontent-type. Because the merge is case-sensitive, the capitalContent-Typeand the lowercasecontent-typeboth survive, andfetchthen comma-joins them into the malformed value above.Fix
Stop contributing
content-type/acceptviarequestInit, and simplify the direct-transport custom fetch to a pass-throughfetch(url, init). This is safe and complete because the SDK already sets those headers itself per request:POSTand the SSEGETstream —headers.set('content-type'/'accept', ...)on aHeadersobject (case-insensitive)Content-TypeAuth/custom headers (e.g.
Authorization) still reach every request through the SDK's_commonHeaders(), which folds inrequestInit.headers. The change also removes a pre-existing inconsistency where the SSE wrapper let the base headers clobber per-request headers while the Streamable HTTP wrapper did the opposite.Tests
Added
oauthHeaderMerge.test.ts, which drives the real SDKcreateFetchWithInitwith the token request's exact shape:Content-Typeon the token request whenrequestInitcarries only auth headers (the fix)Content-TypeinrequestInitre-introduces the dual header (the bug)All existing
useConnectiontests (46) still pass; lint and prettier clean.Also verified end-to-end against a live OAuth-protected MCP server over an ngrok tunnel: the token request now goes out with a single
Content-Type: application/x-www-form-urlencoded, and token exchange/refresh succeed.Scope
Affects direct SSE / Streamable HTTP connections only (proxy connections were unaffected).