Skip to content

feat(core): custom-method support (3-arg setRequestHandler + request schema overload)#1974

Open
felixweinberger wants to merge 7 commits intomainfrom
fweinberger/custom-methods-minimal
Open

feat(core): custom-method support (3-arg setRequestHandler + request schema overload)#1974
felixweinberger wants to merge 7 commits intomainfrom
fweinberger/custom-methods-minimal

Conversation

@felixweinberger
Copy link
Copy Markdown
Contributor

@felixweinberger felixweinberger commented Apr 28, 2026

Stacks on #1976 (_wrapHandler hook refactor). Review that first.

Custom (non-spec) method support: typed registration and sending. Replaces the #1891 / #1916 / #1917 chain with a single overload-based design. Protocol stays private (dispatcher RFC handles framework subclassing).

  • setRequestHandler / setNotificationHandler gain a 3-arg form: (method, { params, result? }, handler). params is any Standard Schema; the handler receives the parsed params object. _meta is stripped before validation (available as ctx.mcpReq._meta). Supplying result types the handler's return value.
  • request() and ctx.mcpReq.send gain a (req, resultSchema) overload for typed custom-method results.

Spec-method calls are unchanged (2-arg setRequestHandler, 1-arg request()). 2 overloads each on setRequestHandler/setNotificationHandler/request.

Motivation and Context

v2-main's setRequestHandler/request() only accept the closed RequestMethod/NotificationMethod unions, so vendor-prefixed methods (which the spec permits) cannot be registered or sent. The #1891/#1916/#1917 stack solved this with both v1-compat ZodSchema overloads and a ProtocolSpec<SpecT> typed-vocabulary mechanism; this PR is the minimal cut: one new overload per method. See discussion on #1891.

How Has This Been Tested?

packages/core/test/shared/customMethods.test.ts (9 tests): 3-arg registration, params validation, _meta strip, typed return via result, request() schema overload, mcpReq.send schema overload. typecheck/lint/build/docs/test:all clean locally.

Breaking Changes

None to v2-main. v1's setRequestHandler(ZodSchema, h) form is not restored here; it is mechanically codemod-able to the 3-arg form (#1950).

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

Additional context

Alternative to #1891 / #1916 / #1917 (which stay open for comparison). Internally widens _requestWithSchema and the TaskManager send-chain from Zod-only AnySchema to StandardSchemaV1; Zod schemas implement StandardSchemaV1 so existing internal callers are unchanged.

Protocol is intentionally not exported in this PR. A separate dispatcher RFC (gist) covers the framework-subclassing use case; until that lands, Protocol stays internal-only.

Includes a _wrapHandler protected hook so subclass per-method validation (tools/call in Server, elicitation/create/sampling/createMessage in Client) does not require redeclaring setRequestHandler's overload set. The hook runs for both registration paths; validation behavior is unchanged.

@felixweinberger felixweinberger added the v2-bc v2 backwards-compatibility series label Apr 28, 2026
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 28, 2026

Open in StackBlitz

@modelcontextprotocol/client

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

@modelcontextprotocol/server

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

@modelcontextprotocol/express

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

@modelcontextprotocol/fastify

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

@modelcontextprotocol/hono

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

@modelcontextprotocol/node

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

commit: 35a9628

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 28, 2026

🦋 Changeset detected

Latest commit: 35a9628

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

This PR includes changesets to release 7 packages
Name Type
@modelcontextprotocol/core Minor
@modelcontextprotocol/client Minor
@modelcontextprotocol/server Minor
@modelcontextprotocol/node Major
@modelcontextprotocol/express Major
@modelcontextprotocol/fastify Major
@modelcontextprotocol/hono 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

@felixweinberger felixweinberger changed the title feat(core): custom-method support (3-arg setRequestHandler + request schema overload + export Protocol) feat(core): custom-method support (3-arg setRequestHandler + request schema overload) Apr 28, 2026
@felixweinberger felixweinberger marked this pull request as ready for review April 28, 2026 17:00
@felixweinberger felixweinberger requested a review from a team as a code owner April 28, 2026 17:00
Comment thread docs/migration-SKILL.md
Comment thread packages/client/src/client/client.ts
Comment thread packages/core/src/types/schemas.ts
Comment thread packages/core/src/shared/protocol.examples.ts Outdated
Comment thread packages/core/src/shared/protocol.ts
Comment thread docs/migration.md Outdated
Comment thread packages/core/src/shared/protocol.ts
@felixweinberger felixweinberger force-pushed the fweinberger/1974a-wraphandler branch from 8f12176 to e69c6a7 Compare April 28, 2026 20:42
@felixweinberger felixweinberger force-pushed the fweinberger/custom-methods-minimal branch from 37df126 to c3382eb Compare April 28, 2026 20:49
Comment thread packages/core/src/shared/protocol.ts Outdated
Comment thread examples/client/src/customMethodExample.ts Outdated
Comment thread packages/core/src/exports/public/index.ts
Copy link
Copy Markdown
Contributor

@KKonstantinov KKonstantinov left a comment

Choose a reason for hiding this comment

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

LGTM, some potential nits, not blocking.

Comment thread packages/client/src/client/client.ts Outdated
Comment thread packages/client/src/client/client.ts Outdated
KKonstantinov
KKonstantinov previously approved these changes Apr 29, 2026
Comment thread packages/core/src/shared/protocol.ts
Comment thread .changeset/custom-methods-minimal.md
Comment thread packages/core/src/shared/protocol.ts Outdated
Base automatically changed from fweinberger/1974a-wraphandler to main April 29, 2026 12:34
@felixweinberger felixweinberger dismissed KKonstantinov’s stale review April 29, 2026 12:34

The base branch was changed.

…t()/mcpReq.send schema overloads for custom methods
…ation as 2nd arg; export StandardSchemaV1; fix example headers
…ce during async result validation); add core to changeset; setNotificationHandler JSDoc notes 2nd arg
@felixweinberger felixweinberger force-pushed the fweinberger/custom-methods-minimal branch from ab1115e to dc09b1d Compare April 29, 2026 12:37
@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.

Thanks — all prior feedback is addressed (abort-race guard, SdkError(InvalidResult), core changeset, notification _meta second arg, JSDoc, KKonstantinov's | string nits). One doc nit inline. I'm not auto-approving only because this adds public API surface on four core methods plus two new exports and is positioned as the chosen alternative to the #1891 stack — that API-shape call should get a maintainer sign-off.

Extended reasoning...

Overview

Adds custom (non-spec) method support via overloads: 3-arg setRequestHandler/setNotificationHandler ((method, {params, result?}, handler)) and a (req, resultSchema) overload on request() / ctx.mcpReq.send. Internally widens _requestWithSchema and the TaskManager send-chain from Zod-only AnySchema to StandardSchemaV1. Touches protocol.ts, taskManager.ts, schemas.ts, standardSchema.ts, sdkErrors.ts, the public barrel, both migration docs, two new examples, and a new test file (12 tests). Stacks on #1976.

What changed since my last pass

Commits dc09b1d and 614dbe8 addressed the three open items: (1) responseReceived guard in cancel() closes the abort-during-async-validation race, with a regression test; (2) result-schema validation failure now rejects with SdkError(SdkErrorCode.InvalidResult) (matching the migration guide's local-vs-wire taxonomy) and the changeset documents it; (3) @modelcontextprotocol/core: minor added to the changeset and the setNotificationHandler JSDoc now mentions the second notification argument. KKonstantinov's RequestMethod | string / NotificationMethod | string nits were applied in 34a4fd5. All 13 prior inline threads are resolved.

Security risks

None identified. No auth, crypto, transport, or permission-surface changes. The isStandardSchema discriminator is a structural duck-type check on ~standard.validate, which is the spec-defined marker; no plausible collision with RequestOptions. User-supplied schemas run locally on the caller's own data — no new untrusted-input surface.

Level of scrutiny

High — this is a deliberate public-API addition on the SDK's core dispatch surface (setRequestHandler, setNotificationHandler, request, ctx.mcpReq.send), plus two new public exports (RequestHandlerSchemas, StandardSchemaV1) and a new SdkErrorCode member. The repo's review conventions put the burden of proof on additions and treat every new export as a conscious decision. The PR is also explicitly framed as the chosen design among #1891/#1916/#1917; that selection is a maintainer call.

Other factors

Implementation quality is good: error codes are correct (InvalidParams for handler-side validation, SdkError(InvalidResult) for response validation, TypeError for misuse), the abort race is guarded and tested, _meta is recoverable on both request and notification paths, and _wrapHandler runs for both registration forms. The one remaining nit (inline) is that SdkErrorCode.InvalidResult isn't yet listed in the migration-guide SdkErrorCode enumerations — doc-only, non-blocking. Deferring solely so a maintainer can sign off on the final API shape.

Comment thread packages/core/src/errors/sdkErrors.ts
Comment thread packages/core/src/shared/protocol.ts Outdated
…RequestHandler/removeNotificationHandler/assertCanSetRequestHandler
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.

2 participants