Skip to content

fix: prevent duplicate Authorization header in SSE client#1919

Open
Christian-Sidak wants to merge 2 commits intomodelcontextprotocol:mainfrom
Christian-Sidak:fix/sse-duplicate-auth-header
Open

fix: prevent duplicate Authorization header in SSE client#1919
Christian-Sidak wants to merge 2 commits intomodelcontextprotocol:mainfrom
Christian-Sidak:fix/sse-duplicate-auth-header

Conversation

@Christian-Sidak
Copy link
Copy Markdown
Contributor

Summary

Fixes #1872. When both requestInit.headers and eventSourceInit.fetch provide the same Authorization header, the server receives a duplicated value (Bearer X, Bearer X) due to a case mismatch between the Headers instance (lowercase authorization) and the user's closure headers (original case Authorization).

  • Remove eventSourceInit.fetch from the fetchImpl resolution chain in _startOrAuth() -- the SDK's internal wrapper already fully controls the fetch call passed to EventSource, so fetchImpl should use the raw transport (opts.fetch or global fetch), not the user's header-injecting wrapper
  • Update existing test that relied on eventSourceInit.fetch being used as fetchImpl
  • Add regression test verifying no duplicate Authorization header when both requestInit.headers and eventSourceInit.fetch inject the same token

Root cause

  1. _commonHeaders() returns a Headers instance which lowercases keys (authorization)
  2. _startOrAuth() used eventSourceInit.fetch as fetchImpl, calling it with the Headers instance
  3. The user's custom fetch iterates the Headers into a plain object (lowercase keys), then merges closure headers (original case), producing both authorization and Authorization as separate keys
  4. new Headers({ authorization: X, Authorization: X }) joins them per HTTP spec into X, X

Test plan

  • Existing SSE tests pass (362/362)
  • New regression test: when requestInit.headers and eventSourceInit.fetch both set Authorization, server receives exactly one value
  • Verified eventSourceInit.fetch is no longer called as fetchImpl (spy test)

When both requestInit.headers and eventSourceInit.fetch provided the
same Authorization header, the SDK's internal fetch wrapper passed a
Headers instance (lowercase keys) to the user's custom fetch, which
then merged it with its own closure headers (original case). The
resulting plain object had both "authorization" and "Authorization"
keys, producing "Bearer X, Bearer X" after Headers normalization.

Fix: remove eventSourceInit.fetch from the fetchImpl resolution chain
in _startOrAuth(). The SDK's wrapper already fully controls the fetch
call to EventSource, so fetchImpl should be the raw transport
(opts.fetch or global fetch), not the user's header-injecting wrapper.

Closes modelcontextprotocol#1872
@Christian-Sidak Christian-Sidak requested a review from a team as a code owner April 17, 2026 04:42
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 17, 2026

🦋 Changeset detected

Latest commit: db4d179

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@modelcontextprotocol/client Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 17, 2026

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/@modelcontextprotocol/client@1919

@modelcontextprotocol/server

npm i https://pkg.pr.new/@modelcontextprotocol/server@1919

@modelcontextprotocol/express

npm i https://pkg.pr.new/@modelcontextprotocol/express@1919

@modelcontextprotocol/fastify

npm i https://pkg.pr.new/@modelcontextprotocol/fastify@1919

@modelcontextprotocol/hono

npm i https://pkg.pr.new/@modelcontextprotocol/hono@1919

@modelcontextprotocol/node

npm i https://pkg.pr.new/@modelcontextprotocol/node@1919

commit: db4d179

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SSE client: duplicate Authorization header when using eventSourceInit.fetch + requestInit.headers

1 participant