Title: Multi-version protocol types: per-version fact tables (candidate 2/3) by maxisbey · Pull Request #2844 · modelcontextprotocol/python-sdk · GitHub
Open Graph Title: Multi-version protocol types: per-version fact tables (candidate 2/3) by maxisbey · Pull Request #2844 · modelcontextprotocol/python-sdk
X Title: Multi-version protocol types: per-version fact tables (candidate 2/3) by maxisbey · Pull Request #2844 · modelcontextprotocol/python-sdk
Description: 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 #2845 (per-version delta packages); 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 the 2026-07-28 protocol revision to mcp.types as one superset model set plus a version-aware wire boundary (mcp.types.wire.serialize_for / parse_as), keyed strictly by (model type, negotiated version). The mechanism is declarative fact tables: one data module (_version_facts.py) with five per-version blocks of strip/inject/refuse/mandate rows and method sets, applied by one small engine (_shaping.py, 479 lines). Everything version-keyed lives in those two files plus wire.py; the model definitions themselves are version-free. Marker note: comments tagged OD-n / M-n in the source reference the decision register in this description (table below). They are review-phase markers — records of considered alternatives, not TODOs — and are resolved before release. 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 _meta identity triple on some client requests, removed server→client requests, multi-round-trip (MRTR) input requests, subscriptions, discovery). This candidate explores the "facts, not copies" answer: version differences are rows in a data table a reviewer can scan in one sitting, applied by a single engine, with no duplicated model definitions. How Has This Been Tested? Full suite: 2,157 passed / 4 skipped / 1 xfailed; coverage 100.00% (branch) with strict-no-cover clean; pyright 0 errors; ruff format + check clean. tests/spec_oracles/: generated per-version schema oracles (pinned to public spec-repo SHA 6d44151…) compared against the SDK types, with a burn-down allowlist whose entries each carry a self-contained reason; green for all five versions plus the extension group. The fact tables themselves are additionally pinned against the per-version oracles (tests/types/test_version_facts_oracle.py). tests/types/: boundary emission/parse pairs, registry + method-table equality, public-surface ratchet (mcp.types.__all__ = 196 names), engine and fact-table cross-checks, union resolution pins (incl. twelve ServerResult pins). Import cost: no boundary/mechanism module is imported by import mcp or import mcp.types (subprocess-asserted; the boundary loads lazily on first use). Breaking Changes None new on this branch: docs/migration.md gains feature notes only, and LATEST_PROTOCOL_VERSION / DEFAULT_NEGOTIATED_VERSION / SUPPORTED_PROTOCOL_VERSIONS are unchanged (tests/types/test_public_surface.py). Types of changes Bug fix (non-breaking change which fixes an issue) New feature (non-breaking change which adds functionality) 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 Additional context Reading order src/mcp/types/_types.py — the superset payload models, including the 2026-07-28 additions (Discover, subscriptions, input-required results, error data) and the marked removed-method section. src/mcp/types/jsonrpc.py — version-independent JSON-RPC envelope. src/mcp/types/_spec_names.py — deliberate SDK-name vs schema-export-name divergences, with reason codes. src/mcp/types/_version_facts.py — the mechanism: five per-version fact blocks, oldest to newest; row kinds documented on their dataclasses. src/mcp/types/_shaping.py — the engine that applies the rows. src/mcp/types/wire.py — the public boundary: version registry re-export, per-version method tables, serialize_for/parse_as, two exceptions. src/mcp/types/__init__.py — public surface; the boundary loads lazily so import mcp.types does not pay for it. tests/types/ — boundary emission/parse pairs, registry + method-table equality, public-surface ratchet, import budget, engine and fact-table cross-checks, union resolution pins. tests/spec_oracles/ — generated per-version schema oracles + burn-down comparison; regenerated by scripts/update_spec_types.py. docs/migration.md — the tasks-types restoration note and release-notes leniency entries. Decision register (targets of the in-source OD-n / M-n markers) Decision Implemented Marker OD-1 structuredContent interim: structured_content: Any = None, no sentinel (open question below) src/mcp/types/_types.py:1972 OD-2 removed-method types kept in mcp.types, grouped in a marked section src/mcp/types/_types.py:695 OD-3 tasks 2025-11-25 core task types restored; the four tasks/* methods stay out of unions and method tables; extension types not built src/mcp/types/_types.py:1538 OD-5 caching defaults inject ttlMs=0, cacheScope="private" when unset on 2026-07-28 emission src/mcp/types/_version_facts.py:676 OD-9 extra fields single extra="allow" carve-out on SubscriptionFilter src/mcp/types/_types.py:1156 OD-10 conformance recorded conformance-suite gaps (list below) this description OD-11 outbound narrowing skipped — pass-through, no strips beyond the fact rows src/mcp/types/wire.py:96 OD-12 union adapters unchanged plain smart unions; the existing form is the disposition (no marker) src/mcp/types/_types.py:2588 ff. OD-13 elicitation requestedSchema stays an untyped dict; vocabulary not modeled src/mcp/types/_spec_names.py:67 M-2 JSONRPCError.id existing shape kept (RequestId | None, no default) src/mcp/types/jsonrpc.py:207 OD-4 was superseded (no per-version naming beyond version strings); OD-6 (| None = None on newer-version-required fields, boundary injects/validates) is pervasive — neither carries a marker. Key behavioral facts (shared across all three candidates) Nothing is stripped or narrowed on old-version emission beyond the named fact rows; newer optional fields on known types pass through. At 2026-07-28, a request that must carry the _meta identity triple refuses emission when the caller has not supplied clientInfo/clientCapabilities — the boundary injects only protocolVersion, never session identity. The tool-content emission refusal ends at 2025-06-18 — the 2025-11-25 schema admits tool content; capability gating is session-layer. User-set reserved io.modelcontextprotocol/* _meta keys pass through on emission at every version (never stripped). serialize_for accepts only message bodies and envelope models; bare fragments (content blocks, capabilities, params) raise TypeError. Result-bearing unions parse by ranked structural member selection: candidates are ranked by recognized top-level keys and validated best match first; the first success wins, and a body is rejected only when every structurally matching arm rejects it (surfacing the best-ranked arm's errors). This keeps the open-shaped empty-result arm from masking a better-matching member's validation failures, and routes wire-valid sampling-with-tools bodies correctly (the two sampling arms share one top-level key set). Embedded input-request entries must carry method at 2026-07-28 (a method-less entry rejects with a missing error at the entry's own method key; earlier and unknown versions stay lenient); an unknown method value classifies as invalid params at every version. Null-valued elicitation content entries (constructible for v1.x compatibility, typed by no schema version) pass through verbatim at every version that models elicitation — this mechanism never reaches content values, now pinned by a boundary test. The 12 tasks-capability sub-models are module-private in _types.py (they back the capabilities tasks field types but are not exported). Open questions for the maintainer OD-1 structuredContent interim: the sentinel design (wire-absent vs explicit-null structuredContent) is jointly unsatisfiable with the existing test suite and the types-only scope at this base; the branch ships the plain-None interim. The sentinel plus its carve-out set remains the post-review option. Annotations.lastModified deferred: the 2025-11-25 field trips an existing listing-snapshot test; the burn-down allowlist carries the three findings as deliberate deviations. Landing the field plus the one snapshot flip remains the post-review option. ServerResult resolution pin: appending the all-optional input-required arm flips one adversarial Discover-keyed frame to DiscoverResult; pinned in tests/types/test_unions.py. Reverting via a callable discriminator or arm exclusion remains the post-review option. ElicitResult.content | None value 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: Generic resultType: "complete" emission on ordinary (non-MRTR) results, incl. the empty-result surface (only MRTR scenarios assert resultType). Superset-union membership / version-gated dispatch+emission as such. ClientCapabilities.extensions / ServerCapabilities.extensions — unasserted on both sides. structuredContent any-JSON-value widening incl. explicit null; inputSchema/outputSchema 2020-12 widening. prompts/list draft pagination/required-params scenario — none; per-method MRTR coverage of prompts/get (vs resources/read) unconfirmed — verify against the conformance repo before counting it. Roots draft behavior: no top-level roots/list on 2026-07-28 (embedded delivery); must not emit notifications/roots/list_changed on draft — none. Required-params/_meta materialization (PaginatedRequest half) — none identifiable. Subscriptions: resourceSubscriptions filter path; extension taskIds; removed-era-method -32601 — three gaps. MRTR-embedded sampling member payloads specifically — none. Tasks: zero scenarios (conformance issues #244/#261 unmerged) — family-wide gap. Known limitations The 2026-07-28 tasks extension's own types are not built; only the 2025-11-25 core task types are restored (typed, never dispatched). Session-layer halves are deferred by design: the sampling.tools and elicitation.url capability gates, the logLevel send condition, and drop-vs-log handling for version-invalid notifications. Caller-supplied identity at 2026-07-28: for the request methods whose _meta must carry the triple (tools/call, prompts/list, server/discover, …), serialize_for injects only protocolVersion; if the caller or session has not pre-seeded clientInfo/clientCapabilities in params._meta, emission refuses rather than synthesizing identity. Oracle regeneration tooling ships in-repo (scripts/update_spec_types.py). AI Disclaimer
Open Graph Description: 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 com...
X Description: 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 com...
Opengraph URL: https://github.com/modelcontextprotocol/python-sdk/pull/2844
X: @github
Domain: github.com
| route-pattern | /:user_id/:repository/pull/:id/files(.:format) |
| route-controller | pull_requests |
| route-action | files |
| fetch-nonce | v2:0cab449d-f18f-5bae-689c-40a3da9c8f50 |
| current-catalog-service-hash | ae870bc5e265a340912cde392f23dad3671a0a881730ffdadd82f2f57d81641b |
| request-id | B778:311DC9:1B18AD1:2462E45:6A59841B |
| html-safe-nonce | b541f54fa278df6e33f7c43331b3c204cee37f2017dc2d6681cf5996afa28d7c |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJCNzc4OjMxMURDOToxQjE4QUQxOjI0NjJFNDU6NkE1OTg0MUIiLCJ2aXNpdG9yX2lkIjoiNjUwMzM5NDQxNDI5NTYxNDQ5MSIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9 |
| visitor-hmac | 5fe39c737bb8d05093d64b1e7b1c6730f519b0c4a80e91512bff498d90b9677d |
| hovercard-subject-tag | pull_request:3853699644 |
| github-keyboard-shortcuts | repository,pull-request-list,pull-request-conversation,pull-request-files-changed,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/modelcontextprotocol/python-sdk/pull/2844/files |
| twitter:image | https://avatars.githubusercontent.com/u/224885523?s=400&v=4 |
| twitter:card | summary_large_image |
| og:image | https://avatars.githubusercontent.com/u/224885523?s=400&v=4 |
| og:image:alt | 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 com... |
| og:site_name | GitHub |
| og:type | object |
| hostname | github.com |
| expected-hostname | github.com |
| None | a540949572872b935b393b36db38922db390ae71c859537d741b8f3eb7e545b5 |
| turbo-cache-control | no-preview |
| diff-view | unified |
| go-import | github.com/modelcontextprotocol/python-sdk git https://github.com/modelcontextprotocol/python-sdk.git |
| octolytics-dimension-user_id | 182288589 |
| octolytics-dimension-user_login | modelcontextprotocol |
| octolytics-dimension-repository_id | 862584018 |
| octolytics-dimension-repository_nwo | modelcontextprotocol/python-sdk |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 862584018 |
| octolytics-dimension-repository_network_root_nwo | modelcontextprotocol/python-sdk |
| turbo-body-classes | logged-out env-production page-responsive full-width |
| disable-turbo | true |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 624bb50a7497aa346bef8cc3743af408a9ea10ca |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width