Multi-version protocol types: per-version delta packages (candidate 3/3)#2845
Draft
maxisbey wants to merge 1 commit into
Draft
Multi-version protocol types: per-version delta packages (candidate 3/3)#2845maxisbey wants to merge 1 commit into
maxisbey wants to merge 1 commit into
Conversation
One superset type set in mcp.types plus per-version packages that define only what changed against the previous version and re-export the rest; serialization validates dumps through the negotiated version's models.
This was referenced Jun 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Draft — not intended to merge as-is. This is one of three candidate implementations of multi-version protocol type support, pushed side by side so the approaches can be compared concretely. The companion drafts are #2843 (complete per-version model packages) and #2844 (per-version fact tables); all three ship the same public surface and the same behavioral contract, differing in how the per-version knowledge is represented. Once a direction is chosen, the selected approach would be re-cut as a reviewable PR series.
This branch adds versioned wire-shape support for protocol revisions 2024-11-05 through 2026-07-28:
mcp.typesstays one superset model set (the "monolith"), and each revision gets a wire-shape module that defines only what changed since the previous revision, importing everything else from the module that last defined it. The mechanism is "delta modules + one boundary":serialize_for/parse_asinsrc/mcp/types/wire.pyre-validate payloads through the negotiated revision's models, loaded lazily on first boundary use. The mechanism lives insrc/mcp/types/v2024_11_05/ … v2026_07_28/andsrc/mcp/types/wire.py; everything else is shared surface.Marker note: comments of the form
# OD-<n> alternative: …(and# M-<n>inmcp.types.jsonrpc) mark reviewed design decisions at the spot where they bite; the convention is explained in themcp.types._typesmodule docstring, and the register below maps each id to its location. They are records, not TODOs.Motivation and Context
The SDK currently models one protocol revision at a time, but a server or client negotiates a version per session and the 2026-07-28 revision changes wire shapes materially (required
resultType, required_metaidentity triple on some client requests, removed server→client requests, multi-round-trip (MRTR) input requests, subscriptions, discovery). This candidate explores the "delta" answer: each revision's module IS the reviewable diff against the previous revision, so future small revisions cost a small module rather than a full copy.How Has This Been Tested?
strict-no-coverclean; pyright strict 0 errors; ruff format + check clean.tests/spec_oracles/: generated per-version schema oracles (pinned to public spec-repo SHA6d44151…) compared against the SDK types, with a burn-down allowlist whose entries each carry a self-contained reason; green for all five revisions plus the extension and SDK directions. Each oracle module doubles as the flattened one-page view of its revision.tests/types/: per-revision surface pins against the oracles (test_version_surfaces.py), boundary emission/parse behavior, version registry and method tables, public-surface ratchet (mcp.types.__all__= 196 names), result-union resolution pins.sys.modulesafterimport mcp(subprocess-asserted); total import time grows ~+30 ms vsmain, of which the version mechanism itself contributes ~+1.4 ms (the rest is the larger shared model set).Breaking Changes
None new on this branch:
docs/migration.mdgains feature notes only, andLATEST_PROTOCOL_VERSION/DEFAULT_NEGOTIATED_VERSION/SUPPORTED_PROTOCOL_VERSIONSare byte-identical.Types of changes
Checklist
Additional context
Reading order
src/mcp/types/_types.py— the superset payload types; the module docstring explains the superset principle and the# OD-<n> alternative:marker convention.src/mcp/types/jsonrpc.py— version-independent JSON-RPC envelope.src/mcp/types/_versions.py— per-version method tables (plain data).src/mcp/types/_spec_names.py— the SDK-name ↔ schema-name divergence record.src/mcp/types/_wire_base.py— the wire-shape model base (closed-by-default rationale).src/mcp/types/v2024_11_05/__init__.py— the full base revision; its header documents the delta layout.src/mcp/types/v2025_03_26/__init__.py— the smallest delta; the template in action. Thenv2025_06_18,v2025_11_25,v2026_07_28(the last leads with its recursive JSON alias pair).src/mcp/types/wire.py—serialize_for(shape rules, validate, re-dump, restore walk) andparse_as(superset parse, mandates, result-union selection).tests/types/— boundary, parse, registry, surface, and import-budget pins.tests/spec_oracles/— generated per-version oracles + burn-down gate; each oracle module is also the flattened one-page view of that revision.Decision register (targets of the in-source
OD-n/M-nmarkers)Any = None(key absent when unset; interim, see open questions) —src/mcp/types/_types.py:1741mcp.types, grouped section —src/mcp/types/_types.py:645src/mcp/types/_types.py:759src/mcp/types/wire.py:311extra="allow"carve-out onSubscriptionFilter—src/mcp/types/_types.py:1266src/mcp/types/wire.py:299src/mcp/types/_types.py:2381, 2396, 2414, 2435requestedSchemastays an untyped dict —src/mcp/types/_spec_names.py:111JSONRPCError.idkeeps the required-nullable v2 shape —src/mcp/types/jsonrpc.py:189Key behavioral facts (shared across all three candidates)
protocolVersionand refuses a request missing caller-suppliedclientInfo/clientCapabilitiesinparams._meta— identity is session-owned by design.io.modelcontextprotocol/*_metakeys pass through verbatim at every revision.serialize_forrefuses bare fragments (content blocks, capabilities, params) withTypeError.NumberSchemabounds/default (the two older revisions that model them) carry a float arm — the pinned schema rendering says integer where schema.ts says number; each widening is pinned per position, and a closure sweep fails any remaining int-only field that is not pinned intended-integer.methodat 2026-07-28 (missingat the entry's own method key; earlier and unknown revisions stay lenient); unknown method values classify as invalid params at every revision.Open questions for the maintainer
structured_content: Any = None, noUnsetsentinel, no wrap serializer — the sentinel form breaks 7 existing tests at this base. The sentinel + carve-out path remains the post-review option.Annotations.last_modifieddeferred in the superset model set (it trips an existing listing-snapshot test); the per-revision modules from 2025-06-18 on modellastModifiedexactly as their schemas do.ServerResultresolution pin: the Discover-keyed adversarial frame resolves toDiscoverResult(pinned intests/types/test_wire_parse.py); reverting remains an option.ElicitResult.content| Nonevalue arm: null has no wire form at any schema version; the arm exists for v1.x constructor compatibility. Drop vs keep is an open API decision.Conformance-suite gaps (surfaced per AGENTS.md, to raise on the conformance repo)
No conformance-suite scenario currently exists for most 2026-07-28 type features; the in-repo oracle and boundary tests substitute for now. Gaps identified:
resultType: "complete"emission on ordinary (non-MRTR) results, incl. the empty-result surface (only MRTR scenarios assertresultType).ClientCapabilities.extensions/ServerCapabilities.extensions— unasserted on both sides.structuredContentany-JSON-value widening incl. explicit null;inputSchema/outputSchema2020-12 widening.prompts/listdraft pagination/required-params scenario — none; per-method MRTR coverage ofprompts/get(vsresources/read) unconfirmed.roots/liston 2026-07-28 (embedded delivery); must not emitnotifications/roots/list_changedon draft — none.params/_metamaterialization (PaginatedRequest half) — none identifiable.resourceSubscriptionsfilter path; extensiontaskIds; removed-era-method-32601— three gaps.@app.resource()does not acceptctx: Context#244/sse client read progress notification #261 unmerged) — family-wide gap.Known limitations
tests/spec_oracles/) or an import-following editor. The genuine advantage is review-shape: the next small revision's delta module IS the reviewable change.mcp/extensions/tasks/) is not built; its types remain provisional upstream.src/mcp/types/wire.py:77-86).tests/types/test_version_surfaces.py); the oracle generator is committed (scripts/update_spec_types.py).AI Disclaimer