Skip to content

feat: dual-era support for the 2026-07-28 protocol version - #238

Open
sideeffffect wants to merge 3 commits into
softwaremill:masterfrom
sideeffffect:feat/protocol-2026-07-28
Open

feat: dual-era support for the 2026-07-28 protocol version#238
sideeffffect wants to merge 3 commits into
softwaremill:masterfrom
sideeffffect:feat/protocol-2026-07-28

Conversation

@sideeffffect

Copy link
Copy Markdown

Groundwork for 2026-07-28 (the "modern" era) support, making chimp dual-era. This is the foundation the reviews on #234 and #235 pointed to (relates to #163).

Why

Per the 2026-07-28 versioning compatibility matrix, server/discover and extensions like Tasks belong to the modern era. Today chimp is legacy-only, so — as @kubinio123 noted on #235 — a modern/dual-era client calling server/discover should get an error. This PR makes chimp genuinely dual-era, so answering server/discover (and recognising modern per-request versions) becomes correct.

What

  • ProtocolVersion.V2026_07_28 + a supported list. Latest still points at the latest legacy revision used by the initialize handshake, so legacy negotiation is unchanged.
  • server/discoverDiscoverResult (supported versions, capabilities, serverInfo in _meta, resultType: "complete", ttlMs, cacheScope).
  • Per-request version negotiation: a request declaring _meta["io.modelcontextprotocol/protocolVersion"] that the server doesn't support is rejected with UnsupportedProtocolVersion (-32022) listing the supported versions; a supported/absent version is processed as before (chimp already handles each request statelessly).
  • New core types DiscoverResult, CacheScope, ProtocolMeta (the reserved _meta keys).

Addressing the review feedback

Deliberately out of scope (WIP / follow-ups)

  • Modern client mode (the client still uses the legacy initialize handshake).
  • HTTP transport-level modern validation (MCP-Protocol-Version header, 400 semantics, era detection).
  • resultType on all existing results (modern clients already treat an absent resultType as "complete", so this is backward-compatible).
  • Full modern required-field validation (clientCapabilities, -32021) and the tasks/other extension modules.

Tests

Schema2026ConformanceSpec (DiscoverResult + the -32022 envelope vs. the official schema) and McpHandlerSpec cases: server/discover returns supported versions + capabilities + identity; an unsupported version → -32022; a supported modern version is processed; legacy initialize still works. sbt scalafmtCheckAll compile Test/compile compileDocs and the unit suites pass locally. Draft/WIP.

🤖 Generated with Claude Code

sideeffffect and others added 3 commits September 5, 2026 10:34
Make chimp a dual-era server: alongside the legacy initialize handshake
(2025-11-25 and earlier), it now speaks the modern 2026-07-28 revision, where
each request carries its protocol version in _meta.

- ProtocolVersion.V2026_07_28 + a `supported` list; `Latest` still points at the
  latest legacy revision used by the initialize handshake.
- server/discover returns a DiscoverResult (supported versions, capabilities,
  serverInfo in _meta), so a modern/dual-era client no longer gets an error from
  a chimp server (addresses the softwaremill#235 review).
- A request declaring an unsupported protocol version in
  _meta[io.modelcontextprotocol/protocolVersion] is rejected with
  UnsupportedProtocolVersion (-32022) listing the supported versions.
- New protocol types (DiscoverResult, CacheScope, ProtocolMeta) are validated
  against the official 2026-07-28 JSON schema (Schema2026ConformanceSpec),
  following the existing schema-conformance convention; the 2025-11-25 types are
  left untouched.

WIP: modern client mode, HTTP transport-level validation (headers / 400
semantics), resultType on all results, and packaging extensions (e.g. Tasks) as
separate modules are follow-ups. Groundwork for softwaremill#163.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Keep the domain type while the wire stays integer milliseconds, via a
file-private millis codec. supportedVersions stays List[String] on purpose: the
field must carry version strings the receiver may not recognise (a newer peer),
which a closed ProtocolVersion enum would reject on decode.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Parses the raw supportedVersions strings into Either[String, ProtocolVersion]:
Right for a known version, Left with the raw string for an unrecognised one, so
callers get the typed view without the wire field rejecting newer versions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@sideeffffect sideeffffect changed the title WIP: feat: dual-era support for the 2026-07-28 protocol version feat: dual-era support for the 2026-07-28 protocol version Sep 7, 2026
@sideeffffect
sideeffffect marked this pull request as ready for review September 7, 2026 20:08
@sideeffffect

Copy link
Copy Markdown
Author

Hello @kubinio123 , what do this about adding support for dual-era like this?
I think this would unlock changes from the other two PRs (big thanks that you've already had a look at them too).

@deemwario

Copy link
Copy Markdown

Relevant field data point if useful: I maintain a 2026-07-28-only (not dual-era) MCP server implementation, and one thing worth testing explicitly once server/discover + per-request _meta negotiation land — a conformance sweep against my server (via @hasmcp/mcp-spec-test) surfaced that the current official MCP SDK client doesn't yet speak 2026-07-28's handshake-less negotiation the way the spec describes it ('the official SDK does not yet implement the newest revision' was the conformance tool's own note). So a stock official-SDK client sending a classic initialize will hit your UnsupportedProtocolVersion path even once dual-era support is correct per-spec — which is expected, but worth an explicit test case ("official SDK client → legacy negotiation path") rather than only schema-validating the modern path, since that's the interop gap that actually surfaces first in practice.

We went the other direction on the design question this PR is solving (latest-only rather than dual-era — wrote up the tradeoff here if the reasoning is useful as a comparison point, not a suggestion to change course) — dual-era is clearly the more correct choice for a general-purpose client-facing library like this, ours was a narrower bet made for a specific reason.

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.

2 participants