# 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](https://ts.sdk.modelcontextprotocol.io/v2/servers/tools.md).

```ts
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](https://ts.sdk.modelcontextprotocol.io/v2/testing.md) 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
}
```

::: info Coming from v1?
Raw shapes (`inputSchema: { name: z.string() }`) are deprecated — pass a schema object. See the [upgrade guide](https://ts.sdk.modelcontextprotocol.io/v2/migration/upgrade-to-v2.md).
:::

## 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`.

```ts
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`.

```ts
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`.

```ts
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](https://ts.sdk.modelcontextprotocol.io/v2/servers/elicitation.md) form responses. Build one from the `validators/ajv` subpath, which re-exports the SDK's bundled `Ajv` and `addFormats`.

```ts
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.

```ts
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's `argsSchema` accept 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.
- `jsonSchemaValidator` on the server options swaps the validator for elicitation form responses; `fromJsonSchema` takes its own as a second argument.
- The default validator is runtime-selected — AJV on Node.js, `@cfworker/json-schema` on workerd and browsers — and the `validators/ajv` and `validators/cf-worker` subpaths force either one.
