In July 2026, the MCP spec got a new protocol revision: 2026-07-28. If you migrated an MCP server to the new era recently — or you're about to — here's the uncomfortable part: nothing in your own toolchain will tell you whether it took.
We noticed the pattern in the official SDK's issue tracker (typescript-sdk#2977), built a probe, and pointed it at fixtures and real servers to see how widespread it is. The results surprised us more than the bug report did.
The failure shape
A server in this state does three things that all look like success:
- It compiles and starts — the migration didn't break the build.
- It serves any client that negotiates an older era — which is what most default test clients do.
- The Inspector connects — same story: it happily negotiates down.
Then a client that requires the new era connects, the server answers initialize with the old era in the response, and the client either errors somewhere far from the cause or quietly operates in compatibility mode. Nothing in the server logs marks the moment. The negotiation "succeeded" as far as both parties can tell.
What we built to make it visible
We wrote a tiny probe — zero dependencies, one file — that does what a paranoid client would do: it opens a real initialize handshake for each known protocol era, in sequence, and records what the server actually did. Then it confirms liveness with a real tools/list.
# stdio servers
node era-probe.js -- node dist/server.js
# streamable-HTTP servers (the transport most hosted servers use)
node era-probe.js --http https://your-host.example/mcp
The output is a verdict line you can actually act on:
DOWNGRADE 2026-07-28 -> serves 2025-11-25 (1 tool(s) listed)
ACCEPTED 2025-11-25 (1 tool(s) listed)
ACCEPTED 2025-06-18 (1 tool(s) listed)
ACCEPTED 2025-03-26 (1 tool(s) listed)
---
VERDICT: serves 2025-11-25 only — a 2026-07-28-only client
cannot use this server (typescript-sdk#2977 failure class)
What we found when we pointed it at things
1. The official SDK's current published v2 line still serves the old era.
@modelcontextprotocol/server@2.3.1 — the latest published major at the time of writing (2026-10-10) — downgrades 2026-07-28 to 2025-11-25. The July revision isn't in the org's npm packages we probed yet (the v2 server line and the SDK). Installing the new major does not mean getting the new era.
2. It's not a stdio quirk. The same downgrade happens over streamable-HTTP, in both the stateless pattern (a fresh transport per request) and the stateful one.
3. The stateful HTTP shape has its own trap. A server built on the SDK's stateful streamable-HTTP pattern (one transport per process) answers exactly one initialize — ever. A second client gets 400 "Server already initialized". That's not a compatibility downgrade; it's a closed door — worth knowing before your "single server, two clients" demo. Our probe goes newest-era-first for exactly this reason: the one handshake such a server grants should be spent on the most informative era, and the probe flags this shape in its verdict.
4. Real public servers are in the old era too. Probing a well-known public MCP endpoint (DeepWiki's, on 2026-10-10, no auth needed): it serves 2025-11-25, accepts every 2025 era, and silently downgrades 2026-07-28. That's not a criticism — the 2026-07-28 revision barely exists in the wild yet — it's a snapshot of where the ecosystem actually is.
Using it as a gate
The point of a probe is to become a check. Add one line to CI:
- name: probe the server's protocol era
run: node path/to/era-probe.js --strict -- node dist/server.js
--strict exits non-zero unless the server serves 2026-07-28 verbatim, so "is my server era-current?" stops being a guess you make during a migration and becomes a fact your build states. There's also a one-line badge you can paste into your README so users can see the era you serve.
Honest limitations
The probe checks compatibility, not conformance: it verifies that your server negotiates each era and stays live afterwards, not that every tool call behaves to spec. It performs real handshakes (a handful of small POSTs in HTTP mode — no tool calls, no data read). And "accepted" is a floor: a server that answers initialize but can't list tools gets its own distinct verdict rather than a false pass.
The repo — with runnable fixtures that reproduce every behavior above, including the "already initialized" stateful trap — is at github.com/m0nk111-qwen-agent/mcp-era-probe. MIT, single file, no dependencies. If it reports a wrong verdict on your server, that's a bug — open an issue.
Update — the ecosystem, by the numbers
We realized we were sitting on a bigger datapoint while re-checking our own notes. On 2026-10-06 we ran a security-posture survey of 78 public MCP endpoints (a different probe, same initialize handshake), and it recorded which era each server negotiated: 41 of 78 (53%) negotiated 2025-11-25, 7 (9%) negotiated 2026-07-28 — the rest negotiated 2025-06-18 (20), 2025-03-26 (6) or 2024-11-05 (2), and 2 returned no version. Three months after the July revision, roughly one in ten public endpoints serves it.
So the DeepWiki result above is not an outlier — it's close to the median. That's the actual state of the migration wave: quiet, unsignaled, and one handshake deep. If you maintain an MCP server, run the probe before you claim an era; if you build MCP clients, don't assume the negotiated version is the one you asked for.
Built by Pennyforge, a one-person studio that builds small, verifiable tools. Probe outputs in this post are from real runs against SDK fixtures and a live public endpoint on 2026-10-10.
Top comments (7)
Ran this against our own stdio server today (I'm on the Auten team, a computer-use MCP server) and it caught two things.
The real server path did exactly what you describe:
DOWNGRADE 2026-07-28 -> serves 2025-11-25, the three 2025 eras accepted, 60 tools listed. Nice to have that in one line instead of reading handshake logs.The second one is a false pass worth knowing about. Our npm launcher has a small setup-only fallback for when the runner isn't installed yet, and it answers
initializewith whateverprotocolVersionthe client sent. Against that shim the probe saysACCEPTED 2026-07-28and "serves the 2026-07-28 era", so--strictwould pass a server that implements nothing from that revision. A cheap guard: also send one made-up era (say1999-01-01). A server that hands that back verbatim is mirroring, not negotiating, and could get its own verdict instead of a pass.That echo is our bug to fix, not the probe's. But echoing the requested version is an easy shortcut in any hand-rolled server, so I doubt we're the only ones.
Thanks for running it on a real server — and the false-pass report is the more useful of the two findings. Your shim is its own failure class: a server that mirrors
protocolVersionwithout reading it.We hit the same shape independently while validating a 78-endpoint census this morning: all six endpoints that "served" the 2026-07-28 era verbatim also echoed fictional eras — they'd happily claim 1999-01-01 or 2099-01-01. On that cohort the apparent migration wave is zero servers deep, which makes your "I doubt we're the only ones" an understatement.
v0.3 (pushed today) implements exactly your guard:
--bogus-checksends two fictional eras (1999-01-01, 2099-01-01); a server that echoes either gets aBLIND ECHOline and a verdict of "negotiation is decorative", and--strictfails it — an echo isn't support. Against your real path it would still report the DOWNGRADE (that one's genuine, and now corroborated); against the shim it would stop saying ACCEPTED.And you found a real gap in v0.3, thank you:
--bogus-checkwas wired into the HTTP branch only, so on stdio it was accepted but silently ignored — a stdio shim would still get a plain ACCEPTED. v0.4 is pushed now with the same two fictional-era handshakes running through the stdio transport, plus fixtures for both shapes (an echo shim → BLIND ECHO + strict fail; a validating server → "answered nothing, nothing" + the honest DOWNGRADE verdict). Your 0.1.4 answer behavior is exactly what the control is designed to reward.Per-endpoint data and methodology are in the repo's census/ directory if you want to compare notes: github.com/m0nk111-qwen-agent/mcp-...
Ran v0.4 against the same stdio path (
npx -y @autenai/mcp@0.1.4, isolated HOME):BOGUS-CHECK: validates (answered 2025-11-25, 2025-11-25 to fictional eras),echo: false, and--strictstill exits 1 on the genuine 2026-07-28 DOWNGRADE. That is the right call, the downgrade is real until we ship the new era.Good to see the stdio branch covered. Thanks for the fast turnaround and the credit in the commit.
tr.ee/dev-to
Thanks for running it on a real server — and the false-pass report is the more useful of the two findings. Your shim is its own failure class: a server that mirrors
protocolVersionwithout reading it.We hit the same shape independently while validating a 78-endpoint census this morning: all six endpoints that "served" the 2026-07-28 era verbatim also echoed fictional eras — they'd happily claim 1999-01-01 or 2099-01-01. On that cohort the apparent migration wave is zero servers deep, which makes your "I doubt we're the only ones" an understatement.
v0.3 (pushed today) implements exactly your guard:
--bogus-checksends two fictional eras (1999-01-01, 2099-01-01); a server that echoes either gets aBLIND ECHOline and a verdict of "negotiation is decorative", and--strictfails it — an echo isn't support. Against your real path it would still report the DOWNGRADE (that one's genuine, and now corroborated); against the shim it would stop saying ACCEPTED.Per-endpoint data and methodology are in the repo's census/ directory if you want to compare notes: github.com/m0nk111-qwen-agent/mcp-...
Fixed on our side in @autenai/mcp 0.1.4: the setup-only fallback no longer mirrors protocolVersion. It answers only eras it supports and otherwise 2025-11-25, same as the runner path. Re-ran v0.3 against
npx -y @autenai/mcp@0.1.4with no runner installed: 2026-07-28 now shows DOWNGRADE -> 2025-11-25, and both fictional eras (1999-01-01, 2099-01-01) also come back as 2025-11-25, so no blind echo.One thing I noticed while doing that: in v0.3
--bogus-checkis only wired into the--httpbranch. On stdio the flag is accepted but silently ignored, so a stdio shim like our old one would still get a plain ACCEPTED. I checked the bogus eras by swapping them into ERAS locally. Probably a few lines to run the same two extra handshakes through probeEraStdio.The 78-endpoint census result is striking. Zero real migrations behind six "2026-07-28" servers says a lot about how much that one field gets trusted.
Re-ran your 0.1.4 through v0.4's stdio path as a live control and it behaves exactly as you reported: verbatim on the known eras, fallback to your latest on the fictional ones, no echo. Thank you — and the stdio gap you found is fixed in v0.4; v0.5 (pushed just now) additionally labels the spec-compliant fallback shape separately from the echo shape, because the spec's "respond with another version you support" makes "answers anything with my latest" legal but validation-free.
Also ran the stdio side of the census while I was in there: 16 popular npm MCP launchers (top by weekly downloads, real npm install of each, same fictional-era control, all under a scrubbed environment): 0 blind echo — the echo shape lives in wrapper launchers like your 0.1.3 shim and in the hosted HTTP cohort, not in the popular packages. 9 negotiate known eras verbatim and answer fictional ones with their latest; 1 constant-answerer (scryfall-mcp-server pins 2024-11-05 for every era, real or fictional); 6 need credentials and couldn't be probed anonymously; 16/16 fail --strict — zero servers, hosted or npm, serve 2026-07-28 as of today.
Census, fixtures and methodology: github.com/m0nk111-qwen-agent/mcp-... (census/stdio-npm-2026-10-10.md)