Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
f490f6d
feat(core): add specTypeSchema() for runtime validation of any spec type
felixweinberger Apr 13, 2026
6e31128
docs: add migration guidance for specTypeSchema()/isSpecType()
felixweinberger Apr 13, 2026
5e77869
feat(core): include OAuth schemas in specTypeSchema() map
felixweinberger Apr 13, 2026
4bef047
refactor(core): Record-form isSpecType/specTypeSchemas instead of spe…
felixweinberger Apr 27, 2026
452a903
fix(core): address review on isSpecType/specTypeSchemas
felixweinberger Apr 27, 2026
94abeea
fix(core): explicit allowlist for SpecTypeName; changeset minor; type…
felixweinberger Apr 27, 2026
63d818d
docs(core): clarify isSpecType validates input type, not output struc…
felixweinberger Apr 28, 2026
94684b6
Merge branch 'main' into fweinberger/spec-type-predicates
felixweinberger Apr 29, 2026
25268f7
test(specTypeSchema): assert isSpecType narrows to input type for sch…
felixweinberger Apr 29, 2026
11b0ba3
docs(specTypeSchema): drop .parse() from isSpecType JSDoc
felixweinberger Apr 29, 2026
a910311
feat(specTypeSchema): include ResourceTemplate in SPEC_SCHEMA_KEYS
felixweinberger Apr 29, 2026
6fa7aa7
test(specTypeSchema): move ResourceTemplate runtime test into isSpecT…
felixweinberger Apr 29, 2026
c603005
style: prettier format specTypeSchema.test.ts import
felixweinberger Apr 29, 2026
17d75b1
Merge branch 'main' into fweinberger/spec-type-predicates
felixweinberger Apr 29, 2026
f6b744e
fix(types): annotate JSON*Schema with input type so isSpecType narrows
felixweinberger Apr 29, 2026
8f3d23b
refactor(specTypeSchema): switch isSpecType/specTypeSchema to string-…
felixweinberger Apr 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/spec-type-schema.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@modelcontextprotocol/client': minor
'@modelcontextprotocol/server': minor
---

Export `isSpecType` and `specTypeSchema` for runtime validation of any MCP spec type by name. `isSpecType('ContentBlock', value)` is a type predicate; `specTypeSchema('ContentBlock')` returns a `StandardSchemaV1<ContentBlock>` validator. Also export the `StandardSchemaV1`,
`SpecTypeName`, and `SpecTypes` types.
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Include what changed, why, and how to migrate. Search for related sections and g
- **Files**: Lowercase with hyphens, test files with `.test.ts` suffix
- **Imports**: ES module style, include `.js` extension, group imports logically
- **Formatting**: 2-space indentation, semicolons required, single quotes preferred
- **Testing**: Co-locate tests with source files, use descriptive test names
- **Testing**: Place tests under each package's `test/` directory (vitest only includes `test/**/*.test.ts`), use descriptive test names
- **Comments**: JSDoc for public APIs, inline comments for complex logic

### JSDoc `@example` Code Snippets
Expand Down
116 changes: 62 additions & 54 deletions docs/migration-SKILL.md

Large diffs are not rendered by default.

64 changes: 40 additions & 24 deletions docs/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ import { StdioServerTransport } from '@modelcontextprotocol/server/stdio';
import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node';
```

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

### Dropped Node.js 18 and CommonJS

Expand Down Expand Up @@ -136,7 +137,8 @@ const transport = new StreamableHTTPClientTransport(new URL('http://localhost:30

Resource Server helpers (`requireBearerAuth`, `mcpAuthMetadataRouter`, `getOAuthProtectedResourceMetadataUrl`, `OAuthTokenVerifier`) are now first-class in `@modelcontextprotocol/express`.

Authorization Server helpers (`mcpAuthRouter`, `OAuthServerProvider`, `ProxyOAuthServerProvider`, `authenticateClient`, `allowedMethods`, etc.) have been removed from the core SDK; new code should use a dedicated IdP/OAuth library. See the [examples](../examples/server/src/) for a working demo with `better-auth`.
Authorization Server helpers (`mcpAuthRouter`, `OAuthServerProvider`, `ProxyOAuthServerProvider`, `authenticateClient`, `allowedMethods`, etc.) have been removed from the core SDK; new code should use a dedicated IdP/OAuth library. See the [examples](../examples/server/src/) for
a working demo with `better-auth`.

Note: `AuthInfo` has moved from `server/auth/types.ts` to the core types and is now re-exported by `@modelcontextprotocol/client` and `@modelcontextprotocol/server`.

Expand Down Expand Up @@ -296,11 +298,11 @@ This applies to:

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

| Removed | Replacement |
|---|---|
| `schemaToJson(schema)` | `standardSchemaToJsonSchema(schema)` |
| `parseSchemaAsync(schema, data)` | `validateStandardSchema(schema, data)` |
| `SchemaInput<T>` | `StandardSchemaWithJSON.InferInput<T>` |
| Removed | Replacement |
| ------------------------------------------------------------------------------------ | ----------------------------------------------------------------- |
| `schemaToJson(schema)` | `standardSchemaToJsonSchema(schema)` |
| `parseSchemaAsync(schema, data)` | `validateStandardSchema(schema, data)` |
| `SchemaInput<T>` | `StandardSchemaWithJSON.InferInput<T>` |
| `getSchemaShape`, `getSchemaDescription`, `isOptionalSchema`, `unwrapOptionalSchema` | No replacement — these are now internal Zod introspection helpers |

### Host header validation moved
Expand Down Expand Up @@ -444,18 +446,30 @@ const result = await client.callTool({ name: 'my-tool', arguments: {} });

The return type is now inferred from the method name via `ResultTypeMap`. For example, `client.request({ method: 'tools/call', ... })` returns `Promise<CallToolResult | CreateTaskResult>`.

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

```typescript
// v1: runtime validation with Zod schema
import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
if (CallToolResultSchema.safeParse(value).success) { /* ... */ }
if (CallToolResultSchema.safeParse(value).success) {
/* ... */
}

// v2: type predicate by name
import { isSpecType } from '@modelcontextprotocol/client';
if (isSpecType('CallToolResult', value)) {
/* ... */
}
const blocks = mixed.filter(v => isSpecType('ContentBlock', v));

// v2: use the type guard
import { isCallToolResult } from '@modelcontextprotocol/client';
if (isCallToolResult(value)) { /* ... */ }
// v2: or get the StandardSchemaV1 validator object directly
import { specTypeSchema } from '@modelcontextprotocol/client';
const result = await specTypeSchema('CallToolResult')['~standard'].validate(value);
```

The first argument to `isSpecType` and `specTypeSchema` is a `SpecTypeName` — a literal union of every named type in the MCP spec — so you get autocomplete and a compile error on typos. `specTypeSchema(name)` returns a `StandardSchemaV1<In, Out>`, which composes with any
Standard-Schema-aware library. The pre-existing `isCallToolResult(value)` guard still works.

### Client list methods return empty results for missing capabilities

`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.
Expand Down Expand Up @@ -489,20 +503,21 @@ import { InMemoryTransport } from '@modelcontextprotocol/client';

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

| Removed | Replacement |
| ---------------------------------------- | ------------------------------------------------ |
| `JSONRPCError` | `JSONRPCErrorResponse` |
| `JSONRPCErrorSchema` | `JSONRPCErrorResponseSchema` |
| `isJSONRPCError` | `isJSONRPCErrorResponse` |
| `isJSONRPCResponse` | `isJSONRPCResultResponse` (see note below) |
| `ResourceReferenceSchema` | `ResourceTemplateReferenceSchema` |
| `ResourceReference` | `ResourceTemplateReference` |
| `IsomorphicHeaders` | Use Web Standard `Headers` |
| Removed | Replacement |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `JSONRPCError` | `JSONRPCErrorResponse` |
| `JSONRPCErrorSchema` | `JSONRPCErrorResponseSchema` |
| `isJSONRPCError` | `isJSONRPCErrorResponse` |
| `isJSONRPCResponse` | `isJSONRPCResultResponse` (see note below) |
| `ResourceReferenceSchema` | `ResourceTemplateReferenceSchema` |
| `ResourceReference` | `ResourceTemplateReference` |
| `IsomorphicHeaders` | Use Web Standard `Headers` |
| `AuthInfo` (from `server/auth/types.js`) | `AuthInfo` (now re-exported by `@modelcontextprotocol/client` and `@modelcontextprotocol/server`) |

All other types and schemas exported from `@modelcontextprotocol/sdk/types.js` retain their original names — import them from `@modelcontextprotocol/client` or `@modelcontextprotocol/server`.

> **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.
> **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.

**Before (v1):**

Expand Down Expand Up @@ -530,7 +545,7 @@ The `RequestHandlerExtra` type has been replaced with a structured context type
| `extra.sendRequest(...)` | `ctx.mcpReq.send(...)` |
| `extra.sendNotification(...)` | `ctx.mcpReq.notify(...)` |
| `extra.authInfo` | `ctx.http?.authInfo` |
| `extra.requestInfo` | `ctx.http?.req` (standard Web `Request`, only on `ServerContext`) |
| `extra.requestInfo` | `ctx.http?.req` (standard Web `Request`, only on `ServerContext`) |
| `extra.closeSSEStream` | `ctx.http?.closeSSE` (only on `ServerContext`) |
| `extra.closeStandaloneSSEStream` | `ctx.http?.closeStandaloneSSE` (only on `ServerContext`) |
| `extra.sessionId` | `ctx.sessionId` |
Expand Down Expand Up @@ -797,7 +812,8 @@ try {

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

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

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

Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/exports/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,9 @@ export { isTerminal } from '../../experimental/tasks/interfaces.js';
export { InMemoryTaskMessageQueue, InMemoryTaskStore } from '../../experimental/tasks/stores/inMemory.js';

// Validator types and classes
export type { StandardSchemaWithJSON } from '../../util/standardSchema.js';
export type { SpecTypeName, SpecTypes } from '../../types/specTypeSchema.js';
export { isSpecType, specTypeSchema } from '../../types/specTypeSchema.js';
export type { StandardSchemaV1, StandardSchemaWithJSON } from '../../util/standardSchema.js';
export { AjvJsonSchemaValidator } from '../../validators/ajvProvider.js';
export type { CfWorkerSchemaDraft } from '../../validators/cfWorkerProvider.js';
// fromJsonSchema is intentionally NOT exported here — the server and client packages
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export * from './enums.js';
export * from './errors.js';
export * from './guards.js';
export * from './schemas.js';
export * from './specTypeSchema.js';
export * from './types.js';
6 changes: 3 additions & 3 deletions packages/core/src/types/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import type {
ResultTypeMap
} from './types.js';

export const JSONValueSchema: z.ZodType<JSONValue> = z.lazy(() =>
export const JSONValueSchema: z.ZodType<JSONValue, JSONValue> = z.lazy(() =>
z.union([z.string(), z.number(), z.boolean(), z.null(), z.record(z.string(), JSONValueSchema), z.array(JSONValueSchema)])
);
export const JSONObjectSchema: z.ZodType<JSONObject> = z.record(z.string(), JSONValueSchema);
export const JSONArraySchema: z.ZodType<JSONArray> = z.array(JSONValueSchema);
export const JSONObjectSchema: z.ZodType<JSONObject, JSONObject> = z.record(z.string(), JSONValueSchema);
export const JSONArraySchema: z.ZodType<JSONArray, JSONArray> = z.array(JSONValueSchema);
/**
* A progress token, used to associate progress notifications with the original request.
*/
Expand Down
38 changes: 38 additions & 0 deletions packages/core/src/types/specTypeSchema.examples.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Type-checked examples for `specTypeSchema.ts`.
*
* These examples are synced into JSDoc comments via the sync-snippets script.
* Each function's region markers define the code snippet that appears in the docs.
*
* @module
*/

import { isSpecType, specTypeSchema } from './specTypeSchema.js';

declare const untrusted: unknown;
declare const value: unknown;
declare const mixed: unknown[];

async function specTypeSchema_basicUsage() {
//#region specTypeSchema_basicUsage
const result = await specTypeSchema('CallToolResult')['~standard'].validate(untrusted);
if (result.issues === undefined) {
// result.value is CallToolResult
}
//#endregion specTypeSchema_basicUsage
void result;
}

function isSpecType_basicUsage() {
//#region isSpecType_basicUsage
if (isSpecType('ContentBlock', value)) {
// value is ContentBlock
}

const blocks = mixed.filter(v => isSpecType('ContentBlock', v));
//#endregion isSpecType_basicUsage
void blocks;
}

void specTypeSchema_basicUsage;
void isSpecType_basicUsage;
Loading
Loading