Skip to content

Commit 72ac715

Browse files
authored
Merge branch 'main' into groups/extensions-capability
2 parents 2ace039 + 9aed95a commit 72ac715

53 files changed

Lines changed: 3642 additions & 279 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
'@modelcontextprotocol/core': minor
3+
'@modelcontextprotocol/server': minor
4+
'@modelcontextprotocol/client': minor
5+
---
6+
7+
Support Standard Schema for tool and prompt schemas
8+
9+
Tool and prompt registration now accepts any schema library that implements the [Standard Schema spec](https://standardschema.dev/): Zod v4, Valibot, ArkType, and others. `RegisteredTool.inputSchema`, `RegisteredTool.outputSchema`, and `RegisteredPrompt.argsSchema` now use `StandardSchemaWithJSON` (requires both `~standard.validate` and `~standard.jsonSchema`) instead of the Zod-specific `AnySchema` type.
10+
11+
**Zod v4 schemas continue to work unchanged** — Zod v4 implements the required interfaces natively.
12+
13+
```typescript
14+
import { type } from 'arktype';
15+
16+
server.registerTool('greet', {
17+
inputSchema: type({ name: 'string' })
18+
}, async ({ name }) => ({ content: [{ type: 'text', text: `Hello, ${name}!` }] }));
19+
```
20+
21+
For raw JSON Schema (e.g. TypeBox output), use the new `fromJsonSchema` adapter:
22+
23+
```typescript
24+
import { fromJsonSchema, AjvJsonSchemaValidator } from '@modelcontextprotocol/core';
25+
26+
server.registerTool('greet', {
27+
inputSchema: fromJsonSchema({ type: 'object', properties: { name: { type: 'string' } } }, new AjvJsonSchemaValidator())
28+
}, handler);
29+
```
30+
31+
**Breaking changes:**
32+
- `experimental.tasks.getTaskResult()` no longer accepts a `resultSchema` parameter. Returns `GetTaskPayloadResult` (a loose `Result`); cast to the expected type at the call site.
33+
- Removed unused exports from `@modelcontextprotocol/core`: `SchemaInput`, `schemaToJson`, `parseSchemaAsync`, `getSchemaShape`, `getSchemaDescription`, `isOptionalSchema`, `unwrapOptionalSchema`. Use the new `standardSchemaToJsonSchema` and `validateStandardSchema` instead.
34+
- `completable()` remains Zod-specific (it relies on Zod's `.shape` introspection).

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

docs/client.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import {
1919
Client,
2020
ClientCredentialsProvider,
2121
createMiddleware,
22+
CrossAppAccessProvider,
23+
discoverAndRequestJwtAuthGrant,
2224
PrivateKeyJwtProvider,
2325
ProtocolError,
2426
SdkError,
@@ -152,6 +154,51 @@ For user-facing applications, implement the {@linkcode @modelcontextprotocol/cli
152154

153155
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).
154156

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+
155202
## Tools
156203

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

docs/migration-SKILL.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ if (error instanceof OAuthError && error.code === OAuthErrorCode.InvalidClient)
209209

210210
The variadic `.tool()`, `.prompt()`, `.resource()` methods are removed. Use the `register*` methods with a config object.
211211

212-
**IMPORTANT**: v2 requires full Zod schemas — raw shapes like `{ name: z.string() }` are no longer supported. You must wrap with `z.object()`. This applies to `inputSchema`, `outputSchema`, and `argsSchema`.
212+
**IMPORTANT**: v2 requires schema objects implementing [Standard Schema](https://standardschema.dev/) — raw shapes like `{ name: z.string() }` are no longer supported. Wrap with `z.object()` (Zod v4), or use ArkType's `type({...})`, or Valibot. For raw JSON Schema, wrap with `fromJsonSchema(schema, validator)` from `@modelcontextprotocol/core`. Applies to `inputSchema`, `outputSchema`, and `argsSchema`.
213213

214214
### Tools
215215

@@ -279,13 +279,22 @@ Note: the third argument (`metadata`) is required — pass `{}` if no metadata.
279279

280280
### Schema Migration Quick Reference
281281

282-
| v1 (raw shape) | v2 (Zod schema) |
282+
| v1 (raw shape) | v2 (Standard Schema object) |
283283
|----------------|-----------------|
284284
| `{ name: z.string() }` | `z.object({ name: z.string() })` |
285285
| `{ count: z.number().optional() }` | `z.object({ count: z.number().optional() })` |
286286
| `{}` (empty) | `z.object({})` |
287287
| `undefined` (no schema) | `undefined` or omit the field |
288288

289+
### Removed core exports
290+
291+
| Removed from `@modelcontextprotocol/core` | Replacement |
292+
|---|---|
293+
| `schemaToJson(schema)` | `standardSchemaToJsonSchema(schema)` |
294+
| `parseSchemaAsync(schema, data)` | `validateStandardSchema(schema, data)` |
295+
| `SchemaInput<T>` | `StandardSchemaWithJSON.InferInput<T>` |
296+
| `getSchemaShape`, `getSchemaDescription`, `isOptionalSchema`, `unwrapOptionalSchema` | none (internal Zod introspection helpers) |
297+
289298
## 7. Headers API
290299

291300
Transport constructors and `RequestInfo.headers` now use the Web Standard `Headers` object instead of plain objects.

docs/migration.md

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -200,17 +200,17 @@ import * as z from 'zod/v4';
200200
const server = new McpServer({ name: 'demo', version: '1.0.0' });
201201

202202
// Tool with schema
203-
server.registerTool('greet', { inputSchema: { name: z.string() } }, async ({ name }) => {
203+
server.registerTool('greet', { inputSchema: z.object({ name: z.string() }) }, async ({ name }) => {
204204
return { content: [{ type: 'text', text: `Hello, ${name}!` }] };
205205
});
206206

207207
// Tool with description
208-
server.registerTool('greet', { description: 'Greet a user', inputSchema: { name: z.string() } }, async ({ name }) => {
208+
server.registerTool('greet', { description: 'Greet a user', inputSchema: z.object({ name: z.string() }) }, async ({ name }) => {
209209
return { content: [{ type: 'text', text: `Hello, ${name}!` }] };
210210
});
211211

212212
// Prompt
213-
server.registerPrompt('summarize', { argsSchema: { text: z.string() } }, async ({ text }) => {
213+
server.registerPrompt('summarize', { argsSchema: z.object({ text: z.string() }) }, async ({ text }) => {
214214
return { messages: [{ role: 'user', content: { type: 'text', text: `Summarize: ${text}` } }] };
215215
});
216216

@@ -220,9 +220,9 @@ server.registerResource('config', 'config://app', {}, async uri => {
220220
});
221221
```
222222

223-
### Zod schemas required (raw shapes no longer supported)
223+
### Standard Schema objects required (raw shapes no longer supported)
224224

225-
v2 requires full Zod schemas for `inputSchema` and `argsSchema`. Raw object shapes are no longer accepted.
225+
v2 requires schema objects implementing the [Standard Schema spec](https://standardschema.dev/) for `inputSchema`, `outputSchema`, and `argsSchema`. Raw object shapes are no longer accepted. Zod v4, ArkType, and Valibot all implement the spec.
226226

227227
**Before (v1):**
228228

@@ -240,11 +240,23 @@ server.registerTool('greet', {
240240
```typescript
241241
import * as z from 'zod/v4';
242242

243-
// Must wrap with z.object()
243+
// Wrap with z.object() (or use any Standard Schema library)
244244
server.registerTool('greet', {
245-
inputSchema: z.object({ name: z.string() }) // full Zod schema
245+
inputSchema: z.object({ name: z.string() })
246246
}, async ({ name }) => { ... });
247247

248+
// ArkType works too
249+
import { type } from 'arktype';
250+
server.registerTool('greet', {
251+
inputSchema: type({ name: 'string' })
252+
}, async ({ name }) => { ... });
253+
254+
// Raw JSON Schema via fromJsonSchema
255+
import { fromJsonSchema, AjvJsonSchemaValidator } from '@modelcontextprotocol/core';
256+
server.registerTool('greet', {
257+
inputSchema: fromJsonSchema({ type: 'object', properties: { name: { type: 'string' } } }, new AjvJsonSchemaValidator())
258+
}, handler);
259+
248260
// For tools with no parameters, use z.object({})
249261
server.registerTool('ping', {
250262
inputSchema: z.object({})
@@ -256,6 +268,15 @@ This applies to:
256268
- `outputSchema` in `registerTool()`
257269
- `argsSchema` in `registerPrompt()`
258270

271+
**Removed Zod-specific helpers** from `@modelcontextprotocol/core` (use Standard Schema equivalents):
272+
273+
| Removed | Replacement |
274+
|---|---|
275+
| `schemaToJson(schema)` | `standardSchemaToJsonSchema(schema)` |
276+
| `parseSchemaAsync(schema, data)` | `validateStandardSchema(schema, data)` |
277+
| `SchemaInput<T>` | `StandardSchemaWithJSON.InferInput<T>` |
278+
| `getSchemaShape`, `getSchemaDescription`, `isOptionalSchema`, `unwrapOptionalSchema` | No replacement — these are now internal Zod introspection helpers |
279+
259280
### Host header validation moved
260281

261282
Express-specific middleware (`hostHeaderValidation()`, `localhostHostValidation()`) moved from the server package to `@modelcontextprotocol/express`. The server package now exports framework-agnostic functions instead: `validateHostHeader()`, `localhostAllowedHostnames()`,

examples/client/src/clientGuide.examples.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import {
1414
Client,
1515
ClientCredentialsProvider,
1616
createMiddleware,
17+
CrossAppAccessProvider,
18+
discoverAndRequestJwtAuthGrant,
1719
PrivateKeyJwtProvider,
1820
ProtocolError,
1921
SdkError,
@@ -135,6 +137,33 @@ async function auth_privateKeyJwt(pemEncodedKey: string) {
135137
return transport;
136138
}
137139

140+
/** Example: Cross-App Access (SEP-990 Enterprise Managed Authorization). */
141+
async function auth_crossAppAccess(getIdToken: () => Promise<string>) {
142+
//#region auth_crossAppAccess
143+
const authProvider = new CrossAppAccessProvider({
144+
assertion: async ctx => {
145+
// ctx provides: authorizationServerUrl, resourceUrl, scope, fetchFn
146+
const result = await discoverAndRequestJwtAuthGrant({
147+
idpUrl: 'https://idp.example.com',
148+
audience: ctx.authorizationServerUrl,
149+
resource: ctx.resourceUrl,
150+
idToken: await getIdToken(),
151+
clientId: 'my-idp-client',
152+
clientSecret: 'my-idp-secret',
153+
scope: ctx.scope,
154+
fetchFn: ctx.fetchFn
155+
});
156+
return result.jwtAuthGrant;
157+
},
158+
clientId: 'my-mcp-client',
159+
clientSecret: 'my-mcp-secret'
160+
});
161+
162+
const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'), { authProvider });
163+
//#endregion auth_crossAppAccess
164+
return transport;
165+
}
166+
138167
// ---------------------------------------------------------------------------
139168
// Using server features
140169
// ---------------------------------------------------------------------------
@@ -513,6 +542,7 @@ void disconnect_streamableHttp;
513542
void serverInstructions_basic;
514543
void auth_clientCredentials;
515544
void auth_privateKeyJwt;
545+
void auth_crossAppAccess;
516546
void callTool_basic;
517547
void callTool_structuredOutput;
518548
void callTool_progress;

examples/server/package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,17 @@
3434
"dependencies": {
3535
"@hono/node-server": "catalog:runtimeServerOnly",
3636
"@modelcontextprotocol/examples-shared": "workspace:^",
37-
"@modelcontextprotocol/node": "workspace:^",
38-
"@modelcontextprotocol/server": "workspace:^",
3937
"@modelcontextprotocol/express": "workspace:^",
4038
"@modelcontextprotocol/hono": "workspace:^",
39+
"@modelcontextprotocol/node": "workspace:^",
40+
"@modelcontextprotocol/server": "workspace:^",
41+
"@valibot/to-json-schema": "catalog:devTools",
42+
"arktype": "catalog:devTools",
4143
"better-auth": "^1.4.17",
4244
"cors": "catalog:runtimeServerOnly",
4345
"express": "catalog:runtimeServerOnly",
4446
"hono": "catalog:runtimeServerOnly",
47+
"valibot": "catalog:devTools",
4548
"zod": "catalog:runtimeShared"
4649
},
4750
"devDependencies": {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Minimal MCP server using ArkType for schema validation.
4+
* ArkType implements the Standard Schema spec with built-in JSON Schema conversion.
5+
*/
6+
7+
import { McpServer, StdioServerTransport } from '@modelcontextprotocol/server';
8+
import { type } from 'arktype';
9+
10+
const server = new McpServer({
11+
name: 'arktype-example',
12+
version: '1.0.0'
13+
});
14+
15+
// Register a tool with ArkType schema
16+
server.registerTool(
17+
'greet',
18+
{
19+
description: 'Generate a greeting',
20+
inputSchema: type({ name: 'string' })
21+
},
22+
async ({ name }) => ({
23+
content: [{ type: 'text', text: `Hello, ${name}!` }]
24+
})
25+
);
26+
27+
const transport = new StdioServerTransport();
28+
await server.connect(transport);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Minimal MCP server using Valibot for schema validation.
4+
* Use toStandardJsonSchema() from @valibot/to-json-schema to create
5+
* StandardJSONSchemaV1-compliant schemas.
6+
*/
7+
8+
import { McpServer, StdioServerTransport } from '@modelcontextprotocol/server';
9+
import { toStandardJsonSchema } from '@valibot/to-json-schema';
10+
import * as v from 'valibot';
11+
12+
const server = new McpServer({
13+
name: 'valibot-example',
14+
version: '1.0.0'
15+
});
16+
17+
// Register a tool with Valibot schema
18+
server.registerTool(
19+
'greet',
20+
{
21+
description: 'Generate a greeting',
22+
inputSchema: toStandardJsonSchema(v.object({ name: v.string() }))
23+
},
24+
async ({ name }) => ({
25+
content: [{ type: 'text', text: `Hello, ${name}!` }]
26+
})
27+
);
28+
29+
const transport = new StdioServerTransport();
30+
await server.connect(transport);

0 commit comments

Comments
 (0)