Schema libraries
Register a tool with an ArkType schema
inputSchema accepts any Standard Schema that can produce JSON Schema — ArkType works as-is, no wrapper, exactly like the Zod schemas in Tools.
import { McpServer } from '@modelcontextprotocol/server';
import { type } from 'arktype';
const server = new McpServer({ name: 'schema-zoo', version: '1.0.0' });
server.registerTool(
'greet',
{
description: 'Greet someone by name',
inputSchema: type({ name: 'string', 'times?': '1 <= number.integer <= 5' })
},
async ({ name, times }) => ({
content: [{ type: 'text', text: Array.from({ length: times ?? 1 }, () => `Hello, ${name}`).join('\n') }]
})
);From that one schema the SDK derives the JSON Schema the model sees, validates arguments before your handler runs, and infers the handler's argument types — name is string, times is number | undefined.
Every call on this page comes from an in-memory Client connected to this server — Test a server shows that wiring. Call greet with times: 99 and the SDK rejects the call with ArkType's own message; the handler never runs:
{
content: [
{
type: 'text',
text: 'Input validation error: Invalid arguments for tool greet: times: times must be at most 5 (was 99)'
}
],
isError: true
}Coming from v1?
Raw shapes (inputSchema: { name: z.string() }) are deprecated — pass a schema object. See the upgrade guide.
Register a tool with a Valibot schema
Valibot does not expose JSON Schema conversion on the schema itself — wrap it with toStandardJsonSchema from @valibot/to-json-schema.
import { toStandardJsonSchema } from '@valibot/to-json-schema';
import * as v from 'valibot';
server.registerTool(
'shout',
{ description: 'Greet someone, loudly', inputSchema: toStandardJsonSchema(v.object({ name: v.string() })) },
async ({ name }) => ({ content: [{ type: 'text', text: `HELLO, ${name.toUpperCase()}` }] })
);tools/list now advertises shout with the JSON Schema the wrapper derives, and Valibot parses every call that reaches the handler.
Start from JSON Schema you already have
fromJsonSchema (exported from @modelcontextprotocol/server) wraps a plain JSON Schema document so you can register it without a schema library. The generic parameter types the handler's arguments; omit it and they are unknown.
import { fromJsonSchema } from '@modelcontextprotocol/server';
server.registerTool(
'farewell',
{
description: 'Say goodbye',
inputSchema: fromJsonSchema<{ name: string }>({
type: 'object',
properties: { name: { type: 'string' } },
required: ['name']
})
},
async ({ name }) => ({ content: [{ type: 'text', text: `Goodbye, ${name}` }] })
);tools/list advertises the document you passed, unchanged:
{
type: 'object',
properties: { name: { type: 'string' } },
required: [ 'name' ]
}The SDK checks every call against it with a real JSON Schema validator — the last two sections pick which one.
Validate structured output with any library
outputSchema — and a prompt's argsSchema — follow the same Standard Schema rule. Return the matching value as structuredContent, next to the human-readable content.
server.registerTool(
'measure',
{
description: 'Measure the length of a name',
inputSchema: type({ name: 'string' }),
outputSchema: type({ name: 'string', length: 'number' })
},
async ({ name }) => {
const output = { name, length: name.length };
return { content: [{ type: 'text', text: JSON.stringify(output) }], structuredContent: output };
}
);The SDK validates structuredContent against the ArkType schema before the result leaves your server. Calling measure with { name: 'Ada' } returns both renderings:
{
content: [ { type: 'text', text: '{"name":"Ada","length":3}' } ],
structuredContent: { name: 'Ada', length: 3 }
}Swap the JSON Schema validator
The server runs a JSON Schema validator in two places: a fromJsonSchema schema, and elicitation form responses. Build one from the validators/ajv subpath, which re-exports the SDK's bundled Ajv and addFormats.
import { addFormats, Ajv, AjvJsonSchemaValidator } from '@modelcontextprotocol/server/validators/ajv';
const ajv = new Ajv({ strict: true, allErrors: true });
addFormats(ajv);
const validator = new AjvJsonSchemaValidator(ajv);
const strict = new McpServer({ name: 'schema-zoo', version: '1.0.0' }, { jsonSchemaValidator: validator });strict now checks elicitation form responses with your Ajv instance.
WARNING
jsonSchemaValidator covers elicitation form responses only. A fromJsonSchema schema binds its validator at creation — pass yours as the second argument: fromJsonSchema(document, validator).
Pick the validator for your runtime
Leave jsonSchemaValidator unset and the SDK selects by runtime: AJV on Node.js, @cfworker/json-schema on workerd and in browsers. Import from the validators/cf-worker subpath to pin the lightweight one anywhere.
import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/server/validators/cf-worker';
const edge = new McpServer({ name: 'schema-zoo', version: '1.0.0' }, { jsonSchemaValidator: new CfWorkerJsonSchemaValidator() });edge runs the same two validation paths through @cfworker/json-schema instead of AJV, on any runtime.
Recap
inputSchema,outputSchema, and a prompt'sargsSchemaaccept any Standard Schema that exposes JSON Schema — Zod and ArkType as-is, Valibot through@valibot/to-json-schema.- The raw-shape overload (
inputSchema: { name: z.string() }) is deprecated; pass a schema object. fromJsonSchema(document)registers a JSON Schema you already have; the generic parameter types the handler's arguments.jsonSchemaValidatoron the server options swaps the validator for elicitation form responses;fromJsonSchematakes its own as a second argument.- The default validator is runtime-selected — AJV on Node.js,
@cfworker/json-schemaon workerd and browsers — and thevalidators/ajvandvalidators/cf-workersubpaths force either one.