Align 2026-07-28 wire with spec PR #3002: serverInfo in result _meta, clientInfo optional

Merged
FE
3mo ago
+933-207

Spec PR modelcontextprotocol/modelcontextprotocol#3002 (part of the final 2026-07-28
revision) moved serverInfo from the DiscoverResult body to the result-_meta key
io.modelcontextprotocol/serverInfo, and demoted the request envelope's clientInfo
from required to SHOULD. This PR updates the 2026-07-28 wire implementation to match —
the final revision is modeled exactly, with no pre-#3002 shape support in either
direction.

Motivation and Context

We shipped the pre-#3002 shape in both directions, which breaks interop with SDKs that
follow the final revision (go-sdk v1.7.0-pre.3+, csharp):

  • Our client required body serverInfo when parsing a DiscoverResult, so a conforming
    server's discover response failed validation, the version-negotiation probe classified
    the server as legacy, and the client attempted an initialize handshake against it —
    a hard connect failure against a modern-only server.
  • Our server rejected requests whose _meta envelope omits clientInfo, which the final
    revision explicitly allows.

What changed

  • Spec artifacts regenerated at spec commit 71e30695 (the #3002 merge): the
    2026-07-28 anchor types, the vendored schema.json twin, and the example corpus.
  • Wire schemas (2026-07-28): the final revision exactly — the envelope's
    clientInfo is optional (present-but-malformed still fails); DiscoverResult has no
    body serverInfo; result _meta is typed as the anchor's ResultMetaObject. A
    malformed _meta serverInfo value is treated as absent on receive: the spec marks the
    field self-reported, unverified, and display-only ("clients SHOULD NOT use it to
    change their behavior"), so a bad identity stamp never invalidates a result. The
    leniency lives in both the 2026 wire schema and the public ResultMetaObjectSchema —
    the latter sits inside JSONRPCResultResponseSchema, whose guard classifies every
    inbound message on both eras, so a strict key there would drop whole responses.
  • Server: every 2026-era response gets the _meta serverInfo stamp (spec SHOULD;
    a handler-authored value wins; the 2025-era wire is untouched) — including the
    entry-built subscriptions/listen graceful-close results, whose _meta carries the
    identity next to the subscription id (SubscriptionsListenResultMeta extends
    ResultMetaObject).
  • Client: still sends clientInfo on every request (SHOULD). Reads server identity
    from the discover result's _meta only. A server that stamps no identity is
    anonymous: the connection works, getServerVersion() is undefined, and the response
    cache partitions under a per-connection surrogate so identity-less servers can never
    share cache entries.
  • Types and public schemas: DiscoverResult no longer declares serverInfo;
    RequestMetaEnvelope['io.modelcontextprotocol/clientInfo'] is optional; new public
    constant SERVER_INFO_META_KEY; new ResultMetaObjectSchema in
    @modelcontextprotocol/core (result _meta now validates against it, and
    SubscriptionsListenResultMetaSchema extends it), with the matching
    ResultMetaObject type.
  • Conformance baseline: the pinned referee (0.2.0-alpha.9) still asserts the OLD
    shape, so server-stateless is expected-failed on exactly three checks
    (sep-2575-request-meta-invalid-missing-client-info, the missing-client-info
    iteration of sep-2575-http-server-meta-invalid-400, and
    sep-2575-server-implements-discover, which requires body serverInfo). The entries
    burn when the pin bumps to a referee release that incorporates #3002.

How Has This Been Tested?

  • Regression fixture: the exact discover response a go-sdk v1.7.0-pre.3 server sends
    (serverInfo in _meta, no body field) — classifies modern now, misclassified
    legacy before this change. A malformed _meta serverInfo also stays modern.
  • Server e2e: discover carries identity in _meta and no body field; requests without
    clientInfo are served; every modern response is stamped; 2025-era responses never
    are.
  • Client e2e: identity resolves from _meta; a stray body serverInfo is ignored
    (anonymous connect); a persisted stale blob carrying a body serverInfo connects
    with anonymous identity; a MALFORMED _meta serverInfo still connects modern through
    the real transport path and a post-connect call carrying one still resolves (the
    neutral-layer classification guard must not drop such frames — pinned on both eras).
  • Full suite: typecheck / lint / unit + integration + e2e / conformance (all six CI
    legs) / docs:check / docs:examples — all green.

Breaking Changes

DiscoverResult no longer declares serverInfo, and RequestMetaEnvelope's
clientInfo is optional. Code that read discover.serverInfo moves to
client.getServerVersion() (or the _meta key directly). See
docs/migration/support-2026-07-28.md.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed