Skip to content

Commit 96db044

Browse files
feat(core): add isSpecType / specTypeSchemas Records for runtime validation of spec types (#1887)
1 parent db83829 commit 96db044

11 files changed

Lines changed: 590 additions & 76 deletions

File tree

.changeset/spec-type-schema.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@modelcontextprotocol/client': minor
3+
'@modelcontextprotocol/server': minor
4+
---
5+
6+
Export `isSpecType` and `specTypeSchemas` records for runtime validation of any MCP spec type by name. `isSpecType.ContentBlock(value)` is a type predicate; `specTypeSchemas.ContentBlock` is a `StandardSchemaV1<ContentBlock>` validator. Guards are standalone functions, so `arr.filter(isSpecType.ContentBlock)` works. Also export the `SpecTypeName` and `SpecTypes` types.

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Include what changed, why, and how to migrate. Search for related sections and g
3838
- **Files**: Lowercase with hyphens, test files with `.test.ts` suffix
3939
- **Imports**: ES module style, include `.js` extension, group imports logically
4040
- **Formatting**: 2-space indentation, semicolons required, single quotes preferred
41-
- **Testing**: Co-locate tests with source files, use descriptive test names
41+
- **Testing**: Place tests under each package's `test/` directory (vitest only includes `test/**/*.test.ts`), use descriptive test names
4242
- **Comments**: JSDoc for public APIs, inline comments for complex logic
4343

4444
### JSDoc `@example` Code Snippets

docs/migration-SKILL.md

Lines changed: 54 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@ Replace all `@modelcontextprotocol/sdk/...` imports using this table.
3636

3737
### Client imports
3838

39-
| v1 import path | v2 package |
40-
| ---------------------------------------------------- | ------------------------------ |
41-
| `@modelcontextprotocol/sdk/client/index.js` | `@modelcontextprotocol/client` |
42-
| `@modelcontextprotocol/sdk/client/auth.js` | `@modelcontextprotocol/client` |
43-
| `@modelcontextprotocol/sdk/client/streamableHttp.js` | `@modelcontextprotocol/client` |
44-
| `@modelcontextprotocol/sdk/client/sse.js` | `@modelcontextprotocol/client` |
45-
| `@modelcontextprotocol/sdk/client/stdio.js` | `@modelcontextprotocol/client/stdio` |
39+
| v1 import path | v2 package |
40+
| ---------------------------------------------------- | ------------------------------------------------------------------------------ |
41+
| `@modelcontextprotocol/sdk/client/index.js` | `@modelcontextprotocol/client` |
42+
| `@modelcontextprotocol/sdk/client/auth.js` | `@modelcontextprotocol/client` |
43+
| `@modelcontextprotocol/sdk/client/streamableHttp.js` | `@modelcontextprotocol/client` |
44+
| `@modelcontextprotocol/sdk/client/sse.js` | `@modelcontextprotocol/client` |
45+
| `@modelcontextprotocol/sdk/client/stdio.js` | `@modelcontextprotocol/client/stdio` |
4646
| `@modelcontextprotocol/sdk/client/websocket.js` | REMOVED (use Streamable HTTP or stdio; implement `Transport` for custom needs) |
4747

4848
### Server imports
@@ -59,8 +59,8 @@ Replace all `@modelcontextprotocol/sdk/...` imports using this table.
5959

6060
### Types / shared imports
6161

62-
| v1 import path | v2 package |
63-
| ------------------------------------------------- | ---------------------------- |
62+
| v1 import path | v2 package |
63+
| ------------------------------------------------- | ---------------------------------------------------------------- |
6464
| `@modelcontextprotocol/sdk/types.js` | `@modelcontextprotocol/client` or `@modelcontextprotocol/server` |
6565
| `@modelcontextprotocol/sdk/shared/protocol.js` | `@modelcontextprotocol/client` or `@modelcontextprotocol/server` |
6666
| `@modelcontextprotocol/sdk/shared/transport.js` | `@modelcontextprotocol/client` or `@modelcontextprotocol/server` |
@@ -81,24 +81,25 @@ Notes:
8181

8282
## 5. Removed / Renamed Type Aliases and Symbols
8383

84-
| v1 (removed) | v2 (replacement) |
85-
| ---------------------------------------- | -------------------------------------------------------- |
86-
| `JSONRPCError` | `JSONRPCErrorResponse` |
87-
| `JSONRPCErrorSchema` | `JSONRPCErrorResponseSchema` |
88-
| `isJSONRPCError` | `isJSONRPCErrorResponse` |
84+
| v1 (removed) | v2 (replacement) |
85+
| ---------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
86+
| `JSONRPCError` | `JSONRPCErrorResponse` |
87+
| `JSONRPCErrorSchema` | `JSONRPCErrorResponseSchema` |
88+
| `isJSONRPCError` | `isJSONRPCErrorResponse` |
8989
| `isJSONRPCResponse` (deprecated in v1) | `isJSONRPCResultResponse` (**not** v2's new `isJSONRPCResponse`, which correctly matches both result and error) |
90-
| `ResourceReference` | `ResourceTemplateReference` |
91-
| `ResourceReferenceSchema` | `ResourceTemplateReferenceSchema` |
92-
| `IsomorphicHeaders` | REMOVED (use Web Standard `Headers`) |
93-
| `AuthInfo` (from `server/auth/types.js`) | `AuthInfo` (now re-exported by `@modelcontextprotocol/client` and `@modelcontextprotocol/server`) |
94-
| `McpError` | `ProtocolError` |
95-
| `ErrorCode` | `ProtocolErrorCode` |
96-
| `ErrorCode.RequestTimeout` | `SdkErrorCode.RequestTimeout` |
97-
| `ErrorCode.ConnectionClosed` | `SdkErrorCode.ConnectionClosed` |
98-
| `StreamableHTTPError` | REMOVED (use `SdkError` with `SdkErrorCode.ClientHttp*`) |
99-
| `WebSocketClientTransport` | REMOVED (use `StreamableHTTPClientTransport` or `StdioClientTransport`) |
100-
101-
All other **type** symbols from `@modelcontextprotocol/sdk/types.js` retain their original names. **Zod schemas** (e.g., `CallToolResultSchema`, `ListToolsResultSchema`) are no longer part of the public API — they are internal to the SDK. For runtime validation, use type guard functions like `isCallToolResult` instead of `CallToolResultSchema.safeParse()`.
90+
| `ResourceReference` | `ResourceTemplateReference` |
91+
| `ResourceReferenceSchema` | `ResourceTemplateReferenceSchema` |
92+
| `IsomorphicHeaders` | REMOVED (use Web Standard `Headers`) |
93+
| `AuthInfo` (from `server/auth/types.js`) | `AuthInfo` (now re-exported by `@modelcontextprotocol/client` and `@modelcontextprotocol/server`) |
94+
| `McpError` | `ProtocolError` |
95+
| `ErrorCode` | `ProtocolErrorCode` |
96+
| `ErrorCode.RequestTimeout` | `SdkErrorCode.RequestTimeout` |
97+
| `ErrorCode.ConnectionClosed` | `SdkErrorCode.ConnectionClosed` |
98+
| `StreamableHTTPError` | REMOVED (use `SdkError` with `SdkErrorCode.ClientHttp*`) |
99+
| `WebSocketClientTransport` | REMOVED (use `StreamableHTTPClientTransport` or `StdioClientTransport`) |
100+
101+
All other **type** symbols from `@modelcontextprotocol/sdk/types.js` retain their original names. **Zod schemas** (e.g., `CallToolResultSchema`, `ListToolsResultSchema`) are no longer part of the public API — they are internal to the SDK. For runtime validation, use
102+
`isSpecType.TypeName(value)` (e.g., `isSpecType.CallToolResult(v)`) or `specTypeSchemas.TypeName` for the `StandardSchemaV1` validator object. The keys are typed as `SpecTypeName`, a literal union of all spec type names.
102103

103104
### Error class changes
104105

@@ -212,7 +213,8 @@ Zod schemas, all callback return types. Note: `callTool()` and `request()` signa
212213

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

215-
**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)` from `@modelcontextprotocol/server` (validator defaults automatically; pass an explicit validator for custom configurations). Applies to `inputSchema`, `outputSchema`, and `argsSchema`.
216+
**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
217+
`fromJsonSchema(schema)` from `@modelcontextprotocol/server` (validator defaults automatically; pass an explicit validator for custom configurations). Applies to `inputSchema`, `outputSchema`, and `argsSchema`.
216218

217219
### Tools
218220

@@ -282,20 +284,20 @@ Note: the third argument (`metadata`) is required — pass `{}` if no metadata.
282284

283285
### Schema Migration Quick Reference
284286

285-
| v1 (raw shape) | v2 (Standard Schema object) |
286-
|----------------|-----------------|
287-
| `{ name: z.string() }` | `z.object({ name: z.string() })` |
287+
| v1 (raw shape) | v2 (Standard Schema object) |
288+
| ---------------------------------- | -------------------------------------------- |
289+
| `{ name: z.string() }` | `z.object({ name: z.string() })` |
288290
| `{ count: z.number().optional() }` | `z.object({ count: z.number().optional() })` |
289291
| `{}` (empty) | `z.object({})` |
290292
| `undefined` (no schema) | `undefined` or omit the field |
291293

292294
### Removed core exports
293295

294-
| Removed from `@modelcontextprotocol/core` | Replacement |
295-
|---|---|
296-
| `schemaToJson(schema)` | `standardSchemaToJsonSchema(schema)` |
297-
| `parseSchemaAsync(schema, data)` | `validateStandardSchema(schema, data)` |
298-
| `SchemaInput<T>` | `StandardSchemaWithJSON.InferInput<T>` |
296+
| Removed from `@modelcontextprotocol/core` | Replacement |
297+
| ------------------------------------------------------------------------------------ | ----------------------------------------- |
298+
| `schemaToJson(schema)` | `standardSchemaToJsonSchema(schema)` |
299+
| `parseSchemaAsync(schema, data)` | `validateStandardSchema(schema, data)` |
300+
| `SchemaInput<T>` | `StandardSchemaWithJSON.InferInput<T>` |
299301
| `getSchemaShape`, `getSchemaDescription`, `isOptionalSchema`, `unwrapOptionalSchema` | none (internal Zod introspection helpers) |
300302

301303
## 7. Headers API
@@ -460,29 +462,33 @@ For **custom (non-spec)** methods, keep the result-schema argument — see §9.
460462

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

463-
If `CallToolResultSchema` was used for **runtime validation** (not just as a `request()` argument), replace with the `isCallToolResult` type guard:
465+
If a `*Schema` constant was used for **runtime validation** (not just as a `request()` argument), replace with `isSpecType` / `specTypeSchemas`:
464466

465-
| v1 pattern | v2 replacement |
466-
| --------------------------------------------------- | -------------------------- |
467-
| `CallToolResultSchema.safeParse(value).success` | `isCallToolResult(value)` |
468-
| `CallToolResultSchema.parse(value)` | Use `isCallToolResult(value)` then cast, or use `CallToolResult` type |
467+
| v1 pattern | v2 replacement |
468+
| -------------------------------------------------- | -------------------------------------------------------------------------------------- |
469+
| `CallToolResultSchema.safeParse(value).success` | `isSpecType.CallToolResult(value)` |
470+
| `<TypeName>Schema.safeParse(value).success` | `isSpecType.<TypeName>(value)` |
471+
| `<TypeName>Schema.parse(value)` | `await specTypeSchemas.<TypeName>['~standard'].validate(value)` (returns a `Result`, not the value) |
472+
| Passing `<TypeName>Schema` as a validator argument | `specTypeSchemas.<TypeName>` (a `StandardSchemaV1<In, Out>`) |
473+
474+
`isCallToolResult(value)` still works, but `isSpecType` covers every spec type by name.
469475

470476
## 12. Experimental: `TaskCreationParams.ttl` no longer accepts `null`
471477

472478
`TaskCreationParams.ttl` changed from `z.union([z.number(), z.null()]).optional()` to `z.number().optional()`. Per the MCP spec, `null` TTL (unlimited lifetime) is only valid in server responses (`Task.ttl`), not in client requests. Omit `ttl` to let the server decide.
473479

474-
| v1 | v2 |
475-
|---|---|
476-
| `task: { ttl: null }` | `task: {}` (omit ttl) |
480+
| v1 | v2 |
481+
| ---------------------- | ---------------------------------- |
482+
| `task: { ttl: null }` | `task: {}` (omit ttl) |
477483
| `task: { ttl: 60000 }` | `task: { ttl: 60000 }` (unchanged) |
478484

479485
Type changes in handler context:
480486

481-
| Type | v1 | v2 |
482-
|---|---|---|
483-
| `TaskContext.requestedTtl` | `number \| null \| undefined` | `number \| undefined` |
487+
| Type | v1 | v2 |
488+
| ------------------------------------------- | ----------------------------- | --------------------- |
489+
| `TaskContext.requestedTtl` | `number \| null \| undefined` | `number \| undefined` |
484490
| `CreateTaskServerContext.task.requestedTtl` | `number \| null \| undefined` | `number \| undefined` |
485-
| `TaskServerContext.task.requestedTtl` | `number \| null \| undefined` | `number \| undefined` |
491+
| `TaskServerContext.task.requestedTtl` | `number \| null \| undefined` | `number \| undefined` |
486492

487493
> These task APIs are `@experimental` and may change without notice.
488494
@@ -513,6 +519,7 @@ new McpServer({ name: 'server', version: '1.0.0' }, {});
513519
```
514520

515521
Access validators explicitly:
522+
516523
- Runtime-aware default: `import { DefaultJsonSchemaValidator } from '@modelcontextprotocol/server/_shims';`
517524
- AJV (Node.js): `import { AjvJsonSchemaValidator } from '@modelcontextprotocol/server';`
518525
- CF Worker: `import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/server/validators/cf-worker';`

0 commit comments

Comments
 (0)