Skip to content

feat: dual-era datatypes and server handling for 2026-07-28 (T4, T8) - #260

Open
sideeffffect wants to merge 2 commits into
softwaremill:2026-07-28-protocol-supportfrom
sideeffffect:feat/protocol-2026-07-28-server
Open

sideeffffect wants to merge 2 commits into
softwaremill:2026-07-28-protocol-supportfrom
sideeffffect:feat/protocol-2026-07-28-server

Conversation

@sideeffffect

@sideeffffect sideeffffect commented Sep 14, 2026

Copy link
Copy Markdown

Follow-up to #238 (T1 + T2, now merged). Targets 2026-07-28-protocol-support.

Closes #242.
Closes #246.

T4 — #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 — #246: serve 2026-07-28 requests next to legacy requests

  • McpHandler: server/discover, per-request modern-version validation (-32022), dual-era dispatch
  • Modern results carry the 2026-07-28 envelope: resultType: complete and serverInfo in _meta; cacheable results (tools/list, resources/*, prompts/list, ...) also carry ttlMs/cacheScope
  • McpHandlerSpec: discover, -32022, modern and legacy-path coverage
  • docs/server/capabilities.md

Server conformance for 2026-07-28

  • New CI step serverConformance/conformance server --requirements 2026-07-28, with a per-spec-version baseline (conformance-baseline-<version>.yml) selected by the sbt task
  • conformance-baseline-2026-07-28.yml lists the scenarios that still need later tickets: tasks/MRTR + input-required (T5, #243), full stateless lifecycle, and modern HTTP-header validation
  • 2025-11-25 conformance unchanged (no regression)

🤖 Generated with Claude Code

@kubinio123

Copy link
Copy Markdown
Collaborator

The T1, T2 PR is merged now. I took a first look and I am missing updates to server conformance tests, will you look into that as well? Agent does a good job on them as long as he has access to it's sources, described in server-conformance/README.md.

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>
@sideeffffect
sideeffffect force-pushed the feat/protocol-2026-07-28-server branch from 60aef3f to b574dd5 Compare September 16, 2026 16:59
Wrap 2026-07-28 results with the modern envelope (resultType: complete,
serverInfo in _meta; cacheable results also carry ttlMs and cacheScope) so the
server passes the modern tools/list, tools/call, resources/*, prompts/*,
completion and caching conformance scenarios.

Wire the server conformance harness for 2026-07-28: a per-spec-version baseline
(conformance-baseline-<version>.yml) selected by the sbt task, a
conformance-baseline-2026-07-28.yml capturing the scenarios that still need
later tickets (tasks/MRTR and input-required → T5 softwaremill#243; full stateless
lifecycle and modern HTTP header validation), and a CI step running it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@sideeffffect

Copy link
Copy Markdown
Author

Done — rebased onto the merged T1/T2 base (the earlier conflict is gone) and added the server conformance updates.

Server conformance for 2026-07-28:

  • Modern results now carry the 2026-07-28 envelope (resultType: complete, serverInfo in _meta; cacheable results also carry ttlMs/cacheScope), so tools/list, tools/call, resources/*, prompts/*, completion and caching pass at 2026-07-28.
  • Wired a serverConformance/conformance server --requirements 2026-07-28 CI step, with a per-spec-version baseline (conformance-baseline-2026-07-28.yml) selected by the sbt task.
  • The baselined scenarios need later tickets: tasks/MRTR + input-required (T5, T5 - MRTR (multi round trip requests) types #243), plus the full stateless lifecycle and modern HTTP-header validation.

Verified locally: 2026-07-28 server conformance passes (all remaining failures are the baselined ones); 2025-11-25 stays green (63 passed, 4 expected) — no regression.

@sideeffffect sideeffffect changed the title WIP feat: dual-era datatypes and server handling for 2026-07-28 (T4, T8) feat: dual-era datatypes and server handling for 2026-07-28 (T4, T8) Sep 17, 2026
@sideeffffect
sideeffffect marked this pull request as ready for review September 17, 2026 11:11

@kubinio123 kubinio123 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some additional findings of the agents, I would look into 1.

1. -32022 goes out as HTTP 200, spec says it must be 400

server/McpHandler.scala:80 builds the error through jsonResponse(...), and
McpResponse.statusCode (lines 17-19) returns Ok for every JsonResponse.

The schema is explicit: "For HTTP, the response status code MUST be 400 Bad Request."
The damage is worse than a wrong status. The spec defines dual-era client fallback as
"inspect the body of a 400 Bad Request before falling back", so a 200 makes the
rejection invisible to a conforming client. The new test only reads the body, so it
passes either way.

Give JsonResponse a status field, or add an error case, and return BadRequest here.

2. An unknown initialize version is answered with 2026-07-28

ProtocolVersion.scala:18. negotiate still falls back to Latest. The PR fixed the
absent version default to LatestLegacy (McpHandler.scala:170) but left the
unknown version path pointing at the modern revision, which has no initialize at
all. A legacy client that sends 2025-03-26 is told to speak a handshake-less protocol
and has nowhere to go.

requested.flatMap(ProtocolVersion.from).filterNot(_.isModern).getOrElse(LatestLegacy)

Smaller things

  • Seven new // comments (Versioning.scala:8, McpHandler.scala:77,101,146,179,
    build.sbt:241, McpHandlerSpec.scala:136). AGENTS.md rule 6 says none. Before this
    PR the whole of core and server main had one. Scaladoc is fine, these aren't.
  • ProtocolVersion.supported (line 15) hand-lists the enum cases while from and
    negotiate use values. Add a revision and server/discover starts lying.
    values.toList.reverse fixes it.
  • resultType: String = "complete" (Versioning.scala:53) is a string sitting right
    next to a properly modelled cacheScope enum. The schema defines a closed set.
  • modernResult (lines 150-152) hand-writes "complete", 0L and "private", which
    bypasses the CacheScope encoder this same PR adds. Two sources of truth for one wire
    format.
  • wrap(cacheable = true) reads as a contradiction, since it emits ttlMs: 0 and
    private.
  • val ProtocolVersion: String inside ProtocolMeta shadows the ProtocolVersion enum
    in the same package.
  • Hot path: _meta gets decoded into a full Map on every request to read one key.
    Implementation(...).asJson.deepDropNullValues is rebuilt per modern response and is
    redundant anyway, since line 25 already drops nulls globally. serverCapabilities and
    the whole discover result are constant but recomputed.
  • getSupportedVersions scaladoc reads "Kept lazy of the wire so an unknown version
    never fails decoding", which isn't grammatical. The name is Java-ish too.
  • build.sbt:242 only matches the space-separated --requirements v form, so
    --requirements=v falls back to the default baseline without saying so.
    server-conformance/README.md still documents conformance-baseline.yml alone, while
    the new baseline file points readers back at that README.

Comment thread .github/workflows/ci.yml
run: sbt -v compileDocs
- name: Unit tests
run: sbt -v test
- name: Client conformance tests

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also rename the older steps so they include the version

@@ -0,0 +1,60 @@
package chimp.protocol

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments in this file are redundant

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