Skip to content

test(e2e): spec-version lifecycle infrastructure for the 2026-07-28 release#2226

Open
felixweinberger wants to merge 2 commits into
mainfrom
fweinberger/m0-manifest-annotations
Open

test(e2e): spec-version lifecycle infrastructure for the 2026-07-28 release#2226
felixweinberger wants to merge 2 commits into
mainfrom
fweinberger/m0-manifest-annotations

Conversation

@felixweinberger
Copy link
Copy Markdown
Contributor

@felixweinberger felixweinberger commented Jun 1, 2026

🗺️ 2026-07-28 Spec Implementation — milestone tracker · This PR: M0: e2e manifest infrastructure (version label, supersession links, symmetry gate)
Adds the manifest infrastructure the 2026-07-28 spec release needs: the version label, and structural supersession links with a symmetry gate. No requirement entries are annotated or added here — each implementation PR carries its own manifest changes (retirements + successors + tests) together with the code that makes them true.

Motivation and Context

The upcoming spec release (stateless/sessionless/MRTR — SEP-2575/2567/2322) retires some 2025 behaviors and replaces others. Recording those changes requires two pieces of manifest infrastructure:

  1. KNOWN_SPEC_VERSIONS — version labels that addedInSpecVersion/removedInSpecVersion bounds and knownFailure scoping may reference. ALL_SPEC_VERSIONS (the active matrix axis) is unchanged: only 2025-11-25 registers cells.
  2. Structural supersession links — when a release replaces a behavior, the retired entry records its successor (supersededBy) and the new entry lists what it replaces (supersedes: readonly string[]). A coverage gate enforces referential integrity and exact symmetry, plus two consistency rules: supersededBy requires removedInSpecVersion, and supersedes requires addedInSpecVersion.

The actual annotations land incrementally: each implementation PR marks the entries it retires and adds the successors it implements, in the same diff as the code — so every manifest change is reviewed alongside the behavior change that justifies it.

How Has This Been Tested?

Full e2e matrix unchanged: 1,231 cells = 1,139 passed + 92 expected-fail, 0 unexpected failures. The new symmetry gate passes (vacuously, until entries populate the fields). Typecheck and lint clean.

Breaking Changes

None. Test-infrastructure only; no SDK code touched.

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

@felixweinberger felixweinberger requested a review from a team as a code owner June 1, 2026 12:23
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jun 1, 2026

⚠️ No Changeset found

Latest commit: 3ecd8a8

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@felixweinberger
Copy link
Copy Markdown
Contributor Author

Stack 1/3#2227 (conformance baseline) and #2228 (SEP-2260) are stacked on top of this PR. Review/merge bottom-up.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Jun 1, 2026

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/client@2226

@modelcontextprotocol/codemod

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/codemod@2226

@modelcontextprotocol/server

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/server@2226

@modelcontextprotocol/server-legacy

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/server-legacy@2226

@modelcontextprotocol/express

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/express@2226

@modelcontextprotocol/fastify

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/fastify@2226

@modelcontextprotocol/hono

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/hono@2226

@modelcontextprotocol/node

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/node@2226

commit: 3ecd8a8

@felixweinberger
Copy link
Copy Markdown
Contributor Author

Review follow-up: every removedInSpecVersion annotation was individually audited against the draft spec text (full table in the implementation workspace). Result: 0 wrongly removed — 38 mechanisms genuinely gone, 34 are the removal-half of supersedes pairs (siblings land with their implementing milestones), 1 ambiguous (logging:set-level:invalid-level — the RPC is gone but the draft has an analogous per-request logLevel→-32602 rule; documented in the entry note, decided when per-request logging lands). A new commit adds supersession/reversal context notes to the 8 highest-risk entries.

Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional findings (outside current diff — PR may have been updated during review):

@felixweinberger edit: this is a leftover from an old version of this diff - significantly simplified now.

Extended reasoning...
  • 🔴 test/e2e/requirements.ts:1492-1500 — The retirement annotation sweep misses several session-dependent requirements defined away from the main session blocks: flow:session:terminate-then-reconnect (line ~2397), hosting:session:lifecycle-callbacks (~2780), and typescript:client-transport:http:session-id-property / session-id-option (~2697/2704) — all describe Mcp-Session-Id lifecycle behavior that the 2026-07-28 release retires, yet none carry removedInSpecVersion: '2026-07-28' (flow:multi-client:stateful-isolation at ~2375 may also qualify). Either annotate them too or add a note recording why they survive; otherwise these cells will register against retired behavior when the 2026-07-28 axis is activated

    What's missing. This PR's deliverable is the complete set of removedInSpecVersion: '2026-07-28' annotations for behaviors the sessionless 2026-07-28 release retires. The diff annotates the entire session family it touches — every hosting:session:* entry in the main block (create, delete, reuse, unknown-id, missing-id, id-charset, isolation, reinitialize, post-termination-404, delete-cancels-inflight, cors-expose) plus the client-side siblings (client-transport:http:session-stored, 404-surfaces, session-404-reinitialize, terminate-405-ok). But four (possibly five) session-dependent requirements defined elsewhere in requirements.ts were left un-annotated:\n\n- flow:session:terminate-then-reconnect (~line 2397) — "After terminating a session, a fresh connection obtains a new session id and operations succeed"; sourced from the same #session-management spec section as the annotated entries.\n- hosting:session:lifecycle-callbacks (~line 2780) — "StreamableHTTPServerTransport invokes onsessioninitialized with the new session id after initialization and onsessionclosed when the client issues DELETE"; composed entirely of session creation + DELETE termination, both retired here, and it even shares the identical note text with the annotated hosting:session:* siblings.\n- typescript:client-transport:http:session-id-property (~line 2697) — ".sessionId exposes the negotiated session id after initialization"; the same store-the-negotiated-Mcp-Session-Id mechanism as the annotated client-transport:http:session-stored. Against a sessionless server there is no negotiated session id to expose.\n- typescript:client-transport:http:session-id-option (~line 2704) — constructor sessionId sent as the Mcp-Session-Id header. (More arguable, since it only constrains client-side header emission — but then it deserves a note recording why it survives.)\n- Possibly also flow:multi-client:stateful-isolation (~line 2375), which depends on each client receiving a distinct session.\n\nWhy it looks like a sweep miss rather than a deliberate exception. No principled rule visible in the diff explains the omissions: source: 'sdk' entries were not exempted (hosting:session:cors-expose, isolation, reinitialize, delete-cancels-inflight, client-transport:http:resume-stream-api are all sdk-sourced and annotated), and typescript:-prefixed entries were not exempted either (typescript:hosting:resume:bad-event-id, typescript:server:get-client-capabilities, typescript:mcpserver:tool:extra are annotated). flow:* entries weren't deferred wholesale (flow:resume:tool-call-resumption-token and flow:elicitation:url-at-session-init are annotated). What the missed entries do have in common is location: each sits hundreds of lines away from its family's main block (the flow section, the late typescript: client-transport section, and the tail of the file just before the Legacy SSE section), which is the classic shape of a partial sweep. Where the PR did deliberately keep an entry alive, it added an explanatory note (e.g. the mrtr:caps-gated-inputrequests rationale on elicitation:capability:mode-mismatch / roots:list:not-supported); these entries got neither an annotation nor a note.\n\nConcrete walk-through of the impact. Take flow:session:terminate-then-reconnect:\n1. Today, ALL_SPEC_VERSIONS = ['2025-11-25'], so the annotation set is inert — nothing changes, exactly as the PR description states.\n2. When the follow-up PR adds '2026-07-28' to ALL_SPEC_VERSIONS, cell registration filters each requirement by removedInSpecVersion (a version registers only while v < removedInSpecVersion).\n3. hosting:session:create, hosting:session:delete, and hosting:session:post-termination-404 correctly drop their 2026-07-28 cells, because they are annotated here.\n4. flow:session:terminate-then-reconnect — which is literally a composition of those three retired behaviors (terminate via DELETE, then obtain a new session id) — is not annotated, so it registers a 2026-07-28 cell and the suite asks a sessionless server to hand out a session id. The same logic applies to lifecycle-callbacks (no session is ever initialized, so onsessioninitialized never fires) and session-id-property (.sessionId is never populated).\n\nWhy nothing else catches this. removedInSpecVersion is the only mechanism that stops a cell from registering for a new version; there is no cross-check that session-dependent requirements are mutually consistent. The gap would only surface as unexpected failures during the activation PR — and the entire point of doing this bookkeeping as a separate M0 step is to have that data correct in advance.\n\nFix. Add removedInSpecVersion: '2026-07-28' to the four entries (and decide on flow:multi-client:stateful-isolation), or — if any of them are intentionally kept (e.g. the constructor sessionId option as a back-compat affordance for older servers, mirroring the hosting:http:batch precedent) — extend their note to record that rationale, the way the diff already does for the surviving elicitation/roots entries. Severity-wise this is inert today, but the completeness of this annotation set is the PR's stated purpose, so it's worth resolving before the stack on top of it builds on the data.

Comment thread test/e2e/requirements.ts
@felixweinberger
Copy link
Copy Markdown
Contributor Author

Re the review finding on missed session-dependent entries: confirmed — all 5 named entries were genuine misses, and a full completeness sweep of the manifest found 10 more (including roots:list-changed, where the draft changelog deletes the notification outright). All 15 are now annotated with the same supersession/gone-for-good treatment as the rest.

This PR also picked up a structural change from maintainer review: every retired entry that has a 2026 successor now gets that successor created in this PR — 27 superseding requirement entries with supersedes links and stub tests (registering zero cells until the 2026 version activates), so the removal decision and the supersession decision land together. Entries retired with no replacement carry an explicit gone-for-good note.

Final shape: 88 retired entries (73 original + 15 from the sweep) · 27 superseding stubs · 20 survives-with-reason notes · matrix unchanged (1,231 cells, 0 unexpected failures).

@felixweinberger
Copy link
Copy Markdown
Contributor Author

Per maintainer review: supersession links are now structural fields, not prose. Requirement.supersedes is a readonly string[] (a new entry lists every entry it replaces), retired entries carry supersededBy, and a new coverage gate enforces referential integrity + exact symmetry (plus: supersededBy requires removedInSpecVersion; supersedes requires addedInSpecVersion). Notes now carry only semantic context. 43 links, 27 superseding entries, 8 of which replace multiple retired entries.

@felixweinberger
Copy link
Copy Markdown
Contributor Author

Per review: the superseding-stub section comments are gone; each stub now carries // TODO(2026-07-28, @felixweinberger) directly above its throw, and the requirements manifest section header was dropped.

@felixweinberger felixweinberger changed the title test(e2e): annotate requirements retired by the 2026-07-28 spec release test(e2e): spec-version lifecycle infrastructure for the 2026-07-28 release Jun 1, 2026
@felixweinberger felixweinberger force-pushed the fweinberger/m0-manifest-annotations branch from 5109d18 to 6784af5 Compare June 1, 2026 14:19
@felixweinberger
Copy link
Copy Markdown
Contributor Author

Restructured per maintainer direction. This PR is now infrastructure-only: the version label + the supersession-link fields + their symmetry gate. The bulk annotation sweep (88 retirements, 27 superseding stubs) has been removed from this PR — that content moves into the implementation PRs, where each retirement/successor lands in the same diff as the code that makes it true. Rationale: reviewing a manifest change next to its code change preserves the association between behavior change and implementation; a big upfront sweep loses it.

Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — small, test-infrastructure-only change (version label, structural supersession fields, symmetry gate); the only finding is the doc nit on CLAUDE.md line 58, which can be folded in here or with the first implementation PR.

Extended reasoning...

Overview

The PR touches only test/e2e/types.ts and test/e2e/coverage.test.ts. It splits the spec-version axis into KNOWN_SPEC_VERSIONS (typing for lifecycle bounds, now including 2026-07-28) and ALL_SPEC_VERSIONS (the active matrix axis, unchanged at 2025-11-25, constrained with satisfies readonly SpecVersion[] so its literal type is preserved), changes Requirement.supersedes from a single string to a readonly array, adds supersededBy, and replaces the old referential-integrity gate with a stricter symmetry gate.

Verification

I confirmed requirements.ts contains no existing supersedes, addedInSpecVersion, or removedInSpecVersion entries, so the field-shape change breaks no current data and the new gate passes vacuously, exactly as the PR description states. helpers/verifies.ts filters cells on ALL_SPEC_VERSIONS, which is unchanged, so the matrix registers the same 1,231 cells. The gate logic itself is straightforward set/back-link checking with clear error messages and the consistency rules (supersededBy ⇒ removedInSpecVersion, supersedes ⇒ addedInSpecVersion) match the stated convention.

Security risks

None — no SDK or runtime code is touched; this is test-suite manifest infrastructure only.

Level of scrutiny

Low. Test-only, ~50 changed lines, self-contained, and inert until a later PR adds 2026-07-28 to ALL_SPEC_VERSIONS. The earlier review concern about missed retirement annotations no longer applies: the bulk annotation sweep was removed from this PR per maintainer direction, so there is nothing here for it to be incomplete against.

Other factors

The single remaining finding is a documentation nit (test/e2e/CLAUDE.md line 58 still describes the old single-string supersedes link), posted as an inline comment. It does not affect correctness and is naturally fixed either here or alongside the first implementation PR that exercises the new fields.

Comment thread test/e2e/types.ts
KNOWN_SPEC_VERSIONS carries version labels the manifest may reference in
addedInSpecVersion/removedInSpecVersion bounds and knownFailure scoping.
ALL_SPEC_VERSIONS (the active matrix axis driving cell registration) is
unchanged and stays 2025-11-25 only.
Comment thread test/e2e/types.ts
When a spec release replaces a behavior, the retired entry records its
successor via supersededBy and the new entry lists what it replaces via
supersedes. A coverage gate enforces referential integrity and exact
symmetry, plus: supersededBy requires removedInSpecVersion, and supersedes
requires addedInSpecVersion. Entries populate these fields in the PRs that
implement the corresponding spec changes.
@felixweinberger felixweinberger force-pushed the fweinberger/m0-manifest-annotations branch from 87f95b3 to 3ecd8a8 Compare June 1, 2026 15:25
Comment thread test/e2e/types.ts
@modelcontextprotocol modelcontextprotocol deleted a comment from claude Bot Jun 1, 2026
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — small, self-contained test-infrastructure change (version label, supersession-link fields, and a symmetry gate); prior review feedback, including the CLAUDE.md convention text, is now reflected in the diff.

Extended reasoning...

Overview

This PR touches only the e2e test harness: test/e2e/types.ts (adds KNOWN_SPEC_VERSIONS with the 2026-07-28 label while keeping ALL_SPEC_VERSIONS — the active matrix axis — at 2025-11-25 only, and converts supersedes from a single string to a readonly string[] plus a new supersededBy back-link), test/e2e/coverage.test.ts (replaces the old referential-integrity check with a symmetry gate that also enforces supersededByremovedInSpecVersion and supersedesaddedInSpecVersion), and test/e2e/CLAUDE.md (contributor-doc text describing the new bidirectional convention). No SDK source code, no published package, and no requirement entries are changed.

Security risks

None. The change is confined to test-suite manifest typing, a static coverage gate, and contributor documentation — there is no runtime, transport, auth, or protocol code involved.

Level of scrutiny

Low. The diff is ~50 lines of test infrastructure. I verified that no entry in requirements.ts currently uses supersedes or supersededBy, so the string→array type change cannot break any existing manifest entry and the new gate passes vacuously, exactly as the description states. helpers/verifies.ts registers cells from ALL_SPEC_VERSIONS only, which is unchanged, so the matrix shape (1,231 cells) is unaffected by adding the 2026-07-28 label to KNOWN_SPEC_VERSIONS. The gate logic itself is straightforward and its error messages are precise.

Other factors

The PR went through several restructures during review; the current head is the infrastructure-only shape requested by maintainers, with the bulk annotation sweep deliberately deferred to the implementation PRs. All previously raised review threads are addressed in the current diff: the missed-annotation concern no longer applies (no annotations ship here), and the CLAUDE.md line-58/59 update that was earlier missing from the pushed head is now present in the diff. The bug hunting system found no issues in the current revision. Stacked PRs (#2227, #2228) depend on this landing first, and the change is the minimal base they need.

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