Skip to content

Commit 8739509

Browse files
Merge remote-tracking branch 'origin/fweinberger/spec-type-predicates' into fweinberger/v2-bc-d1-base
# Conflicts: # docs/migration-SKILL.md # docs/migration.md # packages/core/src/exports/public/index.ts
2 parents 4fbe8fe + 42062b3 commit 8739509

11 files changed

Lines changed: 595 additions & 82 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 `StandardSchemaV1`, `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: 59 additions & 53 deletions
Large diffs are not rendered by default.

docs/migration.md

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ import { StdioServerTransport } from '@modelcontextprotocol/server/stdio';
5959
import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node';
6060
```
6161

62-
Note: `@modelcontextprotocol/client` and `@modelcontextprotocol/server` both re-export shared types from `@modelcontextprotocol/core`, so you can import types and error classes from whichever package you already depend on. Do not import from `@modelcontextprotocol/core` directly — it is an internal package.
62+
Note: `@modelcontextprotocol/client` and `@modelcontextprotocol/server` both re-export shared types from `@modelcontextprotocol/core`, so you can import types and error classes from whichever package you already depend on. Do not import from `@modelcontextprotocol/core` directly
63+
— it is an internal package.
6364

6465
### Dropped Node.js 18 and CommonJS
6566

@@ -300,11 +301,11 @@ This applies to:
300301

301302
**Removed Zod-specific helpers** from `@modelcontextprotocol/core` (use Standard Schema equivalents):
302303

303-
| Removed | Replacement |
304-
|---|---|
305-
| `schemaToJson(schema)` | `standardSchemaToJsonSchema(schema)` |
306-
| `parseSchemaAsync(schema, data)` | `validateStandardSchema(schema, data)` |
307-
| `SchemaInput<T>` | `StandardSchemaWithJSON.InferInput<T>` |
304+
| Removed | Replacement |
305+
| ------------------------------------------------------------------------------------ | ----------------------------------------------------------------- |
306+
| `schemaToJson(schema)` | `standardSchemaToJsonSchema(schema)` |
307+
| `parseSchemaAsync(schema, data)` | `validateStandardSchema(schema, data)` |
308+
| `SchemaInput<T>` | `StandardSchemaWithJSON.InferInput<T>` |
308309
| `getSchemaShape`, `getSchemaDescription`, `isOptionalSchema`, `unwrapOptionalSchema` | No replacement — these are now internal Zod introspection helpers |
309310

310311
### Host header validation moved
@@ -492,18 +493,29 @@ The return type is now inferred from the method name via `ResultTypeMap`. For ex
492493

493494
For **custom (non-spec)** methods, keep the result-schema argument — see [Sending custom-method requests](#sending-custom-method-requests). Only drop the schema when calling a spec method.
494495

495-
If you were using `CallToolResultSchema` for **runtime validation** (not just in `request()`/`callTool()` calls), use the new `isCallToolResult` type guard instead:
496+
If you were using `CallToolResultSchema` (or any `*Schema` constant) for **runtime validation** (not just in `request()`/`callTool()` calls), use `isSpecType` or `specTypeSchemas`:
496497

497498
```typescript
498499
// v1: runtime validation with Zod schema
499500
import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
500-
if (CallToolResultSchema.safeParse(value).success) { /* ... */ }
501+
if (CallToolResultSchema.safeParse(value).success) {
502+
/* ... */
503+
}
504+
505+
// v2: keyed type predicate
506+
import { isSpecType } from '@modelcontextprotocol/client';
507+
if (isSpecType.CallToolResult(value)) {
508+
/* ... */
509+
}
510+
const blocks = mixed.filter(isSpecType.ContentBlock);
501511

502-
// v2: use the type guard
503-
import { isCallToolResult } from '@modelcontextprotocol/client';
504-
if (isCallToolResult(value)) { /* ... */ }
512+
// v2: or get the StandardSchemaV1 validator object directly
513+
import { specTypeSchemas } from '@modelcontextprotocol/client';
514+
const result = await specTypeSchemas.CallToolResult['~standard'].validate(value);
505515
```
506516

517+
`isSpecType` and `specTypeSchemas` are keyed by `SpecTypeName` — a literal union of every named type in the MCP spec — so you get autocomplete and a compile error on typos. `specTypeSchemas.X` is a `StandardSchemaV1<In, Out>`, which composes with any Standard-Schema-aware library. The pre-existing `isCallToolResult(value)` guard still works.
518+
507519
### Client list methods return empty results for missing capabilities
508520

509521
`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.
@@ -537,20 +549,21 @@ import { InMemoryTransport } from '@modelcontextprotocol/client';
537549

538550
The following deprecated type aliases have been removed from `@modelcontextprotocol/core`:
539551

540-
| Removed | Replacement |
541-
| ---------------------------------------- | ------------------------------------------------ |
542-
| `JSONRPCError` | `JSONRPCErrorResponse` |
543-
| `JSONRPCErrorSchema` | `JSONRPCErrorResponseSchema` |
544-
| `isJSONRPCError` | `isJSONRPCErrorResponse` |
545-
| `isJSONRPCResponse` | `isJSONRPCResultResponse` (see note below) |
546-
| `ResourceReferenceSchema` | `ResourceTemplateReferenceSchema` |
547-
| `ResourceReference` | `ResourceTemplateReference` |
548-
| `IsomorphicHeaders` | Use Web Standard `Headers` |
552+
| Removed | Replacement |
553+
| ---------------------------------------- | ------------------------------------------------------------------------------------------------- |
554+
| `JSONRPCError` | `JSONRPCErrorResponse` |
555+
| `JSONRPCErrorSchema` | `JSONRPCErrorResponseSchema` |
556+
| `isJSONRPCError` | `isJSONRPCErrorResponse` |
557+
| `isJSONRPCResponse` | `isJSONRPCResultResponse` (see note below) |
558+
| `ResourceReferenceSchema` | `ResourceTemplateReferenceSchema` |
559+
| `ResourceReference` | `ResourceTemplateReference` |
560+
| `IsomorphicHeaders` | Use Web Standard `Headers` |
549561
| `AuthInfo` (from `server/auth/types.js`) | `AuthInfo` (now re-exported by `@modelcontextprotocol/client` and `@modelcontextprotocol/server`) |
550562

551563
All other **type** symbols from `@modelcontextprotocol/sdk/types.js` retain their original names — import them from `@modelcontextprotocol/client` or `@modelcontextprotocol/server`. **Zod schemas** (e.g., `CallToolRequestSchema`) are not re-exported from the package roots; for runtime validation prefer the `isSpecType` / `specTypeSchemas` Records, or for v1 source compatibility import them from `@modelcontextprotocol/server/zod-schemas`.
552564

553-
> **Note on `isJSONRPCResponse`:** v1's `isJSONRPCResponse` was a deprecated alias that only checked for *result* responses (it was equivalent to `isJSONRPCResultResponse`). v2 removes the deprecated alias and introduces a **new** `isJSONRPCResponse` with corrected semantics — it checks for *any* response (either result or error). If you are migrating v1 code that used `isJSONRPCResponse`, rename it to `isJSONRPCResultResponse` to preserve the original behavior. Use the new `isJSONRPCResponse` only when you want to match both result and error responses.
565+
> **Note on `isJSONRPCResponse`:** v1's `isJSONRPCResponse` was a deprecated alias that only checked for _result_ responses (it was equivalent to `isJSONRPCResultResponse`). v2 removes the deprecated alias and introduces a **new** `isJSONRPCResponse` with corrected semantics — it
566+
> checks for _any_ response (either result or error). If you are migrating v1 code that used `isJSONRPCResponse`, rename it to `isJSONRPCResultResponse` to preserve the original behavior. Use the new `isJSONRPCResponse` only when you want to match both result and error responses.
554567
555568
**Before (v1):**
556569

@@ -578,7 +591,7 @@ The `RequestHandlerExtra` type has been replaced with a structured context type
578591
| `extra.sendRequest(...)` | `ctx.mcpReq.send(...)` |
579592
| `extra.sendNotification(...)` | `ctx.mcpReq.notify(...)` |
580593
| `extra.authInfo` | `ctx.http?.authInfo` |
581-
| `extra.requestInfo` | `ctx.http?.req` (standard Web `Request`, only on `ServerContext`) |
594+
| `extra.requestInfo` | `ctx.http?.req` (standard Web `Request`, only on `ServerContext`) |
582595
| `extra.closeSSEStream` | `ctx.http?.closeSSE` (only on `ServerContext`) |
583596
| `extra.closeStandaloneSSEStream` | `ctx.http?.closeStandaloneSSE` (only on `ServerContext`) |
584597
| `extra.sessionId` | `ctx.sessionId` |
@@ -848,7 +861,8 @@ try {
848861

849862
### Experimental: `TaskCreationParams.ttl` no longer accepts `null`
850863

851-
The `ttl` field in `TaskCreationParams` (used when requesting the server to create a task) no longer accepts `null`. Per the MCP spec, `null` TTL (meaning unlimited lifetime) is only valid in server responses (`Task.ttl`), not in client requests. Clients should omit `ttl` to let the server decide the lifetime.
864+
The `ttl` field in `TaskCreationParams` (used when requesting the server to create a task) no longer accepts `null`. Per the MCP spec, `null` TTL (meaning unlimited lifetime) is only valid in server responses (`Task.ttl`), not in client requests. Clients should omit `ttl` to let
865+
the server decide the lifetime.
852866

853867
This also narrows the type of `requestedTtl` in `TaskContext`, `CreateTaskServerContext`, and `TaskServerContext` from `number | null | undefined` to `number | undefined`.
854868

packages/core/src/exports/public/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ export { isTerminal } from '../../experimental/tasks/interfaces.js';
138138
export { InMemoryTaskMessageQueue, InMemoryTaskStore } from '../../experimental/tasks/stores/inMemory.js';
139139

140140
// Validator types and classes
141+
export type { SpecTypeName, SpecTypes } from '../../types/specTypeSchema.js';
142+
export { isSpecType, specTypeSchemas } from '../../types/specTypeSchema.js';
141143
export type { StandardSchemaV1, StandardSchemaWithJSON } from '../../util/standardSchema.js';
142144
export { AjvJsonSchemaValidator } from '../../validators/ajvProvider.js';
143145
export type { CfWorkerSchemaDraft } from '../../validators/cfWorkerProvider.js';

packages/core/src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ export * from './enums.js';
55
export * from './errors.js';
66
export * from './guards.js';
77
export * from './schemas.js';
8+
export * from './specTypeSchema.js';
89
export * from './types.js';

packages/core/src/types/schemas.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ import type {
1212
ResultTypeMap
1313
} from './types.js';
1414

15-
export const JSONValueSchema: z.ZodType<JSONValue> = z.lazy(() =>
15+
export const JSONValueSchema: z.ZodType<JSONValue, JSONValue> = z.lazy(() =>
1616
z.union([z.string(), z.number(), z.boolean(), z.null(), z.record(z.string(), JSONValueSchema), z.array(JSONValueSchema)])
1717
);
18-
export const JSONObjectSchema: z.ZodType<JSONObject> = z.record(z.string(), JSONValueSchema);
19-
export const JSONArraySchema: z.ZodType<JSONArray> = z.array(JSONValueSchema);
18+
export const JSONObjectSchema: z.ZodType<JSONObject, JSONObject> = z.record(z.string(), JSONValueSchema);
19+
export const JSONArraySchema: z.ZodType<JSONArray, JSONArray> = z.array(JSONValueSchema);
2020
/**
2121
* A progress token, used to associate progress notifications with the original request.
2222
*/
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* Type-checked examples for `specTypeSchema.ts`.
3+
*
4+
* These examples are synced into JSDoc comments via the sync-snippets script.
5+
* Each function's region markers define the code snippet that appears in the docs.
6+
*
7+
* @module
8+
*/
9+
10+
import { isSpecType, specTypeSchemas } from './specTypeSchema.js';
11+
12+
declare const untrusted: unknown;
13+
declare const value: unknown;
14+
declare const mixed: unknown[];
15+
16+
async function specTypeSchemas_basicUsage() {
17+
//#region specTypeSchemas_basicUsage
18+
const result = await specTypeSchemas.CallToolResult['~standard'].validate(untrusted);
19+
if (result.issues === undefined) {
20+
// result.value is CallToolResult
21+
}
22+
//#endregion specTypeSchemas_basicUsage
23+
void result;
24+
}
25+
26+
function isSpecType_basicUsage() {
27+
/* eslint-disable unicorn/no-array-callback-reference -- showcasing the guard-as-callback pattern */
28+
//#region isSpecType_basicUsage
29+
if (isSpecType.ContentBlock(value)) {
30+
// value is ContentBlock
31+
}
32+
33+
const blocks = mixed.filter(isSpecType.ContentBlock);
34+
//#endregion isSpecType_basicUsage
35+
/* eslint-enable unicorn/no-array-callback-reference */
36+
void blocks;
37+
}
38+
39+
void specTypeSchemas_basicUsage;
40+
void isSpecType_basicUsage;

0 commit comments

Comments
 (0)