Skip to content

Commit 28838b0

Browse files
fix: address review — callTool(params, undefined, opts) drops opts; migration.md mcpReq.send claim; complete custom-method example
1 parent 0daaa15 commit 28838b0

File tree

7 files changed

+69
-23
lines changed

7 files changed

+69
-23
lines changed

docs/migration-SKILL.md

Lines changed: 12 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

@@ -416,9 +416,9 @@ Request/notification params remain fully typed. Remove unused schema imports aft
416416
| `ctx.mcpReq.elicitInput(params, options?)` | Elicit user input (form or URL) | `server.elicitInput(...)` from within handler |
417417
| `ctx.mcpReq.requestSampling(params, options?)` | Request LLM sampling from client | `server.createMessage(...)` from within handler |
418418

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

421-
`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.
421+
`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.
422422

423423
```typescript
424424
// v1: schema required
@@ -427,20 +427,20 @@ const result = await client.request({ method: 'tools/call', params: { ... } }, C
427427
const elicit = await ctx.mcpReq.send({ method: 'elicitation/create', params: { ... } }, ElicitResultSchema);
428428
const tool = await client.callTool({ name: 'my-tool', arguments: {} }, CompatibilityCallToolResultSchema);
429429

430-
// v2: no schema argument
430+
// v2: schema optional on request()/callTool(); removed from mcpReq.send()
431431
const result = await client.request({ method: 'tools/call', params: { ... } });
432432
const elicit = await ctx.mcpReq.send({ method: 'elicitation/create', params: { ... } });
433433
const tool = await client.callTool({ name: 'my-tool', arguments: {} });
434434
```
435435

436-
| v1 call | v2 call |
437-
| ------------------------------------------------------------ | ---------------------------------- |
438-
| `client.request(req, ResultSchema)` | `client.request(req)` |
439-
| `client.request(req, ResultSchema, options)` | `client.request(req, options)` |
440-
| `ctx.mcpReq.send(req, ResultSchema)` | `ctx.mcpReq.send(req)` |
441-
| `ctx.mcpReq.send(req, ResultSchema, options)` | `ctx.mcpReq.send(req, options)` |
442-
| `client.callTool(params, CompatibilityCallToolResultSchema)` | `client.callTool(params)` |
443-
| `client.callTool(params, schema, options)` | `client.callTool(params, options)` |
436+
| v1 call | v2 call |
437+
| ------------------------------------------------------------ | ---------------------------------------------- |
438+
| `client.request(req, ResultSchema)` | unchanged (schema optional), or `client.request(req)` |
439+
| `client.request(req, ResultSchema, options)` | unchanged, or `client.request(req, options)` |
440+
| `ctx.mcpReq.send(req, ResultSchema)` | `ctx.mcpReq.send(req)` |
441+
| `ctx.mcpReq.send(req, ResultSchema, options)` | `ctx.mcpReq.send(req, options)` |
442+
| `client.callTool(params, CompatibilityCallToolResultSchema)` | unchanged (schema ignored), or `client.callTool(params)` |
443+
| `client.callTool(params, schema, options)` | unchanged, or `client.callTool(params, options)` |
444444

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

docs/migration.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -397,10 +397,10 @@ server.setRequestHandler(SearchRequestSchema, req => ({ hits: [req.params.query]
397397
const result = await client.request({ method: 'acme/search', params: { query: 'x' } }, SearchResult);
398398
```
399399

400-
### `Protocol.request()`, `ctx.mcpReq.send()`, and `Client.callTool()` schema parameter is now optional
400+
### `Protocol.request()` and `Client.callTool()` schema parameter is now optional
401401

402-
The public `Protocol.request()`, `BaseContext.mcpReq.send()`, 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
403-
like `CallToolResultSchema` or `ElicitResultSchema` when making requests.
402+
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
403+
like `CallToolResultSchema` or `ElicitResultSchema` when making requests. (`BaseContext.mcpReq.send()` no longer accepts a schema; drop it.)
404404

405405
**`client.request()` — Before (v1):**
406406

examples/client/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Most clients expect a server to be running. Start one from [`../server/README.md
3636
| Client credentials (M2M) | Machine-to-machine OAuth client credentials example. | [`src/simpleClientCredentials.ts`](src/simpleClientCredentials.ts) |
3737
| URL elicitation client | Drives URL-mode elicitation flows (sensitive input in a browser). | [`src/elicitationUrlExample.ts`](src/elicitationUrlExample.ts) |
3838
| 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 and handles custom server notifications. | [`src/customMethodExample.ts`](src/customMethodExample.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) |
4040

4141
## URL elicitation example (server + client)
4242

examples/server/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ pnpm tsx src/simpleStreamableHttp.ts
3838
| Task interactive server | Task-based execution with interactive server→client requests. | [`src/simpleTaskInteractive.ts`](src/simpleTaskInteractive.ts) |
3939
| Hono Streamable HTTP server | Streamable HTTP server built with Hono instead of Express. | [`src/honoWebStandardStreamableHttp.ts`](src/honoWebStandardStreamableHttp.ts) |
4040
| SSE polling demo server | Legacy SSE server intended for polling demos. | [`src/ssePollingExample.ts`](src/ssePollingExample.ts) |
41-
| Custom (non-standard) methods server | Registers `acme/*` custom request handlers and sends custom notifications. | [`src/customMethodExample.ts`](src/customMethodExample.ts) |
41+
| Custom (non-standard) methods server | Registers `acme/*` custom request + notification handlers and emits custom progress notifications. | [`src/customMethodExample.ts`](src/customMethodExample.ts) |
4242

4343
## OAuth demo flags (Streamable HTTP server)
4444

examples/server/src/customMethodExample.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,12 @@ const TickNotification = z.object({
2727

2828
const server = new Server({ name: 'custom-method-server', version: '1.0.0' }, { capabilities: {} });
2929

30-
server.setRequestHandler(SearchRequest, request => {
30+
server.setRequestHandler(SearchRequest, async (request, ctx) => {
3131
console.log('[server] acme/search query=' + request.params.query);
32-
return { hits: [request.params.query, request.params.query + '-result'] };
32+
await ctx.mcpReq.notify({ method: 'acme/searchProgress', params: { stage: 'start', pct: 0 } });
33+
const hits = [request.params.query, request.params.query + '-result'];
34+
await ctx.mcpReq.notify({ method: 'acme/searchProgress', params: { stage: 'done', pct: 100 } });
35+
return { hits };
3336
});
3437

3538
server.setNotificationHandler(TickNotification, n => {

packages/client/src/client/client.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -892,10 +892,12 @@ export class Client extends Protocol<ClientContext> {
892892
optionsOrSchema?: RequestOptions | unknown,
893893
maybeOptions?: RequestOptions
894894
): Promise<CallToolResult> {
895-
const options: RequestOptions | undefined =
896-
optionsOrSchema && typeof optionsOrSchema === 'object' && 'parse' in optionsOrSchema
897-
? maybeOptions
898-
: (optionsOrSchema as RequestOptions | undefined);
895+
const arg2IsSchema = optionsOrSchema != null && typeof optionsOrSchema === 'object' && 'parse' in optionsOrSchema;
896+
// v1 allowed `callTool(params, undefined, opts)` (resultSchema was optional-with-default);
897+
// when arg2 is not a schema, prefer arg3 if present so opts aren't dropped.
898+
const options: RequestOptions | undefined = arg2IsSchema
899+
? maybeOptions
900+
: (maybeOptions ?? (optionsOrSchema as RequestOptions | undefined));
899901
// Guard: required-task tools need experimental API
900902
if (this.isToolTaskRequired(params.name)) {
901903
throw new ProtocolError(
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { describe, expect, it, vi } from 'vitest';
2+
import { Client } from '../../src/client/client.js';
3+
4+
describe('callTool v1-compat overload dispatch', () => {
5+
function makeClient() {
6+
const client = new Client({ name: 't', version: '1.0.0' }, { capabilities: {} });
7+
const spy = vi
8+
.spyOn(client as unknown as { _requestWithSchema: (...a: unknown[]) => Promise<unknown> }, '_requestWithSchema')
9+
.mockResolvedValue({ content: [] });
10+
return { client, spy };
11+
}
12+
13+
it('callTool(params, undefined, options) preserves options (v1: optional resultSchema)', async () => {
14+
const { client, spy } = makeClient();
15+
const opts = { timeout: 5000 };
16+
await client.callTool({ name: 'x', arguments: {} }, undefined, opts);
17+
expect(spy).toHaveBeenCalledTimes(1);
18+
expect(spy.mock.calls[0]?.[2]).toBe(opts);
19+
});
20+
21+
it('callTool(params, schema, options) preserves options', async () => {
22+
const { client, spy } = makeClient();
23+
const opts = { timeout: 5000 };
24+
const schema = { parse: (x: unknown) => x };
25+
await client.callTool({ name: 'x', arguments: {} }, schema, opts);
26+
expect(spy.mock.calls[0]?.[2]).toBe(opts);
27+
});
28+
29+
it('callTool(params, options) — 2-arg form still works', async () => {
30+
const { client, spy } = makeClient();
31+
const opts = { timeout: 5000 };
32+
await client.callTool({ name: 'x', arguments: {} }, opts);
33+
expect(spy.mock.calls[0]?.[2]).toBe(opts);
34+
});
35+
36+
it('callTool(params) — no options', async () => {
37+
const { client, spy } = makeClient();
38+
await client.callTool({ name: 'x', arguments: {} });
39+
expect(spy.mock.calls[0]?.[2]).toBeUndefined();
40+
});
41+
});

0 commit comments

Comments
 (0)