Skip to content

Commit 013d166

Browse files
Merge branch 'main' into fix/rpc-error-message-prefix
2 parents ed30b32 + ccb78f2 commit 013d166

74 files changed

Lines changed: 4784 additions & 2176 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
"@modelcontextprotocol/core": minor
3+
"@modelcontextprotocol/server": major
4+
---
5+
6+
Fix error handling for unknown tools and resources per MCP spec.
7+
8+
**Tools:** Unknown or disabled tool calls now return JSON-RPC protocol errors with
9+
code `-32602` (InvalidParams) instead of `CallToolResult` with `isError: true`.
10+
Callers who checked `result.isError` for unknown tools should catch rejected promises instead.
11+
12+
**Resources:** Unknown resource reads now return error code `-32002` (ResourceNotFound)
13+
instead of `-32602` (InvalidParams).
14+
15+
Added `ProtocolErrorCode.ResourceNotFound`.

.changeset/twelve-dodos-taste.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@modelcontextprotocol/express": patch
3+
---
4+
5+
Add jsonLimit option to createMcpExpressApp

.github/workflows/main.yml

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,47 @@ jobs:
5858

5959
- run: pnpm test:all
6060

61+
test-runtimes:
62+
runs-on: ubuntu-latest
63+
strategy:
64+
fail-fast: false
65+
matrix:
66+
include:
67+
- runtime: bun
68+
version: "1.x"
69+
- runtime: deno
70+
version: v2.x
71+
steps:
72+
- uses: actions/checkout@v6
73+
- name: Install pnpm
74+
uses: pnpm/action-setup@v4
75+
with:
76+
run_install: false
77+
- uses: actions/setup-node@v6
78+
with:
79+
node-version: 24
80+
cache: pnpm
81+
cache-dependency-path: pnpm-lock.yaml
82+
- name: Set up Bun
83+
if: matrix.runtime == 'bun'
84+
uses: oven-sh/setup-bun@v2
85+
with:
86+
bun-version: ${{ matrix.version }}
87+
- name: Set up Deno
88+
if: matrix.runtime == 'deno'
89+
uses: denoland/setup-deno@v2
90+
with:
91+
deno-version: ${{ matrix.version }}
92+
- run: pnpm install
93+
- run: pnpm build:all
94+
- name: Run ${{ matrix.runtime }} integration tests
95+
run: pnpm --filter @modelcontextprotocol/test-integration test:integration:${{ matrix.runtime }}
96+
6197
publish:
6298
runs-on: ubuntu-latest
6399
if: github.event_name == 'release'
64100
environment: release
65-
needs: [build, test]
101+
needs: [build, test, test-runtimes]
66102

67103
permissions:
68104
contents: read

CLAUDE.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,10 @@ The SDK uses `zod/v4` internally. Schema utilities live in:
102102

103103
### Validation
104104

105-
Pluggable JSON Schema validation (`packages/core/src/validation/`):
105+
Pluggable JSON Schema validation (`packages/core/src/validators/`):
106106

107-
- `ajv-provider.ts` - Default Ajv-based validator
108-
- `cfworker-provider.ts` - Cloudflare Workers-compatible alternative
107+
- `ajvProvider.ts` - Default Ajv-based validator
108+
- `cfWorkerProvider.ts` - Cloudflare Workers-compatible alternative
109109

110110
### Examples
111111

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
The Model Context Protocol (MCP) allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction.
2727

28-
This repository contains the TypeScript SDK implementation of the MCP specification and ships:
28+
This repository contains the TypeScript SDK implementation of the MCP specification. It runs on **Node.js**, **Bun**, and **Deno**, and ships:
2929

3030
- MCP **server** libraries (tools/resources/prompts, Streamable HTTP, stdio, auth helpers)
3131
- MCP **client** libraries (transports, high-level helpers, OAuth helpers)
@@ -57,12 +57,20 @@ They are intentionally thin adapters: they should not introduce new MCP function
5757

5858
```bash
5959
npm install @modelcontextprotocol/server zod
60+
# or
61+
bun add @modelcontextprotocol/server zod
62+
# or
63+
deno add npm:@modelcontextprotocol/server npm:zod
6064
```
6165

6266
### Client
6367

6468
```bash
6569
npm install @modelcontextprotocol/client zod
70+
# or
71+
bun add @modelcontextprotocol/client zod
72+
# or
73+
deno add npm:@modelcontextprotocol/client npm:zod
6674
```
6775

6876
### Optional middleware packages

docs/client-quickstart.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@ This quickstart assumes you have familiarity with:
1919

2020
Before starting, ensure your system meets these requirements:
2121

22-
- Node.js 20 or higher installed
22+
- Node.js 20 or higher installed (or **Bun** / **Deno** — the SDK supports all three runtimes)
2323
- Latest version of `npm` installed
2424
- An Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys)
2525

26+
> [!TIP]
27+
> This tutorial uses Node.js and npm, but you can substitute `bun` or `deno` commands where appropriate. For example, use `bun add` instead of `npm install`, or run the client with `bun run` / `deno run`.
28+
2629
## Set up your environment
2730

2831
First, let's create and set up our project:
@@ -109,7 +112,7 @@ First, let's set up our imports and create the basic client class in `src/index.
109112

110113
```ts source="../examples/client-quickstart/src/index.ts#prelude"
111114
import Anthropic from '@anthropic-ai/sdk';
112-
import { Client, StdioClientTransport, type CallToolResult } from '@modelcontextprotocol/client';
115+
import { Client, StdioClientTransport } from '@modelcontextprotocol/client';
113116
import readline from 'readline/promises';
114117

115118
const ANTHROPIC_MODEL = 'claude-sonnet-4-5';
@@ -201,7 +204,7 @@ Now let's add the core functionality for processing queries and handling tool ca
201204
const result = await this.mcp.callTool({
202205
name: toolName,
203206
arguments: toolArgs,
204-
}) as CallToolResult;
207+
});
205208

206209
finalText.push(`[Calling tool ${toolName} with args ${JSON.stringify(toolArgs)}]`);
207210

docs/client.md

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ 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,
22+
CrossAppAccessProvider,
23+
discoverAndRequestJwtAuthGrant,
2324
PrivateKeyJwtProvider,
2425
ProtocolError,
2526
SdkError,
@@ -153,6 +154,51 @@ For user-facing applications, implement the {@linkcode @modelcontextprotocol/cli
153154

154155
For a complete working OAuth flow, see [`simpleOAuthClient.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleOAuthClient.ts) and [`simpleOAuthClientProvider.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleOAuthClientProvider.ts).
155156

157+
### Cross-App Access (Enterprise Managed Authorization)
158+
159+
{@linkcode @modelcontextprotocol/client!client/authExtensions.CrossAppAccessProvider | CrossAppAccessProvider} implements Enterprise Managed Authorization (SEP-990) for scenarios where users authenticate with an enterprise identity provider (IdP) and clients need to access protected MCP servers on their behalf.
160+
161+
This provider handles a two-step OAuth flow:
162+
1. Exchange the user's ID Token from the enterprise IdP for a JWT Authorization Grant (JAG) via RFC 8693 token exchange
163+
2. Exchange the JAG for an access token from the MCP server via RFC 7523 JWT bearer grant
164+
165+
```ts source="../examples/client/src/clientGuide.examples.ts#auth_crossAppAccess"
166+
const authProvider = new CrossAppAccessProvider({
167+
assertion: async ctx => {
168+
// ctx provides: authorizationServerUrl, resourceUrl, scope, fetchFn
169+
const result = await discoverAndRequestJwtAuthGrant({
170+
idpUrl: 'https://idp.example.com',
171+
audience: ctx.authorizationServerUrl,
172+
resource: ctx.resourceUrl,
173+
idToken: await getIdToken(),
174+
clientId: 'my-idp-client',
175+
clientSecret: 'my-idp-secret',
176+
scope: ctx.scope,
177+
fetchFn: ctx.fetchFn
178+
});
179+
return result.jwtAuthGrant;
180+
},
181+
clientId: 'my-mcp-client',
182+
clientSecret: 'my-mcp-secret'
183+
});
184+
185+
const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'), { authProvider });
186+
```
187+
188+
The `assertion` callback receives a context object with:
189+
- `authorizationServerUrl` – The MCP server's authorization server (discovered automatically)
190+
- `resourceUrl` – The MCP resource URL (discovered automatically)
191+
- `scope` – Optional scope passed to `auth()` or from `clientMetadata`
192+
- `fetchFn` – Fetch implementation to use for HTTP requests
193+
194+
For manual control over the token exchange steps, use the Layer 2 utilities from `@modelcontextprotocol/client`:
195+
- `requestJwtAuthorizationGrant()` – Exchange ID Token for JAG at IdP
196+
- `discoverAndRequestJwtAuthGrant()` – Discovery + JAG acquisition
197+
- `exchangeJwtAuthGrant()` – Exchange JAG for access token at MCP server
198+
199+
> [!NOTE]
200+
> See [RFC 8693 (Token Exchange)](https://datatracker.ietf.org/doc/html/rfc8693), [RFC 7523 (JWT Bearer Grant)](https://datatracker.ietf.org/doc/html/rfc7523), and [RFC 9728 (Resource Discovery)](https://datatracker.ietf.org/doc/html/rfc9728) for the underlying OAuth standards.
201+
156202
## Tools
157203

158204
Tools are callable actions offered by servers — discovering and invoking them is usually how your client enables an LLM to take action (see [Tools](https://modelcontextprotocol.io/docs/learn/server-concepts#tools) in the MCP overview).
@@ -198,13 +244,16 @@ if (result.structuredContent) {
198244
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:
199245

200246
```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-
});
247+
const result = await client.callTool(
248+
{ name: 'long-operation', arguments: {} },
249+
{
250+
onprogress: ({ progress, total }: { progress: number; total?: number }) => {
251+
console.log(`Progress: ${progress}/${total ?? '?'}`);
252+
},
253+
resetTimeoutOnProgress: true,
254+
maxTotalTimeout: 600_000
255+
}
256+
);
208257
console.log(result.content);
209258
```
210259

@@ -484,7 +533,6 @@ All requests have a 60-second default timeout. Pass a custom `timeout` in the op
484533
try {
485534
const result = await client.callTool(
486535
{ name: 'slow-task', arguments: {} },
487-
undefined,
488536
{ timeout: 120_000 } // 2 minutes instead of the default 60 seconds
489537
);
490538
console.log(result.content);
@@ -523,7 +571,6 @@ const result = await client.request(
523571
method: 'tools/call',
524572
params: { name: 'long-running-task', arguments: {} }
525573
},
526-
CallToolResultSchema,
527574
{
528575
resumptionToken: lastToken,
529576
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

0 commit comments

Comments
 (0)