feat: protocol version enum and per-version schema conformance for 2026-07-28 (T1, T2) - #238
Conversation
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>
|
Hello @kubinio123 , what do this about adding support for dual-era like this? |
Assert that a classic initialize (and any request whose _meta omits the modern protocol version) is served via the legacy handshake, not rejected with UnsupportedProtocolVersion (-32022) — the official-SDK-client interop path that surfaces first in practice. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
@sideeffffect thanks for sending this in. I took some time to plan out the work for implementing the 2026-07-28 support in general and created several issues with with a common label https://github.com/softwaremill/chimp/issues?q=state%3Aopen%20label%3A%222026-07-28%20version%20support%22. I would like to avoid any issues we might might have with stable implementation we have on I created those issues so that it's easier for us to keep track of the progress and for contributors like you to participate. For this PR, I think it pretty much implements #239 and #240, would you consider taking a look at those two? |
|
@deemwario I know some SDK did this the hard way, but there are couple of them (like Rust) that support both versions, let's see how that plays out for chimp. |
…softwaremill#240) T1: make V2026_07_28 the Latest, add LatestLegacy (V2025_11_25) which the initialize negotiation answers, and add release ordering so code can branch with `version >= V2026_07_28`. The client and legacy handshake keep proposing the latest legacy version. T2: extract SchemaConformance as a shared trait parameterised by the schema resource path, with one concrete spec per version (SchemaConformanceSpec for 2025-11-25, Schema2026ConformanceSpec for 2026-07-28). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Pekko, Ox and ZIO streaming HTTP transports defaulted `protocolVersion`
to `ProtocolVersion.Latest` (2026-07-28), so their GET SSE stream carried
`MCP-Protocol-Version: 2026-07-28`. A legacy (2025-11-25-era) server rejects
that with HTTP 400 ("Unsupported protocol version"), which broke every HTTP
integration test in these modules.
Align them with the base `ClientHttpTransport`, which already defaults to
`ProtocolVersion.LatestLegacy`, and update the Pekko/ZIO HTTP integration
specs that explicitly passed `Latest` to the test server.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
re-targeted to the new branch. Please let me know if you'd like to see further changes @kubinio123 🙏 |
kubinio123
left a comment
There was a problem hiding this comment.
I would really appreciate if we stick to the scope of T1 and T2, so we could skip the changes to handler and documentation at this point.
Address softwaremill#238 review: keep this PR to the protocol-version enum (T1, softwaremill#239) and the per-version schema conformance (T2, softwaremill#240). Move the request metadata / result type / cache hints / error codes / discover datatypes (T4, softwaremill#242) and the server-side handling with its docs (T8, softwaremill#246) into a stacked PR. - ProtocolVersion: drop redundant comments, revert `negotiate` to its base form (negotiation reworks land in softwaremill#246), keep enum + Latest + LatestLegacy + ordering. - Rename conformance specs to Schema20251125ConformanceSpec / Schema20260728ConformanceSpec; seed the 2026-07-28 spec with shared datatypes so the per-version harness runs and grows with the revision-specific defs. - Remove Versioning.scala, DiscoverResultSpec, the -32022 error code, the server/discover handling and capabilities docs (they return in the stacked PR). - Keep clients defaulting to LatestLegacy so advancing Latest to 2026-07-28 does not regress them against legacy servers; the legacy initialize test now sends an explicit protocol version for the same reason. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thanks — narrowed this PR to T1 + T2. Changes:
One thing I kept (happy to move it if you'd rather): the client transports now default to |
|
Thanks for the review. Should be clean now. Please let me know if you still like to see more changes @kubinio123 |
|
Thanks @sideeffffect, merging this. |
36e1f03
into
softwaremill:2026-07-28-protocol-support
Stacked on top of softwaremill#238 (T1 + T2). Carries the parts moved out of that PR at review request: T4 (softwaremill#242) - request metadata, result type, cache hints, error codes, discover types: - Versioning.scala: ProtocolMeta, CacheScope, DiscoverResult (+ getSupportedVersions) - JSONRPCErrorCodes.UnsupportedProtocolVersion (-32022) - ProtocolVersion.supported / isModern - DiscoverResultSpec + DiscoverResult / UnsupportedProtocolVersionError schema conformance T8 (softwaremill#246) - serve 2026-07-28 requests next to legacy requests: - McpHandler: server/discover, per-request modern-version validation, dual-era dispatch - McpHandlerSpec: discover, -32022, modern and legacy-path coverage - docs/server/capabilities.md Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Targets the
2026-07-28-protocol-supportbranch. Groundwork for dual-era (legacy + modern) support, scoped to two tickets. The server-side handling and its datatypes are in the stacked follow-up #260.Closes #239.
Closes #240.
Covers
V2026_07_28added and madeLatest;LatestLegacy = V2025_11_25; cases are ordered by release so code can branch withversion >= V2026_07_28.schema/2026-07-28/schema.jsonbundled;SchemaConformanceextracted as a shared trait parameterised by the schema resource path, with one concrete spec per version —Schema20251125ConformanceSpecandSchema20260728ConformanceSpec. The 2026-07-28 spec is seeded with datatypes shared across revisions and grows with the revision-specific defs in feat: dual-era datatypes and server handling for 2026-07-28 (T4, T8) #260.Client default
Advancing
Latestto2026-07-28would otherwise make every default client advertise2026-07-28and fail against legacy servers, so the client transports default toLatestLegacy. For the same reason the single legacy-initializehandler test sends an explicit2025-11-25— the handler itself is untouched.Follow-up
server/discover+ per-request validation + docs.Local gate green (scalafmt, compile, core/server/client unit suites). WIP.
🤖 Generated with Claude Code