Skip to content

change(mcp): serve protocol revision 2026-07-28, dual-era with initialize - #133

Draft
jdevalk wants to merge 3 commits into
mainfrom
change/mcp-2026-07-28
Draft

change(mcp): serve protocol revision 2026-07-28, dual-era with initialize#133
jdevalk wants to merge 3 commits into
mainfrom
change/mcp-2026-07-28

Conversation

@jdevalk

@jdevalk jdevalk commented Jul 28, 2026

Copy link
Copy Markdown
Owner

What changed

MCP's 2026-07-28 revision removes the initialize handshake. Every request now declares its own protocol version in params._meta, mirrored into HTTP headers; sessions, the standalone GET stream and resumable streams are gone. This PR moves the Worker and the spec page together, per the ship-it-before-you-spec-it rule.

Worker (mcp/src/index.ts)

  • server/discover — mandatory in the new revision.
  • Per-request validation: MCP-Protocol-Version, Mcp-Method and Mcp-Name must match the body, including decoding the =?base64?…?= sentinel before comparison.
  • HeaderMismatch (-32020), UnsupportedProtocolVersionError (-32022) carrying {supported, requested}, unknown method as HTTP 404 + -32601.
  • initialize keeps legacy semantics and can no longer answer 2026-07-28 — a handshake must not promise a revision that has no handshake.
  • Usage logging now reads clientInfo from _meta. Without this the client-mix column in /admin/stats would have silently gone blank as clients migrate off initialize.

Discovery + skill — server card at 2026-07-28 with a supportedProtocolVersions list; SKILL.md re-signed.

Spec page (mcp-and-tool-discovery) — describes the handshake-free model, adds a Supporting both eras section, cites the new revision's versioning and Streamable HTTP pages. Changelog entry included (changed).

Design decision worth reviewing

server/discover advertises only 2026-07-28, not the legacy versions, even though the server answers those too. The _meta version key does not exist before this revision, so a request carrying it can only mean 2026-07-28; listing 2025-11-25 there would invite a client to send it as per-request metadata, which that revision does not define. Legacy versions stay reachable the only way they are defined to be — through initialize. The spec's own example does show a legacy version in the list, so this is a deliberate divergence and easy to reverse if you disagree.

Verification

No test harness exists under mcp/, so I bundled the Worker with esbuild and drove the real fetch handler in-process. 27 assertions, all passing — not a reimplementation, the shipped code path:

--- modern era (2026-07-28) ---
  ok   server/discover returns 200
  ok   server/discover lists only the modern version
  ok   server/discover carries serverInfo in _meta
  ok   modern tools/call succeeds
  ok   version header mismatch → 400 / -32020
  ok   missing version header → 400 / -32020
  ok   Mcp-Method mismatch → 400 / -32020
  ok   Mcp-Name mismatch → 400 / -32020
  ok   missing Mcp-Name → 400 / -32020
  ok   base64 sentinel Mcp-Name accepted
  ok   unsupported version → 400 / -32022 with supported + requested
  ok   unknown method → 404 / -32601
  ok   modern notification without headers → 202
--- legacy era (initialize handshake) ---
  ok   initialize(2025-11-25 | 2025-06-18 | 2025-03-26) echoes it
  ok   initialize(2026-07-28) does NOT return the modern version
  ok   legacy tools/call with no headers still works
  ok   legacy unknown method stays 200 / -32601
--- transport ---
  ok   GET /mcp → 405
  ok   DELETE /mcp → 405

Two bugs came out of writing it, both fixed here: notifications were being header-validated (the revision explicitly leaves notification header rules undefined, so conforming clients would have been rejected), and rejected requests were logged with a null response instead of the error.

I did not wire this harness into the repo — it needs an esbuild devDependency and a CI job, which felt like scope creep on a protocol change. Happy to land it separately; a protocol handler with no tests is the obvious place for one.

npm run build, tsc --noEmit, Prettier and the SKILL.md digest check all pass.

Timing caveat

modelcontextprotocol.io/specification/versioning still says "the current protocol version is 2025-11-25" in prose while every link on that page resolves to /specification/2026-07-28/… and its Negotiation section describes the new mechanism. The revision is real and rolling out, but the canonical page has not finished catching up. Everything here is backward compatible, so shipping early costs nothing — but that is worth knowing before merge.

🤖 Generated with Claude Code

jdevalk and others added 2 commits July 28, 2026 22:02
…lize

Revision 2026-07-28 removes the initialize handshake: every request now
declares its own version in params._meta, mirrored into HTTP headers. This
Worker was already stateless, so the shape costs nothing structurally.

- server/discover (mandatory in the new revision)
- per-request validation: MCP-Protocol-Version / Mcp-Method / Mcp-Name must
  match the body, including the =?base64?…?= sentinel decode
- HeaderMismatch (-32020), UnsupportedProtocolVersionError (-32022),
  unknown method as HTTP 404 + -32601
- initialize keeps legacy semantics and can no longer answer 2026-07-28
- usage logging reads clientInfo from _meta, so the client-mix column in
  /admin/stats keeps filling as clients migrate off initialize

Legacy clients are untouched: validation only applies to requests carrying
the modern _meta version key.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Same PR as the Worker change, per the ship-it-before-you-spec-it rule.

- 2026-07-28 removed initialize: per-request version in _meta, mirrored
  into MCP-Protocol-Version; no sessions, no standalone GET stream
- new "Supporting both eras" section — legacy clients cannot fall forward,
  so a dual-era server is the courteous option, with the two traps named
  (never answer initialize with 2026-07-28; never impose the new header
  validation on a legacy request)
- cite the versioning and Streamable HTTP pages of the new revision,
  replacing the 2025-11-25 tools deep link

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 28, 2026

Copy link
Copy Markdown

Deploying specification-website with  Cloudflare Pages  Cloudflare Pages

Latest commit: 8104822
Status: ✅  Deploy successful!
Preview URL: https://7ffa34cb.specification-website.pages.dev
Branch Preview URL: https://change-mcp-2026-07-28.specification-website.pages.dev

View logs

…ke window

Two corrections after checking the deprecated-features registry.

1. server/discover no longer advertises `logging`. It is Deprecated as of
   2026-07-28 (SEP-2577, earliest removal 2027-07-28) and new implementations
   SHOULD NOT adopt it — advertising it in a discovery call written today
   would be adopting it. Legacy `initialize` still declares it for clients
   that may already expect it.

2. Legacy `initialize` responses now carry Deprecation, Sunset (2027-07-28)
   and a rel="deprecation" link. Note what this is not: MCP does NOT
   deprecate the handshake. 2025-11-25 is Final, not withdrawn, and the
   registry does not list it — revisions do not sunset. The window is our
   policy, so the page says so and warns against attributing such dates to
   an upstream standard that never set them.

Scoped to the initialize response, not the endpoint: /mcp is not being
retired, only our willingness to answer the handshake on it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant