Skip to content

Commit f6e8204

Browse files
authored
fix: replace schema-based handlers with string method names (#1446)
1 parent a5670de commit f6e8204

30 files changed

Lines changed: 490 additions & 878 deletions

CLAUDE.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -148,19 +148,19 @@ When a request arrives from the remote side:
148148
- Looks up handler in `_requestHandlers` map (keyed by method name)
149149
- Creates `RequestHandlerExtra` with `signal`, `sessionId`, `sendNotification`, `sendRequest`
150150
- Invokes handler, sends JSON-RPC response back via transport
151-
4. **Handler** was registered via `setRequestHandler(Schema, handler)`
151+
4. **Handler** was registered via `setRequestHandler('method', handler)`
152152

153153
### Handler Registration
154154

155155
```typescript
156156
// In Client (for server→client requests like sampling, elicitation)
157-
client.setRequestHandler(CreateMessageRequestSchema, async (request, extra) => {
157+
client.setRequestHandler('sampling/createMessage', async (request, extra) => {
158158
// Handle sampling request from server
159159
return { role: "assistant", content: {...}, model: "..." };
160160
});
161161

162162
// In Server (for client→server requests like tools/call)
163-
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
163+
server.setRequestHandler('tools/call', async (request, extra) => {
164164
// Handle tool call from client
165165
return { content: [...] };
166166
});
@@ -207,7 +207,7 @@ const result = await server.createMessage({
207207
});
208208

209209
// Client must have registered handler:
210-
client.setRequestHandler(CreateMessageRequestSchema, async (request, extra) => {
210+
client.setRequestHandler('sampling/createMessage', async (request, extra) => {
211211
// Client-side LLM call
212212
return { role: "assistant", content: {...} };
213213
});
@@ -218,7 +218,7 @@ client.setRequestHandler(CreateMessageRequestSchema, async (request, extra) => {
218218
### Request Handler Registration (Low-Level Server)
219219

220220
```typescript
221-
server.setRequestHandler(SomeRequestSchema, async (request, extra) => {
221+
server.setRequestHandler('tools/call', async (request, extra) => {
222222
// extra contains sessionId, authInfo, sendNotification, etc.
223223
return {
224224
/* result */

docs/migration-SKILL.md

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,11 +198,50 @@ app.use(hostHeaderValidation(['example.com']));
198198

199199
The server package now exports framework-agnostic alternatives: `validateHostHeader()`, `localhostAllowedHostnames()`, `hostHeaderValidationResponse()`.
200200

201-
## 9. Client Behavioral Changes
201+
## 9. `setRequestHandler` / `setNotificationHandler` API
202+
203+
The low-level handler registration methods now take a method string instead of a Zod schema.
204+
205+
```typescript
206+
// v1: schema-based
207+
server.setRequestHandler(InitializeRequestSchema, async (request) => { ... });
208+
server.setNotificationHandler(LoggingMessageNotificationSchema, (notification) => { ... });
209+
210+
// v2: method string
211+
server.setRequestHandler('initialize', async (request) => { ... });
212+
server.setNotificationHandler('notifications/message', (notification) => { ... });
213+
```
214+
215+
Schema to method string mapping:
216+
217+
| v1 Schema | v2 Method String |
218+
|-----------|-----------------|
219+
| `InitializeRequestSchema` | `'initialize'` |
220+
| `CallToolRequestSchema` | `'tools/call'` |
221+
| `ListToolsRequestSchema` | `'tools/list'` |
222+
| `ListPromptsRequestSchema` | `'prompts/list'` |
223+
| `GetPromptRequestSchema` | `'prompts/get'` |
224+
| `ListResourcesRequestSchema` | `'resources/list'` |
225+
| `ReadResourceRequestSchema` | `'resources/read'` |
226+
| `CreateMessageRequestSchema` | `'sampling/createMessage'` |
227+
| `ElicitRequestSchema` | `'elicitation/create'` |
228+
| `SetLevelRequestSchema` | `'logging/setLevel'` |
229+
| `PingRequestSchema` | `'ping'` |
230+
| `LoggingMessageNotificationSchema` | `'notifications/message'` |
231+
| `ToolListChangedNotificationSchema` | `'notifications/tools/list_changed'` |
232+
| `ResourceListChangedNotificationSchema` | `'notifications/resources/list_changed'` |
233+
| `PromptListChangedNotificationSchema` | `'notifications/prompts/list_changed'` |
234+
| `ProgressNotificationSchema` | `'notifications/progress'` |
235+
| `CancelledNotificationSchema` | `'notifications/cancelled'` |
236+
| `InitializedNotificationSchema` | `'notifications/initialized'` |
237+
238+
Request/notification params remain fully typed. Remove unused schema imports after migration.
239+
240+
## 10. Client Behavioral Changes
202241

203242
`Client.listPrompts()`, `listResources()`, `listResourceTemplates()`, `listTools()` now return empty results when the server lacks the corresponding capability (instead of sending the request). Set `enforceStrictCapabilities: true` in `ClientOptions` to throw an error instead.
204243

205-
## 10. Migration Steps (apply in this order)
244+
## 11. Migration Steps (apply in this order)
206245

207246
1. Update `package.json`: `npm uninstall @modelcontextprotocol/sdk`, install the appropriate v2 packages
208247
2. Replace all imports from `@modelcontextprotocol/sdk/...` using the import mapping tables (sections 3-4), including `StreamableHTTPServerTransport``NodeStreamableHTTPServerTransport`

docs/migration.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,66 @@ app.use(hostHeaderValidation(['example.com']));
237237

238238
Note: the v2 signature takes a plain `string[]` instead of an options object.
239239

240+
### `setRequestHandler` and `setNotificationHandler` use method strings
241+
242+
The low-level `setRequestHandler` and `setNotificationHandler` methods on `Client`, `Server`, and `Protocol` now take a method string instead of a Zod schema.
243+
244+
**Before (v1):**
245+
246+
```typescript
247+
import { Server, InitializeRequestSchema, LoggingMessageNotificationSchema } from '@modelcontextprotocol/sdk/server/index.js';
248+
249+
const server = new Server({ name: 'my-server', version: '1.0.0' });
250+
251+
// Request handler with schema
252+
server.setRequestHandler(InitializeRequestSchema, async (request) => {
253+
return { protocolVersion: '...', capabilities: {}, serverInfo: { name: '...', version: '...' } };
254+
});
255+
256+
// Notification handler with schema
257+
server.setNotificationHandler(LoggingMessageNotificationSchema, (notification) => {
258+
console.log(notification.params.data);
259+
});
260+
```
261+
262+
**After (v2):**
263+
264+
```typescript
265+
import { Server } from '@modelcontextprotocol/server';
266+
267+
const server = new Server({ name: 'my-server', version: '1.0.0' });
268+
269+
// Request handler with method string
270+
server.setRequestHandler('initialize', async (request) => {
271+
return { protocolVersion: '...', capabilities: {}, serverInfo: { name: '...', version: '...' } };
272+
});
273+
274+
// Notification handler with method string
275+
server.setNotificationHandler('notifications/message', (notification) => {
276+
console.log(notification.params.data);
277+
});
278+
```
279+
280+
The request and notification parameters remain fully typed via `RequestTypeMap` and `NotificationTypeMap`. You no longer need to import the individual `*RequestSchema` or `*NotificationSchema` constants for handler registration.
281+
282+
Common method string replacements:
283+
284+
| Schema (v1) | Method string (v2) |
285+
|-------------|-------------------|
286+
| `InitializeRequestSchema` | `'initialize'` |
287+
| `CallToolRequestSchema` | `'tools/call'` |
288+
| `ListToolsRequestSchema` | `'tools/list'` |
289+
| `ListPromptsRequestSchema` | `'prompts/list'` |
290+
| `GetPromptRequestSchema` | `'prompts/get'` |
291+
| `ListResourcesRequestSchema` | `'resources/list'` |
292+
| `ReadResourceRequestSchema` | `'resources/read'` |
293+
| `CreateMessageRequestSchema` | `'sampling/createMessage'` |
294+
| `ElicitRequestSchema` | `'elicitation/create'` |
295+
| `LoggingMessageNotificationSchema` | `'notifications/message'` |
296+
| `ToolListChangedNotificationSchema` | `'notifications/tools/list_changed'` |
297+
| `ResourceListChangedNotificationSchema` | `'notifications/resources/list_changed'` |
298+
| `PromptListChangedNotificationSchema` | `'notifications/prompts/list_changed'` |
299+
240300
### Client list methods return empty results for missing capabilities
241301

242302
`Client.listPrompts()`, `listResources()`, `listResourceTemplates()`, and `listTools()` now return empty results when the server didn't advertise the corresponding capability, instead of sending the request. This respects the MCP spec's capability negotiation.

examples/client/src/elicitationUrlExample.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ import type {
2121
import {
2222
CallToolResultSchema,
2323
Client,
24-
ElicitationCompleteNotificationSchema,
25-
ElicitRequestSchema,
2624
ErrorCode,
2725
getDisplayName,
2826
ListToolsResultSchema,
@@ -560,10 +558,10 @@ async function connect(url?: string): Promise<void> {
560558
console.log('👤 Client created');
561559

562560
// Set up elicitation request handler with proper validation
563-
client.setRequestHandler(ElicitRequestSchema, elicitationRequestHandler);
561+
client.setRequestHandler('elicitation/create', elicitationRequestHandler);
564562

565563
// Set up notification handler for elicitation completion
566-
client.setNotificationHandler(ElicitationCompleteNotificationSchema, notification => {
564+
client.setNotificationHandler('notifications/elicitation/complete', notification => {
567565
const { elicitationId } = notification.params;
568566
const pending = pendingURLElicitations.get(elicitationId);
569567
if (pending) {

examples/client/src/multipleClientsParallel.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
import type { CallToolRequest, CallToolResult } from '@modelcontextprotocol/client';
2-
import {
3-
CallToolResultSchema,
4-
Client,
5-
LoggingMessageNotificationSchema,
6-
StreamableHTTPClientTransport
7-
} from '@modelcontextprotocol/client';
2+
import { CallToolResultSchema, Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
83

94
/**
105
* Multiple Clients MCP Example
@@ -42,7 +37,7 @@ async function createAndRunClient(config: ClientConfig): Promise<{ id: string; r
4237
};
4338

4439
// Set up client-specific notification handler
45-
client.setNotificationHandler(LoggingMessageNotificationSchema, notification => {
40+
client.setNotificationHandler('notifications/message', notification => {
4641
console.log(`[${config.id}] Notification: ${notification.params.data}`);
4742
});
4843

examples/client/src/parallelToolCallsClient.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
import type { CallToolResult, ListToolsRequest } from '@modelcontextprotocol/client';
2-
import {
3-
CallToolResultSchema,
4-
Client,
5-
ListToolsResultSchema,
6-
LoggingMessageNotificationSchema,
7-
StreamableHTTPClientTransport
8-
} from '@modelcontextprotocol/client';
2+
import { CallToolResultSchema, Client, ListToolsResultSchema, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
93

104
/**
115
* Parallel Tool Calls MCP Client
@@ -44,7 +38,7 @@ async function main(): Promise<void> {
4438
console.log('Successfully connected to MCP server');
4539

4640
// Set up notification handler with caller identification
47-
client.setNotificationHandler(LoggingMessageNotificationSchema, notification => {
41+
client.setNotificationHandler('notifications/message', notification => {
4842
console.log(`Notification: ${notification.params.data}`);
4943
});
5044

examples/client/src/simpleStreamableHttp.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,15 @@ import type {
1212
import {
1313
CallToolResultSchema,
1414
Client,
15-
ElicitRequestSchema,
1615
ErrorCode,
1716
getDisplayName,
1817
GetPromptResultSchema,
1918
ListPromptsResultSchema,
2019
ListResourcesResultSchema,
2120
ListToolsResultSchema,
22-
LoggingMessageNotificationSchema,
2321
McpError,
2422
ReadResourceResultSchema,
2523
RELATED_TASK_META_KEY,
26-
ResourceListChangedNotificationSchema,
2724
StreamableHTTPClientTransport
2825
} from '@modelcontextprotocol/client';
2926
import { Ajv } from 'ajv';
@@ -271,7 +268,7 @@ async function connect(url?: string): Promise<void> {
271268
};
272269

273270
// Set up elicitation request handler with proper validation
274-
client.setRequestHandler(ElicitRequestSchema, async request => {
271+
client.setRequestHandler('elicitation/create', async request => {
275272
if (request.params.mode !== 'form') {
276273
throw new McpError(ErrorCode.InvalidParams, `Unsupported elicitation mode: ${request.params.mode}`);
277274
}
@@ -496,14 +493,14 @@ async function connect(url?: string): Promise<void> {
496493
});
497494

498495
// Set up notification handlers
499-
client.setNotificationHandler(LoggingMessageNotificationSchema, notification => {
496+
client.setNotificationHandler('notifications/message', notification => {
500497
notificationCount++;
501498
console.log(`\nNotification #${notificationCount}: ${notification.params.level} - ${notification.params.data}`);
502499
// Re-display the prompt
503500
process.stdout.write('> ');
504501
});
505502

506-
client.setNotificationHandler(ResourceListChangedNotificationSchema, async _ => {
503+
client.setNotificationHandler('notifications/resources/list_changed', async _ => {
507504
console.log(`\nResource list changed notification received!`);
508505
try {
509506
if (!client) {

examples/client/src/simpleTaskInteractiveClient.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,7 @@
1010
import { createInterface } from 'node:readline';
1111

1212
import type { CreateMessageRequest, CreateMessageResult, TextContent } from '@modelcontextprotocol/client';
13-
import {
14-
CallToolResultSchema,
15-
Client,
16-
CreateMessageRequestSchema,
17-
ElicitRequestSchema,
18-
ErrorCode,
19-
McpError,
20-
StreamableHTTPClientTransport
21-
} from '@modelcontextprotocol/client';
13+
import { CallToolResultSchema, Client, ErrorCode, McpError, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
2214

2315
// Create readline interface for user input
2416
const readline = createInterface({
@@ -43,7 +35,7 @@ async function elicitationCallback(params: {
4335
mode?: string;
4436
message: string;
4537
requestedSchema?: object;
46-
}): Promise<{ action: string; content?: Record<string, unknown> }> {
38+
}): Promise<{ action: 'accept' | 'cancel' | 'decline'; content?: Record<string, string | number | boolean | string[]> }> {
4739
console.log(`\n[Elicitation] Server asks: ${params.message}`);
4840

4941
// Simple terminal prompt for y/n
@@ -102,15 +94,15 @@ async function run(url: string): Promise<void> {
10294
);
10395

10496
// Set up elicitation request handler
105-
client.setRequestHandler(ElicitRequestSchema, async request => {
97+
client.setRequestHandler('elicitation/create', async request => {
10698
if (request.params.mode && request.params.mode !== 'form') {
10799
throw new McpError(ErrorCode.InvalidParams, `Unsupported elicitation mode: ${request.params.mode}`);
108100
}
109101
return elicitationCallback(request.params);
110102
});
111103

112104
// Set up sampling request handler
113-
client.setRequestHandler(CreateMessageRequestSchema, async request => {
105+
client.setRequestHandler('sampling/createMessage', async request => {
114106
return samplingCallback(request.params) as unknown as ReturnType<typeof samplingCallback>;
115107
});
116108

examples/client/src/ssePollingClient.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,7 @@
1212
* Run with: pnpm tsx src/ssePollingClient.ts
1313
* Requires: ssePollingExample.ts server running on port 3001
1414
*/
15-
import {
16-
CallToolResultSchema,
17-
Client,
18-
LoggingMessageNotificationSchema,
19-
StreamableHTTPClientTransport
20-
} from '@modelcontextprotocol/client';
15+
import { CallToolResultSchema, Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
2116

2217
const SERVER_URL = 'http://localhost:3001/mcp';
2318

@@ -60,7 +55,7 @@ async function main(): Promise<void> {
6055
});
6156

6257
// Set up notification handler to receive progress updates
63-
client.setNotificationHandler(LoggingMessageNotificationSchema, notification => {
58+
client.setNotificationHandler('notifications/message', notification => {
6459
const data = notification.params.data;
6560
console.log(`[Notification] ${data}`);
6661
});

examples/client/src/streamableHttpWithSseFallbackClient.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {
33
CallToolResultSchema,
44
Client,
55
ListToolsResultSchema,
6-
LoggingMessageNotificationSchema,
76
SSEClientTransport,
87
StreamableHTTPClientTransport
98
} from '@modelcontextprotocol/client';
@@ -39,7 +38,7 @@ async function main(): Promise<void> {
3938
transport = connection.transport;
4039

4140
// Set up notification handler
42-
client.setNotificationHandler(LoggingMessageNotificationSchema, notification => {
41+
client.setNotificationHandler('notifications/message', notification => {
4342
console.log(`Notification: ${notification.params.level} - ${notification.params.data}`);
4443
});
4544

0 commit comments

Comments
 (0)