Claude Code v2.1.292 shipped October 6 with a one-line change that touches every local MCP server you run: stdio connections now negotiate protocol version 2026-07-28 by default, on every install, including Bedrock, Vertex and Foundry setups. The escape hatch is a single environment variable, MCP_PROTOCOL_NEGOTIATION=legacy. That line reads like housekeeping until you remember what 2026-07-28 actually is: the stateless rewrite of MCP that deleted the initialize handshake, killed the Mcp-Session-Id header, and replaced connection-time capability exchange with per-request _meta and a new server/discover RPC.
So I probed the servers. Not by reading docs, by sending the actual JSON-RPC. Four official Anthropic reference packages, spawned fresh with npx -y, one initialize request offering protocolVersion: "2026-07-28", and I read what came back on stdout.
What the changelog actually says
Two entries matter, verbatim from the official changelog:
- "Changed local (stdio) MCP server connections to negotiate protocol version 2026-07-28 by default on every install, including Bedrock, Vertex and Foundry;
MCP_PROTOCOL_NEGOTIATION=legacyopts out" - "Improved startup with local (stdio) MCP servers that ignore the newer protocol check: after one slow connect they are remembered for 7 days and connected the older way without the wait"
Read those together and the failure mode is specific: one slow connect per server, then a 7-day cache of the fallback. Not a crash, a stall. If your claude startup felt heavier this week and you run three or four stdio servers, this is a likely suspect.
The probe: four servers, one question each
I wrote a 30-line Node script that spawns each server, waits for it to boot, sends initialize with a chosen protocol version, and prints the protocolVersion that comes back. Results from today, package versions as spawned by npm:
| Package | Offered 2026-07-28 | Offered 2025-06-18 |
|---|---|---|
@modelcontextprotocol/server-filesystem 0.2.0 |
answered 2025-11-25 | answered 2025-06-18 |
@modelcontextprotocol/server-memory 0.6.3 |
answered 2025-11-25 | answered 2025-06-18 |
@modelcontextprotocol/server-everything 2.0.0 |
answered 2025-11-25 | answered 2025-06-18 |
@modelcontextprotocol/server-sequential-thinking 2026.8.31 |
answered 2025-11-25 | answered 2025-06-18 |
Zero of four speak the wire that Claude Code now asks for by default. And when I sent the new-era server/discover RPC after a legacy handshake, both servers I tried answered -32601 Method not found. The reference servers, the ones the docs point you at, are the oldest wire in the ecosystem.
This mirrors what an independent census found on the remote side: probing 186 public MCP endpoints, only 7 of the 72 that returned any protocol version spoke 2026-07-28, and only 3 accepted server/discover at all (the dated count is here).
Why "negotiates down" is fine and also not the whole story
Notice what did NOT happen in my probe: no error, no refused connection. Offered a version above their cap, the servers answered with their maximum, 2025-11-25. Offered a version below it, they echoed it back. That is well-behaved negotiation per the spec's versioning rules, and it is why most people upgrading to 2.1.292 will see nothing break. Claude Code offers the new version, the server answers the old one, both sides proceed on the old wire.
The gap is behavioral, not functional. Every new-era property, the stateless core, server/discover, Multi Round-Trip Requests for mid-tool user input, stays dormant. And the cost is the startup probe: the client has to ask, wait, discover the server caps below 2026-07-28, and remember that for 7 days before repeating the question.
The servers that actually bite are the ones that fail instead of negotiating. A hand-rolled server, or one pinned to an SDK that treats an unknown version as a hard error, will reject the 2026-07-28 offer outright instead of answering with its maximum. That is the case MCP_PROTOCOL_NEGOTIATION=legacy exists for. If a server dies on connect after your upgrade, that variable is the first thing to try, ideally scoped to the one server, not exported globally into your shell profile forever.
Run the probe yourself
You do not need to trust my table. This is the whole probe, trimmed to the useful part:
// probe.js: node probe.js <package> [args..]
const { spawn } = require("node:child_process");
const child = spawn("npx", ["-y", process.argv[2], ..process.argv.slice(3)],
{ stdio: ["pipe", "pipe", "ignore"] });
child.stdout.on("data", (d) => {
for (const line of d.toString().split("\n")) {
try {
const msg = JSON.parse(line);
if (msg.id === 1 && msg.result) {
console.log("negotiated:", msg.result.protocolVersion);
process.exit(0);
}
} catch {}
}
});
setTimeout(() => child.stdin.write(JSON.stringify({
jsonrpc: "2.0", id: 1, method: "initialize",
params: {
protocolVersion: "2026-07-28",
capabilities: {},
clientInfo: { name: "probe", version: "0.0.1" },
},
}) + "\n"), 8000); // let npx finish installing
Run node probe.js @modelcontextprotocol/server-memory and you get negotiated: 2025-11-25. Substitute the package you actually depend on. If you get an error response or a hang instead of a version, you have found a server that will misbehave under the new default, and you now know which config needs the legacy variable. I keep a small battery of these checks per project because, as I found when measuring the token cost of 9 MCP servers, the invisible startup behavior of MCP servers is where the budget leaks.
The rest of 2.1.292 worth thirty seconds
The same release added an effort parameter to the Agent tool, so a subagent can run at a lower reasoning effort for mechanical work like format conversions and a higher one for design judgment. If you fan out parallel subagents, that is a direct cost lever, and it belongs in the same file as your subagent definitions. There is also a security fix labeled as such: PreToolUse hook approvals and auto mode were bypassing the permission prompt for file reads from UNC network paths, plus five more sandbox escapes closed. And claude plugin install --marketplace <source> now registers the marketplace and installs in one step. If you run plugins from an internal marketplace, that is one less onboarding sentence for your teammates.
Pin the wire, not just the package
The quiet default flip is the part I keep coming back to. Nothing announced it, no major version bumped, and the behavior of every stdio server you run changed the moment npm gave you 2.1.292. This is exactly the class of change that a version-pinned agent config is supposed to catch: your CLAUDE.md does not reference the protocol, but your startup latency, your failure modes on a bad server, and your rollback plan all do. I track both sides explicitly, the client version in one pinned file and the protocol expectations in another, which is the same discipline behind the validated config kits we ship (there is a free Next.js sample if you want to see the structure before paying for anything). The tools-over-plugins balance is its own question, and I went through it when Skills over MCP landed.
For now the practical checklist is short: upgrade, watch your first connect per server, run the probe on anything you depend on, and reach for MCP_PROTOCOL_NEGOTIATION=legacy only when a server fails rather than negotiates. In a few months the reference servers will catch up to 2026-07-28 and the question inverts: the legacy variable becomes the thing you have to remember to remove.
Top comments (0)