feat(core): custom-method support (3-arg setRequestHandler + request schema overload)#1974
feat(core): custom-method support (3-arg setRequestHandler + request schema overload)#1974felixweinberger wants to merge 7 commits intomainfrom
Conversation
@modelcontextprotocol/client
@modelcontextprotocol/server
@modelcontextprotocol/express
@modelcontextprotocol/fastify
@modelcontextprotocol/hono
@modelcontextprotocol/node
commit: |
🦋 Changeset detectedLatest commit: 35a9628 The changes in this PR will be included in the next version bump. This PR includes changesets to release 7 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 |
5497a22 to
37df126
Compare
8f12176 to
e69c6a7
Compare
37df126 to
c3382eb
Compare
KKonstantinov
left a comment
There was a problem hiding this comment.
LGTM, some potential nits, not blocking.
The base branch was changed.
…t()/mcpReq.send schema overloads for custom methods
…ation as 2nd arg; export StandardSchemaV1; fix example headers
…g for discoverability (KKonstantinov)
…ce during async result validation); add core to changeset; setNotificationHandler JSDoc notes 2nd arg
ab1115e to
dc09b1d
Compare
…nstead of ProtocolError(InternalError)
|
@claude review |
There was a problem hiding this comment.
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.
…and §5 scenario table
…RequestHandler/removeNotificationHandler/assertCanSetRequestHandler
Custom (non-spec) method support: typed registration and sending. Replaces the #1891 / #1916 / #1917 chain with a single overload-based design.
Protocolstays private (dispatcher RFC handles framework subclassing).setRequestHandler/setNotificationHandlergain a 3-arg form:(method, { params, result? }, handler).paramsis any Standard Schema; the handler receives the parsed params object._metais stripped before validation (available asctx.mcpReq._meta). Supplyingresulttypes the handler's return value.request()andctx.mcpReq.sendgain a(req, resultSchema)overload for typed custom-method results.Spec-method calls are unchanged (2-arg
setRequestHandler, 1-argrequest()). 2 overloads each onsetRequestHandler/setNotificationHandler/request.Motivation and Context
v2-main's
setRequestHandler/request()only accept the closedRequestMethod/NotificationMethodunions, 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 aProtocolSpec<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,_metastrip, typed return viaresult,request()schema overload,mcpReq.sendschema 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
Checklist
Additional context
Alternative to #1891 / #1916 / #1917 (which stay open for comparison). Internally widens
_requestWithSchemaand the TaskManager send-chain from Zod-onlyAnySchematoStandardSchemaV1; Zod schemas implementStandardSchemaV1so existing internal callers are unchanged.Protocolis intentionally not exported in this PR. A separate dispatcher RFC (gist) covers the framework-subclassing use case; until that lands,Protocolstays internal-only.Includes a
_wrapHandlerprotected hook so subclass per-method validation (tools/callinServer,elicitation/create/sampling/createMessageinClient) does not require redeclaringsetRequestHandler's overload set. The hook runs for both registration paths; validation behavior is unchanged.