From 34bccc9d292ca9f188a895a23053dd787cdb9f8f Mon Sep 17 00:00:00 2001 From: Konstantin Konstantinov Date: Mon, 11 May 2026 18:19:38 +0300 Subject: [PATCH 1/4] backwards compat: specTypeSchema exported - synchronous StandardSchemaV1 --- packages/core/src/exports/public/index.ts | 2 +- packages/core/src/types/specTypeSchema.ts | 4 ++-- packages/core/src/util/standardSchema.ts | 15 +++++++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/core/src/exports/public/index.ts b/packages/core/src/exports/public/index.ts index 5c1689ca6..e305f32a4 100644 --- a/packages/core/src/exports/public/index.ts +++ b/packages/core/src/exports/public/index.ts @@ -140,7 +140,7 @@ export { InMemoryTaskMessageQueue, InMemoryTaskStore } from '../../experimental/ // Validator types and classes export type { SpecTypeName, SpecTypes } from '../../types/specTypeSchema.js'; export { isSpecType, specTypeSchemas } from '../../types/specTypeSchema.js'; -export type { StandardSchemaV1, StandardSchemaWithJSON } from '../../util/standardSchema.js'; +export type { StandardSchemaV1, StandardSchemaV1Sync, 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 diff --git a/packages/core/src/types/specTypeSchema.ts b/packages/core/src/types/specTypeSchema.ts index cde3555d0..942e4781e 100644 --- a/packages/core/src/types/specTypeSchema.ts +++ b/packages/core/src/types/specTypeSchema.ts @@ -13,7 +13,7 @@ import { OpenIdProviderDiscoveryMetadataSchema, OpenIdProviderMetadataSchema } from '../shared/auth.js'; -import type { StandardSchemaV1 } from '../util/standardSchema.js'; +import type { StandardSchemaV1, StandardSchemaV1Sync } from '../util/standardSchema.js'; import * as schemas from './schemas.js'; /** @@ -235,7 +235,7 @@ type SpecTypeInputs = { [K in SchemaKey as StripSchemaSuffix]: SchemaFor extends z.ZodType ? z.input> : never; }; -type SchemaRecord = { readonly [K in SpecTypeName]: StandardSchemaV1 }; +type SchemaRecord = { readonly [K in SpecTypeName]: StandardSchemaV1Sync }; type GuardRecord = { readonly [K in SpecTypeName]: (value: unknown) => value is SpecTypeInputs[K] }; const _specTypeSchemas: Record = {}; diff --git a/packages/core/src/util/standardSchema.ts b/packages/core/src/util/standardSchema.ts index ee1a63067..0702739a4 100644 --- a/packages/core/src/util/standardSchema.ts +++ b/packages/core/src/util/standardSchema.ts @@ -114,6 +114,21 @@ export namespace StandardSchemaWithJSON { export type InferOutput = StandardTypedV1.InferOutput; } +/** + * Narrowing of {@linkcode StandardSchemaV1} whose `validate` is guaranteed synchronous. + * + * Zod schemas always validate synchronously, so every entry in `specTypeSchemas` satisfies + * this interface. Consumers can call `validate()` and access `.issues` / `.value` on the + * result without `await`. + * + * `StandardSchemaV1Sync` is assignable to `StandardSchemaV1` — it is a strict subtype. + */ +export interface StandardSchemaV1Sync extends StandardSchemaV1 { + readonly '~standard': StandardSchemaV1['~standard'] & { + readonly validate: (value: unknown, options?: StandardSchemaV1.Options | undefined) => StandardSchemaV1.Result; + }; +} + // Type guards export function isStandardJSONSchema(schema: unknown): schema is StandardJSONSchemaV1 { From 5bdaf6c72e0cae043d0770dd3ccd06f39e1a488c Mon Sep 17 00:00:00 2001 From: Konstantin Konstantinov Date: Fri, 15 May 2026 12:20:34 +0300 Subject: [PATCH 2/4] fix: align docs/examples with StandardSchemaV1Sync and fix type narrowing Replace intersection-based StandardSchemaV1Sync with interface-extends approach so TypeScript properly narrows validate() return type. Drop async/await from examples and migration docs to match the synchronous API this PR introduces. --- .changeset/spec-type-schema.md | 2 +- docs/migration-SKILL.md | 6 +++--- docs/migration.md | 6 +++--- packages/core/src/types/specTypeSchema.examples.ts | 4 ++-- packages/core/src/types/specTypeSchema.ts | 2 +- packages/core/src/util/standardSchema.ts | 8 ++++++-- 6 files changed, 16 insertions(+), 12 deletions(-) diff --git a/.changeset/spec-type-schema.md b/.changeset/spec-type-schema.md index 187ad768c..7bcc977e4 100644 --- a/.changeset/spec-type-schema.md +++ b/.changeset/spec-type-schema.md @@ -3,4 +3,4 @@ '@modelcontextprotocol/server': minor --- -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` validator. Guards are standalone functions, so `arr.filter(isSpecType.ContentBlock)` works. Also export the `SpecTypeName` and `SpecTypes` types. +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 `StandardSchemaV1Sync` validator — `validate()` returns the result synchronously. Guards are standalone functions, so `arr.filter(isSpecType.ContentBlock)` works. Also export the `SpecTypeName`, `SpecTypes`, and `StandardSchemaV1Sync` types. diff --git a/docs/migration-SKILL.md b/docs/migration-SKILL.md index 9cff719bb..dbe6a4e9f 100644 --- a/docs/migration-SKILL.md +++ b/docs/migration-SKILL.md @@ -99,7 +99,7 @@ Notes: | `WebSocketClientTransport` | REMOVED (use `StreamableHTTPClientTransport` or `StdioClientTransport`) | 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 -`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. +`isSpecType.TypeName(value)` (e.g., `isSpecType.CallToolResult(v)`) or `specTypeSchemas.TypeName` for the `StandardSchemaV1Sync` validator object. The keys are typed as `SpecTypeName`, a literal union of all spec type names. ### Error class changes @@ -468,8 +468,8 @@ If a `*Schema` constant was used for **runtime validation** (not just as a `requ | -------------------------------------------------- | -------------------------------------------------------------------------------------- | | `CallToolResultSchema.safeParse(value).success` | `isSpecType.CallToolResult(value)` | | `Schema.safeParse(value).success` | `isSpecType.(value)` | -| `Schema.parse(value)` | `await specTypeSchemas.['~standard'].validate(value)` (returns a `Result`, not the value) | -| Passing `Schema` as a validator argument | `specTypeSchemas.` (a `StandardSchemaV1`) | +| `Schema.parse(value)` | `specTypeSchemas.['~standard'].validate(value)` (returns a `Result` synchronously, not the value) | +| Passing `Schema` as a validator argument | `specTypeSchemas.` (a `StandardSchemaV1Sync`) | `isCallToolResult(value)` still works, but `isSpecType` covers every spec type by name. diff --git a/docs/migration.md b/docs/migration.md index fecf18599..cd3da6dcd 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -505,12 +505,12 @@ if (isSpecType.CallToolResult(value)) { } const blocks = mixed.filter(isSpecType.ContentBlock); -// v2: or get the StandardSchemaV1 validator object directly +// v2: or get the StandardSchemaV1Sync validator object directly import { specTypeSchemas } from '@modelcontextprotocol/client'; -const result = await specTypeSchemas.CallToolResult['~standard'].validate(value); +const result = specTypeSchemas.CallToolResult['~standard'].validate(value); ``` -`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`, which composes with any Standard-Schema-aware library. The pre-existing `isCallToolResult(value)` guard still works. +`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 `StandardSchemaV1Sync` — `validate()` returns the result synchronously, so you can access `.issues` / `.value` without `await`. It composes with any Standard-Schema-aware library. The pre-existing `isCallToolResult(value)` guard still works. ### Client list methods return empty results for missing capabilities diff --git a/packages/core/src/types/specTypeSchema.examples.ts b/packages/core/src/types/specTypeSchema.examples.ts index 05b706748..8e991d4f9 100644 --- a/packages/core/src/types/specTypeSchema.examples.ts +++ b/packages/core/src/types/specTypeSchema.examples.ts @@ -13,9 +13,9 @@ declare const untrusted: unknown; declare const value: unknown; declare const mixed: unknown[]; -async function specTypeSchemas_basicUsage() { +function specTypeSchemas_basicUsage() { //#region specTypeSchemas_basicUsage - const result = await specTypeSchemas.CallToolResult['~standard'].validate(untrusted); + const result = specTypeSchemas.CallToolResult['~standard'].validate(untrusted); if (result.issues === undefined) { // result.value is CallToolResult } diff --git a/packages/core/src/types/specTypeSchema.ts b/packages/core/src/types/specTypeSchema.ts index 942e4781e..477d61a55 100644 --- a/packages/core/src/types/specTypeSchema.ts +++ b/packages/core/src/types/specTypeSchema.ts @@ -265,7 +265,7 @@ for (const [key, schema] of Object.entries(authSchemas)) { * * @example * ```ts source="./specTypeSchema.examples.ts#specTypeSchemas_basicUsage" - * const result = await specTypeSchemas.CallToolResult['~standard'].validate(untrusted); + * const result = specTypeSchemas.CallToolResult['~standard'].validate(untrusted); * if (result.issues === undefined) { * // result.value is CallToolResult * } diff --git a/packages/core/src/util/standardSchema.ts b/packages/core/src/util/standardSchema.ts index 0702739a4..b22b4e2dd 100644 --- a/packages/core/src/util/standardSchema.ts +++ b/packages/core/src/util/standardSchema.ts @@ -124,9 +124,13 @@ export namespace StandardSchemaWithJSON { * `StandardSchemaV1Sync` is assignable to `StandardSchemaV1` — it is a strict subtype. */ export interface StandardSchemaV1Sync extends StandardSchemaV1 { - readonly '~standard': StandardSchemaV1['~standard'] & { + readonly '~standard': StandardSchemaV1Sync.Props; +} + +export namespace StandardSchemaV1Sync { + export interface Props extends StandardSchemaV1.Props { readonly validate: (value: unknown, options?: StandardSchemaV1.Options | undefined) => StandardSchemaV1.Result; - }; + } } // Type guards From 348d3a21d1d7cf2215a898ca1c10c55d78d8aab3 Mon Sep 17 00:00:00 2001 From: Konstantin Konstantinov Date: Fri, 15 May 2026 13:48:02 +0300 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20address=20claude[bot]=20review=20nit?= =?UTF-8?q?s=20=E2=80=94=20drop=20vestigial=20casts,=20add=20InferInput/In?= =?UTF-8?q?ferOutput=20aliases?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove unnecessary `as { issues?: ... }` casts in specTypeSchema.test.ts that were workarounds for the old async validate return type. The bare `result.issues` access now serves as compile-time regression coverage for the StandardSchemaV1Sync narrowing. - Add InferInput/InferOutput convenience aliases to the StandardSchemaV1Sync namespace, matching the pattern of all sibling Standard* namespaces. --- packages/core/src/util/standardSchema.ts | 3 +++ packages/core/test/types/specTypeSchema.test.ts | 12 ++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/core/src/util/standardSchema.ts b/packages/core/src/util/standardSchema.ts index b22b4e2dd..9ffe98cea 100644 --- a/packages/core/src/util/standardSchema.ts +++ b/packages/core/src/util/standardSchema.ts @@ -131,6 +131,9 @@ export namespace StandardSchemaV1Sync { export interface Props extends StandardSchemaV1.Props { readonly validate: (value: unknown, options?: StandardSchemaV1.Options | undefined) => StandardSchemaV1.Result; } + + export type InferInput = StandardTypedV1.InferInput; + export type InferOutput = StandardTypedV1.InferOutput; } // Type guards diff --git a/packages/core/test/types/specTypeSchema.test.ts b/packages/core/test/types/specTypeSchema.test.ts index be8c41922..198e104f9 100644 --- a/packages/core/test/types/specTypeSchema.test.ts +++ b/packages/core/test/types/specTypeSchema.test.ts @@ -16,14 +16,14 @@ import type { } from '../../src/types/types.js'; describe('specTypeSchemas', () => { - it('returns a StandardSchemaV1 validator that accepts valid values', () => { + it('returns a StandardSchemaV1Sync validator that accepts valid values', () => { const result = specTypeSchemas.Implementation['~standard'].validate({ name: 'x', version: '1.0.0' }); - expect((result as { issues?: unknown }).issues).toBeUndefined(); + expect(result.issues).toBeUndefined(); }); it('returns a validator that rejects invalid values with issues', () => { const result = specTypeSchemas.Implementation['~standard'].validate({ name: 'x' }); - expect((result as { issues?: readonly unknown[] }).issues?.length).toBeGreaterThan(0); + expect(result.issues?.length).toBeGreaterThan(0); }); it('rejects unknown names at compile time and is undefined at runtime', () => { @@ -33,14 +33,14 @@ describe('specTypeSchemas', () => { it('covers JSON-RPC envelope types', () => { const ok = specTypeSchemas.JSONRPCRequest['~standard'].validate({ jsonrpc: '2.0', id: 1, method: 'ping' }); - expect((ok as { issues?: unknown }).issues).toBeUndefined(); + expect(ok.issues).toBeUndefined(); }); it('covers OAuth types from shared/auth.ts', () => { const ok = specTypeSchemas.OAuthTokens['~standard'].validate({ access_token: 'x', token_type: 'Bearer' }); - expect((ok as { issues?: unknown }).issues).toBeUndefined(); + expect(ok.issues).toBeUndefined(); const bad = specTypeSchemas.OAuthTokens['~standard'].validate({ token_type: 'Bearer' }); - expect((bad as { issues?: readonly unknown[] }).issues?.length).toBeGreaterThan(0); + expect(bad.issues?.length).toBeGreaterThan(0); }); }); From db32ed5501a032bea9aad87ea8f2d672b8ba1838 Mon Sep 17 00:00:00 2001 From: Konstantin Konstantinov Date: Fri, 15 May 2026 15:09:38 +0300 Subject: [PATCH 4/4] JSDoc fix --- packages/core/src/util/standardSchema.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/util/standardSchema.ts b/packages/core/src/util/standardSchema.ts index 9ffe98cea..b938885de 100644 --- a/packages/core/src/util/standardSchema.ts +++ b/packages/core/src/util/standardSchema.ts @@ -117,9 +117,9 @@ export namespace StandardSchemaWithJSON { /** * Narrowing of {@linkcode StandardSchemaV1} whose `validate` is guaranteed synchronous. * - * Zod schemas always validate synchronously, so every entry in `specTypeSchemas` satisfies - * this interface. Consumers can call `validate()` and access `.issues` / `.value` on the - * result without `await`. + * The Zod schemas backing `specTypeSchemas` contain no async refinements or transforms, + * so every entry satisfies this interface. Consumers can call `validate()` and access + * `.issues` / `.value` on the result without `await`. * * `StandardSchemaV1Sync` is assignable to `StandardSchemaV1` — it is a strict subtype. */