feat(core): make Protocol concrete and exported (alternative to #1846)#1891
Draft
felixweinberger wants to merge 16 commits intomainfrom
Draft
feat(core): make Protocol concrete and exported (alternative to #1846)#1891felixweinberger wants to merge 16 commits intomainfrom
felixweinberger wants to merge 16 commits intomainfrom
Conversation
Adds setCustomRequestHandler, setCustomNotificationHandler, sendCustomRequest, sendCustomNotification (plus remove* variants) to the Protocol class. These allow registering handlers for vendor-specific methods outside the standard RequestMethod/NotificationMethod unions, with user-provided Zod schemas for param/result validation. Custom handlers share the existing _requestHandlers map and dispatch path, so they receive full context (cancellation, task support, send/notify) for free. Capability checks are skipped for custom methods. Also exports InMemoryTransport from core/public so examples and tests can use createLinkedPair() without depending on the internal core barrel, and adds examples/server/src/customMethodExample.ts demonstrating the API.
- Guard setCustom*/removeCustom* against standard MCP method names (throws directing users to setRequestHandler/setNotificationHandler) - Add isRequestMethod/isNotificationMethod runtime predicates - Add comprehensive unit tests (15 cases) for all 6 custom-method APIs - Add ext-apps style example demonstrating mcp-ui/* methods and DOM-style event listeners built on setCustomNotificationHandler - Add @modelcontextprotocol/client path mapping to examples/server tsconfig so the example resolves source instead of dist
…id prototype-chain false positives
…typed-params overloads; migration docs
- sendCustomNotification now delegates to notification() so debouncing and
task-queued delivery apply to custom methods
- sendCustomRequest/sendCustomNotification gain a {params, result}/{params}
schema-bundle overload that validates outbound params before sending
- clarify JSDoc: capability checks are a no-op for custom methods regardless
of enforceStrictCapabilities
- add migration.md / migration-SKILL.md sections for custom protocol methods
The {params, result} schema bundle in sendCustomRequest/sendCustomNotification
is a type guard, not a transformer — the caller-provided value is sent as-is,
matching request()/v1 behavior. Transforms/defaults on the params schema are
not applied outbound (parsed.data is intentionally unused on the send path).
Adds JSDoc and a test asserting params are sent verbatim.
…r, drop ext-apps demo
… example notification to request stream; add changeset - setCustomRequestHandler/setCustomNotificationHandler now strip _meta from params before validating against the user schema, so .strict() schemas do not reject SDK-injected fields like progressToken. _meta remains available via ctx.mcpReq._meta. Adds regression test. - examples/server/src/customMethodExample.ts: pass relatedRequestId so the acme/statusUpdate notification routes to the request response stream as the comment claims (was going to the standalone SSE stream). - Add .changeset/custom-method-handlers.md (minor bump for client+server).
…st Zod) AnySchema is now StandardSchemaV1 (which Zod schemas implement), and parseSchema routes through validateStandardSchema. This lets specTypeSchema() output and other non-Zod Standard Schemas be passed directly to setCustomRequestHandler / setCustomNotificationHandler / sendCustomRequest / sendCustomNotification. parseSchema is now async; all 16 callers were already in async contexts except _setupListChangedHandler, which is now async too (called from connect()). validateStandardSchema constraint relaxed from StandardSchemaWithJSON to StandardSchemaV1. isSchemaBundle was already StandardSchema-aware.
…eric Replaces the setCustom*/sendCustom* API with overloads on setRequestHandler / setNotificationHandler / request / notification: - Two-arg spec-method form (existing): handler receives the full request object, validated by the SDK. Used by Client/Server overrides. - Three-arg form (new): (method: string, paramsSchema, handler-receives-params). Accepts any method string; params validated against caller-supplied schema. Protocol is no longer abstract: - 5 assert*Capability methods become no-op virtuals; Client/Server override. - buildContext gets a default implementation returning the BaseContext as-is. - Protocol<S extends ProtocolSpec, ContextT> — S declares the request/notification vocabulary (autocomplete reserved for follow-up; runtime path complete). - McpSpec derived from RequestTypeMap/ResultTypeMap/NotificationTypeMap; Client/Server extend Protocol<McpSpec, ...>. This lets MCP-dialect protocols (ext-apps) subclass Protocol directly with no role enforcement, no wire renames, no setCustom* split — restoring the v1 model with a cleaner generic shape. Exports Protocol, ProtocolSpec, McpSpec and InMemoryTransport from core/public. Tests: customMethods.test.ts rewritten (13 tests covering concrete instantiation, 3-arg overloads, ProtocolSpec, non-Zod StandardSchemaV1). protocol.test.ts and protocolTransportHandling.test.ts adapted to the new generic position. Examples: customMethodExample.ts (server + client) rewritten to use the 3-arg overloads on stock Client/Server. customMethodExtAppsExample.ts rewritten as class App extends Protocol<AppSpec> mixing ui/* and tools/call on one object. Docs: migration.md and migration-SKILL.md custom-methods sections updated.
🦋 Changeset detectedLatest commit: db49955 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 |
@modelcontextprotocol/client
@modelcontextprotocol/server
@modelcontextprotocol/express
@modelcontextprotocol/fastify
@modelcontextprotocol/hono
@modelcontextprotocol/node
commit: |
- SpecRequests<S>/SpecNotifications<S> conditional types: resolve to never for the default ProtocolSpec, enabling the spec-typed overload only when a concrete S is supplied - Add the typed overload (first) to setRequestHandler/setNotificationHandler/ request/notification; remove eslint-disable on the now-used S generic - Replace 'as never' casts in Client/Server.setRequestHandler with proper handler-type casts (or none where assignable) - buildContext: JSDoc warning that overriding ContextT requires overriding it - Drop _specCheck hacks (Protocol<AppSpec> constraint now enforces it) - Export SpecRequests/SpecNotifications from core/public - Type-level tests: SpecRequests resolves correctly for concrete vs default S; request/setRequestHandler infer params/result from S; string fallback works - Revert cfWorker.ts lint-reorder (pre-existing on base; not this PR's concern) - Rename/rewrite changeset to describe the actual API
…Server use default S Fix type/runtime disagreement: when calling 3-arg setRequestHandler with a method in S, overload 1 typed handler params from S[K]['params'] but runtime parses with the passed paramsSchema. Now constrains paramsSchema to StandardSchemaV1<S[K]['params']> and types handler from SchemaOutput<P>. Client/Server now extend Protocol<ProtocolSpec, ...> (default S) instead of Protocol<McpSpec, ...>, so the typed-S overload doesn't fire on them — spec methods use the 2-arg form, custom methods use the 3-arg string fallback. McpSpec stays exported for users who want to compose it.
This was referenced Apr 15, 2026
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.
Alternative to #1846/#1868.
Protocolbecomes concrete and exported;setRequestHandler/setNotificationHandler/request/notificationgain a 3-arg(method: string, paramsSchema, handler)overload alongside the spec-typed form. OptionalProtocol<S extends ProtocolSpec>generic for declaring a typed method vocabulary.Approach
assert*Capabilityabstracts +buildContext→ no-op virtuals (Client/Serveroverride to enforce)setRequestHandlerwith three overloads: typed-via-S(autocomplete fromProtocolSpec), spec-typed (RequestMethod), and string fallback.SpecRequests<S>conditional resolves toneverfor defaultS, so the typed overload only fires for concrete specs.setCustom*/sendCustom*from feat(core): add custom request/notification handler API to Protocol #1846 deleted — folded into the overloads.What this enables
class App extends Protocol<AppSpec>mixingui/*,tools/call,notifications/messageon one object — seeexamples/server/src/customMethodExtAppsExample.tsserver.setRequestHandler('acme/foo', schema, h)on a stockServer— no separate APIext-apps comparison (validated against local tarball build)
app-bridge.test.tstests passing unmodifiedProtocolWithEventsMotivation and Context
Exploring whether exporting concrete
Protocolis a simpler answer than #1846's separate*Custom*API + #1868'sExtensionHandlefor consumers that speak an MCP-dialect over a non-MCP channel.How Has This Been Tested?
core 503 / client 350 / server 55 tests;
typecheck:all+check:allclean; all 3 examples run. ext-apps re-port at the comparison numbers above (tsc clean, 276/2/0).Breaking Changes
None vs #1846 base. Restores
Protocolas public (was public in v1).Types of changes
Checklist
Additional context
Known follow-up: 3-arg
setNotificationHandleradds an asyncparseSchemahop that can break sync-dispatch expectations overInMemoryTransport. Fix is to take the sync path when the schema's~standard.validatereturns a non-Promise.