feat: auto-select JSON Schema validator based on runtime (#1456)

This commit is contained in:
Matt 2026-02-03 19:22:39 +00:00 • committed by GitHub
commit bc883afe3c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 1176 additions and 107 deletions

View file

@ -47,15 +47,15 @@ Replace all `@modelcontextprotocol/sdk/...` imports using this table.
### Server imports
| v1 import path | v2 package |
| ---------------------------------------------------- | ----------------------------------------------------------------------------------- |
| `@modelcontextprotocol/sdk/server/mcp.js` | `@modelcontextprotocol/server` |
| `@modelcontextprotocol/sdk/server/index.js` | `@modelcontextprotocol/server` |
| `@modelcontextprotocol/sdk/server/stdio.js` | `@modelcontextprotocol/server` |
| `@modelcontextprotocol/sdk/server/streamableHttp.js` | `@modelcontextprotocol/node` (class renamed to `NodeStreamableHTTPServerTransport`) |
| `@modelcontextprotocol/sdk/server/sse.js` | REMOVED (migrate to Streamable HTTP) |
| `@modelcontextprotocol/sdk/server/auth/*` | REMOVED (use external auth library) |
| `@modelcontextprotocol/sdk/server/middleware.js` | `@modelcontextprotocol/express` (signature changed, see section 8) |
| v1 import path | v2 package |
| ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `@modelcontextprotocol/sdk/server/mcp.js` | `@modelcontextprotocol/server` |
| `@modelcontextprotocol/sdk/server/index.js` | `@modelcontextprotocol/server` |
| `@modelcontextprotocol/sdk/server/stdio.js` | `@modelcontextprotocol/server` |
| `@modelcontextprotocol/sdk/server/streamableHttp.js` | `@modelcontextprotocol/node` (class renamed to `NodeStreamableHTTPServerTransport`) OR `@modelcontextprotocol/server` (web-standard `WebStandardStreamableHTTPServerTransport` for Cloudflare Workers, Deno, etc.) |
| `@modelcontextprotocol/sdk/server/sse.js` | REMOVED (migrate to Streamable HTTP) |
| `@modelcontextprotocol/sdk/server/auth/*` | REMOVED (use external auth library) |
| `@modelcontextprotocol/sdk/server/middleware.js` | `@modelcontextprotocol/express` (signature changed, see section 8) |
### Types / shared imports
@ -203,8 +203,7 @@ import { OAuthError, OAuthErrorCode } from '@modelcontextprotocol/core';
if (error instanceof OAuthError && error.code === OAuthErrorCode.InvalidClient) { ... }
```
**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.
**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.
## 6. McpServer API Changes
@ -360,7 +359,27 @@ Request/notification params remain fully typed. Remove unused schema imports aft
`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.
## 11. Migration Steps (apply in this order)
## 11. Runtime-Specific JSON Schema Validators (Enhancement)
The SDK now auto-selects the appropriate JSON Schema validator based on runtime:
- Node.js → `AjvJsonSchemaValidator` (no change from v1)
- Cloudflare Workers (workerd) → `CfWorkerJsonSchemaValidator` (previously required manual config)
**No action required** for most users. Cloudflare Workers users can remove explicit `jsonSchemaValidator` configuration:
```typescript
// v1 (Cloudflare Workers): Required explicit validator
new McpServer({ name: 'server', version: '1.0.0' }, {
jsonSchemaValidator: new CfWorkerJsonSchemaValidator()
});
// v2 (Cloudflare Workers): Auto-selected, explicit config optional
new McpServer({ name: 'server', version: '1.0.0' }, {});
```
Access validators via `_shims` export: `import { DefaultJsonSchemaValidator } from '@modelcontextprotocol/server/_shims';`
## 12. Migration Steps (apply in this order)
1. Update `package.json`: `npm uninstall @modelcontextprotocol/sdk`, install the appropriate v2 packages
2. Replace all imports from `@modelcontextprotocol/sdk/...` using the import mapping tables (sections 3-4), including `StreamableHTTPServerTransport` → `NodeStreamableHTTPServerTransport`

View file

@ -51,7 +51,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
```typescript
import { Client, StreamableHTTPClientTransport, StdioClientTransport } from '@modelcontextprotocol/client';
import { McpServer, StdioServerTransport } from '@modelcontextprotocol/server';
import { McpServer, StdioServerTransport, WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/server';
import { CallToolResultSchema } from '@modelcontextprotocol/core';
// Node.js HTTP server transport is in the @modelcontextprotocol/node package
@ -465,8 +465,7 @@ try {
#### Why this change?
Previously, `ErrorCode.RequestTimeout` (-32001) and `ErrorCode.ConnectionClosed` (-32000) were used for local timeout/connection errors. However, these errors never cross the wire as JSON-RPC responses - they are rejected locally. Using protocol error codes for local errors was
semantically inconsistent.
Previously, `ErrorCode.RequestTimeout` (-32001) and `ErrorCode.ConnectionClosed` (-32000) were used for local timeout/connection errors. However, these errors never cross the wire as JSON-RPC responses - they are rejected locally. Using protocol error codes for local errors was semantically inconsistent.
The new design:
@ -546,6 +545,51 @@ try {
}
```
## Enhancements
### Automatic JSON Schema validator selection by runtime
The SDK now automatically selects the appropriate JSON Schema validator based on your runtime environment:
- **Node.js**: Uses `AjvJsonSchemaValidator` (same as v1 default)
- **Cloudflare Workers**: Uses `CfWorkerJsonSchemaValidator` (previously required manual configuration)
This means Cloudflare Workers users no longer need to explicitly pass the validator:
**Before (v1) - Cloudflare Workers required explicit configuration:**
```typescript
import { McpServer, CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/server';
const server = new McpServer(
{ name: 'my-server', version: '1.0.0' },
{
capabilities: { tools: {} },
jsonSchemaValidator: new CfWorkerJsonSchemaValidator() // Required in v1
}
);
```
**After (v2) - Works automatically:**
```typescript
import { McpServer } from '@modelcontextprotocol/server';
const server = new McpServer(
{ name: 'my-server', version: '1.0.0' },
{ capabilities: { tools: {} } }
// Validator auto-selected based on runtime
);
```
You can still explicitly override the validator if needed. The validators are available via the `_shims` export:
```typescript
import { DefaultJsonSchemaValidator } from '@modelcontextprotocol/server/_shims';
// or
import { AjvJsonSchemaValidator, CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/server';
```
## Unchanged APIs
The following APIs are unchanged between v1 and v2 (only the import paths changed):

View file

@ -6,6 +6,7 @@
"paths": {
"*": ["./*"],
"@modelcontextprotocol/client": ["./node_modules/@modelcontextprotocol/client/src/index.ts"],
"@modelcontextprotocol/client/_shims": ["./node_modules/@modelcontextprotocol/client/src/shimsNode.ts"],
"@modelcontextprotocol/core": [
"./node_modules/@modelcontextprotocol/client/node_modules/@modelcontextprotocol/core/src/index.ts"
],

View file

@ -6,6 +6,7 @@
"paths": {
"*": ["./*"],
"@modelcontextprotocol/server": ["./node_modules/@modelcontextprotocol/server/src/index.ts"],
"@modelcontextprotocol/server/_shims": ["./node_modules/@modelcontextprotocol/server/src/shimsNode.ts"],
"@modelcontextprotocol/express": ["./node_modules/@modelcontextprotocol/express/src/index.ts"],
"@modelcontextprotocol/node": ["./node_modules/@modelcontextprotocol/node/src/index.ts"],
"@modelcontextprotocol/hono": ["./node_modules/@modelcontextprotocol/hono/src/index.ts"],

View file

@ -25,6 +25,24 @@
".": {
"types": "./dist/index.d.mts",
"import": "./dist/index.mjs"
},
"./_shims": {
"workerd": {
"types": "./dist/shimsWorkerd.d.mts",
"import": "./dist/shimsWorkerd.mjs"
},
"browser": {
"types": "./dist/shimsWorkerd.d.mts",
"import": "./dist/shimsWorkerd.mjs"
},
"node": {
"types": "./dist/shimsNode.d.mts",
"import": "./dist/shimsNode.mjs"
},
"default": {
"types": "./dist/shimsNode.d.mts",
"import": "./dist/shimsNode.mjs"
}
}
},
"files": [

View file

@ -1,3 +1,4 @@
import { DefaultJsonSchemaValidator } from '@modelcontextprotocol/client/_shims';
import type {
CallToolRequest,
ClientCapabilities,
@ -35,7 +36,6 @@ import type {
UnsubscribeRequest
} from '@modelcontextprotocol/core';
import {
AjvJsonSchemaValidator,
assertClientRequestTaskCapability,
assertToolsCallTaskCapability,
CallToolResultSchema,
@ -153,28 +153,7 @@ export type ClientOptions = ProtocolOptions & {
* The validator is used to validate structured content returned by tools
* against their declared output schemas.
*
* @default AjvJsonSchemaValidator
*
* @example
* ```typescript
* // ajv
* const client = new Client(
* { name: 'my-client', version: '1.0.0' },
* {
* capabilities: {},
* jsonSchemaValidator: new AjvJsonSchemaValidator()
* }
* );
*
* // @cfworker/json-schema
* const client = new Client(
* { name: 'my-client', version: '1.0.0' },
* {
* capabilities: {},
* jsonSchemaValidator: new CfWorkerJsonSchemaValidator()
* }
* );
* ```
* @default DefaultJsonSchemaValidator (AjvJsonSchemaValidator on Node.js, CfWorkerJsonSchemaValidator on Cloudflare Workers)
*/
jsonSchemaValidator?: jsonSchemaValidator;
@ -259,7 +238,7 @@ export class Client<
) {
super(options);
this._capabilities = options?.capabilities ?? {};
this._jsonSchemaValidator = options?.jsonSchemaValidator ?? new AjvJsonSchemaValidator();
this._jsonSchemaValidator = options?.jsonSchemaValidator ?? new DefaultJsonSchemaValidator();
this._enforceStrictCapabilities = options?.enforceStrictCapabilities ?? false;
// Store list changed config for setup after connection (when we know server capabilities)

View file

@ -0,0 +1,6 @@
/**
* Node.js runtime shims for client package
*
* This file is selected via package.json export conditions when running in Node.js.
*/
export { AjvJsonSchemaValidator as DefaultJsonSchemaValidator } from '@modelcontextprotocol/core';

View file

@ -0,0 +1,6 @@
/**
* Cloudflare Workers runtime shims for client package
*
* This file is selected via package.json export conditions when running in workerd.
*/
export { CfWorkerJsonSchemaValidator as DefaultJsonSchemaValidator } from '@modelcontextprotocol/core';

View file

@ -6,7 +6,8 @@
"paths": {
"*": ["./*"],
"@modelcontextprotocol/core": ["./node_modules/@modelcontextprotocol/core/src/index.ts"],
"@modelcontextprotocol/test-helpers": ["./node_modules/@modelcontextprotocol/test-helpers/src/index.ts"]
"@modelcontextprotocol/test-helpers": ["./node_modules/@modelcontextprotocol/test-helpers/src/index.ts"],
"@modelcontextprotocol/client/_shims": ["./src/shimsNode.ts"]
}
}
}

View file

@ -3,7 +3,7 @@ import { defineConfig } from 'tsdown';
export default defineConfig({
// 1. Entry Points
// Directly matches package.json include/exclude globs
entry: ['src/index.ts'],
entry: ['src/index.ts', 'src/shimsNode.ts', 'src/shimsWorkerd.ts'],
// 2. Output Configuration
format: ['esm'],
@ -30,5 +30,8 @@ export default defineConfig({
},
// 5. Vendoring Strategy - Bundle the code for this specific package into the output,
// but treat all other dependencies as external (require/import).
noExternal: ['@modelcontextprotocol/core']
noExternal: ['@modelcontextprotocol/core'],
// 6. External packages - keep self-reference imports external for runtime resolution
external: ['@modelcontextprotocol/client/_shims']
});

View file

@ -1,12 +1,8 @@
/**
* In-memory implementations of TaskStore and TaskMessageQueue.
* WARNING: These APIs are experimental and may change without notice.
*
* @experimental
*/
import { randomBytes } from 'node:crypto';
import type { Request, RequestId, Result, Task } from '../../../types/types.js';
import type { CreateTaskOptions, QueuedMessage, TaskMessageQueue, TaskStore } from '../interfaces.js';
import { isTerminal } from '../interfaces.js';
@ -19,14 +15,8 @@ interface StoredTask {
}
/**
* A simple in-memory implementation of TaskStore for demonstration purposes.
*
* This implementation stores all tasks in memory and provides automatic cleanup
* based on the ttl duration specified in the task creation parameters.
*
* Note: This is not suitable for production use as all data is lost on restart.
* For production, consider implementing TaskStore with a database or distributed cache.
*
* In-memory TaskStore implementation for development and testing.
* For production, use a database or distributed cache.
* @experimental
*/
export class InMemoryTaskStore implements TaskStore {
@ -34,11 +24,10 @@ export class InMemoryTaskStore implements TaskStore {
private cleanupTimers = new Map<string, ReturnType<typeof setTimeout>>();
/**
* Generates a unique task ID.
* Uses 16 bytes of random data encoded as hex (32 characters).
* Generates a unique task ID using Web Crypto API.
*/
private generateTaskId(): string {
return randomBytes(16).toString('hex');
return crypto.randomUUID().replaceAll('-', '');
}
async createTask(taskParams: CreateTaskOptions, requestId: RequestId, request: Request, _sessionId?: string): Promise<Task> {
@ -216,14 +205,8 @@ export class InMemoryTaskStore implements TaskStore {
}
/**
* A simple in-memory implementation of TaskMessageQueue for demonstration purposes.
*
* This implementation stores messages in memory, organized by task ID and optional session ID.
* Messages are stored in FIFO queues per task.
*
* Note: This is not suitable for production use in distributed systems.
* For production, consider implementing TaskMessageQueue with Redis or other distributed queues.
*
* In-memory TaskMessageQueue implementation for development and testing.
* For production, use Redis or another distributed queue.
* @experimental
*/
export class InMemoryTaskMessageQueue implements TaskMessageQueue {

View file

@ -37,8 +37,8 @@
"build": "tsdown",
"build:watch": "tsdown --watch",
"prepack": "npm run build",
"lint": "eslint src/ && prettier --ignore-path ../../.prettierignore --check .",
"lint:fix": "eslint src/ --fix && prettier --ignore-path ../../.prettierignore --write .",
"lint": "eslint src/ && prettier --ignore-path ../../../.prettierignore --check .",
"lint:fix": "eslint src/ --fix && prettier --ignore-path ../../../.prettierignore --write .",
"check": "npm run typecheck && npm run lint",
"test": "vitest run",
"test:watch": "vitest"

View file

@ -6,6 +6,7 @@
"paths": {
"*": ["./*"],
"@modelcontextprotocol/server": ["./node_modules/@modelcontextprotocol/server/src/index.ts"],
"@modelcontextprotocol/server/_shims": ["./node_modules/@modelcontextprotocol/server/src/shimsNode.ts"],
"@modelcontextprotocol/core": [
"./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core/src/index.ts"
]

View file

@ -37,8 +37,8 @@
"build": "tsdown",
"build:watch": "tsdown --watch",
"prepack": "npm run build",
"lint": "eslint src/ && prettier --ignore-path ../../.prettierignore --check .",
"lint:fix": "eslint src/ --fix && prettier --ignore-path ../../.prettierignore --write .",
"lint": "eslint src/ && prettier --ignore-path ../../../.prettierignore --check .",
"lint:fix": "eslint src/ --fix && prettier --ignore-path ../../../.prettierignore --write .",
"check": "npm run typecheck && npm run lint",
"test": "vitest run",
"test:watch": "vitest"

View file

@ -6,6 +6,7 @@
"paths": {
"*": ["./*"],
"@modelcontextprotocol/server": ["./node_modules/@modelcontextprotocol/server/src/index.ts"],
"@modelcontextprotocol/server/_shims": ["./node_modules/@modelcontextprotocol/server/src/shimsNode.ts"],
"@modelcontextprotocol/core": [
"./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core/src/index.ts"
]

View file

@ -36,8 +36,8 @@
"build": "tsdown",
"build:watch": "tsdown --watch",
"prepack": "npm run build",
"lint": "eslint src/ && prettier --ignore-path ../../.prettierignore --check .",
"lint:fix": "eslint src/ --fix && prettier --ignore-path ../../.prettierignore --write .",
"lint": "eslint src/ && prettier --ignore-path ../../../.prettierignore --check .",
"lint:fix": "eslint src/ --fix && prettier --ignore-path ../../../.prettierignore --write .",
"check": "npm run typecheck && npm run lint",
"test": "vitest run",
"test:watch": "vitest",

View file

@ -6,6 +6,7 @@
"paths": {
"*": ["./*"],
"@modelcontextprotocol/server": ["./node_modules/@modelcontextprotocol/server/src/index.ts"],
"@modelcontextprotocol/server/_shims": ["./node_modules/@modelcontextprotocol/server/src/shimsNode.ts"],
"@modelcontextprotocol/core": ["./node_modules/@modelcontextprotocol/core/src/index.ts"],
"@modelcontextprotocol/test-helpers": ["./node_modules/@modelcontextprotocol/test-helpers/src/index.ts"]
}

View file

@ -25,6 +25,24 @@
".": {
"types": "./dist/index.d.mts",
"import": "./dist/index.mjs"
},
"./_shims": {
"workerd": {
"types": "./dist/shimsWorkerd.d.mts",
"import": "./dist/shimsWorkerd.mjs"
},
"browser": {
"types": "./dist/shimsWorkerd.d.mts",
"import": "./dist/shimsWorkerd.mjs"
},
"node": {
"types": "./dist/shimsNode.d.mts",
"import": "./dist/shimsNode.mjs"
},
"default": {
"types": "./dist/shimsNode.d.mts",
"import": "./dist/shimsNode.mjs"
}
}
},
"files": [

View file

@ -5,9 +5,6 @@
* @experimental
*/
// SDK implementation interfaces
export * from './interfaces.js';
// Wrapper classes
export * from './mcpServer.js';
export * from './server.js';

View file

@ -34,7 +34,6 @@ import type {
ToolUseContent
} from '@modelcontextprotocol/core';
import {
AjvJsonSchemaValidator,
assertClientRequestTaskCapability,
assertToolsCallTaskCapability,
CallToolRequestSchema,
@ -55,6 +54,7 @@ import {
SdkError,
SdkErrorCode
} from '@modelcontextprotocol/core';
import { DefaultJsonSchemaValidator } from '@modelcontextprotocol/server/_shims';
import { ExperimentalServerTasks } from '../experimental/tasks/server.js';
@ -75,28 +75,7 @@ export type ServerOptions = ProtocolOptions & {
* The validator is used to validate user input returned from elicitation
* requests against the requested schema.
*
* @default AjvJsonSchemaValidator
*
* @example
* ```typescript
* // ajv (default)
* const server = new Server(
* { name: 'my-server', version: '1.0.0' },
* {
* capabilities: {}
* jsonSchemaValidator: new AjvJsonSchemaValidator()
* }
* );
*
* // @cfworker/json-schema
* const server = new Server(
* { name: 'my-server', version: '1.0.0' },
* {
* capabilities: {},
* jsonSchemaValidator: new CfWorkerJsonSchemaValidator()
* }
* );
* ```
* @default DefaultJsonSchemaValidator (AjvJsonSchemaValidator on Node.js, CfWorkerJsonSchemaValidator on Cloudflare Workers)
*/
jsonSchemaValidator?: jsonSchemaValidator;
};
@ -154,7 +133,7 @@ export class Server<
super(options);
this._capabilities = options?.capabilities ?? {};
this._instructions = options?.instructions;
this._jsonSchemaValidator = options?.jsonSchemaValidator ?? new AjvJsonSchemaValidator();
this._jsonSchemaValidator = options?.jsonSchemaValidator ?? new DefaultJsonSchemaValidator();
this.setRequestHandler('initialize', request => this._oninitialize(request));
this.setNotificationHandler('notifications/initialized', () => this.oninitialized?.());

Some files were not shown because too many files have changed in this diff Show more