Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/custom-method-overloads.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@modelcontextprotocol/client': minor
'@modelcontextprotocol/server': minor
---

`setRequestHandler`/`setNotificationHandler` accept the v1 `(ZodSchema, handler)` form as a first-class alternative to `(methodString, handler)`. `request()` and `ctx.mcpReq.send()` accept an explicit result schema (`request(req, resultSchema, options?)`) and have method-keyed return types for spec methods. `callTool(params, resultSchema?)` accepts the v1 schema arg (ignored). `removeRequestHandler`/`removeNotificationHandler`/`assertCanSetRequestHandler` accept any method string.
37 changes: 23 additions & 14 deletions docs/migration-SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ if (error instanceof OAuthError && error.code === OAuthErrorCode.InvalidClient)
```

**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).
Zod schemas, all callback return types. Note: `callTool()` and `request()` schema parameter is now optional (see section 11).

## 6. McpServer API Changes

Expand Down Expand Up @@ -340,7 +340,7 @@ The server package now exports framework-agnostic alternatives: `validateHostHea

## 9. `setRequestHandler` / `setNotificationHandler` API

The low-level handler registration methods now take a method string instead of a Zod schema.
The low-level handler registration methods now accept a method string in addition to the v1 Zod-schema form (both are supported).

```typescript
// v1: schema-based
Expand Down Expand Up @@ -377,6 +377,15 @@ Schema to method string mapping:

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

**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:

| Form | Notes |
| ------------------------------------------------------------ | --------------------------------------------------------------------- |
| `setRequestHandler(CustomReqSchema, (req, ctx) => ...)` | unchanged |
| `setNotificationHandler(CustomNotifSchema, n => ...)` | unchanged |
| `this.request({ method: 'vendor/x', params }, ResultSchema)` | unchanged |
| `this.notification({ method: 'vendor/x', params })` | unchanged |
Comment thread
felixweinberger marked this conversation as resolved.

## 10. Request Handler Context Types

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

## 11. Schema parameter removed from `request()`, `send()`, and `callTool()`
## 11. Schema parameter on `request()` / `callTool()` / `mcpReq.send()` is optional

`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.
`Protocol.request()`, `BaseContext.mcpReq.send()`, and `Client.callTool()` still accept a result schema as the second argument (the v1 form), but for spec methods it is optional — the SDK resolves the schema internally from the method name. The schema argument remains the supported call form for custom (non-spec) methods.

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

// v2: no schema argument
// v2: schema optional on request()/callTool()/mcpReq.send() for spec methods
const result = await client.request({ method: 'tools/call', params: { ... } });
const elicit = await ctx.mcpReq.send({ method: 'elicitation/create', params: { ... } });
const tool = await client.callTool({ name: 'my-tool', arguments: {} });
```

| v1 call | v2 call |
| ------------------------------------------------------------ | ---------------------------------- |
| `client.request(req, ResultSchema)` | `client.request(req)` |
| `client.request(req, ResultSchema, options)` | `client.request(req, options)` |
| `ctx.mcpReq.send(req, ResultSchema)` | `ctx.mcpReq.send(req)` |
| `ctx.mcpReq.send(req, ResultSchema, options)` | `ctx.mcpReq.send(req, options)` |
| `client.callTool(params, CompatibilityCallToolResultSchema)` | `client.callTool(params)` |
| `client.callTool(params, schema, options)` | `client.callTool(params, options)` |
| v1 call | v2 call |
| ------------------------------------------------------------ | ---------------------------------------------- |
| `client.request(req, ResultSchema)` | unchanged (schema optional), or `client.request(req)` |
| `client.request(req, ResultSchema, options)` | unchanged, or `client.request(req, options)` |
| `ctx.mcpReq.send(req, ResultSchema)` | unchanged (schema optional), or `ctx.mcpReq.send(req)` |
| `ctx.mcpReq.send(req, ResultSchema, options)` | unchanged, or `ctx.mcpReq.send(req, options)` |
| `client.callTool(params, CompatibilityCallToolResultSchema)` | unchanged (schema ignored), or `client.callTool(params)` |
| `client.callTool(params, schema, options)` | unchanged, or `client.callTool(params, options)` |

Remove unused schema imports: `CallToolResultSchema`, `CompatibilityCallToolResultSchema`, `ElicitResultSchema`, `CreateMessageResultSchema`, etc., when they were only used in `request()`/`send()`/`callTool()` calls.
For spec methods you can drop now-unused schema imports (`CallToolResultSchema`, `CompatibilityCallToolResultSchema`, `ElicitResultSchema`, `CreateMessageResultSchema`, etc.) when they were only used in `request()`/`send()`/`callTool()` calls.

If `CallToolResultSchema` was used for **runtime validation** (not just as a `request()` argument), replace with the `isCallToolResult` type guard:

Expand Down
25 changes: 20 additions & 5 deletions docs/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ Note: the v2 signature takes a plain `string[]` instead of an options object.

### `setRequestHandler` and `setNotificationHandler` use method strings

The low-level `setRequestHandler` and `setNotificationHandler` methods on `Client`, `Server`, and `Protocol` now take a method string instead of a Zod schema.
The low-level `setRequestHandler` and `setNotificationHandler` methods on `Client`, `Server`, and `Protocol` now accept a method string in addition to the v1 Zod-schema form (both are supported).

**Before (v1):**

Expand Down Expand Up @@ -382,10 +382,25 @@ Common method string replacements:
| `ResourceListChangedNotificationSchema` | `'notifications/resources/list_changed'` |
| `PromptListChangedNotificationSchema` | `'notifications/prompts/list_changed'` |

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

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.
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.

```typescript
import { Server } from '@modelcontextprotocol/server';

const server = new Server({ name: 'app', version: '1.0.0' }, { capabilities: {} });

server.setRequestHandler(SearchRequestSchema, req => ({ hits: [req.params.query] }));

// Calling from a Client — unchanged from v1:
const result = await client.request({ method: 'acme/search', params: { query: 'x' } }, SearchResult);
```

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

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
like `CallToolResultSchema` or `ElicitResultSchema` when making spec-method requests. The schema argument remains the supported call form for custom (non-spec) methods.

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

Expand Down Expand Up @@ -888,7 +903,7 @@ import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/server/valida

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

- `Client` constructor and most client methods (`connect`, `listTools`, `listPrompts`, `listResources`, `readResource`, etc.) — note: `callTool()` signature changed (schema parameter removed)
- `Client` constructor and most client methods (`connect`, `listTools`, `listPrompts`, `listResources`, `readResource`, etc.) — note: `callTool()` schema parameter is now optional
- `McpServer` constructor, `server.connect(transport)`, `server.close()`
- `Server` (low-level) constructor and all methods
- `StreamableHTTPClientTransport`, `SSEClientTransport`, `StdioClientTransport` constructors and options
Expand Down
25 changes: 13 additions & 12 deletions examples/client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,19 @@ Most clients expect a server to be running. Start one from [`../server/README.md

## Example index

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

## URL elicitation example (server + client)

Expand Down
36 changes: 36 additions & 0 deletions examples/client/src/customMethodExample.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env node
/**
* Calling vendor-specific (non-spec) JSON-RPC methods from a `Client`.
*
* - Send a custom request: `client.request({ method, params }, resultSchema)`
* - Send a custom notification: `client.notification({ method, params })`
* - Receive a custom notification: `client.setNotificationHandler(ZodSchemaWithMethodLiteral, handler)`
*
* Pair with the server in examples/server/src/customMethodExample.ts.
*/

import { Client, StdioClientTransport } from '@modelcontextprotocol/client';
import { z } from 'zod';

const SearchResult = z.object({ hits: z.array(z.string()) });

const ProgressNotification = z.object({
method: z.literal('acme/searchProgress'),
params: z.object({ stage: z.string(), pct: z.number() })
});

const client = new Client({ name: 'custom-method-client', version: '1.0.0' }, { capabilities: {} });

client.setNotificationHandler(ProgressNotification, n => {
console.log(`[client] progress: ${n.params.stage} ${n.params.pct}%`);
});

await client.connect(new StdioClientTransport({ command: 'npx', args: ['tsx', '../server/src/customMethodExample.ts'] }));

const r = await client.request({ method: 'acme/search', params: { query: 'widgets' } }, SearchResult);
console.log('[client] hits=' + JSON.stringify(r.hits));

await client.notification({ method: 'acme/tick', params: { n: 1 } });
await client.notification({ method: 'acme/tick', params: { n: 2 } });

await client.close();
Loading
Loading