Skip to content

feat(core): restore v1 Zod-schema overloads on setRequestHandler/setNotificationHandler/request#1891

Open
felixweinberger wants to merge 12 commits intomainfrom
fweinberger/protocol-concrete
Open

feat(core): restore v1 Zod-schema overloads on setRequestHandler/setNotificationHandler/request#1891
felixweinberger wants to merge 12 commits intomainfrom
fweinberger/protocol-concrete

Conversation

@felixweinberger
Copy link
Copy Markdown
Contributor

@felixweinberger felixweinberger commented Apr 14, 2026

Part of the v2 BC series — see reviewer guide.

Restores the v1 calling forms on Protocol (inherited by Client/Server) so v1 code keeps working unchanged:

  • setRequestHandler(ZodSchema, handler) / setNotificationHandler(ZodSchema, handler) — method name read from the schema's method literal
  • request(req, resultSchema, options?) — explicit-result-schema form, plus method-keyed return type for spec methods
  • callTool(params, resultSchema?, options?) — accepts the v1 schema arg (ignored) for source compat
  • removeRequestHandler / removeNotificationHandler / assertCanSetRequestHandler accept any method string

Custom (non-spec) methods work via the Zod-schema form, same as v1. schema.ts/standardSchema.ts are unchanged from main.

The 3-arg validator-agnostic form is split out as #1916. Exporting Protocol + ProtocolSpec is #1917.

Motivation and Context

v1 → v2 backwards compatibility. The largest single source of v2 typecheck breaks across consumer audits was setRequestHandler no longer accepting the Zod-schema form; this restores it as a first-class overload.

How Has This Been Tested?

packages/core/test/shared/customMethods.test.ts — Zod-schema round-trip, invalid-params rejection, removeRequestHandler('any/string'), two-arg spec form, notification() mock-assignability.

Breaking Changes

None — additive.

Types of changes

  • New feature (non-breaking change which adds functionality)

Additional context

#1916 and #1917 stack on this.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 14, 2026

🦋 Changeset detected

Latest commit: d2e046d

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 Minor
@modelcontextprotocol/server Minor
@modelcontextprotocol/express Major
@modelcontextprotocol/fastify Major
@modelcontextprotocol/hono Major
@modelcontextprotocol/node Major

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

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 14, 2026

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/@modelcontextprotocol/client@1891

@modelcontextprotocol/server

npm i https://pkg.pr.new/@modelcontextprotocol/server@1891

@modelcontextprotocol/express

npm i https://pkg.pr.new/@modelcontextprotocol/express@1891

@modelcontextprotocol/fastify

npm i https://pkg.pr.new/@modelcontextprotocol/fastify@1891

@modelcontextprotocol/hono

npm i https://pkg.pr.new/@modelcontextprotocol/hono@1891

@modelcontextprotocol/node

npm i https://pkg.pr.new/@modelcontextprotocol/node@1891

commit: d2e046d

@felixweinberger felixweinberger force-pushed the fweinberger/protocol-concrete branch from db49955 to 070a9cd Compare April 15, 2026 19:38
@felixweinberger felixweinberger added the v2-bc v2 backwards-compatibility series label Apr 15, 2026
@felixweinberger felixweinberger changed the title feat(core): make Protocol concrete and exported (alternative to #1846) feat(core): concrete Protocol with custom-method overloads + deprecated v1 schema-arg shims Apr 15, 2026
@felixweinberger felixweinberger added this to the v2.0.0-bc milestone Apr 15, 2026
@felixweinberger felixweinberger force-pushed the fweinberger/protocol-concrete branch from 070a9cd to 54c7b21 Compare April 16, 2026 09:35
@felixweinberger felixweinberger force-pushed the fweinberger/protocol-concrete branch 3 times, most recently from 31f4544 to fec9cce Compare April 16, 2026 14:01
@felixweinberger felixweinberger changed the title feat(core): concrete Protocol with custom-method overloads + deprecated v1 schema-arg shims feat(core): custom-method overloads + deprecated v1 schema-arg shims + ProtocolSpec generic Apr 16, 2026
@felixweinberger felixweinberger force-pushed the fweinberger/protocol-concrete branch 7 times, most recently from f573eb9 to f20ca3b Compare April 16, 2026 18:11
@felixweinberger felixweinberger changed the title feat(core): custom-method overloads + deprecated v1 schema-arg shims + ProtocolSpec generic feat(core): restore v1 Zod-schema overloads on setRequestHandler/setNotificationHandler/request Apr 16, 2026
@felixweinberger felixweinberger force-pushed the fweinberger/protocol-concrete branch from f20ca3b to 0daaa15 Compare April 16, 2026 18:42
@felixweinberger felixweinberger marked this pull request as ready for review April 16, 2026 18:50
@felixweinberger felixweinberger requested a review from a team as a code owner April 16, 2026 18:50
Comment thread docs/migration.md
Comment thread packages/client/src/client/client.ts Outdated
Comment thread examples/server/README.md Outdated
@felixweinberger felixweinberger marked this pull request as draft April 16, 2026 19:14
@felixweinberger
Copy link
Copy Markdown
Contributor Author

@claude review

Comment thread packages/core/src/shared/protocol.ts Outdated
Comment thread docs/migration.md Outdated
@felixweinberger
Copy link
Copy Markdown
Contributor Author

@claude review

Comment thread docs/migration-SKILL.md Outdated
@felixweinberger
Copy link
Copy Markdown
Contributor Author

@claude review

Comment thread examples/client/src/customMethodExample.ts Outdated
Comment thread docs/migration-SKILL.md
@felixweinberger
Copy link
Copy Markdown
Contributor Author

@claude review

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.

All prior feedback is addressed and I didn't find anything new on this pass — leaving the final call to a human since this expands the public API surface (new overloads on request/setRequestHandler/setNotificationHandler/callTool/mcpReq.send, plus new ZodLikeRequestSchema/isZodLikeSchema/extractMethodLiteral exports) as part of the v2-BC series.

Extended reasoning...

Overview

Restores v1 calling forms on Protocol/Client/Server: Zod-schema overloads for setRequestHandler/setNotificationHandler, explicit-result-schema overload for request() and mcpReq.send(), and a v1-compat 3-arg overload for callTool(). Adds packages/core/src/util/compatSchema.ts (ZodLikeRequestSchema, isZodLikeSchema, extractMethodLiteral) and exports them from the internal barrel and public index. Widens removeRequestHandler/removeNotificationHandler/assertCanSetRequestHandler to accept any method string. Includes new tests (customMethods.test.ts, callTool.compat.test.ts, setRequestHandlerSchemaParity.test.ts), new examples, and migration-doc updates. Also a small drive-by: _requestWithSchema now rejects with SdkError(NotConnected) instead of plain Error('Not connected'), which aligns with what migration-SKILL.md already documents.

State of prior review threads

This PR has been through six review rounds with me. Everything I raised is now resolved at HEAD (296e179): the callTool(params, undefined, opts) arg-dropping bug is fixed and tested; the schema-form parity bug (bypassing tools/call/elicitation/create/sampling/createMessage wrapping) is fixed with a regression test; @deprecated was dropped from all schema-arg overloads; _registerCompatRequestHandler was inlined; migration.md/migration-SKILL.md §9/§11/Unchanged-APIs prose now agree with the implementation including mcpReq.send; the §9 intro was reworded to "in addition to" in both files; the client example now spawns the server via npx tsx; the server example now actually emits acme/searchProgress so the client handler fires.

Security risks

None identified. No auth/crypto/permission code paths touched. The new schema-sniffing logic (isZodLikeSchema, '~standard' in arg, 'parse' in arg) only affects overload dispatch on local function arguments, not wire data.

Level of scrutiny

This is a deliberate public-API expansion in core protocol code, part of a coordinated v2-BC series with a linked reviewer guide and stacked follow-ups (#1916/#1917). Per the repo's "burden of proof is on addition" / "every new export is intentional" principles, the decision to ship these overloads as first-class (vs. deprecated compat shims) and to export ZodLikeRequestSchema/isZodLikeSchema/extractMethodLiteral publicly is one a maintainer should sign off on, not a bot.

Other factors

Test coverage for the new behavior is solid (round-trip, invalid-params rejection, parity test, callTool arg-dispatch matrix). The two cfWorker.ts diffs are pure export-reorder noise. I'm not approving solely because of scope/API-surface, not because of any outstanding concern with the implementation.

…otificationHandler/request

Adds to Protocol (inherited by Client/Server):
- setRequestHandler/setNotificationHandler(ZodSchema, handler) — the v1 form, first-class
- request(req, resultSchema, opts?) — the v1 explicit-schema form, first-class
- callTool(params, resultSchema?, opts?) — accepts the v1 schema arg (ignored) for source compat
- removeRequestHandler/removeNotificationHandler/assertCanSetRequestHandler accept any method string

Custom (non-spec) methods work via the Zod-schema form, same as v1.
schema.ts/standardSchema.ts unchanged from main.
…igration.md mcpReq.send claim; complete custom-method example
…ent/Server

Normalize schema-arg to method string + parse-wrapped handler, then fall through to
the per-method dispatch (tools/call task validation, elicitation/create capability checks).
Previously the schema form short-circuited via _registerCompatRequestHandler, so e.g.
setRequestHandler(CallToolRequestSchema, h) and setRequestHandler('tools/call', h) had
different runtime behavior.

Also reword @deprecated message: schema form is not deprecated for non-spec methods (the
method-string overload is constrained to RequestMethod), so the advice now reads
'For spec methods, pass the method string instead.'
…gistration

The normalize-then-fall-through change routes custom methods through
_setRequestHandlerByMethod, which previously assumed a spec method
(unconditional getRequestSchema(method).parse). Guard with schema?. so
custom methods registered via Server/Client overrides do not crash.
…c methods

Mirrors Protocol.request()'s schema overload so handlers can send
custom-method requests via ctx.mcpReq.send. Also corrects the
_registerCompatRequestHandler JSDoc (called by Protocol's own
overload dispatch, not Client/Server overrides).
…mpatRequestHandler

The schema-arg forms of request()/setRequestHandler()/setNotificationHandler()
are a parallel supported style, not superseded: request(req, schema) is the only
typed form for non-spec method results, and the schema-first handler form gives
full-envelope validation. Replaces the @deprecated tags with plain JSDoc guidance
so the PR's own custom-method examples no longer render with strikethrough.

Also inlines _registerCompatRequestHandler (single callsite) and fixes the stale
'schema parameter removed' note in migration.md.
@felixweinberger felixweinberger force-pushed the fweinberger/protocol-concrete branch from f4b5f42 to ddd6776 Compare April 17, 2026 12:34
@felixweinberger felixweinberger marked this pull request as ready for review April 17, 2026 12:53
@felixweinberger felixweinberger requested review from KKonstantinov and removed request for KKonstantinov April 17, 2026 12:58
Comment thread packages/server/test/server/setRequestHandlerSchemaParity.test.ts
Comment thread examples/server/src/customMethodExample.ts Outdated
@felixweinberger felixweinberger marked this pull request as draft April 17, 2026 13:03
…n stdio example

The parity test sent params.task without declaring tasks capability, so
assertTaskHandlerCapability threw before the tools/call wrapper ran and
both forms produced the same capability error rather than the wrapper's
task-result-validation error. Declare tasks.requests.tools.call and
assert on the wrapper's 'Invalid task creation result' message so the
test cannot pass for the wrong reason.

Also: console.log over StdioServerTransport writes to the JSON-RPC
channel (stdout); use console.error so debug output reaches the user.
@felixweinberger
Copy link
Copy Markdown
Contributor Author

@claude review

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.

All my prior comments are addressed and I have nothing further to flag — but this adds meaningful public API surface to core Protocol (new overloads, ZodLikeRequestSchema export, compatSchema helpers), so a maintainer should sign off on the final API shape.

Extended reasoning...

Overview

This PR restores v1 calling conventions on Protocol (inherited by Client/Server): Zod-schema overloads for setRequestHandler/setNotificationHandler, explicit-result-schema overloads for request() and ctx.mcpReq.send(), a v1-compat callTool(params, schema?, opts?) overload, and widening removeRequestHandler/removeNotificationHandler/assertCanSetRequestHandler to accept any string. It adds a new util/compatSchema.ts module (ZodLikeRequestSchema, isZodLikeSchema, extractMethodLiteral), exports ZodLikeRequestSchema from the public API, updates both migration docs, and adds paired client/server examples plus three new test files. 18 files changed.

Review history

This PR has been through eight rounds of review with me. Issues raised and now resolved include: doc/code drift on mcpReq.send() schema support (twice, in opposite directions), callTool(params, undefined, opts) dropping options, schema-form setRequestHandler bypassing per-method wrapping for tools/call/elicitation/create/sampling/createMessage, contradictory @deprecated tags on the only typecheckable form for non-spec methods, stale §9 intro wording, the example spawning a non-existent dist/ build, console.log on a stdio server's stdout, and a hollow regression test that never reached the wrapper it guards. The latest commit (d2e046d) adds the tasks capability so the parity test reaches wrappedHandler and tightens the assertion to .toContain('Invalid task creation result'), and switches the stdio example to console.error. The bug-hunting system found nothing this round.

Security risks

None identified. No auth, crypto, or permission code is touched. The new overload-dispatch logic sniffs argument shape ('~standard' in arg, 'parse' in arg) but only to route between internal code paths; no user-controlled data influences anything beyond which validation schema is applied.

Level of scrutiny

High. This expands the public API of the core Protocol class and adds a new public type export. Per the repo's review principles ("burden of proof is on addition", "every new export is intentional"), API-surface additions warrant maintainer sign-off even when the implementation is correct. The PR is also the base of a stack (#1916, #1917), so the shape chosen here constrains follow-ups.

Other factors

Test coverage is solid (custom-method round-trip, callTool compat dispatch, schema-parity regression). Docs and examples are now internally consistent after several iterations. The two cfWorker.ts files have a no-op export-reorder that's cosmetic. I'm deferring rather than approving because the scope and API-design weight of this change is beyond what I should green-light without a human maintainer's review, not because I see remaining issues.

@felixweinberger felixweinberger marked this pull request as ready for review April 17, 2026 14:07
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.

Nothing further from me — d2e046d addresses the last two items (parity test now reaches the wrapper, stdio example uses stderr). Leaving final sign-off to a human since this lands new public API surface on Protocol (schema-arg overloads + ZodLikeRequestSchema export) as part of the v2-BC series with #1916/#1917 stacked on it.

Extended reasoning...

Overview

Restores v1 calling conventions on Protocol/Client/Server: Zod-schema overloads for setRequestHandler/setNotificationHandler, explicit-result-schema overload for request() and ctx.mcpReq.send(), v1-compat 3-arg callTool(), and widening removeRequestHandler/removeNotificationHandler/assertCanSetRequestHandler to string. Adds compatSchema.ts helpers, exports ZodLikeRequestSchema from the public surface, ships paired client/server examples, and rewrites migration.md / migration-SKILL.md §9/§11 to reflect both forms. 18 files, ~+550 net.

Security risks

None identified. No auth/crypto/transport-lifecycle changes; the new dispatch is type/shape sniffing on caller-supplied schemas with the same downstream validation paths as before.

Level of scrutiny

High — this is additive public API on the core Protocol class that intentionally introduces a parallel call form alongside the method-string form, which the repo's own REVIEW.md flags ("one way to do things", "burden of proof on addition"). It's a deliberate BC tradeoff that anchors #1916/#1917, so a maintainer should own the design sign-off rather than a bot.

Other factors

Seven prior review rounds surfaced real issues (schema-form bypassing per-method wrapping, dropped callTool options, doc/code drift on mcpReq.send, hollow regression test) — all now addressed with tests (customMethods.test.ts, callTool.compat.test.ts, setRequestHandlerSchemaParity.test.ts). The current revision looks internally consistent; I found nothing new to flag.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v2-bc v2 backwards-compatibility series

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant