Skip to content

Commit 10a7878

Browse files
fix: address review — setNotificationHandler 3-arg passes raw notification as 2nd arg; export StandardSchemaV1; fix example headers
1 parent 690af0d commit 10a7878

7 files changed

Lines changed: 25 additions & 7 deletions

File tree

docs/migration-SKILL.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,9 +359,11 @@ server.setRequestHandler(z.object({ method: z.literal('acme/search'), params: P
359359

360360
// v2: method string + schemas object; handler receives parsed params
361361
server.setRequestHandler('acme/search', { params: P, result: R }, async (params, ctx) => { ... });
362-
client.setNotificationHandler('acme/progress', { params: P }, params => { ... });
362+
client.setNotificationHandler('acme/progress', { params: P }, (params, notification) => { ... });
363363
```
364364

365+
The 3-arg notification handler receives the raw notification as its second argument, so `_meta` is recoverable via `notification.params?._meta`.
366+
365367
To send a custom-method request, pass a result schema as the second argument to `request()` (and `ctx.mcpReq.send()`):
366368

367369
```typescript

docs/migration.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,8 @@ server.setRequestHandler('acme/search', { params: SearchParams, result: SearchRe
393393

394394
The handler receives the parsed `params` directly (not the full request envelope). `_meta` is stripped before validation and is available as `ctx.mcpReq._meta`. Supplying `result` types the handler's return value; omit it to return any `Result`.
395395

396+
For `setNotificationHandler`, the 3-arg handler is `(params, notification) => void`. The raw notification is the second argument, so `_meta` is recoverable via `notification.params?._meta`.
397+
396398
#### Sending custom-method requests
397399

398400
`request()` and `ctx.mcpReq.send()` accept a result schema as the second argument; for custom methods this is required:

examples/client/src/customMethodExample.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Custom (non-spec) method example: a client that sends `acme/search` and
33
* listens for `acme/searchProgress` notifications.
44
*
5-
* Run after starting `examples/server/src/customMethodExample.ts`.
5+
* Build `examples/server` first; this client spawns the server via stdio.
66
*/
77
import { Client, StdioClientTransport } from '@modelcontextprotocol/client';
88
import { z } from 'zod/v4';

examples/server/src/customMethodExample.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Custom (non-spec) method example: a server that handles a vendor-prefixed
33
* `acme/search` request and emits `acme/searchProgress` notifications.
44
*
5-
* Run alongside `examples/client/src/customMethodExample.ts`.
5+
* Spawned via stdio by `examples/client/src/customMethodExample.ts`; do not run standalone.
66
*/
77
import { McpServer, StdioServerTransport } from '@modelcontextprotocol/server';
88
import { z } from 'zod/v4';

packages/core/src/exports/public/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ export { isTerminal } from '../../experimental/tasks/interfaces.js';
138138
export { InMemoryTaskMessageQueue, InMemoryTaskStore } from '../../experimental/tasks/stores/inMemory.js';
139139

140140
// Validator types and classes
141-
export type { StandardSchemaWithJSON } from '../../util/standardSchema.js';
141+
export type { StandardSchemaV1, StandardSchemaWithJSON } from '../../util/standardSchema.js';
142142
export { AjvJsonSchemaValidator } from '../../validators/ajvProvider.js';
143143
export type { CfWorkerSchemaDraft } from '../../validators/cfWorkerProvider.js';
144144
// fromJsonSchema is intentionally NOT exported here — the server and client packages

packages/core/src/shared/protocol.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,12 +1152,12 @@ export abstract class Protocol<ContextT extends BaseContext> {
11521152
setNotificationHandler<P extends StandardSchemaV1>(
11531153
method: string,
11541154
schemas: { params: P },
1155-
handler: (params: StandardSchemaV1.InferOutput<P>) => void | Promise<void>
1155+
handler: (params: StandardSchemaV1.InferOutput<P>, notification: Notification) => void | Promise<void>
11561156
): void;
11571157
setNotificationHandler(
11581158
method: string,
11591159
schemasOrHandler: { params: StandardSchemaV1 } | ((notification: unknown) => void | Promise<void>),
1160-
maybeHandler?: (params: unknown) => void | Promise<void>
1160+
maybeHandler?: (params: unknown, notification: Notification) => void | Promise<void>
11611161
): void {
11621162
if (typeof schemasOrHandler === 'function') {
11631163
const schema = getNotificationSchema(method);
@@ -1180,7 +1180,7 @@ export abstract class Protocol<ContextT extends BaseContext> {
11801180
if (!parsed.success) {
11811181
throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Invalid params for notification ${method}: ${parsed.error}`);
11821182
}
1183-
await maybeHandler(parsed.data);
1183+
await maybeHandler(parsed.data, notification);
11841184
});
11851185
}
11861186

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,20 @@ describe('Protocol custom-method support', () => {
112112
await new Promise(r => setTimeout(r, 0));
113113
expect(seen).toEqual([{ stage: 'fetch', pct: 0.5 }]);
114114
});
115+
116+
it('passes the raw notification (with _meta) as the second handler argument', async () => {
117+
const [a, b] = await pair();
118+
const Strict = z.strictObject({ stage: z.string() });
119+
let seenMeta: unknown;
120+
b.setNotificationHandler('acme/searchProgress', { params: Strict }, (params, notification) => {
121+
expect(params).toEqual({ stage: 'fetch' });
122+
seenMeta = notification.params?._meta;
123+
});
124+
125+
await a.notification({ method: 'acme/searchProgress', params: { stage: 'fetch', _meta: { traceId: 't1' } } });
126+
await new Promise(r => setTimeout(r, 0));
127+
expect(seenMeta).toEqual({ traceId: 't1' });
128+
});
115129
});
116130

117131
describe('request() schema overload', () => {

0 commit comments

Comments
 (0)