fix(core-internal): let explicit-schema handlers/calls escape the era-universe gate - #2599
Open
freya0926 wants to merge 1 commit into
Open
Conversation
…-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
🦋 Changeset detectedLatest commit: c49b7ba The changes in this PR will be included in the next version bump. This PR includes changesets to release 6 packages
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #2598.
The inbound and outbound era gates in
Protocolrejected 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) definestasks/getandtasks/cancel, both of which the 2025-11-25 revision used for now-removed core methods, so:server.setRequestHandler('tasks/get', { params, result }, handler)and the handler would never run — every inbound request answered-32601before the handler was consultedtasks/getviarequest({ method: 'tasks/get', params }, schema)either — it threwSdkError(MethodNotSupportedByProtocolVersion)locally before reaching the transport, despitedocs/migration/support-2026-07-28.mddocumenting this exact call as the recommended task-interop patternBoth 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/pingstill can't cross eras). 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 name collision no longer blocks it.Changes
packages/core-internal/src/shared/protocol.ts: track explicit-schemasetRequestHandlerregistrations in a new_customSchemaRequestMethodsset; exempt them from the inbound era gate in_onrequest. Reorder the publicrequest()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/getwith a registered handler answering-32601) to assert the fix, add a case for the still-strict typed path, and add outbound coverage forrequest()with an explicit schema..changeset/era-gate-explicit-schema-handlers.md: patch bump for@modelcontextprotocol/clientand@modelcontextprotocol/server.Test plan
pnpm --filter @modelcontextprotocol/core-internal test— all passing except a pre-existing, unrelatedschemaTwinConformancefailure (reproduces identically on a cleanmaincheckout in this environment)pnpm --filter @modelcontextprotocol/server test/@modelcontextprotocol/client test— all passing except a pre-existing, unrelatedworkerdSchemaPreloadbuild-dependent failure (also reproduces on cleanmain)pnpm --filter @modelcontextprotocol/core-internal typecheck/ server / client typecheck — clean-32601, typed handlers (ping) still fully era-gated, outbound explicit-schemarequest()now reaches the transport on the deleted-method era