Skip to content

Commit db49955

Browse files
fix(core): typed-S overload types handler from passed schema; Client/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.
1 parent 92a859d commit db49955

File tree

4 files changed

+30
-14
lines changed

4 files changed

+30
-14
lines changed

packages/client/src/client/client.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import type {
2121
ListResourceTemplatesRequest,
2222
ListToolsRequest,
2323
LoggingLevel,
24-
McpSpec,
24+
ProtocolSpec,
2525
MessageExtraInfo,
2626
NotificationMethod,
2727
ProtocolOptions,
@@ -206,7 +206,7 @@ export type ClientOptions = ProtocolOptions & {
206206
* The client will automatically begin the initialization flow with the server when {@linkcode connect} is called.
207207
*
208208
*/
209-
export class Client extends Protocol<McpSpec, ClientContext> {
209+
export class Client extends Protocol<ProtocolSpec, ClientContext> {
210210
private _serverCapabilities?: ServerCapabilities;
211211
private _serverVersion?: Implementation;
212212
private _negotiatedProtocolVersion?: string;

packages/core/src/shared/protocol.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {
4545
SUPPORTED_PROTOCOL_VERSIONS
4646
} from '../types/index.js';
4747
import type { AnySchema, SchemaOutput } from '../util/schema.js';
48+
import type { StandardSchemaV1 } from '../util/standardSchema.js';
4849
import { parseSchema } from '../util/schema.js';
4950
import type { TaskContext, TaskManagerHost, TaskManagerOptions, TaskRequestOptions } from './taskManager.js';
5051
import { NullTaskManager, TaskManager } from './taskManager.js';
@@ -308,7 +309,8 @@ export type ProtocolSpec = {
308309
* The {@linkcode ProtocolSpec} that describes the standard MCP method vocabulary, derived from
309310
* {@linkcode RequestTypeMap}, {@linkcode ResultTypeMap} and {@linkcode NotificationTypeMap}.
310311
*
311-
* `Client` and `Server` both extend `Protocol<McpSpec>`.
312+
* `Client` and `Server` extend `Protocol<ProtocolSpec, ...>` (default `S`), so the typed-`S` overloads do not
313+
* fire on them — spec methods use the 2-arg form, custom methods use the 3-arg string form.
312314
*/
313315
export type McpSpec = {
314316
requests: { [M in RequestMethod]: { params: RequestTypeMap[M]['params']; result: ResultTypeMap[M] } };
@@ -831,12 +833,12 @@ export class Protocol<S extends ProtocolSpec = ProtocolSpec, ContextT extends Ba
831833
* Do not use this method to emit notifications! Use
832834
* {@linkcode Protocol.notification | notification()} instead.
833835
*/
834-
request<K extends SpecRequests<S>>(
836+
request<K extends SpecRequests<S>, R extends StandardSchemaV1<_Requests<S>[K]['result']>>(
835837
method: K,
836838
params: _Requests<S>[K]['params'],
837-
resultSchema: AnySchema,
839+
resultSchema: R,
838840
options?: RequestOptions
839-
): Promise<_Requests<S>[K]['result']>;
841+
): Promise<SchemaOutput<R>>;
840842
request<M extends RequestMethod>(
841843
request: { method: M; params?: Record<string, unknown> },
842844
options?: RequestOptions
@@ -1118,10 +1120,10 @@ export class Protocol<S extends ProtocolSpec = ProtocolSpec, ContextT extends Ba
11181120
* `paramsSchema`. Absent or undefined `params` are normalized to `{}` (after stripping
11191121
* `_meta`) before validation, so for no-params methods use `z.object({})`.
11201122
*/
1121-
setRequestHandler<K extends SpecRequests<S>>(
1123+
setRequestHandler<K extends SpecRequests<S>, P extends StandardSchemaV1<_Requests<S>[K]['params']>>(
11221124
method: K,
1123-
paramsSchema: AnySchema,
1124-
handler: (params: _Requests<S>[K]['params'], ctx: ContextT) => _Requests<S>[K]['result'] | Promise<_Requests<S>[K]['result']>
1125+
paramsSchema: P,
1126+
handler: (params: SchemaOutput<P>, ctx: ContextT) => _Requests<S>[K]['result'] | Promise<_Requests<S>[K]['result']>
11251127
): void;
11261128
setRequestHandler<M extends RequestMethod>(
11271129
method: M,
@@ -1186,10 +1188,10 @@ export class Protocol<S extends ProtocolSpec = ProtocolSpec, ContextT extends Ba
11861188
* `params`). The three-arg form accepts any method string; when `method` is listed in this
11871189
* instance's {@linkcode ProtocolSpec} the params type is inferred from `S`.
11881190
*/
1189-
setNotificationHandler<K extends SpecNotifications<S>>(
1191+
setNotificationHandler<K extends SpecNotifications<S>, P extends StandardSchemaV1<_Notifications<S>[K]['params']>>(
11901192
method: K,
1191-
paramsSchema: AnySchema,
1192-
handler: (params: _Notifications<S>[K]['params']) => void | Promise<void>
1193+
paramsSchema: P,
1194+
handler: (params: SchemaOutput<P>) => void | Promise<void>
11931195
): void;
11941196
setNotificationHandler<M extends NotificationMethod>(
11951197
method: M,

packages/core/test/shared/customMethods.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,4 +225,18 @@ describe('non-Zod StandardSchemaV1', () => {
225225
const r = await a.request('acme/double', { n: 21 }, Result);
226226
expect(r.doubled).toBe(42);
227227
});
228+
229+
it('typed-S overload types handler from passed schema, not S (regression)', () => {
230+
type Spec = { requests: { 'x/y': { params: { a: string; b: string }; result: { ok: boolean } } } };
231+
const p = new Protocol<Spec>();
232+
const Narrow = z.object({ a: z.string() });
233+
p.setRequestHandler('x/y', Narrow, params => {
234+
const _a: string = params.a;
235+
// @ts-expect-error -- params is SchemaOutput<Narrow>, has no 'b' even though Spec does
236+
const _b: string = params.b;
237+
void _a;
238+
void _b;
239+
return { ok: true };
240+
});
241+
});
228242
});

packages/server/src/server/server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import type {
1818
ListRootsRequest,
1919
LoggingLevel,
2020
LoggingMessageNotification,
21-
McpSpec,
21+
ProtocolSpec,
2222
MessageExtraInfo,
2323
NotificationMethod,
2424
NotificationOptions,
@@ -101,7 +101,7 @@ export type ServerOptions = ProtocolOptions & {
101101
*
102102
* @deprecated Use {@linkcode server/mcp.McpServer | McpServer} instead for the high-level API. Only use `Server` for advanced use cases.
103103
*/
104-
export class Server extends Protocol<McpSpec, ServerContext> {
104+
export class Server extends Protocol<ProtocolSpec, ServerContext> {
105105
private _clientCapabilities?: ClientCapabilities;
106106
private _clientVersion?: Implementation;
107107
private _capabilities: ServerCapabilities;

0 commit comments

Comments
 (0)