v2 backwards compat: specTypeSchema exported - synchronous StandardSchemaV1 (#2047)
This commit is contained in:
parent
2c0c481cb9
commit
22595b9685
8 changed files with 41 additions and 19 deletions
|
|
@ -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<ContentBlock>` 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<ContentBlock>` validator — `validate()` returns the result synchronously. Guards are standalone functions, so `arr.filter(isSpecType.ContentBlock)` works. Also export the `SpecTypeName`, `SpecTypes`, and `StandardSchemaV1Sync` types.
|
||||
|
|
|
|||
|
|
@ -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)` |
|
||||
| `<TypeName>Schema.safeParse(value).success` | `isSpecType.<TypeName>(value)` |
|
||||
| `<TypeName>Schema.parse(value)` | `await specTypeSchemas.<TypeName>['~standard'].validate(value)` (returns a `Result`, not the value) |
|
||||
| Passing `<TypeName>Schema` as a validator argument | `specTypeSchemas.<TypeName>` (a `StandardSchemaV1<In, Out>`) |
|
||||
| `<TypeName>Schema.parse(value)` | `specTypeSchemas.<TypeName>['~standard'].validate(value)` (returns a `Result` synchronously, not the value) |
|
||||
| Passing `<TypeName>Schema` as a validator argument | `specTypeSchemas.<TypeName>` (a `StandardSchemaV1Sync<In, Out>`) |
|
||||
|
||||
`isCallToolResult(value)` still works, but `isSpecType` covers every spec type by name.
|
||||
|
||||
|
|
|
|||
|
|
@ -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<In, Out>`, 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<In, Out>` — `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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<K>]: SchemaFor<K> extends z.ZodType ? z.input<SchemaFor<K>> : never;
|
||||
};
|
||||
|
||||
type SchemaRecord = { readonly [K in SpecTypeName]: StandardSchemaV1<SpecTypeInputs[K], SpecTypes[K]> };
|
||||
type SchemaRecord = { readonly [K in SpecTypeName]: StandardSchemaV1Sync<SpecTypeInputs[K], SpecTypes[K]> };
|
||||
type GuardRecord = { readonly [K in SpecTypeName]: (value: unknown) => value is SpecTypeInputs[K] };
|
||||
|
||||
const _specTypeSchemas: Record<string, StandardSchemaV1> = {};
|
||||
|
|
@ -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
|
||||
* }
|
||||
|
|
|
|||
|
|
@ -114,6 +114,28 @@ export namespace StandardSchemaWithJSON {
|
|||
export type InferOutput<Schema extends StandardTypedV1> = StandardTypedV1.InferOutput<Schema>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Narrowing of {@linkcode StandardSchemaV1} whose `validate` is guaranteed synchronous.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
export interface StandardSchemaV1Sync<Input = unknown, Output = Input> extends StandardSchemaV1<Input, Output> {
|
||||
readonly '~standard': StandardSchemaV1Sync.Props<Input, Output>;
|
||||
}
|
||||
|
||||
export namespace StandardSchemaV1Sync {
|
||||
export interface Props<Input = unknown, Output = Input> extends StandardSchemaV1.Props<Input, Output> {
|
||||
readonly validate: (value: unknown, options?: StandardSchemaV1.Options | undefined) => StandardSchemaV1.Result<Output>;
|
||||
}
|
||||
|
||||
export type InferInput<Schema extends StandardTypedV1> = StandardTypedV1.InferInput<Schema>;
|
||||
export type InferOutput<Schema extends StandardTypedV1> = StandardTypedV1.InferOutput<Schema>;
|
||||
}
|
||||
|
||||
// Type guards
|
||||
|
||||
export function isStandardJSONSchema(schema: unknown): schema is StandardJSONSchemaV1 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue