stacklok / stacklok/toolhive-core

LATEST_PROTOCOL_VERSION misdescribes what the shim requests, and Initialize silently discards the caller's version

Open
#193 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

needs-triage
Dominant language
Go
Stars
2
Forks
4
Avg merge
1d 12h
Merged PRs (30d)
46

Description

Rescoped. This issue originally bundled two conformance findings that turned out to be upstream-only; those are now go-sdk#1112 and go-sdk#1113. See the correction comment for why neither was fixable here. What remains below is shim-side.

The defect

mcpcompat exports a constant that no longer describes what the shim does:

// mcpcompat/mcp/jsonrpc.go:15
const LATEST_PROTOCOL_VERSION = "2025-11-25"

Meanwhile go-sdk's latestProtocolVersion is 2026-07-28, and client.Initialize calls Connect(ctx, tr, nil) — so the shim always requests 2026-07-28 on the wire, whatever the caller asked for. request.Params.ProtocolVersion is read nowhere in Initialize.

The combination is what makes this worth fixing: a consumer reads LATEST_PROTOCOL_VERSION, reasonably concludes the shim speaks 2025-11-25, passes it to Initialize, and gets 2026-07-28 requested — silently.

That is not hypothetical. Every ToolHive production initialize does exactly this:

  • pkg/transport/bridge.go:197
  • pkg/vmcp/client/client.go:852
  • pkg/vmcp/session/internal/backend/mcp_session.go:553

Important: do not "fix" this by honouring the request

The substitution is load-bearing for ToolHive today. Because the wire request is 2026-07-28, a Modern-capable backend answers initialize with 2026-07-28, which is precisely how ToolHive's legacyInit detects a mis-cached backend and flips its revision cache Legacy→Modern (stacklok/toolhive#5997, pinned by TestListCapabilities_MisCachedLegacy_SelfHealsViaSDKNegotiation).

Honouring a requested 2025-11-25 would make that backend negotiate 2025-11-25, the self-heal would never fire, and vMCP's Legacy→Modern correction would silently regress. Rejecting an unhonourable request would break all three production call sites immediately, since they all pass a version that cannot be honoured.

Honouring is also not currently possible: ClientSessionOptions.protocolVersion is unexported upstream (go-sdk#1113).

Proposed work — documentation and validation only, no behaviour change

  • Document client.Initialize: state plainly that Params.ProtocolVersion does not influence the wire version, that go-sdk requests its own latest and negotiates down, and link go-sdk#1113. This removes the silence, which is the actual harm.
  • Fix or document LATEST_PROTOCOL_VERSION. Two options, and this is the decision worth discussing:
    • Document it as an mcp-go compatibility constant that reflects neither the revision the shim requests nor the newest it supports. Zero risk.
    • Change it to 2026-07-28. Behaviourally a no-op for Initialize (the value is discarded anyway), but it is exported and consumers may compare against it, so it needs a consumer sweep first. toolhive uses it in at least the three production sites above plus several tests.
  • Validate genuinely invalid input: a requested version that go-sdk does not recognise at all is unambiguously a caller bug and can return an error, with no effect on any current caller.

Non-goal

Making the shim honour arbitrary requested versions. That is blocked on go-sdk#1113 and, per the above, would need a coordinated change in toolhive because the current behaviour is depended upon.

Generated with Claude Code

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with mcpcompat/mcp/jsonrpc.go and client.Initialize, then inspect the three production call sites in pkg/transport/bridge.go, pkg/vmcp/client/client.go, and pkg/vmcp/session/internal/backend/mcp_session.go. Review TestListCapabilities_MisCachedLegacy_SelfHealsViaSDKNegotiation and the linked go-sdk limitation before choosing how to document LATEST_PROTOCOL_VERSION. Done means the Initialize behavior and constant semantics are explicit, with validation covered without changing current negotiation behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
api, backend
Issue type
Documentation
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.