Skip to content

Commit f88c376

Browse files
v2 refactor!: remove Zod schema parameter from public-facing APIs (modelcontextprotocol#1606)
1 parent 1a78b01 commit f88c376

30 files changed

+1483
-1708
lines changed

docs/client.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ The examples below use these imports. Adjust based on which features and transpo
1616
import type { Prompt, Resource, Tool } from '@modelcontextprotocol/client';
1717
import {
1818
applyMiddlewares,
19-
CallToolResultSchema,
2019
Client,
2120
ClientCredentialsProvider,
2221
createMiddleware,
@@ -198,13 +197,16 @@ if (result.structuredContent) {
198197
Pass `onprogress` to receive incremental progress notifications from long-running tools. Use `resetTimeoutOnProgress` to keep the request alive while the server is actively reporting, and `maxTotalTimeout` as an absolute cap:
199198

200199
```ts source="../examples/client/src/clientGuide.examples.ts#callTool_progress"
201-
const result = await client.callTool({ name: 'long-operation', arguments: {} }, undefined, {
202-
onprogress: ({ progress, total }) => {
203-
console.log(`Progress: ${progress}/${total ?? '?'}`);
204-
},
205-
resetTimeoutOnProgress: true,
206-
maxTotalTimeout: 600_000
207-
});
200+
const result = await client.callTool(
201+
{ name: 'long-operation', arguments: {} },
202+
{
203+
onprogress: ({ progress, total }: { progress: number; total?: number }) => {
204+
console.log(`Progress: ${progress}/${total ?? '?'}`);
205+
},
206+
resetTimeoutOnProgress: true,
207+
maxTotalTimeout: 600_000
208+
}
209+
);
208210
console.log(result.content);
209211
```
210212

@@ -484,7 +486,6 @@ All requests have a 60-second default timeout. Pass a custom `timeout` in the op
484486
try {
485487
const result = await client.callTool(
486488
{ name: 'slow-task', arguments: {} },
487-
undefined,
488489
{ timeout: 120_000 } // 2 minutes instead of the default 60 seconds
489490
);
490491
console.log(result.content);
@@ -523,7 +524,6 @@ const result = await client.request(
523524
method: 'tools/call',
524525
params: { name: 'long-running-task', arguments: {} }
525526
},
526-
CallToolResultSchema,
527527
{
528528
resumptionToken: lastToken,
529529
onresumptiontoken: (token: string) => {

docs/migration-SKILL.md

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ import { OAuthError, OAuthErrorCode } from '@modelcontextprotocol/core';
203203
if (error instanceof OAuthError && error.code === OAuthErrorCode.InvalidClient) { ... }
204204
```
205205

206-
**Unchanged APIs** (only import paths changed): `Client` constructor and methods, `McpServer` constructor, `server.connect()`, `server.close()`, all client transports (`StreamableHTTPClientTransport`, `SSEClientTransport`, `StdioClientTransport`), `StdioServerTransport`, all Zod schemas, all callback return types.
206+
**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 Zod schemas, all callback return types. Note: `callTool()` and `request()` signatures changed (schema parameter removed, see section 11).
207207

208208
## 6. McpServer API Changes
209209

@@ -396,11 +396,39 @@ Request/notification params remain fully typed. Remove unused schema imports aft
396396
| `ctx.mcpReq.elicitInput(params, options?)` | Elicit user input (form or URL) | `server.elicitInput(...)` from within handler |
397397
| `ctx.mcpReq.requestSampling(params, options?)` | Request LLM sampling from client | `server.createMessage(...)` from within handler |
398398

399-
## 11. Client Behavioral Changes
399+
## 11. Schema parameter removed from `request()`, `send()`, and `callTool()`
400+
401+
`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.
402+
403+
```typescript
404+
// v1: schema required
405+
import { CallToolResultSchema, ElicitResultSchema } from '@modelcontextprotocol/sdk/types.js';
406+
const result = await client.request({ method: 'tools/call', params: { ... } }, CallToolResultSchema);
407+
const elicit = await ctx.mcpReq.send({ method: 'elicitation/create', params: { ... } }, ElicitResultSchema);
408+
const tool = await client.callTool({ name: 'my-tool', arguments: {} }, CompatibilityCallToolResultSchema);
409+
410+
// v2: no schema argument
411+
const result = await client.request({ method: 'tools/call', params: { ... } });
412+
const elicit = await ctx.mcpReq.send({ method: 'elicitation/create', params: { ... } });
413+
const tool = await client.callTool({ name: 'my-tool', arguments: {} });
414+
```
415+
416+
| v1 call | v2 call |
417+
|---------|---------|
418+
| `client.request(req, ResultSchema)` | `client.request(req)` |
419+
| `client.request(req, ResultSchema, options)` | `client.request(req, options)` |
420+
| `ctx.mcpReq.send(req, ResultSchema)` | `ctx.mcpReq.send(req)` |
421+
| `ctx.mcpReq.send(req, ResultSchema, options)` | `ctx.mcpReq.send(req, options)` |
422+
| `client.callTool(params, CompatibilityCallToolResultSchema)` | `client.callTool(params)` |
423+
| `client.callTool(params, schema, options)` | `client.callTool(params, options)` |
424+
425+
Remove unused schema imports: `CallToolResultSchema`, `CompatibilityCallToolResultSchema`, `ElicitResultSchema`, `CreateMessageResultSchema`, etc., when they were only used in `request()`/`send()`/`callTool()` calls.
426+
427+
## 12. Client Behavioral Changes
400428

401429
`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.
402430

403-
## 12. Runtime-Specific JSON Schema Validators (Enhancement)
431+
## 13. Runtime-Specific JSON Schema Validators (Enhancement)
404432

405433
The SDK now auto-selects the appropriate JSON Schema validator based on runtime:
406434
- Node.js → `AjvJsonSchemaValidator` (no change from v1)
@@ -420,7 +448,7 @@ new McpServer({ name: 'server', version: '1.0.0' }, {});
420448

421449
Access validators via `_shims` export: `import { DefaultJsonSchemaValidator } from '@modelcontextprotocol/server/_shims';`
422450

423-
## 13. Migration Steps (apply in this order)
451+
## 14. Migration Steps (apply in this order)
424452

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

docs/migration.md

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,73 @@ Common method string replacements:
337337
| `ResourceListChangedNotificationSchema` | `'notifications/resources/list_changed'` |
338338
| `PromptListChangedNotificationSchema` | `'notifications/prompts/list_changed'` |
339339

340+
### `Protocol.request()`, `ctx.mcpReq.send()`, and `Client.callTool()` no longer take a schema parameter
341+
342+
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 like `CallToolResultSchema` or `ElicitResultSchema` when making requests.
343+
344+
**`client.request()` — Before (v1):**
345+
346+
```typescript
347+
import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
348+
349+
const result = await client.request(
350+
{ method: 'tools/call', params: { name: 'my-tool', arguments: {} } },
351+
CallToolResultSchema
352+
);
353+
```
354+
355+
**After (v2):**
356+
357+
```typescript
358+
const result = await client.request(
359+
{ method: 'tools/call', params: { name: 'my-tool', arguments: {} } }
360+
);
361+
```
362+
363+
**`ctx.mcpReq.send()` — Before (v1):**
364+
365+
```typescript
366+
import { CreateMessageResultSchema } from '@modelcontextprotocol/sdk/types.js';
367+
368+
server.setRequestHandler('tools/call', async (request, ctx) => {
369+
const samplingResult = await ctx.mcpReq.send(
370+
{ method: 'sampling/createMessage', params: { messages: [...], maxTokens: 100 } },
371+
CreateMessageResultSchema
372+
);
373+
return { content: [{ type: 'text', text: 'done' }] };
374+
});
375+
```
376+
377+
**After (v2):**
378+
379+
```typescript
380+
server.setRequestHandler('tools/call', async (request, ctx) => {
381+
const samplingResult = await ctx.mcpReq.send(
382+
{ method: 'sampling/createMessage', params: { messages: [...], maxTokens: 100 } }
383+
);
384+
return { content: [{ type: 'text', text: 'done' }] };
385+
});
386+
```
387+
388+
**`client.callTool()` — Before (v1):**
389+
390+
```typescript
391+
import { CompatibilityCallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
392+
393+
const result = await client.callTool(
394+
{ name: 'my-tool', arguments: {} },
395+
CompatibilityCallToolResultSchema
396+
);
397+
```
398+
399+
**After (v2):**
400+
401+
```typescript
402+
const result = await client.callTool({ name: 'my-tool', arguments: {} });
403+
```
404+
405+
The return type is now inferred from the method name via `ResultTypeMap`. For example, `client.request({ method: 'tools/call', ... })` returns `Promise<CallToolResult | CreateTaskResult>`.
406+
340407
### Client list methods return empty results for missing capabilities
341408

342409
`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.
@@ -708,7 +775,7 @@ import { AjvJsonSchemaValidator, CfWorkerJsonSchemaValidator } from '@modelconte
708775

709776
The following APIs are unchanged between v1 and v2 (only the import paths changed):
710777

711-
- `Client` constructor and all client methods (`connect`, `callTool`, `listTools`, `listPrompts`, `listResources`, `readResource`, etc.)
778+
- `Client` constructor and most client methods (`connect`, `listTools`, `listPrompts`, `listResources`, `readResource`, etc.) — note: `callTool()` signature changed (schema parameter removed)
712779
- `McpServer` constructor, `server.connect(transport)`, `server.close()`
713780
- `Server` (low-level) constructor and all methods
714781
- `StreamableHTTPClientTransport`, `SSEClientTransport`, `StdioClientTransport` constructors and options

examples/client/src/clientGuide.examples.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import type { Prompt, Resource, Tool } from '@modelcontextprotocol/client';
1212
import {
1313
applyMiddlewares,
14-
CallToolResultSchema,
1514
Client,
1615
ClientCredentialsProvider,
1716
createMiddleware,
@@ -181,13 +180,16 @@ async function callTool_structuredOutput(client: Client) {
181180
/** Example: Track progress of a long-running tool call. */
182181
async function callTool_progress(client: Client) {
183182
//#region callTool_progress
184-
const result = await client.callTool({ name: 'long-operation', arguments: {} }, undefined, {
185-
onprogress: ({ progress, total }) => {
186-
console.log(`Progress: ${progress}/${total ?? '?'}`);
187-
},
188-
resetTimeoutOnProgress: true,
189-
maxTotalTimeout: 600_000
190-
});
183+
const result = await client.callTool(
184+
{ name: 'long-operation', arguments: {} },
185+
{
186+
onprogress: ({ progress, total }: { progress: number; total?: number }) => {
187+
console.log(`Progress: ${progress}/${total ?? '?'}`);
188+
},
189+
resetTimeoutOnProgress: true,
190+
maxTotalTimeout: 600_000
191+
}
192+
);
191193
console.log(result.content);
192194
//#endregion callTool_progress
193195
}
@@ -450,7 +452,6 @@ async function errorHandling_timeout(client: Client) {
450452
try {
451453
const result = await client.callTool(
452454
{ name: 'slow-task', arguments: {} },
453-
undefined,
454455
{ timeout: 120_000 } // 2 minutes instead of the default 60 seconds
455456
);
456457
console.log(result.content);
@@ -492,7 +493,6 @@ async function resumptionToken_basic(client: Client) {
492493
method: 'tools/call',
493494
params: { name: 'long-running-task', arguments: {} }
494495
},
495-
CallToolResultSchema,
496496
{
497497
resumptionToken: lastToken,
498498
onresumptiontoken: (token: string) => {

examples/client/src/elicitationUrlExample.ts

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

1111
import type {
1212
CallToolRequest,
13+
CallToolResult,
1314
ElicitRequest,
1415
ElicitRequestURLParams,
1516
ElicitResult,
@@ -18,10 +19,8 @@ import type {
1819
ResourceLink
1920
} from '@modelcontextprotocol/client';
2021
import {
21-
CallToolResultSchema,
2222
Client,
2323
getDisplayName,
24-
ListToolsResultSchema,
2524
ProtocolError,
2625
ProtocolErrorCode,
2726
StreamableHTTPClientTransport,
@@ -666,7 +665,7 @@ async function listTools(): Promise<void> {
666665
method: 'tools/list',
667666
params: {}
668667
};
669-
const toolsResult = await client.request(toolsRequest, ListToolsResultSchema);
668+
const toolsResult = await client.request(toolsRequest);
670669

671670
console.log('Available tools:');
672671
if (toolsResult.tools.length === 0) {
@@ -697,7 +696,7 @@ async function callTool(name: string, args: Record<string, unknown>): Promise<vo
697696
};
698697

699698
console.log(`Calling tool '${name}' with args:`, args);
700-
const result = await client.request(request, CallToolResultSchema);
699+
const result = (await client.request(request)) as CallToolResult;
701700

702701
console.log('Tool result:');
703702
const resourceLinks: ResourceLink[] = [];

examples/client/src/multipleClientsParallel.ts

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

44
/**
55
* Multiple Clients MCP Example
@@ -60,7 +60,7 @@ async function createAndRunClient(config: ClientConfig): Promise<{ id: string; r
6060
}
6161
};
6262

63-
const result = await client.request(toolRequest, CallToolResultSchema);
63+
const result = (await client.request(toolRequest)) as CallToolResult;
6464
console.log(`[${config.id}] Tool call completed`);
6565

6666
// Keep the connection open for a bit to receive notifications

examples/client/src/parallelToolCallsClient.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { CallToolResult, ListToolsRequest } from '@modelcontextprotocol/client';
2-
import { CallToolResultSchema, Client, ListToolsResultSchema, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
1+
import type { CallToolRequest, CallToolResult, ListToolsRequest } from '@modelcontextprotocol/client';
2+
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
33

44
/**
55
* Parallel Tool Calls MCP Client
@@ -86,7 +86,7 @@ async function listTools(client: Client): Promise<void> {
8686
method: 'tools/list',
8787
params: {}
8888
};
89-
const toolsResult = await client.request(toolsRequest, ListToolsResultSchema);
89+
const toolsResult = await client.request(toolsRequest);
9090

9191
console.log('Available tools:');
9292
if (toolsResult.tools.length === 0) {
@@ -108,7 +108,7 @@ async function listTools(client: Client): Promise<void> {
108108
async function startParallelNotificationTools(client: Client): Promise<Record<string, CallToolResult>> {
109109
try {
110110
// Define multiple tool calls with different configurations
111-
const toolCalls = [
111+
const toolCalls: Array<{ caller: string; request: CallToolRequest }> = [
112112
{
113113
caller: 'fast-notifier',
114114
request: {
@@ -159,7 +159,7 @@ async function startParallelNotificationTools(client: Client): Promise<Record<st
159159
const toolPromises = toolCalls.map(({ caller, request }) => {
160160
console.log(`Starting tool call for ${caller}...`);
161161
return client
162-
.request(request, CallToolResultSchema)
162+
.request(request)
163163
.then(result => ({ caller, result }))
164164
.catch(error => {
165165
console.error(`Error in tool call for ${caller}:`, error);
@@ -173,7 +173,7 @@ async function startParallelNotificationTools(client: Client): Promise<Record<st
173173
// Organize results by caller
174174
const resultsByTool: Record<string, CallToolResult> = {};
175175
for (const { caller, result } of results) {
176-
resultsByTool[caller] = result;
176+
resultsByTool[caller] = result as CallToolResult;
177177
}
178178

179179
return resultsByTool;

examples/client/src/simpleOAuthClient.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,8 @@ import { createServer } from 'node:http';
44
import { createInterface } from 'node:readline';
55
import { URL } from 'node:url';
66

7-
import type { CallToolRequest, ListToolsRequest, OAuthClientMetadata } from '@modelcontextprotocol/client';
8-
import {
9-
CallToolResultSchema,
10-
Client,
11-
ListToolsResultSchema,
12-
StreamableHTTPClientTransport,
13-
UnauthorizedError
14-
} from '@modelcontextprotocol/client';
7+
import type { CallToolRequest, CallToolResult, ListToolsRequest, OAuthClientMetadata } from '@modelcontextprotocol/client';
8+
import { Client, StreamableHTTPClientTransport, UnauthorizedError } from '@modelcontextprotocol/client';
159
import open from 'open';
1610

1711
import { InMemoryOAuthClientProvider } from './simpleOAuthClientProvider.js';
@@ -262,7 +256,7 @@ class InteractiveOAuthClient {
262256
params: {}
263257
};
264258

265-
const result = await this.client.request(request, ListToolsResultSchema);
259+
const result = await this.client.request(request);
266260

267261
if (result.tools && result.tools.length > 0) {
268262
console.log('\n📋 Available tools:');
@@ -320,7 +314,7 @@ class InteractiveOAuthClient {
320314
}
321315
};
322316

323-
const result = await this.client.request(request, CallToolResultSchema);
317+
const result = (await this.client.request(request)) as CallToolResult;
324318

325319
console.log(`\n🔧 Tool '${toolName}' result:`);
326320
if (result.content) {
@@ -404,7 +398,8 @@ class InteractiveOAuthClient {
404398

405399
case 'result': {
406400
console.log('✓ Completed!');
407-
for (const content of message.result.content) {
401+
const toolResult = message.result as CallToolResult;
402+
for (const content of toolResult.content) {
408403
if (content.type === 'text') {
409404
console.log(content.text);
410405
} else {

0 commit comments

Comments
 (0)