Pinned protocol version is sent as a duplicate header ("2025-11-25, 2026-07-28"), breaking connect with a 502
- Dominant language
- TypeScript
- Stars
- 2.2k
- Forks
- 283
- Avg merge
- 11h 47m
- Merged PRs (30d)
- 737
Description
## Summary
Setting **Connection overrides → Protocol version** to a specific value (e.g. `2026 RC (2026-07-28)`) on a remote HTTPS server makes the client send **two** values in one `MCP-Protocol-Version` header, comma-joined:
MCP-Protocol-Version: 2025-11-25, 2026-07-28
Spec-compliant servers match that header exactly, so they reject it as an unsupported version and return 400. The hosted app surfaces this as a **`Request failed (502)`** toast from its own proxy, which makes it look like a server outage rather than a malformed request. The pin feature is therefore unusable against
any server that validates the header.
## Steps to reproduce
1. Add a remote HTTPS MCP server (OAuth auth) in the hosted app.
2. ⋮ → **Configure** → **Connection overrides** → **Protocol version** → `2026 RC (2026-07-28)` → Save Changes.
3. Toggle the server on.
**Expected:** the client sends `MCP-Protocol-Version: 2026-07-28` and connects.
**Actual:** `Request failed (502)`. Server-side, the request arrives with `MCP-Protocol-Version: '2025-11-25, 2026-07-28'` and is rejected as an unsupported version. A second connect attempt never reaches the server at all (client-side short-circuit).
Setting Protocol version back to **Client default** connects instantly against the same server, same credentials — so this is specific to the pin path, not the server or the OAuth session.
## Root cause
`StreamableHTTPClientTransport._commonHeaders` (`@modelcontextprotocol/client`, observed on `2.0.0-beta.4`) sets the header **lowercase** and then spreads the caller's connection-level `requestInit.headers` verbatim:
```js
new Headers({ "mcp-protocol-version": negotiated, ...requestInit.headers })
Headers treats field names case-insensitively but an object literal does not de-duplicate across different casings, so a canonical-cased MCP-Protocol-Version supplied by the caller appends instead of replacing. Reproducible standalone on Node 24:
new Headers({ "mcp-protocol-version": "2025-11-25", "MCP-Protocol-Version": "2026-07-28" }).get("mcp-protocol-version")
// => "2025-11-25, 2026-07-28"
```
The SDK's RESERVED_REQUEST_HEADER_NAMES guard covers per-request options.headers only — not connection-level requestInit.headers — so nothing upstream catches it.
The app used to have its own case-insensitive de-dup that prevented this; it was removed in #3317 (merged 2026-07-20) and not replaced. The per-server pin UI then landed in #3365 (merged 2026-07-22), which is when the combination became reachable.
Contributor guide
Research direction
Start with StreamableHTTPClientTransport._commonHeaders in @modelcontextprotocol/client and run the standalone Node 24 Headers reproduction from the issue. Then inspect the app changes from #3317 and the protocol-version pin path from #3365. Done means a pinned connection sends one MCP-Protocol-Version value, uses the selected version, and connects successfully instead of returning 502.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100