modelcontextprotocol / modelcontextprotocol/typescript-sdk

[v2] Era-negotiation probe drops spec-conformant server/discover responses (top-level resultType/_meta) and misclassifies modern servers as legacy

Open
#2,722 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
13.4k
Forks
2.2k
Avg merge
3d 15h
Merged PRs (30d)
4

Description

What happened?

Connecting a Client (stdio transport, pinned negotiation versionNegotiation: { mode: { pin: "2026-07-28" } }) to a spec-conformant MCP 2026-07-28 server fails:

SdkError [EraNegotiationFailed]: Version negotiation failed: the server did not offer
pinned protocol version 2026-07-28 via server/discover (no fallback in pin mode)

The server's response is spec-conformant per the 2026-07-28 Server Discovery spec example, where resultType and _meta appear as top-level siblings of result. Wire-captured exchange (server side verified correct via a binary stdio interceptor):

{"jsonrpc":"2.0","id":"server-discover-probe-1","resultType":"complete","result":{"supportedVersions":["2026-07-28"],"capabilities":{"tools":{"listChanged":false}},"instructions":"..."},"_meta":{"io.modelcontextprotocol/serverInfo":{"name":"<server-name>","version":"0.1.0"}}}

Root cause chain (dist line numbers from @modelcontextprotocol/client 2.0.0 / @modelcontextprotocol/core 2.0.0):

  1. JSONRPCResultResponseSchema = z.object({ jsonrpc, id, result }).strict() (@modelcontextprotocol/core/dist/auth-CUe6YdwF.mjs:191-197; mirrored as wireResultResponse(...).strict() in client/dist/src-D_zzAWoS.mjs:3178) rejects the unknown top-level members resultType / _meta.
  2. ProbeWindow.open's transport.onmessage guard (client/dist/index.mjs:2518-2524) resolves pending probes only when isJSONRPCResultResponse(...) || isJSONRPCErrorResponse(...); the rejected message is dropped silently.
  3. On timeout, the stdio classifier returns {kind:'legacy'} (index.mjs:2321-2323), so pin mode throws at index.mjs:2727 — a modern, spec-literal server gets misdiagnosed as legacy.
  4. Related: decodeResult (src-D_zzAWoS.mjs:3920+) runs only on the post-handshake request path (~line 6120), never during the probe, so even a non-dropped envelope would reach classifyResult's validateResult without decoding.

A raw newline-delimited JSON probe sending exactly this request shape succeeds against the same binary — only the SDK path fails.

What did you expect?

Per the 2026-07-28 Server Discovery spec, the probe should decode the response envelope before classification (or tolerate extra top-level members), and a silently-dropped/timed-out probe reply should surface as a diagnosable error rather than being classified as "legacy".

Suggested fixes (any of)
  • Run decodeResult in the probe path before classifyResult.
  • Relax .strict() → passthrough when parsing response envelopes in the probe window.
  • Minimum viable: log when a probe reply fails envelope parsing instead of silently timing out into a misleading legacy verdict.
Code to reproduce
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";

// Any stdio server answering server/discover with the spec-example shape
// (resultType/_meta as top-level siblings of result):
const transport = new StdioClientTransport({ command: "<server-command>" });
const client = new Client(
  { name: "repro", version: "1.0.0" },
  { versionNegotiation: { mode: { pin: "2026-07-28" } }, capabilities: {} },
);
await client.connect(transport); // -> EraNegotiationFailed after ~60s silent timeout
SDK version

@modelcontextprotocol/client 2.0.0, @modelcontextprotocol/core 2.0.0

Area

Client

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with ProbeWindow.open in client/dist/index.mjs and trace JSONRPCResultResponseSchema, wireResultResponse, decodeResult, and classifyResult in the mentioned bundles. Reproduce with the pinned Client and spec-shaped server/discover response. Done means the conformant response is accepted during negotiation and parse failures or timeouts produce a diagnosable result rather than a misleading legacy classification.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api, networking
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
62/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.