Skip to content

Align with spec #3002: optional clientInfo, serverInfo in result _meta#3143

Draft
maxisbey wants to merge 5 commits into
mainfrom
spec-update
Draft

Align with spec #3002: optional clientInfo, serverInfo in result _meta#3143
maxisbey wants to merge 5 commits into
mainfrom
spec-update

Conversation

@maxisbey

Copy link
Copy Markdown
Contributor

Aligns the SDK with spec #3002 (merged 2026-07-16), the 2026-07-28 draft's identity reshape. Reference implementations: go#1097, ts#2513 (shipped in 2.0.0-beta.5).

What the spec changed

  • The request _meta key io.modelcontextprotocol/clientInfo is now optional (SHOULD-include); the required pair is protocolVersion + clientCapabilities, and the -32602/HTTP 400 MUST-reject applies only to those.
  • DiscoverResult no longer has a top-level serverInfo field. Servers SHOULD stamp io.modelcontextprotocol/serverInfo into every result's _meta instead (new ResultMetaObject type), "unless specifically configured not to do so".

What this PR does

Types: re-vendors the draft schema at spec commit 71e30695 and regenerates the surface models. The new ResultMetaObject joins the generator's open-classes list (the spec defines it as an open key-value bag; without this the serialize sieve would drop custom result _meta keys).

Server: accepts requests that omit clientInfo (rejecting a missing required key with -32602 naming the key); records client_capabilities as its own connection fact so capability checks work for pair-only requests (ServerSession.client_capabilities is the new preferred accessor); era evidence on the stdio dual-era loop is now the reserved protocolVersion key alone. Stamps serverInfo into every 2026-era result's _meta at the runner's single exit point, after the middleware chain, so coverage holds by construction (custom methods, empty results, and middleware short-circuits included; notifications, error responses, and handshake-era results never stamped). A handler-authored value wins.

Client: reads server identity from the discover result's _meta stamp; Client.server_info / ClientSession.server_info are now Implementation | None. This also fixes a live interop break: typescript-sdk 2.0.0-beta.5 already omits the body serverInfo, which made our auto-mode probe treat a spec-conformant discover reply as unparseable and silently fall back to the legacy handshake.

Conformance: bumps the pinned referee to conformance da56f663 (includes conformance#403, the post-#3002 checks, and #406, per-check baselines). All server legs pass with zero skip entries. The bump window adds new client auth scenarios (DPoP per SEP-1932, and a jwt-bearer grant check) for features the OAuth client does not implement; those are baselined per-check. The requiredCapabilities fixtures move from the old array shape to the schema's object shape, and the everything-server capability gate now reads client_capabilities so it works for pair-only clients.

Decisions to review

  • Public API break: Client.server_info is Implementation | None. Identity is optional wire metadata now; an anonymous server (or a pinned connection without prior_discover=) reads as None, where it previously was a placeholder Implementation(name="", version=""). Documented in the migration guide.
  • Malformed stamp reads as absent: the spec marks serverInfo display-only (clients SHOULD NOT act on it), so a malformed value must never reject an otherwise-valid response. The wire surface keeps the field opaque (schema patch) and the typed, lenient parse happens at the read edge. typescript-sdk does the same with a schema-level catch.
  • "clientInfo": null is accepted as absent. go rejects an explicit null with -32602; this SDK treats it like a missing key. The field is optional and display-only, so we lean tolerant; flagging it in case reviewers prefer strictness.
  • Empty results are stamped ({} becomes {"_meta": {...}} on the 2026 wire). The spec says every result; go's empty-result carve-out is an artifact of its internal types, not spec semantics. Note: a client that rejects unknown keys on empty results (rust's rmcp does) already breaks on resultType, so this adds no new incompatibility.
  • Stamping opt-out is top-level server config (Server(include_server_info=False), also on MCPServer), consumed at the single stamping site. No per-request knobs. Neither go nor ts shipped the opt-out the spec language anticipates; this PR does.
  • The interaction matrix runs with stamping off (one line in the suite's connect fixture): the stamp would bake the commit-dependent package version into every exact-comparison snapshot. Stamping behavior has dedicated tests in tests/server/ and is exercised on the real wire by the conformance suite.

Testing

  • Full suite green; coverage stays at 100% (branch).
  • All six conformance legs green locally against the new referee (server active/draft/2026/all, client all/2026).
  • Live wire drive: pair-only and null-clientInfo requests, malformed-envelope rejections, discover shape, stamp presence/opt-out, legacy handshake untouched, and auto-mode negotiation against both an identifying and an anonymous server.

AI Disclaimer

maxisbey added 5 commits July 21, 2026 16:21
Spec PR #3002 (merged upstream 07-16) made io.modelcontextprotocol/clientInfo
optional on request _meta, removed the top-level serverInfo field from
DiscoverResult, and introduced ResultMetaObject: every result's _meta now
carries an optional io.modelcontextprotocol/serverInfo Implementation.

ResultMetaObject joins OPEN_CLASSES: the spec defines it as an open
key-value bag (all MetaObject key rules apply), so it must keep
extra="allow" or the serialize sieve would silently drop custom result
_meta keys.
…3002)

Spec PR #3002 made io.modelcontextprotocol/clientInfo optional
(SHOULD-include): the required per-request envelope is now the
protocolVersion + clientCapabilities pair.

- classify_inbound_request rung 1 demands the pair and reads absent
  clientInfo as None; a missing required key rejects -32602 with a
  message naming the key(s), per basic/index.mdx.
- Era evidence on the stdio dual-era loop is now presence of the
  reserved protocolVersion _meta key alone: the io.modelcontextprotocol/
  prefix is spec-reserved, so legacy traffic never mints it, and a
  half-built envelope (version without capabilities) routes modern to
  get the classifier's named rejection instead of the legacy path's
  generic one. Failed classification still locks no era.
- The regenerated surface types (previous commit) already carry the
  optional clientInfo on RequestMetaObject.
- Connection records client_capabilities as its own fact so capability
  checks (check_capability, sampling tools validation, extension and
  apps gates) work for pair-only requests; the client_params setter
  keeps the two in lockstep on the handshake path. ServerSession
  exposes client_capabilities and all capability consumers read it.
- The Mcp-Param schema-resolving tools/list walk omits the clientInfo
  key from its synthetic envelope when the caller sent none.
…(spec #3002)

The 2026-07-28 draft removed the serverInfo body field from DiscoverResult:
servers now report identity by stamping io.modelcontextprotocol/serverInfo
into every result's _meta (new ResultMetaObject type), and clients treat it
as optional, display-only metadata.

Server side:
- DiscoverResult loses server_info (monolith and generated surface); the
  default discover handler no longer passes identity.
- Every 2026-era result is stamped at the runner's single exit point, after
  the middleware chain, so custom methods, empty results, and middleware
  short-circuits are covered by construction. Stamping builds a shallow copy
  rather than mutating a dict the handler may retain, a handler-authored
  value wins, and the dumped stamp is cached per server. Notifications,
  error responses, and handshake-era results are never stamped.
- Opt-out per the spec's 'unless specifically configured not to do so':
  Server(include_server_info=False), also exposed on MCPServer.

Client side:
- ClientSession.server_info reads the discover result's _meta stamp, parsed
  once at adopt time; absent or malformed reads as None (the spec forbids
  acting on the value, so a bad stamp must not fail the connection). The
  wire surface keeps the field untyped for the same reason, via a
  shape-driven transform in the generator that stays lenient for future
  result-meta definitions.
- Client.server_info is now Implementation | None. This also fixes a live
  interop break: servers that already shipped the new shape omit the body
  field, which previously failed our discover validation and silently
  downgraded auto mode to the legacy handshake.

Tests: the interaction matrix runs with stamping off (the stamp would bake
the commit-dependent package version into every snapshot); stamping has
dedicated unit and wire-level coverage in tests/server/, and the tests that
exercise identity assert the new _meta form.
Migration guide gets a grouped section for the spec #3002 reshape (field
removal, stamping default and opt-out, Optional server_info, pair-only
requests and the client_capabilities accessor). Whats-new, protocol-versions,
client, session-groups, media, testing, and low-level pages updated to the
new shapes.
The last published referee (0.2.0-alpha.9) still enforces the pre-#3002
shape, so implementing the reshape at the old pin fails three checks.
Conformance main@da56f663 includes #403 (checks flipped to the final
revision) and #406 (per-check expected-failures granularity); the sha256-
verified tarball pin mechanism is unchanged.

All server scenarios pass with zero baseline entries. The bump window adds
client auth scenarios for features the OAuth client does not implement
(DPoP per SEP-1932, and the jwt-bearer grant); those are baselined
per-check so their passing checks stay live.

Riding the same window: requiredCapabilities error data moves from the old
array shape to the schema's object shape (referee #376), and the
everything-server capability gate reads client_capabilities so it works
for clients that omit the now-optional clientInfo.
@github-actions

Copy link
Copy Markdown
Contributor

📚 Documentation preview

Preview https://pr-3143.mcp-python-docs.pages.dev
Deployment https://336868fc.mcp-python-docs.pages.dev
Commit 8eb8d70
Triggered by @maxisbey
Updated 2026-07-21 21:06:32 UTC

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.

1 participant