From 6f699eac017a037eef13c1bdc04cefa9a8e4b070 Mon Sep 17 00:00:00 2001 From: Felix Weinberger Date: Thu, 16 Apr 2026 18:10:44 +0000 Subject: [PATCH 1/6] feat(core): export Protocol class + ProtocolSpec generic for typed custom vocabularies Exports the abstract Protocol class (was reachable in v1 via deep imports) and adds Protocol. Subclasses supplying a concrete ProtocolSpec get method-name autocomplete and params/result correlation on the typed setRequestHandler/setNotificationHandler overloads. --- .changeset/export-protocol-spec.md | 6 ++ packages/core/src/exports/public/index.ts | 10 ++- packages/core/src/shared/protocol.ts | 62 ++++++++++++++++- .../core/test/shared/protocolSpec.test.ts | 68 +++++++++++++++++++ 4 files changed, 140 insertions(+), 6 deletions(-) create mode 100644 .changeset/export-protocol-spec.md create mode 100644 packages/core/test/shared/protocolSpec.test.ts diff --git a/.changeset/export-protocol-spec.md b/.changeset/export-protocol-spec.md new file mode 100644 index 000000000..841845c53 --- /dev/null +++ b/.changeset/export-protocol-spec.md @@ -0,0 +1,6 @@ +--- +'@modelcontextprotocol/client': minor +'@modelcontextprotocol/server': minor +--- + +Export the abstract `Protocol` class (was reachable in v1 via deep imports) and add `Protocol` for typed custom-method vocabularies. Subclasses supplying a concrete `ProtocolSpec` get method-name autocomplete and params/result correlation on the typed `setRequestHandler`/`setNotificationHandler` overloads. diff --git a/packages/core/src/exports/public/index.ts b/packages/core/src/exports/public/index.ts index fd2cada0c..1821b4f8e 100644 --- a/packages/core/src/exports/public/index.ts +++ b/packages/core/src/exports/public/index.ts @@ -38,17 +38,21 @@ export { checkResourceAllowed, resourceUrlFromServerUrl } from '../../shared/aut // Metadata utilities export { getDisplayName } from '../../shared/metadataUtils.js'; -// Protocol types (NOT the Protocol class itself or mergeCapabilities) +// Protocol class (abstract — subclass for custom vocabularies) + types. NOT mergeCapabilities. export type { BaseContext, ClientContext, + McpSpec, NotificationOptions, ProgressCallback, ProtocolOptions, + ProtocolSpec, RequestOptions, - ServerContext + ServerContext, + SpecNotifications, + SpecRequests } from '../../shared/protocol.js'; -export { DEFAULT_REQUEST_TIMEOUT_MSEC } from '../../shared/protocol.js'; +export { DEFAULT_REQUEST_TIMEOUT_MSEC, Protocol } from '../../shared/protocol.js'; export type { ZodLikeRequestSchema } from '../../util/compatSchema.js'; // Task manager types (NOT TaskManager class itself — internal) diff --git a/packages/core/src/shared/protocol.ts b/packages/core/src/shared/protocol.ts index 8e7abbb12..4f9115ef7 100644 --- a/packages/core/src/shared/protocol.ts +++ b/packages/core/src/shared/protocol.ts @@ -305,11 +305,51 @@ type TimeoutInfo = { onTimeout: () => void; }; +/** + * Declares the request and notification vocabulary a `Protocol` subclass speaks. + * + * Supplying a concrete `ProtocolSpec` as `Protocol`'s second type argument gives method-name + * autocomplete and params/result correlation on the typed overloads of `setRequestHandler` + * and `setNotificationHandler`. The default leaves them string-keyed and untyped. + */ +export type ProtocolSpec = { + requests?: Record; + notifications?: Record; +}; + +/** + * The {@linkcode ProtocolSpec} that describes the standard MCP method vocabulary, derived from + * {@linkcode RequestTypeMap}, {@linkcode ResultTypeMap} and {@linkcode NotificationTypeMap}. + */ +export type McpSpec = { + requests: { [M in RequestMethod]: { params: RequestTypeMap[M]['params']; result: ResultTypeMap[M] } }; + notifications: { [M in NotificationMethod]: { params: NotificationTypeMap[M]['params'] } }; +}; + +type _Requests = NonNullable; +type _Notifications = NonNullable; + +/** + * Method-name keys from a {@linkcode ProtocolSpec}'s `requests` map, or `never` for the + * unconstrained default `ProtocolSpec`. Making the keys `never` for the default disables the + * spec-typed overloads on `setRequestHandler` until the caller supplies a concrete `SpecT`. + */ +export type SpecRequests = string extends keyof _Requests ? never : keyof _Requests & string; + +/** See {@linkcode SpecRequests}. */ +export type SpecNotifications = string extends keyof _Notifications + ? never + : keyof _Notifications & string; + /** * Implements MCP protocol framing on top of a pluggable transport, including * features like request/response linking, notifications, and progress. + * + * `Protocol` is abstract; `Client` and `Server` are the concrete role-specific implementations. + * Subclasses (such as MCP-dialect protocols like MCP Apps) can supply a {@linkcode ProtocolSpec} + * as the second type argument to get method-name autocomplete on their own vocabulary. */ -export abstract class Protocol { +export abstract class Protocol { private _transport?: Transport; private _requestMessageId = 0; private _requestHandlers: Map Promise> = new Map(); @@ -1042,10 +1082,19 @@ export abstract class Protocol { * Any method string; the supplied schema validates incoming `params`. Absent or undefined * `params` are normalized to `{}` (after stripping `_meta`) before validation, so for * no-params methods use `z.object({})`. `paramsSchema` may be any Standard Schema (Zod, - * Valibot, ArkType, etc.). + * Valibot, ArkType, etc.). When `method` is listed in this instance's + * {@linkcode ProtocolSpec}, params and result types are inferred from `SpecT`. * - **Zod schema** — `setRequestHandler(RequestZodSchema, (request, ctx) => …)`. The method * name is read from the schema's `method` literal; the handler receives the parsed request. */ + setRequestHandler, P extends StandardSchemaV1<_Requests[K]['params']>>( + method: K, + paramsSchema: P, + handler: ( + params: StandardSchemaV1.InferOutput

, + ctx: ContextT + ) => _Requests[K]['result'] | Promise<_Requests[K]['result']> + ): void; setRequestHandler( method: M, handler: (request: RequestTypeMap[M], ctx: ContextT) => Result | Promise @@ -1143,8 +1192,15 @@ export abstract class Protocol { * * Mirrors {@linkcode setRequestHandler}: a two-arg spec-method form (handler receives the full * notification object), a three-arg form with a `paramsSchema` (handler receives validated - * `params`), and a Zod-schema form (method read from the schema's `method` literal). + * `params`), and a Zod-schema form (method read from the schema's `method` literal). When the + * three-arg form's `method` is listed in this instance's {@linkcode ProtocolSpec}, the params + * type is inferred from `SpecT`. */ + setNotificationHandler, P extends StandardSchemaV1<_Notifications[K]['params']>>( + method: K, + paramsSchema: P, + handler: (params: StandardSchemaV1.InferOutput

) => void | Promise + ): void; setNotificationHandler( method: M, handler: (notification: NotificationTypeMap[M]) => void | Promise diff --git a/packages/core/test/shared/protocolSpec.test.ts b/packages/core/test/shared/protocolSpec.test.ts new file mode 100644 index 000000000..4058b6b1e --- /dev/null +++ b/packages/core/test/shared/protocolSpec.test.ts @@ -0,0 +1,68 @@ +import { describe, expect, it } from 'vitest'; +import { z } from 'zod'; + +import type { BaseContext, ProtocolSpec, SpecRequests } from '../../src/shared/protocol.js'; +import { Protocol } from '../../src/shared/protocol.js'; +import { InMemoryTransport } from '../../src/util/inMemory.js'; + +class TestProtocol extends Protocol { + protected assertCapabilityForMethod(): void {} + protected assertNotificationCapability(): void {} + protected assertRequestHandlerCapability(): void {} + protected assertTaskCapability(): void {} + protected assertTaskHandlerCapability(): void {} + protected buildContext(ctx: BaseContext): BaseContext { + return ctx; + } +} + +describe('ProtocolSpec typing', () => { + type AppSpec = { + requests: { + 'ui/open-link': { params: { url: string }; result: { opened: boolean } }; + }; + notifications: { + 'ui/size-changed': { params: { width: number; height: number } }; + }; + }; + + type _Assert = T; + type _Eq = [A] extends [B] ? ([B] extends [A] ? true : false) : false; + type _t1 = _Assert<_Eq, 'ui/open-link'>>; + type _t2 = _Assert<_Eq, never>>; + void (undefined as unknown as [_t1, _t2]); + + it('typed-SpecT overload infers params/result; string fallback still works', async () => { + const [t1, t2] = InMemoryTransport.createLinkedPair(); + const app = new TestProtocol(); + const host = new TestProtocol(); + await app.connect(t1); + await host.connect(t2); + + host.setRequestHandler('ui/open-link', z.object({ url: z.string() }), p => { + const _typed: string = p.url; + void _typed; + return { opened: true }; + }); + const r = await app.request({ method: 'ui/open-link', params: { url: 'https://x' } }, z.object({ opened: z.boolean() })); + expect(r.opened).toBe(true); + + host.setRequestHandler('not/in-spec', z.object({ n: z.number() }), p => ({ doubled: p.n * 2 })); + const r2 = await app.request({ method: 'not/in-spec', params: { n: 3 } }, z.object({ doubled: z.number() })); + expect(r2.doubled).toBe(6); + }); + + it('typed-SpecT overload types handler from passed schema, not SpecT (regression)', () => { + type Spec = { requests: { 'x/y': { params: { a: string; b: string }; result: { ok: boolean } } } }; + const p = new TestProtocol(); + const Narrow = z.object({ a: z.string() }); + p.setRequestHandler('x/y', Narrow, params => { + const _a: string = params.a; + // @ts-expect-error -- params is InferOutput, has no 'b' even though Spec does + const _b: string = params.b; + void _a; + void _b; + return { ok: true }; + }); + }); +}); From 7401e8b90bbb721edea5033cbf20feabc72da433 Mon Sep 17 00:00:00 2001 From: Felix Weinberger Date: Thu, 16 Apr 2026 23:40:13 +0000 Subject: [PATCH 2/6] docs: update prose now that Protocol is concrete and public --- CLAUDE.md | 2 +- packages/core/src/exports/public/index.ts | 8 ++++---- packages/core/src/shared/protocol.ts | 13 ++++++++----- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 609c920cb..aef24d828 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -67,7 +67,7 @@ The SDK is organized into three main layers: The SDK has a two-layer export structure to separate internal code from the public API: - **`@modelcontextprotocol/core`** (main entry, `packages/core/src/index.ts`) — Internal barrel. Exports everything (including Zod schemas, Protocol class, stdio utils). Only consumed by sibling packages within the monorepo (`private: true`). -- **`@modelcontextprotocol/core/public`** (`packages/core/src/exports/public/index.ts`) — Curated public API. Exports only TypeScript types, error classes, constants, and guards. Re-exported by client and server packages. +- **`@modelcontextprotocol/core/public`** (`packages/core/src/exports/public/index.ts`) — Curated public API. Exports TypeScript types, error classes, constants, guards, and the `Protocol` class. Re-exported by client and server packages. - **`@modelcontextprotocol/client`** and **`@modelcontextprotocol/server`** (`packages/*/src/index.ts`) — Final public surface. Package-specific exports (named explicitly) plus re-exports from `core/public`. When modifying exports: diff --git a/packages/core/src/exports/public/index.ts b/packages/core/src/exports/public/index.ts index 1821b4f8e..13feef07e 100644 --- a/packages/core/src/exports/public/index.ts +++ b/packages/core/src/exports/public/index.ts @@ -4,9 +4,9 @@ * This module defines the stable, public-facing API surface. Client and server * packages re-export from here so that end users only see supported symbols. * - * Internal utilities (Protocol class, stdio parsing, schema helpers, etc.) - * remain available via the internal barrel (@modelcontextprotocol/core) for - * use by client/server packages. + * Internal utilities (stdio parsing, schema helpers, etc.) remain available via + * the internal barrel (@modelcontextprotocol/core) for use by client/server + * packages. */ // Auth error classes @@ -38,7 +38,7 @@ export { checkResourceAllowed, resourceUrlFromServerUrl } from '../../shared/aut // Metadata utilities export { getDisplayName } from '../../shared/metadataUtils.js'; -// Protocol class (abstract — subclass for custom vocabularies) + types. NOT mergeCapabilities. +// Protocol class (concrete; subclass for custom vocabularies via SpecT) + types. NOT mergeCapabilities. export type { BaseContext, ClientContext, diff --git a/packages/core/src/shared/protocol.ts b/packages/core/src/shared/protocol.ts index 4f9115ef7..fe5c5874f 100644 --- a/packages/core/src/shared/protocol.ts +++ b/packages/core/src/shared/protocol.ts @@ -310,7 +310,8 @@ type TimeoutInfo = { * * Supplying a concrete `ProtocolSpec` as `Protocol`'s second type argument gives method-name * autocomplete and params/result correlation on the typed overloads of `setRequestHandler` - * and `setNotificationHandler`. The default leaves them string-keyed and untyped. + * and `setNotificationHandler`. `Protocol` defaults to {@linkcode McpSpec}; using the bare + * `ProtocolSpec` type leaves methods string-keyed and untyped. */ export type ProtocolSpec = { requests?: Record; @@ -1082,8 +1083,9 @@ export abstract class Protocol …)`. The method * name is read from the schema's `method` literal; the handler receives the parsed request. */ @@ -1193,8 +1195,9 @@ export abstract class Protocol, P extends StandardSchemaV1<_Notifications[K]['params']>>( method: K, From 441a762fd4d4334976320f2b568d2d95ef415ffb Mon Sep 17 00:00:00 2001 From: Felix Weinberger Date: Fri, 17 Apr 2026 10:12:35 +0000 Subject: [PATCH 3/6] =?UTF-8?q?docs(core):=20public/index.ts=20section=20c?= =?UTF-8?q?omment=20=E2=80=94=20Protocol=20is=20abstract,=20not=20concrete?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/exports/public/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/exports/public/index.ts b/packages/core/src/exports/public/index.ts index 13feef07e..304ff45d6 100644 --- a/packages/core/src/exports/public/index.ts +++ b/packages/core/src/exports/public/index.ts @@ -38,7 +38,7 @@ export { checkResourceAllowed, resourceUrlFromServerUrl } from '../../shared/aut // Metadata utilities export { getDisplayName } from '../../shared/metadataUtils.js'; -// Protocol class (concrete; subclass for custom vocabularies via SpecT) + types. NOT mergeCapabilities. +// Protocol class (abstract; subclass for custom vocabularies via SpecT) + types. NOT mergeCapabilities. export type { BaseContext, ClientContext, From cca9964c5557584f240120d4eb707b4ee27400d7 Mon Sep 17 00:00:00 2001 From: Felix Weinberger Date: Fri, 17 Apr 2026 11:39:35 +0000 Subject: [PATCH 4/6] fix(core): enforce SpecT result type on setRequestHandler (no loose-overload fallthrough) The SpecT-typed overload's P constraint (StandardSchemaV1) was too tight to ever match real schemas, so calls always fell through to the loose (method: string, schema, h: => Result) overload, silently accepting any return type. Loosen overload 1's P to bare StandardSchemaV1 (params type still inferred from the schema; only the result type is SpecT-constrained) and never-guard the loose overload's method param against SpecRequests so spec-method calls that don't satisfy the typed overload error instead of falling through. Same guard on setNotificationHandler for symmetry. JSDoc updated; type tests added. --- packages/core/src/shared/protocol.ts | 20 +++++++++---------- .../core/test/shared/protocolSpec.test.ts | 13 ++++++++++++ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/packages/core/src/shared/protocol.ts b/packages/core/src/shared/protocol.ts index fe5c5874f..c8bafbfa1 100644 --- a/packages/core/src/shared/protocol.ts +++ b/packages/core/src/shared/protocol.ts @@ -1085,11 +1085,11 @@ export abstract class Protocol …)`. The method * name is read from the schema's `method` literal; the handler receives the parsed request. */ - setRequestHandler, P extends StandardSchemaV1<_Requests[K]['params']>>( + setRequestHandler, P extends StandardSchemaV1>( method: K, paramsSchema: P, handler: ( @@ -1101,8 +1101,8 @@ export abstract class Protocol Result | Promise ): void; - setRequestHandler

( - method: string, + setRequestHandler( + method: M extends SpecRequests ? never : M, paramsSchema: P, handler: (params: StandardSchemaV1.InferOutput

, ctx: ContextT) => Result | Promise ): void; @@ -1194,12 +1194,10 @@ export abstract class Protocol, P extends StandardSchemaV1<_Notifications[K]['params']>>( + setNotificationHandler, P extends StandardSchemaV1>( method: K, paramsSchema: P, handler: (params: StandardSchemaV1.InferOutput

) => void | Promise @@ -1208,8 +1206,8 @@ export abstract class Protocol void | Promise ): void; - setNotificationHandler

( - method: string, + setNotificationHandler( + method: M extends SpecNotifications ? never : M, paramsSchema: P, handler: (params: StandardSchemaV1.InferOutput

) => void | Promise ): void; diff --git a/packages/core/test/shared/protocolSpec.test.ts b/packages/core/test/shared/protocolSpec.test.ts index 4058b6b1e..c86486bfc 100644 --- a/packages/core/test/shared/protocolSpec.test.ts +++ b/packages/core/test/shared/protocolSpec.test.ts @@ -65,4 +65,17 @@ describe('ProtocolSpec typing', () => { return { ok: true }; }); }); + + it('typed-SpecT setRequestHandler enforces result type (no fallthrough to loose string overload)', () => { + const p = new TestProtocol(); + // @ts-expect-error -- result must be { opened: boolean }; string overload is `never`-guarded for spec methods + p.setRequestHandler('ui/open-link', z.object({ url: z.string() }), () => ({ ok: 'wrong-type' })); + // @ts-expect-error -- empty object doesn't satisfy { opened: boolean } + p.setRequestHandler('ui/open-link', z.object({ url: z.string() }), () => ({})); + // non-spec methods still allow loose Result + p.setRequestHandler('not/in-spec', z.object({}), () => ({ anything: 1 })); + // notifications: spec and non-spec both allow any schema and return void + p.setNotificationHandler('ui/size-changed', z.object({ width: z.number(), height: z.number() }), () => {}); + p.setNotificationHandler('not/in-spec', z.object({ x: z.number() }), () => {}); + }); }); From 5707536d3649efc3e979a25524a50f7001b1d6f0 Mon Sep 17 00:00:00 2001 From: Felix Weinberger Date: Fri, 17 Apr 2026 12:26:15 +0000 Subject: [PATCH 5/6] fix: enforce result type on Client/Server 3-arg setRequestHandler; clarify ProtocolSpec.params is informational --- .changeset/export-protocol-spec.md | 2 +- packages/client/src/client/client.ts | 9 +++++++-- packages/core/src/shared/protocol.ts | 5 ++++- packages/server/src/server/server.ts | 9 +++++++-- .../test/server/setRequestHandlerSchemaParity.test.ts | 8 ++++++++ 5 files changed, 27 insertions(+), 6 deletions(-) diff --git a/.changeset/export-protocol-spec.md b/.changeset/export-protocol-spec.md index 841845c53..4ff0cf7ac 100644 --- a/.changeset/export-protocol-spec.md +++ b/.changeset/export-protocol-spec.md @@ -3,4 +3,4 @@ '@modelcontextprotocol/server': minor --- -Export the abstract `Protocol` class (was reachable in v1 via deep imports) and add `Protocol` for typed custom-method vocabularies. Subclasses supplying a concrete `ProtocolSpec` get method-name autocomplete and params/result correlation on the typed `setRequestHandler`/`setNotificationHandler` overloads. +Export the abstract `Protocol` class (was reachable in v1 via deep imports) and add `Protocol` for typed custom-method vocabularies. Subclasses supplying a concrete `ProtocolSpec` get method-name autocomplete and result-type correlation on the typed `setRequestHandler`/`setNotificationHandler` overloads (handler param types come from the `paramsSchema` argument; `ProtocolSpec['params']` is informational). diff --git a/packages/client/src/client/client.ts b/packages/client/src/client/client.ts index f22a5ee4f..dfc65666c 100644 --- a/packages/client/src/client/client.ts +++ b/packages/client/src/client/client.ts @@ -344,8 +344,13 @@ export class Client extends Protocol { method: M, handler: (request: RequestTypeMap[M], ctx: ClientContext) => ResultTypeMap[M] | Promise ): void; - public override setRequestHandler

( - method: string, + public override setRequestHandler( + method: M, + paramsSchema: P, + handler: (params: StandardSchemaV1.InferOutput

, ctx: ClientContext) => ResultTypeMap[M] | Promise + ): void; + public override setRequestHandler( + method: M extends RequestMethod ? never : M, paramsSchema: P, handler: (params: StandardSchemaV1.InferOutput

, ctx: ClientContext) => Result | Promise ): void; diff --git a/packages/core/src/shared/protocol.ts b/packages/core/src/shared/protocol.ts index c8bafbfa1..f0d960e1a 100644 --- a/packages/core/src/shared/protocol.ts +++ b/packages/core/src/shared/protocol.ts @@ -309,9 +309,12 @@ type TimeoutInfo = { * Declares the request and notification vocabulary a `Protocol` subclass speaks. * * Supplying a concrete `ProtocolSpec` as `Protocol`'s second type argument gives method-name - * autocomplete and params/result correlation on the typed overloads of `setRequestHandler` + * autocomplete and result-type correlation on the typed overloads of `setRequestHandler` * and `setNotificationHandler`. `Protocol` defaults to {@linkcode McpSpec}; using the bare * `ProtocolSpec` type leaves methods string-keyed and untyped. + * + * Only `requests[K].result` is enforced by the type system; `params` shapes are informational + * (handler param types come from the `paramsSchema` you pass at the call site). */ export type ProtocolSpec = { requests?: Record; diff --git a/packages/server/src/server/server.ts b/packages/server/src/server/server.ts index fd7b93e88..19b8f8ec3 100644 --- a/packages/server/src/server/server.ts +++ b/packages/server/src/server/server.ts @@ -232,8 +232,13 @@ export class Server extends Protocol { method: M, handler: (request: RequestTypeMap[M], ctx: ServerContext) => ResultTypeMap[M] | Promise ): void; - public override setRequestHandler

( - method: string, + public override setRequestHandler( + method: M, + paramsSchema: P, + handler: (params: StandardSchemaV1.InferOutput

, ctx: ServerContext) => ResultTypeMap[M] | Promise + ): void; + public override setRequestHandler( + method: M extends RequestMethod ? never : M, paramsSchema: P, handler: (params: StandardSchemaV1.InferOutput

, ctx: ServerContext) => Result | Promise ): void; diff --git a/packages/server/test/server/setRequestHandlerSchemaParity.test.ts b/packages/server/test/server/setRequestHandlerSchemaParity.test.ts index 313cd0e8e..1f5ff4153 100644 --- a/packages/server/test/server/setRequestHandlerSchemaParity.test.ts +++ b/packages/server/test/server/setRequestHandlerSchemaParity.test.ts @@ -63,4 +63,12 @@ describe('Server.setRequestHandler — Zod-schema form parity', () => { }); expect(res.result).toEqual({ reply: 'hi' }); }); + + it('three-arg form on Server enforces spec-method result type (no fallthrough to loose overload)', () => { + const s = new Server({ name: 't', version: '1.0' }, { capabilities: { tools: {} } }); + // @ts-expect-error -- result for 'ping' must be EmptyResult-compatible; loose overload is never-guarded for spec methods + s.setRequestHandler('ping', z.object({}), () => ({ ok: 'wrong-type' }) as { ok: string }); + // non-spec methods still allow loose Result + s.setRequestHandler('acme/custom', z.object({}), () => ({ anything: 1 })); + }); }); From 04d9133c51da74ee35f64a6a626babbf6f81ef07 Mon Sep 17 00:00:00 2001 From: Felix Weinberger Date: Fri, 17 Apr 2026 12:52:48 +0000 Subject: [PATCH 6/6] docs(core): add @remarks on Protocol subclassing stability --- packages/core/src/shared/protocol.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/core/src/shared/protocol.ts b/packages/core/src/shared/protocol.ts index f0d960e1a..76ef417d5 100644 --- a/packages/core/src/shared/protocol.ts +++ b/packages/core/src/shared/protocol.ts @@ -352,6 +352,11 @@ export type SpecNotifications = string extends keyof * `Protocol` is abstract; `Client` and `Server` are the concrete role-specific implementations. * Subclasses (such as MCP-dialect protocols like MCP Apps) can supply a {@linkcode ProtocolSpec} * as the second type argument to get method-name autocomplete on their own vocabulary. + * + * @remarks + * Subclassing `Protocol` directly is supported for MCP-dialect frameworks. The protected + * surface (`buildContext`, `assertCapability*`, `_setRequestHandlerByMethod`) may evolve in + * minor versions; prefer `Client`/`Server` unless you need a custom method vocabulary. */ export abstract class Protocol { private _transport?: Transport;