Skip to content

fix(core-internal): let explicit-schema handlers/calls escape the era-universe gate - #2599

Open
freya0926 wants to merge 1 commit into
modelcontextprotocol:mainfrom
freya0926:fix/2598-tasks-get-cancel-era-gate
Open

fix(core-internal): let explicit-schema handlers/calls escape the era-universe gate#2599
freya0926 wants to merge 1 commit into
modelcontextprotocol:mainfrom
freya0926:fix/2598-tasks-get-cancel-era-gate

Conversation

@freya0926

Copy link
Copy Markdown

Summary

Fixes #2598.

The inbound and outbound era gates in Protocol rejected any method name that ever appeared in a past protocol revision's registry but is absent from the current era's registry — even when the consumer had explicitly registered a handler for it (or supplied a schema when sending). This makes any extension that reuses a historical core method name permanently unreachable: the Tasks extension (SEP-2663) defines tasks/get and tasks/cancel, both of which the 2025-11-25 revision used for now-removed core methods, so:

  • a 2026-era server could register server.setRequestHandler('tasks/get', { params, result }, handler) and the handler would never run — every inbound request answered -32601 before the handler was consulted
  • a 2026-era client could never send tasks/get via request({ method: 'tasks/get', params }, schema) either — it threw SdkError(MethodNotSupportedByProtocolVersion) locally before reaching the transport, despite docs/migration/support-2026-07-28.md documenting this exact call as the recommended task-interop pattern

Both gates now only apply to typed dispatch — setRequestHandler(method, handler) inbound, request(method, options) outbound — which is exactly the path the SDK's own built-ins (initialize, ping, logging/setLevel) use, and which correctly stays fully era-gated (verified by a new/updated test: initialize/ping still can't cross eras). A method registered or sent with an explicit schemasetRequestHandler(method, schemas, handler) / request(request, resultSchema, options) — is the extension-authoring path: the consumer supplied their own validation, so a historical registry name collision no longer blocks it.

Changes

  • packages/core-internal/src/shared/protocol.ts: track explicit-schema setRequestHandler registrations in a new _customSchemaRequestMethods set; exempt them from the inbound era gate in _onrequest. Reorder the public request() overload so the outbound era gate only runs on the typed-dispatch branch, not the explicit-schema branch.
  • packages/core-internal/src/wire/codec.ts: update the module-doc to describe the refined (schema-aware) shadowing policy instead of the old blanket one.
  • docs/migration/support-2026-07-28.md: correct the two spots that documented the old (broken) behavior, including the task-interop recipe that never actually worked inbound.
  • packages/core-internal/test/wire/eraGates.test.ts: flip the test that pinned the bug (tasks/get with a registered handler answering -32601) to assert the fix, add a case for the still-strict typed path, and add outbound coverage for request() with an explicit schema.
  • .changeset/era-gate-explicit-schema-handlers.md: patch bump for @modelcontextprotocol/client and @modelcontextprotocol/server.

Test plan

  • pnpm --filter @modelcontextprotocol/core-internal test — all passing except a pre-existing, unrelated schemaTwinConformance failure (reproduces identically on a clean main checkout in this environment)
  • pnpm --filter @modelcontextprotocol/server test / @modelcontextprotocol/client test — all passing except a pre-existing, unrelated workerdSchemaPreload build-dependent failure (also reproduces on clean main)
  • pnpm --filter @modelcontextprotocol/core-internal typecheck / server / client typecheck — clean
  • New/updated unit tests cover: inbound explicit-schema handler now reachable on the era that deleted the core method, inbound with no handler still -32601, typed handlers (ping) still fully era-gated, outbound explicit-schema request() now reaches the transport on the deleted-method era

…-universe gate

The inbound and outbound era gates rejected any method name that ever
appeared in a past protocol revision's registry but is absent from the
current era's registry, even when the consumer explicitly registered a
handler (or supplied a schema on send) for it. This made extension
methods that reuse a historical core method name unreachable: the Tasks
extension (SEP-2663) defines `tasks/get` and `tasks/cancel`, both of
which the 2025-11-25 revision used for now-removed core methods, so a
2026-era server could never serve them and a 2026-era client could
never send them — every attempt answered -32601 or threw
MethodNotSupportedByProtocolVersion before the handler or the transport
were ever consulted.

Both gates now only apply to TYPED dispatch (setRequestHandler(method,
handler) inbound, request(method, options) outbound) — exactly the path
the SDK's own built-ins (initialize, ping, logging/setLevel) use, which
correctly stays era-gated. A method registered or sent with an EXPLICIT
schema (setRequestHandler(method, schemas, handler) /
request(request, resultSchema, options)) is the extension-authoring
path: the consumer supplied their own validation, so a historical
registry collision no longer blocks it.

Fixes modelcontextprotocol#2598
@freya0926
freya0926 requested a review from a team as a code owner August 1, 2026 15:31
@changeset-bot

changeset-bot Bot commented Aug 1, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c49b7ba

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 6 packages
Name Type
@modelcontextprotocol/client Patch
@modelcontextprotocol/server Patch
@modelcontextprotocol/core Patch
@modelcontextprotocol/server-legacy Patch
@modelcontextprotocol/codemod Patch
@modelcontextprotocol/core-internal Patch

Not sure what this means? Click here to learn what changesets are.

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

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.

v2: extension methods shadowed by legacy spec-method registry — custom tasks/get & tasks/cancel handlers unreachable (-32601 before handler lookup)

1 participant