Skip to content

Commit ce8fc53

Browse files
Merge remote-tracking branch 'origin/fweinberger/protocol-concrete' into fweinberger/v2-bc-d1-base
2 parents a856a3b + cafa5ec commit ce8fc53

File tree

18 files changed

+583
-84
lines changed

18 files changed

+583
-84
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@modelcontextprotocol/client': minor
3+
'@modelcontextprotocol/server': minor
4+
---
5+
6+
`setRequestHandler`/`setNotificationHandler` accept the v1 `(ZodSchema, handler)` form as a first-class alternative to `(methodString, handler)`. `request()` accepts an explicit result schema (`request(req, resultSchema, options?)`) and has a method-keyed return type for spec methods. `callTool(params, resultSchema?)` accepts the v1 schema arg (ignored). `removeRequestHandler`/`removeNotificationHandler`/`assertCanSetRequestHandler` accept any method string.

docs/migration-SKILL.md

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ if (error instanceof OAuthError && error.code === OAuthErrorCode.InvalidClient)
204204
```
205205

206206
**Unchanged APIs** (only import paths changed): `Client` constructor and most methods, `McpServer` constructor, `server.connect()`, `server.close()`, all client transports (`StreamableHTTPClientTransport`, `SSEClientTransport`, `StdioClientTransport`), `StdioServerTransport`, all
207-
Zod schemas, all callback return types. Note: `callTool()` and `request()` signatures changed (schema parameter removed, see section 11).
207+
Zod schemas, all callback return types. Note: `callTool()` and `request()` schema parameter is now optional (see section 11).
208208

209209
## 6. McpServer API Changes
210210

@@ -376,6 +376,15 @@ Schema to method string mapping:
376376

377377
Request/notification params remain fully typed. Remove unused schema imports after migration.
378378

379+
**Custom (non-standard) methods** — vendor extensions or sub-protocols whose method strings are not in the MCP spec — work on `Client`/`Server` directly using the same v1 Zod-schema form:
380+
381+
| Form | Notes |
382+
| ------------------------------------------------------------ | --------------------------------------------------------------------- |
383+
| `setRequestHandler(CustomReqSchema, (req, ctx) => ...)` | unchanged |
384+
| `setNotificationHandler(CustomNotifSchema, n => ...)` | unchanged |
385+
| `this.request({ method: 'vendor/x', params }, ResultSchema)` | unchanged |
386+
| `this.notification({ method: 'vendor/x', params })` | unchanged |
387+
379388
## 10. Request Handler Context Types
380389

381390
`RequestHandlerExtra` → structured context types with nested groups. Rename `extra``ctx` in all handler callbacks.
@@ -406,9 +415,9 @@ Request/notification params remain fully typed. Remove unused schema imports aft
406415
| `ctx.mcpReq.elicitInput(params, options?)` | Elicit user input (form or URL) | `server.elicitInput(...)` from within handler |
407416
| `ctx.mcpReq.requestSampling(params, options?)` | Request LLM sampling from client | `server.createMessage(...)` from within handler |
408417

409-
## 11. Schema parameter removed from `request()`, `send()`, and `callTool()`
418+
## 11. Schema parameter on `request()` / `callTool()` is optional; removed from `send()`
410419

411-
`Protocol.request()`, `BaseContext.mcpReq.send()`, and `Client.callTool()` no longer take a Zod result schema argument. The SDK resolves the schema internally from the method name.
420+
`Protocol.request()` and `Client.callTool()` still accept a Zod result schema as the second argument (the v1 form), but it is optional for spec methods — the SDK resolves the schema internally from the method name. `BaseContext.mcpReq.send()` no longer takes a schema.
412421

413422
```typescript
414423
// v1: schema required
@@ -417,20 +426,20 @@ const result = await client.request({ method: 'tools/call', params: { ... } }, C
417426
const elicit = await ctx.mcpReq.send({ method: 'elicitation/create', params: { ... } }, ElicitResultSchema);
418427
const tool = await client.callTool({ name: 'my-tool', arguments: {} }, CompatibilityCallToolResultSchema);
419428

420-
// v2: no schema argument
429+
// v2: schema optional on request()/callTool(); removed from mcpReq.send()
421430
const result = await client.request({ method: 'tools/call', params: { ... } });
422431
const elicit = await ctx.mcpReq.send({ method: 'elicitation/create', params: { ... } });
423432
const tool = await client.callTool({ name: 'my-tool', arguments: {} });
424433
```
425434

426-
| v1 call | v2 call |
427-
| ------------------------------------------------------------ | ---------------------------------- |
428-
| `client.request(req, ResultSchema)` | `client.request(req)` |
429-
| `client.request(req, ResultSchema, options)` | `client.request(req, options)` |
430-
| `ctx.mcpReq.send(req, ResultSchema)` | `ctx.mcpReq.send(req)` |
431-
| `ctx.mcpReq.send(req, ResultSchema, options)` | `ctx.mcpReq.send(req, options)` |
432-
| `client.callTool(params, CompatibilityCallToolResultSchema)` | `client.callTool(params)` |
433-
| `client.callTool(params, schema, options)` | `client.callTool(params, options)` |
435+
| v1 call | v2 call |
436+
| ------------------------------------------------------------ | ---------------------------------------------- |
437+
| `client.request(req, ResultSchema)` | unchanged (schema optional), or `client.request(req)` |
438+
| `client.request(req, ResultSchema, options)` | unchanged, or `client.request(req, options)` |
439+
| `ctx.mcpReq.send(req, ResultSchema)` | `ctx.mcpReq.send(req)` |
440+
| `ctx.mcpReq.send(req, ResultSchema, options)` | `ctx.mcpReq.send(req, options)` |
441+
| `client.callTool(params, CompatibilityCallToolResultSchema)` | unchanged (schema ignored), or `client.callTool(params)` |
442+
| `client.callTool(params, schema, options)` | unchanged, or `client.callTool(params, options)` |
434443

435444
Remove unused schema imports: `CallToolResultSchema`, `CompatibilityCallToolResultSchema`, `ElicitResultSchema`, `CreateMessageResultSchema`, etc., when they were only used in `request()`/`send()`/`callTool()` calls.
436445

docs/migration.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -388,10 +388,25 @@ Common method string replacements:
388388
| `ResourceListChangedNotificationSchema` | `'notifications/resources/list_changed'` |
389389
| `PromptListChangedNotificationSchema` | `'notifications/prompts/list_changed'` |
390390

391-
### `Protocol.request()`, `ctx.mcpReq.send()`, and `Client.callTool()` no longer take a schema parameter
391+
### Custom (non-standard) protocol methods
392392

393-
The public `Protocol.request()`, `BaseContext.mcpReq.send()`, and `Client.callTool()` methods no longer accept a Zod result schema argument. The SDK now resolves the correct result schema internally based on the method name. This means you no longer need to import result schemas
394-
like `CallToolResultSchema` or `ElicitResultSchema` when making requests.
393+
Vendor-specific methods are registered directly on `Client` or `Server` using the same Zod-schema form as v1: `setRequestHandler(zodSchemaWithMethodLiteral, handler)`. `request({ method, params }, ResultSchema)` and `notification({ method, params })` are unchanged from v1.
394+
395+
```typescript
396+
import { Server } from '@modelcontextprotocol/server';
397+
398+
const server = new Server({ name: 'app', version: '1.0.0' }, { capabilities: {} });
399+
400+
server.setRequestHandler(SearchRequestSchema, req => ({ hits: [req.params.query] }));
401+
402+
// Calling from a Client — unchanged from v1:
403+
const result = await client.request({ method: 'acme/search', params: { query: 'x' } }, SearchResult);
404+
```
405+
406+
### `Protocol.request()` and `Client.callTool()` schema parameter is now optional
407+
408+
The public `Protocol.request()` and `Client.callTool()` methods still accept a result schema argument, but for spec methods it is optional — the SDK resolves the correct schema internally from the method name. You no longer need to import result schemas
409+
like `CallToolResultSchema` or `ElicitResultSchema` when making requests. (`BaseContext.mcpReq.send()` no longer accepts a schema; drop it.)
395410

396411
**`client.request()` — Before (v1):**
397412

examples/client/README.md

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,19 @@ Most clients expect a server to be running. Start one from [`../server/README.md
2424

2525
## Example index
2626

27-
| Scenario | Description | File |
28-
| --------------------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
29-
| Interactive Streamable HTTP client | CLI client that exercises tools/resources/prompts, notifications, elicitation, and tasks. | [`src/simpleStreamableHttp.ts`](src/simpleStreamableHttp.ts) |
30-
| Backwards-compatible client (Streamable HTTP → SSE) | Tries Streamable HTTP first, falls back to legacy SSE on 4xx responses. | [`src/streamableHttpWithSseFallbackClient.ts`](src/streamableHttpWithSseFallbackClient.ts) |
31-
| SSE polling client (legacy) | Polls a legacy HTTP+SSE server and demonstrates notification handling. | [`src/ssePollingClient.ts`](src/ssePollingClient.ts) |
32-
| Parallel tool calls | Runs multiple tool calls in parallel. | [`src/parallelToolCallsClient.ts`](src/parallelToolCallsClient.ts) |
33-
| Multiple clients in parallel | Connects multiple clients concurrently to the same server. | [`src/multipleClientsParallel.ts`](src/multipleClientsParallel.ts) |
34-
| OAuth client (interactive) | OAuth-enabled client (dynamic registration, auth flow). | [`src/simpleOAuthClient.ts`](src/simpleOAuthClient.ts) |
35-
| OAuth provider helper | Demonstrates reusable OAuth providers. | [`src/simpleOAuthClientProvider.ts`](src/simpleOAuthClientProvider.ts) |
36-
| Client credentials (M2M) | Machine-to-machine OAuth client credentials example. | [`src/simpleClientCredentials.ts`](src/simpleClientCredentials.ts) |
37-
| URL elicitation client | Drives URL-mode elicitation flows (sensitive input in a browser). | [`src/elicitationUrlExample.ts`](src/elicitationUrlExample.ts) |
38-
| Task interactive client | Demonstrates task-based execution + interactive server→client requests. | [`src/simpleTaskInteractiveClient.ts`](src/simpleTaskInteractiveClient.ts) |
27+
| Scenario | Description | File |
28+
| --------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
29+
| Interactive Streamable HTTP client | CLI client that exercises tools/resources/prompts, notifications, elicitation, and tasks. | [`src/simpleStreamableHttp.ts`](src/simpleStreamableHttp.ts) |
30+
| Backwards-compatible client (Streamable HTTP → SSE) | Tries Streamable HTTP first, falls back to legacy SSE on 4xx responses. | [`src/streamableHttpWithSseFallbackClient.ts`](src/streamableHttpWithSseFallbackClient.ts) |
31+
| SSE polling client (legacy) | Polls a legacy HTTP+SSE server and demonstrates notification handling. | [`src/ssePollingClient.ts`](src/ssePollingClient.ts) |
32+
| Parallel tool calls | Runs multiple tool calls in parallel. | [`src/parallelToolCallsClient.ts`](src/parallelToolCallsClient.ts) |
33+
| Multiple clients in parallel | Connects multiple clients concurrently to the same server. | [`src/multipleClientsParallel.ts`](src/multipleClientsParallel.ts) |
34+
| OAuth client (interactive) | OAuth-enabled client (dynamic registration, auth flow). | [`src/simpleOAuthClient.ts`](src/simpleOAuthClient.ts) |
35+
| OAuth provider helper | Demonstrates reusable OAuth providers. | [`src/simpleOAuthClientProvider.ts`](src/simpleOAuthClientProvider.ts) |
36+
| Client credentials (M2M) | Machine-to-machine OAuth client credentials example. | [`src/simpleClientCredentials.ts`](src/simpleClientCredentials.ts) |
37+
| URL elicitation client | Drives URL-mode elicitation flows (sensitive input in a browser). | [`src/elicitationUrlExample.ts`](src/elicitationUrlExample.ts) |
38+
| Task interactive client | Demonstrates task-based execution + interactive server→client requests. | [`src/simpleTaskInteractiveClient.ts`](src/simpleTaskInteractiveClient.ts) |
39+
| Custom (non-standard) methods client | Sends `acme/*` custom requests + notifications and handles custom progress notifications from the server. | [`src/customMethodExample.ts`](src/customMethodExample.ts) |
3940

4041
## URL elicitation example (server + client)
4142

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Calling vendor-specific (non-spec) JSON-RPC methods from a `Client`.
4+
*
5+
* - Send a custom request: `client.request({ method, params }, resultSchema)`
6+
* - Send a custom notification: `client.notification({ method, params })`
7+
* - Receive a custom notification: `client.setNotificationHandler(ZodSchemaWithMethodLiteral, handler)`
8+
*
9+
* Pair with the server in examples/server/src/customMethodExample.ts.
10+
*/
11+
12+
import { Client, StdioClientTransport } from '@modelcontextprotocol/client';
13+
import { z } from 'zod';
14+
15+
const SearchResult = z.object({ hits: z.array(z.string()) });
16+
17+
const ProgressNotification = z.object({
18+
method: z.literal('acme/searchProgress'),
19+
params: z.object({ stage: z.string(), pct: z.number() })
20+
});
21+
22+
const client = new Client({ name: 'custom-method-client', version: '1.0.0' }, { capabilities: {} });
23+
24+
client.setNotificationHandler(ProgressNotification, n => {
25+
console.log(`[client] progress: ${n.params.stage} ${n.params.pct}%`);
26+
});
27+
28+
await client.connect(new StdioClientTransport({ command: 'node', args: ['../server/dist/customMethodExample.js'] }));
29+
30+
const r = await client.request({ method: 'acme/search', params: { query: 'widgets' } }, SearchResult);
31+
console.log('[client] hits=' + JSON.stringify(r.hits));
32+
33+
await client.notification({ method: 'acme/tick', params: { n: 1 } });
34+
await client.notification({ method: 'acme/tick', params: { n: 2 } });
35+
36+
await client.close();

0 commit comments

Comments
 (0)