Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions packages/core-internal/src/types/spec.types.2026-07-28.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*
* Source: https://github.com/modelcontextprotocol/modelcontextprotocol
* Pulled from: https://raw.githubusercontent.com/modelcontextprotocol/modelcontextprotocol/main/schema/draft/schema.ts
* Last updated from commit: 71e306956a4959c9655e5036be215d41986596e6
* Last updated from commit: f7e99af6417ec978233d8e27e1ac878b12106542
*
* DO NOT EDIT THIS FILE MANUALLY. Changes will be overwritten by automated updates.
* To update this file, run: pnpm run fetch:spec-types 2026-07-28
Expand Down Expand Up @@ -1317,7 +1317,7 @@ export interface SubscriptionsListenRequest extends JSONRPCRequest {
* @see {@link MetaObject} for key naming rules and reserved prefixes.
* @category `subscriptions/listen`
*/
export interface SubscriptionsListenResultMeta extends ResultMetaObject {
export interface SubscriptionsListenResultMetaObject extends ResultMetaObject {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Prose sweep to fold into the already-blocking companion port: (1) the rename SubscriptionsListenResultMetaSubscriptionsListenResultMetaObject stales six SDK comments that cite the old spec export as a spec citation (listenRouter.ts:108/:308, server.ts:247, buildSchemas.ts:1073, createMcpHandlerListen.test.ts:256, serveStdioListen.test.ts:125) — none covered by the existing line-1323 companion-port comment, which enumerates only code-symbol sites; (2) the new SubscriptionsListenResultResponse makes the pre-existing justification comment at encodeContract.ts:49-51 ("subscriptions/listen … never emits a JSON-RPC result … per the spec") unambiguously false, since the spec now formalizes exactly that response wrapper and the SDK's own listenRouter emits the graceful-close listen result on both transports. Both fixes are comment-only: sweep the six citations to the new name, and rewrite the encodeContract justification (the functional exclusion from EXTENDED_RESULT_TYPE_METHODS stays correct — listen results are always 'complete'). The encodeContract comment is pre-existing (that file is untouched by this PR), but the companion port this sync forces touches exactly that wire surface, so it's the right moment to fix it.

Extended reasoning...

Part 1 — rename stales six spec-citation comments

This sync renames the anchor export SubscriptionsListenResultMetaSubscriptionsListenResultMetaObject (spec.types.2026-07-28.ts:1320, plus the _meta reference at :1344). Six SDK comments cite the old name as a spec citation, and after this PR merges, grep for SubscriptionsListenResultMeta\b in the anchor finds nothing — the cited spec export no longer exists:

  • packages/server/src/server/listenRouter.ts:108 — "graceful-close result's _meta (the spec's SubscriptionsListenResultMeta…"
  • packages/server/src/server/listenRouter.ts:308 — "results' _meta (the spec's SubscriptionsListenResultMeta extends…"
  • packages/server/src/server/server.ts:247 — "SubscriptionsListenResultMeta extends ResultMetaObject, so it carries…"
  • packages/core-internal/src/wire/rev2026-07-28/buildSchemas.ts:1073 — "Anchor SubscriptionsListenResultMeta — required subscriptionId stamp…" (explicitly cites the anchor type by its now-dead name)
  • packages/server/test/server/createMcpHandlerListen.test.ts:256 and packages/server/test/server/serveStdioListen.test.ts:125 — same citation in test comments

Not a duplicate of the existing blocking companion-port comment on line 1323: that comment enumerates only code-symbol sites (the test/spec.types.2026-07-28.test.ts:807 parity key, the export-count/coverage pins, the missing Wire2026 SubscriptionsListenResultResponseSchema, and the optional SDK-side SubscriptionsListenResultMetaSchema rename in types.ts:384 / schemas.ts:990 / the codemod mapping). Fixing everything on that list still leaves these six prose citations pointing at a nonexistent spec export — squarely the repo's Completeness recurring catch (surviving instances of the old form after a rename). CHANGELOG entries citing the old name are historical records and need no edit.

Proof: merge this PR, then run grep -n 'SubscriptionsListenResultMeta\\b' packages/core-internal/src/types/spec.types.2026-07-28.ts → zero hits. Now open listenRouter.ts:108: the comment tells a reader the graceful-close _meta shape comes from "the spec's SubscriptionsListenResultMeta" — a symbol the spec anchor no longer exports. A reader cross-referencing the anchor finds only SubscriptionsListenResultMetaObject and has to guess whether the SDK comment refers to a deleted type or a renamed one.

Part 2 — encodeContract.ts justification comment is now unambiguously false

packages/core-internal/src/wire/rev2026-07-28/encodeContract.ts:49-51 justifies excluding subscriptions/listen from EXTENDED_RESULT_TYPE_METHODS with: "subscriptions/listen is NOT in this set: it never emits a JSON-RPC result — termination is stream close (HTTP) or notifications/cancelled (stdio) per the spec." Every factual claim in that sentence is wrong:

  1. Contradicted by the spec anchor this PR updates. The anchor already defines SubscriptionsListenResult ("this result is sent only when the server tears the subscription down", spec.types.2026-07-28.ts:1331-1346), and this PR adds SubscriptionsListenResultResponse (:1348-1358) formalizing the exact JSON-RPC success response the comment says never exists "per the spec".
  2. Contradicted by the SDK's own implementation on both transports. The HTTP listen router emits the graceful-close listen JSON-RPC result as the final SSE frame (listenRouter.ts:169-186 — writeFrame of {jsonrpc, id, result: {resultType: 'complete', _meta: {subscriptionId, serverInfo}}}; interface prose at :112-117: "closeAll(): emits the empty subscriptions/listen JSON-RPC result"). The stdio router likewise returns graceful-close ListenCloseFrame results from StdioListenRouter.teardownAll (listenRouter.ts:271, :406-408) — so on stdio, graceful termination is the listen result, not notifications/cancelled.

Why it matters: this is the normative justification on the encode contract that decides which methods may pass a non-'complete' resultType through the 2026 wire stamp. A maintainer doing the companion port this PR forces (which per the existing blocking comment must add a Wire2026 SubscriptionsListenResultResponseSchema or an owned allowlist entry) reads "listen never emits a result" and could wrongly conclude the listen graceful-close frames bypass the encode/stamping contract — when the entry-built frames deliberately mirror it (resultType + serverInfo stamps at listenRouter.ts:181-184).

Pre-existing scope: encodeContract.ts is not modified by this PR, and the comment was already false against the pre-PR anchor's SubscriptionsListenResult and the listenRouter implementation (it survived #2513, the very companion port that added serverInfo stamping onto listen graceful-close results). This PR's new response wrapper merely makes the "per the spec" attribution unambiguously false. It does not block on its own.

Fix

Both fixes are comment-only, no runtime or CI effect, and belong in the companion port this PR already forces:

  • Sweep s/SubscriptionsListenResultMeta/SubscriptionsListenResultMetaObject/ across the six citation sites.
  • Rewrite the encodeContract.ts:49-51 justification to something like: "its only JSON-RPC result is the entry-built graceful-close SubscriptionsListenResult, which is always 'complete'". The functional exclusion from EXTENDED_RESULT_TYPE_METHODS stays as-is — it remains correct.

/**
* Identifies the subscription stream this response closes, so the client can
* correlate it with the originating subscription — mirroring the same key on
Comment on lines 1317 to 1323

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 This re-pull (71e30695 → f7e99af6) renames the exported SubscriptionsListenResultMeta interface to SubscriptionsListenResultMetaObject and adds a new SubscriptionsListenResultResponse export, with no companion update — so as filed the tree is deterministically red: pnpm typecheck fails with TS2724 at test/spec.types.2026-07-28.test.ts:807 (the wireParityChecks entry still references the old name), and the drift-guard vitest fails twice (export-count pin 154 vs actual 155 at line 878; comprehensive-coverage check reports both new names missing). The companion port needs more than a test-key rename: every other *ResultResponse spec type has a Wire2026 parity schema, but no SubscriptionsListenResultResponseSchema exists under src/wire/rev2026-07-28/ — so either add one plus a parity entry, or an owned FEATURE_OWNED_PENDING_2026 allowlist entry; also consider renaming the SDK-side SubscriptionsListenResultMetaSchema (types.ts:384, schemas.ts:990, codemod specSchemaNames.ts:147) for naming parity.

Extended reasoning...

What the bug is

This spec re-pull (71e30695 → f7e99af6) makes two changes to the 2026-07-28 anchor file with no companion update anywhere else in the tree:

  1. Renames the exported interface SubscriptionsListenResultMetaSubscriptionsListenResultMetaObject (spec.types.2026-07-28.ts:1320, plus the _meta reference at :1344) — presumably aligning with the *MetaObject naming convention established by ResultMetaObject / MetaObject.
  2. Adds a new exported interface SubscriptionsListenResultResponse extends JSONRPCResultResponse (spec.types.2026-07-28.ts:1349-1357).

The anchor file exists precisely so the drift-guard test trips CI when the spec moves — and it trips three ways here, but the PR ships only the anchor, leaving the tree red.

Verified failures (each reproduced empirically at the PR head, 36c2108)

A) pnpm typecheck breaks. tsc --noEmit on packages/core-internal fails at test/spec.types.2026-07-28.test.ts(807,90) with TS2724: '...spec.types.2026-07-28' has no exported member named 'SubscriptionsListenResultMeta'. The wireParityChecks entry still declares (sdk: WSubscriptionsListenResultMeta, spec: SpecTypes.SubscriptionsListenResultMeta), and that spec export no longer exists after the rename. pnpm typecheck / pnpm check:all are deterministically red as filed.

B) Export-count pin fails. The test's 'should define some expected types' asserts expect(specTypes).toHaveLength(154) (line 878), but extractExportedTypes (lines 848-851, regex /export\s+(?:interface|class|type)\s+(\w+)\b/g) now finds 155 exported declarations in the post-PR spec file — the new SubscriptionsListenResultResponse is the +1. Running pnpm test -- spec.types.2026-07-28.test.ts fails with expected ... to have a length of 154 but got 155.

C) Comprehensive-coverage check fails. The 'should have comprehensive compatibility tests' test (lines 887-897) requires every exported spec type to appear in allTypeChecks or the FEATURE_OWNED_PENDING_2026 allowlist (lines 832-844). Neither SubscriptionsListenResultMetaObject nor SubscriptionsListenResultResponse appears anywhere in the test file — the only parity key is the old name at line 807, and the allowlist contains neither — so missingTests has 2 entries and the test fails with expected [ …(2) ] to have a length of +0 but got 2.

Step-by-step proof

  1. Merge this PR as-is; check out main.
  2. Run pnpm --filter @modelcontextprotocol/core-internal typecheck → exits non-zero with TS2724 at test/spec.types.2026-07-28.test.ts(807,90), because SpecTypes.SubscriptionsListenResultMeta was renamed out from under the parity entry.
  3. Run pnpm --filter @modelcontextprotocol/core-internal test -- spec.types.2026-07-28.test.ts → 2 of 10 tests fail: the export-count pin (154 vs 155) and the coverage check (SubscriptionsListenResultMetaObject, SubscriptionsListenResultResponse both unaccounted for).
  4. Every subsequent PR on the repo now sees red typecheck and test gates through no fault of its own.

Why nothing existing prevents it

All prior review comments on this PR cover the earlier 71e30695-era deltas (the clientInfo optionality change and the serverInfo body→_meta relocation, both already flagged with their own companion-port checklists). This 71e30695→f7e99af6 delta — the SubscriptionsListenResultMetaObject rename and the new SubscriptionsListenResultResponse envelope — is a separate, unreported change; no existing checklist entry names either symbol.

How to fix

One companion port alongside the anchor update:

  • Rename the wireParityChecks key and type references at test/spec.types.2026-07-28.test.ts:807 from SubscriptionsListenResultMeta to SubscriptionsListenResultMetaObject.
  • Bump the export-count pin at line 878 from 154 to 155.
  • Account for SubscriptionsListenResultResponse: every other *ResultResponse spec type has a Wire2026 parity entry backed by a wire schema (e.g. DiscoverResultResponseSchema), but grep of src/wire/rev2026-07-28/ finds no SubscriptionsListenResultResponseSchema — so either add the response-envelope schema to buildSchemas.ts plus a parity entry, or add an owned FEATURE_OWNED_PENDING_2026 allowlist entry referencing the tracking issue.
  • Consider renaming the SDK-side SubscriptionsListenResultMetaSchema (types.ts:384, schemas.ts:990, codemod mapping specSchemaNames.ts:147) for naming parity with the spec; if renamed, that's a mechanical rename for packages/codemod/src/migrations/v1-to-v2/mappings/ per CLAUDE.md.

Per the repo's own REVIEW.md Schema Compliance rule ("confirm the spec.types.*.test.ts comparisons still pass bidirectionally"), this must be green before merge.

Expand All @@ -1341,7 +1341,20 @@ export interface SubscriptionsListenResultMeta extends ResultMetaObject {
* @category `subscriptions/listen`
*/
export interface SubscriptionsListenResult extends Result {
_meta: SubscriptionsListenResultMeta;
_meta: SubscriptionsListenResultMetaObject;
}

/**
* A successful response from the server for a {@link SubscriptionsListenRequest | subscriptions/listen}
* request, sent when the server tears the subscription down gracefully.
*
* @example Subscription closed gracefully response
* {@includeCode ./examples/SubscriptionsListenResultResponse/listen-closed-response.json}
*
* @category `subscriptions/listen`
*/
export interface SubscriptionsListenResultResponse extends JSONRPCResultResponse {
result: SubscriptionsListenResult;
}
Comment on lines +1346 to 1358

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Upstream commit f7e99af6 changed schema/draft/schema.ts (this rename + the new SubscriptionsListenResultResponse) without regenerating schema.json, so for the first time on this PR the anchor and its generated schema-twin oracle disagree at the same upstream SHA — and the standing rule-4 refresh recipe is now self-blocking: vendoring upstream's new examples/SubscriptionsListenResultResponse/listen-closed-response.json (the @includeCode target above) turns schemaTwinConformance.test.ts red (unmapped directory, no allowlist), while skipping it leaves the new type with zero accept-side fixtures and the twin silently semantically behind the anchor with no CI signal. For the companion port, either re-pin to an upstream commit where schema.json is regenerated so anchor/twin/examples refresh atomically, or land the examples refresh now, adjudicate the unmapped directory by name in schemaTwinConformance.test.ts, and bump the specCorpus pins 87/128 → 88/129.

Extended reasoning...

What the bug is

This re-pull (71e30695 → f7e99af6) creates a situation none of the prior twin/corpus comments cover: the anchor and its generated schema.json twin disagree at the same upstream commit. Upstream's schema/draft/schema.json at f7e99af6 was not regenerated for this schema.ts change — it is byte-identical to the vendored twin at test/corpus/schema-twins/2026-07-28.schema.json (sha256 9281c4890630e2d1e61792fa23b4084c4ea360cd58519610cd050545ab7b8708, 180695 bytes, exactly the values pinned in test/corpus/schema-twins/manifest.json), still contains $defs.SubscriptionsListenResultMeta, and has zero occurrences of the renamed SubscriptionsListenResultMetaObject or the new SubscriptionsListenResultResponse.

That makes the OPERATIVE lifecycle rule 4 (packages/core-internal/src/types/README.md:20-26 — anchor and derived twins must never be out of sync at any commit on main) self-blocking rather than merely skipped for this delta.

Why the refresh is mutually blocking

Upstream did add the example this hunk's JSDoc references: schema/draft/examples/SubscriptionsListenResultResponse/listen-closed-response.json exists at f7e99af6 (verified HTTP 200; content {jsonrpc, id, result: {resultType: 'complete', _meta: {'io.modelcontextprotocol/subscriptionId': 'listen-1'}}}). The vendored fixtures corpus (test/corpus/fixtures/2026-07-28/, manifest pinned at 71e30695, directoryCount 87 / fileCount 128) has no such directory. The two halves of the rule-4 refresh now conflict:

  • Vendor the examples (pnpm fetch:spec-examples f7e99af6…): test/wire/schemaTwinConformance.test.ts:94-101 fails — expect(unmapped).toEqual([]) receives ['SubscriptionsListenResultResponse'], because the twin (vendored or freshly re-fetched at f7e99af6 — they are byte-identical) has no matching $def. Unlike specCorpus.test.ts (which has PENDING_2026/PENDING_2026_FILES allowlists and maps *ResultResponse dirs generically at line 98), schemaTwinConformance has no pending mechanism — its own comment says the set is 'pinned exactly, not bounded with slack' and 'must be adjudicated here by name'.
  • Skip the examples: the new spec type ships with zero accept-side fixtures, the @includeCode target stays unvendored, and the generated twin oracle is semantically behind the anchor at the anchor's own pin.

Why existing CI doesn't catch the desync

The sha-lock in schemaTwinConformance.test.ts:41-57 only checks the vendored bytes against the manifest; nothing anywhere compares the twin's $def names to the anchor's exports (registryDiffOracle reads the anchor .ts, not the twin). So an anchor-only merge lands a CI-invisible anchor↔twin semantic desync on main — exactly the state rule 4 exists to forbid.

Step-by-step proof

  1. At the PR head, sha256sum test/corpus/schema-twins/2026-07-28.schema.json9281c489…b708, 180695 bytes — matching manifest.json (still pinned at source.commit 71e30695).
  2. Fetch https://raw.githubusercontent.com/modelcontextprotocol/modelcontextprotocol/f7e99af6417ec978233d8e27e1ac878b12106542/schema/draft/schema.json → byte-identical to (1). So even a perfectly executed twin re-fetch at the anchor's new pin is a byte-noop.
  3. grep SubscriptionsListenResultMetaObject on that file → 0 hits; grep SubscriptionsListenResultMeta → 2 hits (the old name). The oracle contradicts the anchor's type inventory.
  4. Fetch …/f7e99af6…/schema/draft/examples/SubscriptionsListenResultResponse/listen-closed-response.json → HTTP 200. ls test/corpus/fixtures/2026-07-28/ → 87 directories, no SubscriptionsListenResultResponse.
  5. Vendoring that directory and running pnpm --filter @modelcontextprotocol/core-internal test -- schemaTwinConformance → the unmapped-set assertion at :94-101 fails with ['SubscriptionsListenResultResponse'].

Relationship to prior comments

This is not a duplicate of the existing companion-port comments: the confirmed spec.types finding on this PR scopes the drift-guard pins and the missing wire SubscriptionsListenResultResponseSchema, never touching twins/fixtures/schemaTwinConformance. Earlier twin/corpus comments prescribed 'refresh at the anchor's merge-time SHA' and predicted 'a fixtures refresh should produce a manifest-only diff, leaving directoryCount 87 / fileCount 128 — anything else is itself a signal to investigate.' This delta is that signal: those recipes now either leave the oracle semantically stale (twin half) or break CI in an unpredicted way (examples half). Prior deltas were content-noops or had schema.json regenerated in sync (e.g. 9a4ff8af); this is the first anchor↔twin desync at a single upstream SHA.

How to fix

Two workable scopings for the (already-blocking) companion port:

  1. Re-pin: wait for / re-pin to an upstream commit where schema.json is regenerated, so anchor, twin, and examples refresh atomically per rule 4.
  2. Land now with adjudication: vendor the examples refresh, adjudicate the unmapped SubscriptionsListenResultResponse directory by name in schemaTwinConformance.test.ts (per its own 'must be adjudicated here by name' escape hatch), bump the specCorpus pins at specCorpus.test.ts:189-190 from 87/128 → 88/129, and remove the adjudication once upstream's schema.json catches up.

Either way, the twin manifest's source.commit (still 71e30695) must move with the anchor per rule 4 — noting the previously-flagged caveat that fetch:schema-twins applies one SHA to every twin and would re-stamp the frozen 2025-11-25 oracle.


/**
Expand Down