-
Notifications
You must be signed in to change notification settings - Fork 1.6k
feat: support Standard JSON Schema (StandardJSONSchemaV1) for tool/prompt schemas #1473
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
@modelcontextprotocol/client
@modelcontextprotocol/server
@modelcontextprotocol/express
@modelcontextprotocol/hono
@modelcontextprotocol/node
commit: |
35f4667 to
edc3098
Compare
- Replace z.safeParse with parseSchema utility in client.ts and protocol.ts - Replace z.core.$ZodType with AnySchema alias - Replace z.core.$ZodObject with AnyObjectSchema alias - Replace z.output<T> with SchemaOutput<T> alias - Remove direct zod import from protocol.ts (now uses schema utilities)
- Add arktypeExample.ts demonstrating ArkType schema validation - Add valibotExample.ts demonstrating Valibot schema validation - Fix type guards to accept function-based schemas (ArkType uses functions) - Fix validation result handling to check issues array first (Valibot compatibility) - Add arktype, valibot, and @valibot/to-json-schema dependencies Both examples demonstrate: - Tool registration with inputSchema and outputSchema - Native validation via ~standard.validate() - JSON Schema conversion via ~standard.jsonSchema.input()
Tests cover: - ArkType tool registration (input/output schemas) - Valibot tool registration (with descriptions) - Tool validation with valid/invalid input for both libraries - Mixed schema libraries in the same server (Zod + ArkType + Valibot) - Prompt completions with Zod completable - Error message quality from all three libraries - Type inference verification All libraries produce clear, user-friendly error messages: - ArkType: "age must be a number (was a string)" - Valibot: "Invalid type: Expected string but received 123" - Zod: "Invalid input: expected string, received number"
- Add eslint-disable for namespace rule in standardSchema.ts (matches Standard Schema spec design) - Remove unused imports in standardSchema.test.ts - Fix switch case braces in example files - Apply prettier formatting
- Add AnySchema import to experimental/tasks/server.ts - Add parseSchema import to server/server.ts
edc3098 to
2983603
Compare
- Simplify ArkType and Valibot examples to minimal single-tool demos - Remove verbose docstrings and section dividers from standardSchema.ts
|
|
||
| /** | ||
| * The base Standard interface for typed schemas. | ||
| * @see https://standardschema.dev |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: copy/paste from https://github.com/standard-schema/standard-schema as recommended - this file basically is the spec.
examples/server/package.json
Outdated
| "@modelcontextprotocol/hono": "workspace:^", | ||
| "@modelcontextprotocol/node": "workspace:^", | ||
| "@modelcontextprotocol/server": "workspace:^", | ||
| "@valibot/to-json-schema": "^1.5.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
example only dep
| @@ -0,0 +1,179 @@ | |||
| /** | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Add arktype, valibot, @valibot/to-json-schema to pnpm catalog - Replace z.safeParse with parseSchema in server.ts - Replace z.output/z.core.$ZodType with SchemaOutput/AnySchema aliases - Remove unused zod imports
Update comments in extractCompleters to explicitly document that the completable feature only supports Zod schemas due to reliance on Zod-specific .shape property introspection.
packages/server/src/server/mcp.ts
Outdated
| /** | ||
| * @internal Extracts completable callbacks from a Zod schema at registration time. | ||
| * NOTE: This only works with Zod schemas. ArkType and Valibot are not supported. | ||
| */ | ||
| function extractCompleters(schema: StandardJSONSchemaV1 | undefined): Map<string, CompleteCallback<StandardJSONSchemaV1>> | undefined { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't it weird to annotate StandardJSONSchemaV1 but it actually requires a zod type as input?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, cant be helped
packages/server/src/server/mcp.ts
Outdated
| if (!schema) return undefined; | ||
|
|
||
| const shape = getZodSchemaShape(schema); | ||
| if (!shape) return undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the guard for passing in a non-zod schema in here
…le lookup Revert the completable handling back to the original approach where completable fields are looked up at request time rather than pre-extracted at registration time. This removes: - extractCompleters() function and completers Map optimization - CompleteCallback type import (unused) - completers field from RegisteredPrompt The original approach directly inspects the schema at completion request time using getSchemaShape/isCompletable/getCompleter.
| ): PromptHandler { | ||
| if (argsSchema) { | ||
| const typedCallback = callback as (args: SchemaOutput<AnySchema>, ctx: ServerContext) => GetPromptResult | Promise<GetPromptResult>; | ||
| const typedCallback = callback as (args: unknown, ctx: ServerContext) => GetPromptResult | Promise<GetPromptResult>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why the change to unknown?
|
One thought for a potential follow-up: now that the SDK is moving to schema-library-agnostic types, the Instead of embedding completion callbacks inside schema objects (which requires Zod-specific introspection to extract), completers could be passed explicitly in the prompt config: server.registerPrompt('greeting', {
argsSchema: type({ name: 'string', language: 'string' }), // any library
completers: {
name: (value) => ['Alice', 'Bob'].filter(n => n.startsWith(value)),
language: (value) => ['en', 'fr'].filter(l => l.startsWith(value))
}
}, callback);The type simplifies too, since completion values are always strings over the wire: type CompleteCallback = (
value: string,
context?: { arguments?: Record<string, string> }
) => string[] | Promise<string[]>;This PR already does most of the internal plumbing — Not suggesting this as a blocker for this PR — just flagging as a natural follow-up that would complete the schema-agnostic story. |
Summary
Replace the Zod-specific
AnySchematype withStandardJSONSchemaV1from the Standard Schema spec for user-provided tool and prompt schemas. This enables any schema library that implements the spec (Zod v4, Valibot, ArkType, etc.) to be used for tool/prompt schemas.Motivation and Context
Currently, the MCP SDK is tightly coupled to Zod for user-provided schemas. This PR makes the SDK library-agnostic by using the Standard Schema spec interfaces:
StandardJSONSchemaV1: Primary interface - uses~standard.jsonSchema.input/output()for JSON Schema conversion (wire protocol)StandardSchemaV1: Optional interface - uses~standard.validate()for native validation when availableLibraries that implement these interfaces:
@valibot/to-json-schema(native validation + JSON Schema)Changes
Core (
packages/core)src/util/standardSchema.ts- Standard Schema interfaces and utilitiesisStandardJSONSchema(),isStandardSchema(),isStandardSchemaWithJSON()standardSchemaToJsonSchema()validateStandardSchema()(uses native validation if available, otherwise JSON Schema)promptArgumentsFromStandardSchema()src/util/schema.ts- Internal Zod utilities for protocol handling (unchanged API)src/shared/protocol.ts- Use schema utility aliases instead of direct Zod importsServer (
packages/server)src/server/mcp.tsRegisteredTool.inputSchema/outputSchemanow acceptStandardJSONSchemaV1RegisteredPrompt.argsSchemanow acceptsStandardJSONSchemaV1StandardJSONSchemaV1.InferOutput<T>for type inferencevalidateStandardSchema()for native validation when availableClient (
packages/client)src/client/client.ts- UseparseSchemautility instead of directz.safeParseExamples
examples/server/src/arktypeExample.ts- ArkType schema exampleexamples/server/src/valibotExample.ts- Valibot schema exampleTests
test/integration/test/standardSchema.test.ts- Comprehensive integration testsHow Has This Been Tested?
Error Message Quality
All libraries produce clear, user-friendly error messages:
age must be a number (was a string)Invalid type: Expected string but received 123Invalid input: expected string, received numberBreaking Changes
inputSchema,outputSchema, andargsSchemanow expectStandardJSONSchemaV1instead of Zod'sAnySchemaStandardJSONSchemaV1completable()function for prompt argument completion remains Zod-specificAPI Examples
Zod (existing, unchanged)
ArkType (new)
Valibot (new)
Types of changes
Checklist