Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 9 additions & 11 deletions pkg/vmcp/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -1031,14 +1031,12 @@ func newBackendHTTPClient(rt http.RoundTripper, timeout time.Duration) *http.Cli
// capability flags. Used both by probeRevision and, on a Modern cache hit, by
// ListCapabilities to re-fetch the flags without re-running the fallback ladder.
//
// A clean discover response is not, by itself, proof the peer speaks the
// Modern (2026-07-28) revision: a go-sdk v1.7 shim answers server/discover even
// for a backend that negotiates down to Legacy. The authoritative signal is
// supportedVersions (SEP-2575; mirroring go-sdk's own reference client at
// mcp/client.go:428-444) — when it does not contain MCPVersionModern (including
// when it is absent or empty), this returns errModernNegotiatedDown instead of
// the capabilities, so callers do not mistake a negotiated-down Legacy backend
// for Modern.
// A clean discover response is not, by itself, proof the peer speaks Modern —
// supportedVersions is the authoritative signal. See errModernNegotiatedDown
// (modern.go) for why. Concretely: when supportedVersions does not contain
// MCPVersionModern (including when it is absent or empty), this returns
// errModernNegotiatedDown instead of the capabilities, so callers do not mistake
// a negotiated-down Legacy backend for Modern.
func (h *httpBackendClient) modernDiscover(
ctx context.Context, target *vmcp.BackendTarget,
) (*mcp.ServerCapabilities, error) {
Expand Down Expand Up @@ -1091,9 +1089,9 @@ func discoverModernCapabilities(ctx context.Context, hc *http.Client, endpoint s
// errWrongEra, a -32601 (discover is mandatory for Modern), a generic
// JSON-RPC error, a bare 404/400/405, an empty/non-JSON body, a
// 200-with-Legacy-result, OR errModernNegotiatedDown (a clean discover
// envelope whose supportedVersions lacks 2026-07-28 — a go-sdk v1.7 shim
// answers server/discover even for a backend negotiating down to Legacy, so
// a clean response alone is not proof of Modern; see modernDiscover);
// envelope whose supportedVersions lacks 2026-07-28 — see
// errModernNegotiatedDown in modern.go for why a clean response alone is not
// proof of Modern);
// - returns the error UNCACHED (leaving the backend unprobed) on an
// INCONCLUSIVE outcome — an auth blip (errModernAuth, 401/403) or a transient
// failure (errModernTransient: 408/429/5xx, mid-read, or transport/timeout).
Expand Down
17 changes: 11 additions & 6 deletions pkg/vmcp/client/modern.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,17 @@ var errModernTransient = errors.New("modern backend returned a transient error")
// JSON-RPC error whose `data.supported` list omits it (including an absent
// or undecodable data payload).
//
// Per SEP-2575 (and go-sdk's own reference client, mcp/client.go:360-369),
// the advertised version list — not a clean discover response or a bare
// -32022 alone — is the authoritative signal of whether a peer actually
// speaks the Modern (2026-07-28) revision: a go-sdk v1.7 shim answers both
// server/discover and protocol errors even for a backend negotiating down to
// Legacy, so neither on its own is proof of Modern.
// CANONICAL RATIONALE for the negotiate-down rule. Per SEP-2575 (and go-sdk's
// own reference client, mcp/client.go:360-369), the advertised version list —
// not a clean discover response or a bare -32022 alone — is the authoritative
// signal of whether a peer actually speaks the Modern (2026-07-28) revision: a
// go-sdk v1.7 shim answers both server/discover and protocol errors even for a
// backend negotiating down to Legacy, so neither on its own is proof of Modern.
//
// modernDiscover and probeRevision (client.go) both depend on this rule and
// back-reference it here rather than restating it. Keep the explanation in one
// place: it is the surface that has to be edited whenever the exact-match
// tripwire in modernDiscover fires for a newer Modern revision.
//
// This is a definitive Legacy signal carried in a valid Modern envelope —
// distinct from errWrongEra (peer does not speak Modern's wire shape at all)
Expand Down
Loading