-
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
Open
mattzcarey
wants to merge
12
commits into
main
Choose a base branch
from
support-standard-json-schema
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,115
−116
Open
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
423c66f
wip
mattzcarey c0c5241
refactor: use schema utility functions instead of direct zod imports
mattzcarey a075e1d
feat: add ArkType and Valibot examples for Standard Schema support
mattzcarey b93a01e
test: add comprehensive integration tests for Standard Schema support
mattzcarey fe48476
chore: fix lint and formatting issues
mattzcarey 2983603
fix: add missing imports after rebase
mattzcarey 93e3858
chore: simplify examples and reduce docstrings
mattzcarey 6f38754
refactor: use schema utility aliases and add deps to catalog
mattzcarey afefc79
docs: clarify that completable() only works with Zod schemas
mattzcarey 7997105
revert: remove extractCompleters optimization, use original completab…
mattzcarey c4cfd8e
style: remove verbose comment block
mattzcarey cb1e1ef
refactor: remove promptArgumentsFromSchema wrapper, use imported func…
mattzcarey File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| #!/usr/bin/env node | ||
| /** | ||
| * Minimal MCP server using ArkType for schema validation. | ||
| * ArkType implements the Standard Schema spec with built-in JSON Schema conversion. | ||
| */ | ||
|
|
||
| import { McpServer, StdioServerTransport } from '@modelcontextprotocol/server'; | ||
| import { type } from 'arktype'; | ||
|
|
||
| const server = new McpServer({ | ||
| name: 'arktype-example', | ||
| version: '1.0.0' | ||
| }); | ||
|
|
||
| // Register a tool with ArkType schema | ||
| server.registerTool( | ||
| 'greet', | ||
| { | ||
| description: 'Generate a greeting', | ||
| inputSchema: type({ name: 'string' }) | ||
| }, | ||
| async ({ name }) => ({ | ||
| content: [{ type: 'text', text: `Hello, ${name}!` }] | ||
| }) | ||
| ); | ||
|
|
||
| const transport = new StdioServerTransport(); | ||
| await server.connect(transport); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| #!/usr/bin/env node | ||
| /** | ||
| * Minimal MCP server using Valibot for schema validation. | ||
| * Use toStandardJsonSchema() from @valibot/to-json-schema to create | ||
| * StandardJSONSchemaV1-compliant schemas. | ||
| */ | ||
|
|
||
| import { McpServer, StdioServerTransport } from '@modelcontextprotocol/server'; | ||
| import { toStandardJsonSchema } from '@valibot/to-json-schema'; | ||
| import * as v from 'valibot'; | ||
|
|
||
| const server = new McpServer({ | ||
| name: 'valibot-example', | ||
| version: '1.0.0' | ||
| }); | ||
|
|
||
| // Register a tool with Valibot schema | ||
| server.registerTool( | ||
| 'greet', | ||
| { | ||
| description: 'Generate a greeting', | ||
| inputSchema: toStandardJsonSchema(v.object({ name: v.string() })) | ||
| }, | ||
| async ({ name }) => ({ | ||
| content: [{ type: 'text', text: `Hello, ${name}!` }] | ||
| }) | ||
| ); | ||
|
|
||
| const transport = new StdioServerTransport(); | ||
| await server.connect(transport); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,179 @@ | ||
| /** | ||
| * Standard Schema utilities for user-provided schemas. | ||
| * Supports Zod v4, Valibot, ArkType, and other Standard Schema implementations. | ||
| * @see https://standardschema.dev | ||
| */ | ||
|
|
||
| /* eslint-disable @typescript-eslint/no-namespace */ | ||
|
|
||
| import type { JsonSchemaType, jsonSchemaValidator } from '../validation/types.js'; | ||
|
|
||
| // Standard Schema interfaces (from https://standardschema.dev) | ||
|
|
||
| export interface StandardTypedV1<Input = unknown, Output = Input> { | ||
| readonly '~standard': StandardTypedV1.Props<Input, Output>; | ||
| } | ||
|
|
||
| export namespace StandardTypedV1 { | ||
| export interface Props<Input = unknown, Output = Input> { | ||
| readonly version: 1; | ||
| readonly vendor: string; | ||
| readonly types?: Types<Input, Output> | undefined; | ||
| } | ||
|
|
||
| export interface Types<Input = unknown, Output = Input> { | ||
| readonly input: Input; | ||
| readonly output: Output; | ||
| } | ||
|
|
||
| export type InferInput<Schema extends StandardTypedV1> = NonNullable<Schema['~standard']['types']>['input']; | ||
| export type InferOutput<Schema extends StandardTypedV1> = NonNullable<Schema['~standard']['types']>['output']; | ||
| } | ||
|
|
||
| export interface StandardSchemaV1<Input = unknown, Output = Input> { | ||
| readonly '~standard': StandardSchemaV1.Props<Input, Output>; | ||
| } | ||
|
|
||
| export namespace StandardSchemaV1 { | ||
| export interface Props<Input = unknown, Output = Input> extends StandardTypedV1.Props<Input, Output> { | ||
| readonly validate: (value: unknown, options?: Options | undefined) => Result<Output> | Promise<Result<Output>>; | ||
| } | ||
|
|
||
| export interface Options { | ||
| readonly libraryOptions?: Record<string, unknown> | undefined; | ||
| } | ||
|
|
||
| export type Result<Output> = SuccessResult<Output> | FailureResult; | ||
|
|
||
| export interface SuccessResult<Output> { | ||
| readonly value: Output; | ||
| readonly issues?: undefined; | ||
| } | ||
|
|
||
| export interface FailureResult { | ||
| readonly issues: ReadonlyArray<Issue>; | ||
| } | ||
|
|
||
| export interface Issue { | ||
| readonly message: string; | ||
| readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined; | ||
| } | ||
|
|
||
| export interface PathSegment { | ||
| readonly key: PropertyKey; | ||
| } | ||
|
|
||
| export type InferInput<Schema extends StandardTypedV1> = StandardTypedV1.InferInput<Schema>; | ||
| export type InferOutput<Schema extends StandardTypedV1> = StandardTypedV1.InferOutput<Schema>; | ||
| } | ||
|
|
||
| export interface StandardJSONSchemaV1<Input = unknown, Output = Input> { | ||
| readonly '~standard': StandardJSONSchemaV1.Props<Input, Output>; | ||
| } | ||
|
|
||
| export namespace StandardJSONSchemaV1 { | ||
| export interface Props<Input = unknown, Output = Input> extends StandardTypedV1.Props<Input, Output> { | ||
| readonly jsonSchema: Converter; | ||
| } | ||
|
|
||
| export interface Converter { | ||
| readonly input: (options: Options) => Record<string, unknown>; | ||
| readonly output: (options: Options) => Record<string, unknown>; | ||
| } | ||
|
|
||
| export type Target = 'draft-2020-12' | 'draft-07' | 'openapi-3.0' | (object & string); | ||
|
|
||
| export interface Options { | ||
| readonly target: Target; | ||
| readonly libraryOptions?: Record<string, unknown> | undefined; | ||
| } | ||
|
|
||
| export type InferInput<Schema extends StandardTypedV1> = StandardTypedV1.InferInput<Schema>; | ||
| export type InferOutput<Schema extends StandardTypedV1> = StandardTypedV1.InferOutput<Schema>; | ||
| } | ||
|
|
||
| /** Combined interface for schemas with both validation and JSON Schema conversion (e.g., Zod v4). */ | ||
| export interface StandardSchemaWithJSON<Input = unknown, Output = Input> { | ||
| readonly '~standard': StandardSchemaV1.Props<Input, Output> & StandardJSONSchemaV1.Props<Input, Output>; | ||
| } | ||
|
|
||
| // Type guards | ||
|
|
||
| export function isStandardJSONSchema(schema: unknown): schema is StandardJSONSchemaV1 { | ||
| if (schema == null) return false; | ||
| const schemaType = typeof schema; | ||
| if (schemaType !== 'object' && schemaType !== 'function') return false; | ||
| if (!('~standard' in (schema as object))) return false; | ||
| const std = (schema as StandardJSONSchemaV1)['~standard']; | ||
| return typeof std?.jsonSchema?.input === 'function' && typeof std?.jsonSchema?.output === 'function'; | ||
| } | ||
|
|
||
| export function isStandardSchema(schema: unknown): schema is StandardSchemaV1 { | ||
| if (schema == null) return false; | ||
| const schemaType = typeof schema; | ||
| if (schemaType !== 'object' && schemaType !== 'function') return false; | ||
| if (!('~standard' in (schema as object))) return false; | ||
| const std = (schema as StandardSchemaV1)['~standard']; | ||
| return typeof std?.validate === 'function'; | ||
| } | ||
|
|
||
| export function isStandardSchemaWithJSON(schema: unknown): schema is StandardSchemaWithJSON { | ||
| return isStandardJSONSchema(schema) && isStandardSchema(schema); | ||
| } | ||
|
|
||
| // JSON Schema conversion | ||
|
|
||
| export function standardSchemaToJsonSchema(schema: StandardJSONSchemaV1, io: 'input' | 'output' = 'input'): Record<string, unknown> { | ||
| return schema['~standard'].jsonSchema[io]({ target: 'draft-2020-12' }); | ||
| } | ||
|
|
||
| // Validation | ||
|
|
||
| export type StandardSchemaValidationResult<T> = { success: true; data: T } | { success: false; error: string }; | ||
|
|
||
| export async function validateStandardSchema<T extends StandardJSONSchemaV1>( | ||
| schema: T, | ||
| data: unknown, | ||
| jsonSchemaValidatorInstance?: jsonSchemaValidator | ||
| ): Promise<StandardSchemaValidationResult<StandardJSONSchemaV1.InferOutput<T>>> { | ||
| // Use native validation if available | ||
| if (isStandardSchema(schema)) { | ||
| const result = await schema['~standard'].validate(data); | ||
| if (result.issues && result.issues.length > 0) { | ||
| const errorMessage = result.issues.map((i: StandardSchemaV1.Issue) => i.message).join(', '); | ||
| return { success: false, error: errorMessage }; | ||
| } | ||
| return { success: true, data: (result as StandardSchemaV1.SuccessResult<unknown>).value as StandardJSONSchemaV1.InferOutput<T> }; | ||
| } | ||
|
|
||
| // Fall back to JSON Schema validation | ||
| if (jsonSchemaValidatorInstance) { | ||
| const jsonSchema = standardSchemaToJsonSchema(schema, 'input'); | ||
| const validator = jsonSchemaValidatorInstance.getValidator<StandardJSONSchemaV1.InferOutput<T>>(jsonSchema as JsonSchemaType); | ||
| const validationResult = validator(data); | ||
|
|
||
| if (validationResult.valid) { | ||
| return { success: true, data: validationResult.data }; | ||
| } | ||
| return { success: false, error: validationResult.errorMessage ?? 'Validation failed' }; | ||
| } | ||
|
|
||
| // No validation - trust the data | ||
| return { success: true, data: data as StandardJSONSchemaV1.InferOutput<T> }; | ||
| } | ||
|
|
||
| // Prompt argument extraction | ||
|
|
||
| export function promptArgumentsFromStandardSchema( | ||
| schema: StandardJSONSchemaV1 | ||
| ): Array<{ name: string; description?: string; required: boolean }> { | ||
| const jsonSchema = standardSchemaToJsonSchema(schema, 'input'); | ||
| const properties = (jsonSchema.properties as Record<string, { description?: string }>) || {}; | ||
| const required = (jsonSchema.required as string[]) || []; | ||
|
|
||
| return Object.entries(properties).map(([name, prop]) => ({ | ||
| name, | ||
| description: prop?.description, | ||
| required: required.includes(name) | ||
| })); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
file copied from https://github.com/standard-schema/standard-schema?tab=readme-ov-file