Skip to content

Commit 5707536

Browse files
fix: enforce result type on Client/Server 3-arg setRequestHandler; clarify ProtocolSpec.params is informational
1 parent cca9964 commit 5707536

File tree

5 files changed

+27
-6
lines changed

5 files changed

+27
-6
lines changed

.changeset/export-protocol-spec.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
'@modelcontextprotocol/server': minor
44
---
55

6-
Export the abstract `Protocol` class (was reachable in v1 via deep imports) and add `Protocol<ContextT, SpecT extends ProtocolSpec = McpSpec>` for typed custom-method vocabularies. Subclasses supplying a concrete `ProtocolSpec` get method-name autocomplete and params/result correlation on the typed `setRequestHandler`/`setNotificationHandler` overloads.
6+
Export the abstract `Protocol` class (was reachable in v1 via deep imports) and add `Protocol<ContextT, SpecT extends ProtocolSpec = McpSpec>` 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).

packages/client/src/client/client.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,8 +344,13 @@ export class Client extends Protocol<ClientContext> {
344344
method: M,
345345
handler: (request: RequestTypeMap[M], ctx: ClientContext) => ResultTypeMap[M] | Promise<ResultTypeMap[M]>
346346
): void;
347-
public override setRequestHandler<P extends StandardSchemaV1>(
348-
method: string,
347+
public override setRequestHandler<M extends RequestMethod, P extends StandardSchemaV1>(
348+
method: M,
349+
paramsSchema: P,
350+
handler: (params: StandardSchemaV1.InferOutput<P>, ctx: ClientContext) => ResultTypeMap[M] | Promise<ResultTypeMap[M]>
351+
): void;
352+
public override setRequestHandler<M extends string, P extends StandardSchemaV1>(
353+
method: M extends RequestMethod ? never : M,
349354
paramsSchema: P,
350355
handler: (params: StandardSchemaV1.InferOutput<P>, ctx: ClientContext) => Result | Promise<Result>
351356
): void;

packages/core/src/shared/protocol.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,9 +309,12 @@ type TimeoutInfo = {
309309
* Declares the request and notification vocabulary a `Protocol` subclass speaks.
310310
*
311311
* Supplying a concrete `ProtocolSpec` as `Protocol`'s second type argument gives method-name
312-
* autocomplete and params/result correlation on the typed overloads of `setRequestHandler`
312+
* autocomplete and result-type correlation on the typed overloads of `setRequestHandler`
313313
* and `setNotificationHandler`. `Protocol` defaults to {@linkcode McpSpec}; using the bare
314314
* `ProtocolSpec` type leaves methods string-keyed and untyped.
315+
*
316+
* Only `requests[K].result` is enforced by the type system; `params` shapes are informational
317+
* (handler param types come from the `paramsSchema` you pass at the call site).
315318
*/
316319
export type ProtocolSpec = {
317320
requests?: Record<string, { params?: unknown; result: unknown }>;

packages/server/src/server/server.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,13 @@ export class Server extends Protocol<ServerContext> {
232232
method: M,
233233
handler: (request: RequestTypeMap[M], ctx: ServerContext) => ResultTypeMap[M] | Promise<ResultTypeMap[M]>
234234
): void;
235-
public override setRequestHandler<P extends StandardSchemaV1>(
236-
method: string,
235+
public override setRequestHandler<M extends RequestMethod, P extends StandardSchemaV1>(
236+
method: M,
237+
paramsSchema: P,
238+
handler: (params: StandardSchemaV1.InferOutput<P>, ctx: ServerContext) => ResultTypeMap[M] | Promise<ResultTypeMap[M]>
239+
): void;
240+
public override setRequestHandler<M extends string, P extends StandardSchemaV1>(
241+
method: M extends RequestMethod ? never : M,
237242
paramsSchema: P,
238243
handler: (params: StandardSchemaV1.InferOutput<P>, ctx: ServerContext) => Result | Promise<Result>
239244
): void;

packages/server/test/server/setRequestHandlerSchemaParity.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,12 @@ describe('Server.setRequestHandler — Zod-schema form parity', () => {
6363
});
6464
expect(res.result).toEqual({ reply: 'hi' });
6565
});
66+
67+
it('three-arg form on Server enforces spec-method result type (no fallthrough to loose overload)', () => {
68+
const s = new Server({ name: 't', version: '1.0' }, { capabilities: { tools: {} } });
69+
// @ts-expect-error -- result for 'ping' must be EmptyResult-compatible; loose overload is never-guarded for spec methods
70+
s.setRequestHandler('ping', z.object({}), () => ({ ok: 'wrong-type' }) as { ok: string });
71+
// non-spec methods still allow loose Result
72+
s.setRequestHandler('acme/custom', z.object({}), () => ({ anything: 1 }));
73+
});
6674
});

0 commit comments

Comments
 (0)