Skip to content

Support MCP 2026-07-28 spec (go-sdk v1.7 via toolhive-core)#5993

Open
JAORMX wants to merge 2 commits into
mainfrom
adopt-go-sdk-v1.7
Open

Support MCP 2026-07-28 spec (go-sdk v1.7 via toolhive-core)#5993
JAORMX wants to merge 2 commits into
mainfrom
adopt-go-sdk-v1.7

Conversation

@JAORMX

@JAORMX JAORMX commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

Part of #5743 (Workstream C). Advances #5754.

The goal is support for the new MCP spec revision, 2026-07-28 ("stateless") — not a Go SDK for its own sake. Bumping toolhive-core to v0.0.34 pulls in go-sdk v1.7.0-pre.3, the SDK that implements 2026-07-28, so vMCP can negotiate and speak both revisions per backend (Legacy 2025-11-25 + Modern 2026-07-28). The build is unchanged (the mcpcompat shim absorbs the go-sdk API change); this PR fixes the behavioral gaps that only surface once the real v1.7 SDK is imported, without regressing 2025-11-25 (dual-revision is a hard requirement).

Note / CC @jhrozek: this supersedes the prior "hand-roll the Modern envelope, don't import go-sdk v1.7 into the root module" approach (#5981). Importing the SDK is what makes the new spec actually reachable — you can't speak 2026-07-28 without the SDK that implements it. Flagging because it changes the probeRevision/classification design you authored (#5913 / 885e8b14).

What changed

  • Revision classification (the crux). Decide a backend's revision by whether its server/discover result advertises 2026-07-28 in supportedVersions — the SEP-2575 negotiation signal, and exactly what go-sdk's own reference client keys on (mcp/client.go:428-444). The old "any clean server/discover ⇒ Modern" heuristic mis-classified stateful 2025-11-25 backends, which under v1.7 answer discover too (negotiating down) → their Legacy tools/list was parsed as Modern → sessions died. New sentinel errModernNegotiatedDown routes the negotiate-down case through the existing reclassify-on-mismatch machinery (retry-safe: only read-only discover/list can produce it).
  • Reserved-_meta egress strip. Strip the per-hop io.modelcontextprotocol/* control keys at both Legacy backend tool-call egresses (httpBackendClient.CallTool, mcpSession.CallTool) — a stateful backend 400s them under v1.7. Exact keys only, copy-before-mutate, no-op for Legacy.
  • Test adaptations + pins. Real v1.7 client is server/discover-first over SSE (parser assertion made transport-aware); the SSE-forwarding fake now answers discover with -32601; a nil-registry test-construction fix; and new real-backend pin tests asserting stateful → Legacy / stateless → Modern so a future SDK discover-semantics change fails loudly here, not as mystery failures.
  • Cleanup: reword the placeholder ponytail: comments to NOTE:.

Type of change

  • Dependency update enabling new protocol support (+ the behavioral fixes it surfaces)

Test plan

  • Unit tests (task test)
  • Linting (task lint-fix)

The full suite goes from 137 failures to exactly the 3 pre-existing, sandbox-environment failures (no container runtime ×2, no keyring ×1 — none in touched packages). New/changed tests pass under -race.

Special notes for reviewers

  • Reviewed by a 3-way Opus panel — protocol-correctness/dual-revision (headline), security, and test-quality. The protocol reviewer verified every load-bearing claim against the actual go-sdk v1.7.0-pre.3 source (the SupportsProtocolVersion stateless gate at mcp/streamable.go:871, the supportedVersions JSON tag, the reference-client fallback). Verdict: SHIP / SHIP-WITH-NITS ×2, no MUST-FIX. All actionable nits addressed (added a StripReservedModernMeta unit test, a version-exact-match tripwire comment, and filed the cache-staleness follow-up).
  • Known, characterized limitation → vMCP backend revision cache has no success/TTL re-probe (stale on stateless redeploy) #5992: the revision cache self-corrects on error (Modern→Legacy redeploys) but not on success, so a Legacy→stateless redeploy leaves the cached label stale (wire behavior stays correct via SDK negotiation; it's telemetry/handshake-overhead, not correctness). A TTL re-probe is deferred to vMCP backend revision cache has no success/TTL re-probe (stale on stateless redeploy) #5992.
  • probeRevision deliberately builds its own discover client (rather than delegating) so a transport-misconfiguration hard-fails instead of being mis-cached as Legacy — see the doc comment.

🤖 Generated with Claude Code

Move ToolHive onto toolhive-core v0.0.34, which embeds go-sdk
v1.7.0-pre.3 — the SDK that implements the new MCP 2026-07-28 "stateless"
revision. This lets vMCP negotiate and speak both revisions per backend:
Legacy 2025-11-25 and Modern 2026-07-28. The build is unchanged (the
mcpcompat shim absorbs the go-sdk API change); this fixes the behavioral
gaps that only surface once the real v1.7 SDK is imported, without
regressing 2025-11-25 (dual-revision is preserved).

Supersedes the prior "hand-rolled Modern envelope, do not import go-sdk
v1.7 into the root module" approach: importing the SDK is what makes the
new spec reachable. CC @jhrozek (probe/classification and #5981 author).

- Revision classification (the crux): decide a backend's revision by
  whether its server/discover result advertises 2026-07-28 in
  supportedVersions — the SEP-2575 negotiation signal, and exactly what
  go-sdk's own reference client keys on. The old "any clean discover is
  Modern" heuristic mis-classified stateful 2025-11-25 backends, which
  under v1.7 answer discover too (negotiating down). New sentinel
  errModernNegotiatedDown wires the negotiate-down case through the
  existing reclassify machinery.

- Strip reserved io.modelcontextprotocol/* _meta at the Legacy backend
  tool-call egresses (httpBackendClient and the session client): these
  per-hop protocol-control keys must not cross onto the vMCP->backend hop
  (a stateful backend 400s them under v1.7). No-op for Legacy traffic.

- Test adaptations for the real v1.7 client (server/discover-first over
  SSE; SSE-forwarding fake answers discover with -32601), a nil-registry
  test-construction fix, and new real-backend pin tests that assert
  stateful->Legacy / stateless->Modern so a future SDK discover-semantics
  change fails loudly here.

Follow-up: #5992 (revision-cache TTL re-probe). Part of #5743 (#5754).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WG3mSjVGWNc8nfgbkdd79
@github-actions github-actions Bot added the size/M Medium PR: 300-599 lines changed label Jul 25, 2026
@codecov

codecov Bot commented Jul 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 72.06%. Comparing base (885e8b1) to head (ca1198a).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5993      +/-   ##
==========================================
- Coverage   72.06%   72.06%   -0.01%     
==========================================
  Files         718      718              
  Lines       74462    74477      +15     
==========================================
+ Hits        53662    53669       +7     
- Misses      16965    16975      +10     
+ Partials     3835     3833       -2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

The MCPRevision backend-status field (added by the earlier per-backend
revision work) was missing from the generated docs/operator/crd-api.md;
regenerate via task crdref-gen so the Operator CI docs check passes.
@JAORMX
JAORMX requested a review from reyortiz3 as a code owner July 25, 2026 16:54
@github-actions github-actions Bot added size/M Medium PR: 300-599 lines changed and removed size/M Medium PR: 300-599 lines changed labels Jul 25, 2026
@JAORMX

JAORMX commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator Author

Context vs the #5754 gate (proceeding on pre.3 — deliberate maintainer decision)

Flagging this explicitly for reviewers since it touches the go/no-go analysis on #5754:

  • This adopts go-sdk v1.7.0-pre.3 (via toolhive-core v0.0.34), ahead of the "adopt when v1.7.0 final" consumability gate in Adopt go-sdk v1.7 for MCP 2026-07-28 stateless support #5754. This is a deliberate decision to move now (final is ~2026-07-28); the v0.0.34 tag was cut manually. Re-pinning to a final-built toolhive-core release once v1.7.0 ships is a trivial follow-up.
  • It does NOT trigger the two regressions Adopt go-sdk v1.7 for MCP 2026-07-28 stateless support #5754 warns about. It intentionally keeps the hand-rolled modern_envelope.go and the client modernCall path — it does not swap to the SDK-backed stateless client/server. So:
    • cacheScope: "private" is preserved (no cross-identity cache leak — the envelope still hardcodes it; the SDK's setDefaultCacheableValues() "public" default is never reached).
    • The RFC-0083 D6 per-call-auth / poolable connection model is untouched (no per-(principal, backend) SDK session introduced).
  • What it is: the parser/version-negotiation slice of v1.7 — correctly classifying each backend's revision by server/discover supportedVersions (the SEP-2575 signal), plus the reserved-_meta egress strip, fixing the 137 failures that surface once the shim is discover-first under real v1.7. The Modern-envelope deletion (Phase 2) and the SDK-backed-client swap are explicitly out of scope and remain gated as Adopt go-sdk v1.7 for MCP 2026-07-28 stateless support #5754 describes.

Reviewed by a 3-way Opus panel (protocol-correctness/dual-revision headline) with every load-bearing claim verified against the real go-sdk v1.7.0-pre.3 source. cc @jhrozek

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/M Medium PR: 300-599 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant