Skip to content

Protocol types for 2026-07-28: superset monolith, committed per-version packages, and wire-method maps#2849

Draft
maxisbey wants to merge 1 commit into
mainfrom
maxisbey/types-two-surface-packages
Draft

Protocol types for 2026-07-28: superset monolith, committed per-version packages, and wire-method maps#2849
maxisbey wants to merge 1 commit into
mainfrom
maxisbey/types-two-surface-packages

Conversation

@maxisbey

@maxisbey maxisbey commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

This revision replaces the previous candidate body. The candidate pairing with #2850 is over: the declarative fact-row registry is dropped, and the wire boundary module this PR previously carried (mcp.types.wire) is gone with it. What remains is deliberately simpler — types, data, and two-step parse functions, with no protocol behavior hiding in a serializer.

What this is

  • mcp.types stays the single public type set (the "monolith"): one class per protocol construct, a superset of every released revision plus the upcoming 2026-07-28 revision. Existing names and shapes are unchanged (pinned by tests/types/test_public_surface.py); new constructs are additive.
  • Two committed per-version packages, mcp.types.v2025_11_25 and mcp.types.v2026_07_28, each a complete model package readable beside its pinned schema. The released schemas (2024-11-05 through 2025-11-25) only ever added fields, so the v2025_11_25 package serves all of them; the 2026-07-28 package carries the upcoming revision's stricter shapes. These packages are now the validating layer: schema-exact validation happens through the package serving the negotiated version, not through a separate boundary module.
  • A new leaf module mcp.types.methods: plain maps keyed (method, version) and valued with the per-version model types — what servers receive, what clients receive, and the response type for the request you sent — plus version-free monolith maps and six two-step parse functions (validate against the surface row, then construct the monolith type user code sees). Absence of a key is the version gate. The maps are plain data: importing mcp.types does not import them, and no validators are built at import time.
  • The monolith serializes the 2026-07-28 result fields as ordinary defaults: resultType: "complete" on the nine result classes that carry it, the input_required tag on InputRequiredResult, and ttlMs: 0 / cacheScope: "private" on the six cacheable results. EmptyResult still dumps {}. Outbound emission is the model's own dump everywhere — no version parameter, no field injection, no stripping.

Motivation and Context

Every session negotiates its own protocol version, and 2026-07-28 changes wire shapes materially: a required resultType on results, required reserved _meta entries on client requests, server/discover and embedded input requests replacing the server→client request channel, and resource subscriptions. One public type set can carry all of that only if version knowledge lives somewhere. In this revision it lives in data — the committed packages and the (method, version) maps — instead of in serializer code.

What changed since the previous revision of this PR

  • mcp.types.wire is deleted. serialize_for / parse_as, the version-keyed injections, and the strict-check-on-emit machinery are gone. The fields the old boundary injected on 2026-07-28 sessions are now plain serialized defaults on the monolith models, present at every version (deployed peers ignore unknown result fields).
  • The generated oracle packages are deleted. tests/spec_oracles/, the parity suite, and the generator script are gone. The committed surface packages are themselves the validating layer, exercised directly by the parse functions and the new map tests rather than compared against generated mirrors.
  • The per-version method fact tables are replaced by plain maps. Full literal enumeration in mcp.types.methods, one entry per (method, version) pair, with key absence as the gate — no compression, no dispatch logic in the data.
  • Result tag handling is data, not policy. An unrecognized resultType value no longer fails validation; it flows through as data on the open-tagged field.
  • _spec_names.py is gone; the deliberate SDK↔schema naming divergences are documented where they occur.

Behavior notes

  • Serialized output changes. The defaults above now appear in every dump of the carrying classes at every protocol version. Expect snapshot churn in exact-payload tests; in this repo the fallout was one inline snapshot and one plain assertion. Documented in docs/migration.md.
  • Nothing in the SDK consults the maps or parse functions yet. Per-version strict validation of live traffic arrives with the session-wiring follow-up PR; the module docstrings state this explicitly. SUPPORTED_PROTOCOL_VERSIONS is unchanged — 2026-07-28 is modeled, not yet negotiable.
  • Unrecognized resultType values parse. Previously the wire boundary rejected them; now they are preserved on the open-tagged field and the payload validates normally.

How Has This Been Tested?

  • Full suite: 1826 passed, 4 skipped (platform-specific), 1 xfailed, 0 failed; coverage 100.00% line + branch (26859 statements, 1864 branches, 0 missed); strict-no-cover clean; pyright strict and ruff clean.
  • tests/types/test_methods.py pins the complete method matrix per version (set equality against the known-version list, value pins for both response maps including union arm order, cross-map invariants, immutability), replays fixtures for all 208 per-version map rows through the six parse functions, and pins the gate and rejection shapes (unknown method/version, missing reserved _meta entries, required resultType at 2026-07-28, params omission, lazy adapter caching).
  • tests/types/test_wire_frames.py pins the emitted bytes of representative JSON-RPC frames (request, notification, results including the new serialized defaults and the EmptyResult carve-out, error) with full-object inline snapshots.
  • The 185-case wire-fixture corpus from the previous revision was replayed against this revision: 169/185 pass, 0 errors. All 16 differences are individually traceable to the documented behavior changes — 10 are the serialized defaults appearing in (or, for EmptyResult, leaving) dumps, 3 are error-classification changes (tag rejection removed; reserved-_meta requiredness now enforced by the 2026-07-28 package's schema), and 3 are fixtures that pin the deleted outbound injection/stripping behavior itself and need re-authoring for the new contract.

Conformance status

The conformance suite has scenarios matching the 2026-07-28 behaviors modeled here — caching fields on cacheable results (SEP-2549), input-required results (SEP-2322), request metadata — and this repo's conformance workflow runs the suite on CI. Those scenarios are gated to spec version 2026-07-28, which this PR does not make negotiable, so they exercise these types end-to-end only once the session-wiring follow-up lands.

Breaking Changes

Serialized result payloads gain the default fields described above; documented with migration guidance in docs/migration.md. Everything else is additive: existing mcp.types names and shapes are unchanged.

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

Reading order

  1. src/mcp/types/_types.py — the monolith: one class per construct, every revision's fields, the new serialized defaults.
  2. src/mcp/types/v2025_11_25/__init__.py — the released-revisions package, read beside the pinned 2025-11-25 schema (src/mcp/types/_wire_base.py holds its base classes).
  3. src/mcp/types/v2026_07_28/__init__.py — the upcoming-revision package; skim for the 2026-07-28 delta.
  4. src/mcp/types/methods.py — the maps and the six parse functions; the module docstring spells out the integration contract.
  5. tests/types/test_methods.py — the method matrix, value pins, invariants, and parse/rejection behavior.
  6. docs/migration.md — the serialized-defaults change and how to adapt exact-payload assertions.

…on packages, and wire-method maps

One public type set (mcp.types) covering every protocol revision through
2026-07-28, two committed per-version model packages that act as the
schema-exact validating layer, and plain (method, version) maps with
two-step parse functions replacing the previous serializer boundary.
Version gating is data: key absence is the gate. Outbound serialization
is the model dump, with the new 2026-07-28 result fields as ordinary
serialized defaults.
@maxisbey maxisbey changed the title Two-surface protocol types: validated model packages (candidate 1/2) Protocol types for 2026-07-28: superset monolith, committed per-version packages, and wire-method maps Jun 12, 2026
@maxisbey maxisbey force-pushed the maxisbey/types-two-surface-packages branch from 6e1bfac to 9a38b0d Compare June 12, 2026 18:46
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