MCPJam / MCPJam/inspector

Pinned protocol version is sent as a duplicate header ("2025-11-25, 2026-07-28"), breaking connect with a 502

Open
#3,453 3 comments 2 reactions 0 assignees View on GitHub
bug
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.