Skip to content

fix(client): include Mcp-Session-Id header on proxy-mode requests (spec compliance)#1376

Open
aicayzer wants to merge 1 commit into
modelcontextprotocol:mainfrom
aicayzer:fix/streamable-http-session-id-proxy
Open

fix(client): include Mcp-Session-Id header on proxy-mode requests (spec compliance)#1376
aicayzer wants to merge 1 commit into
modelcontextprotocol:mainfrom
aicayzer:fix/streamable-http-session-id-proxy

Conversation

@aicayzer
Copy link
Copy Markdown

Closes #905.

Problem

Per the MCP spec at docs/specification/2025-11-25/basic/transports.mdx:

If an MCP-Session-Id is returned by the server during initialization, clients using the Streamable HTTP transport MUST include it in the MCP-Session-Id header on all of their subsequent HTTP requests.

The Inspector's proxy-mode fetch closure throws the SDK-supplied session header away. The SDK's StreamableHTTPClientTransport captures Mcp-Session-Id from the init response and threads it into init.headers on every subsequent fetch. The closure at client/src/lib/hooks/useConnection.ts:711-737 spreads ...init before setting headers: { ...headers, ...proxyHeaders }, so the latter overwrites the SDK headers — including the session ID.

The direct-mode closure at L602-633 has the inverse spread order (headers first, then ...init) and works correctly. The asymmetry is the bug.

This is independent of and complementary to the Access-Control-Expose-Headers: Mcp-Session-Id CORS workaround that several users discovered on the issue thread (#905). That workaround fixes a separate direct-mode CORS-visibility problem; this PR fixes a proxy-mode header-propagation bug that exists regardless of CORS configuration.

Fix

Build the final headers object with explicit precedence: user headersproxyHeaders → SDK-supplied init.headers. The three sources don't share keys today, but explicit ordering documents intent and keeps the SDK's Mcp-Session-Id winning.

Applied to all three proxy-mode eventSourceInit.fetch closures (stdio, sse, streamable-http). For the streamable-http proxy path additionally pre-seed mcp-session-id from React state into requestInit.headers belt-and-braces, mirroring the direct-mode pattern at L573-576. Direct mode is unchanged.

Files

  • client/src/lib/hooks/useConnection.ts — three proxy eventSourceInit.fetch closures + streamable-http requestInit pre-seed
  • client/src/lib/hooks/__tests__/useConnection.test.tsx — new Mcp-Session-Id header propagation (issue #905) describe block with four tests:
    • proxy streamable-http preserves SDK-supplied Mcp-Session-Id
    • proxy streamable-http preserves X-MCP-Proxy-Auth when SDK-supplied headers are present (precedence regression guard)
    • proxy SSE preserves SDK-supplied headers (e.g. Mcp-Protocol-Version)
    • direct streamable-http unchanged (regression guard for the already-correct path)

Test plan

  • npm test passes (full unit suite, 519 tests including 4 new regression tests)
  • npm run prettier-check passes
  • cd client && npm run lint passes
  • Manual: each new test would fail if the corresponding closure reverted to the spread-order-inverted form

Notes

  • V2 (upstream/v2/main clients/web/) has a different transport architecture (core/mcp/remote/createRemoteTransport.ts + useInspectorClient) with no equivalent closure — no V2 mirror needed.
  • HTTP DELETE on disconnect for clean session termination (jvanhill's observation on the issue thread) is a separate concern and will be filed as a follow-up.

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.

Streamable HTTP: header Mcp-Session-Id is not set for "notifications/initialized" request

1 participant