> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mcp-b.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Use WebMCP input schemas and structured output

> Validate and transform WebMCP tool arguments with Standard Schema, then add optional MCP output metadata.

The core [`usewebmcp`](/packages/usewebmcp/reference) hook accepts upstream JSON Schema metadata and does not validate runtime input; it reports a Standard Schema object, such as a Zod schema, as a registration error. Standard Schema conversion and validation are MCP-B React extensions, described in the [schema boundary explanation](/explanation/strict-core-vs-mcp-b-extensions#why-standard-schema-support-lives-in-the-adapters).

## Validate React tool input

Install [`@mcp-b/react-webmcp`](/packages/react-webmcp/reference) and the schema library used in
this example:

```bash title="Install the hook and Zod" theme={null}
pnpm add @mcp-b/react-webmcp zod@^4.2
```

Provide a runtime as described in the [framework guide](/how-to/frameworks). Pass the complete
schema as `inputSchema`. The adapter publishes JSON Schema metadata and invokes the schema's
validator before the handler:

```ts title="useTotalTool.ts" theme={null}
'use client';

import { useWebMCP } from '@mcp-b/react-webmcp';
import { z } from 'zod';

const totalInput = z.object({
  count: z.string().regex(/^\d+$/, 'Use digits for count').transform(Number),
  limit: z.number().default(10),
});

export function useTotalTool() {
  return useWebMCP({
    name: 'calculate_total',
    description: 'Add a numeric count to a limit, which defaults to 10',
    inputSchema: totalInput,
    execute: ({ count, limit }) => ({ total: count + limit }),
  });
}
```

Call `useTotalTool()` in your component. An agent call with `{ count: "2" }`, or a local
`tool.execute({ count: "2" })`, returns `{ total: 12 }`. The handler receives the number `2` and
the default limit `10`. Invalid input fails before execution; async validation is awaited.

If your library supplies only Standard Schema validation, also provide its Standard JSON Schema
converter. Consult the [Standard JSON Schema implementer guide](https://standardschema.dev/json-schema)
for your library's conversion API. The core hook rejects any object with a `~standard` property,
including a converter-only object; the MCP-B React adapter requires both conversion and validation.

## Infer input from a JSON Schema literal

Pass an inline JSON Schema object to either hook. Both hooks preserve literal types without `as const`.
For a reusable schema constant, use `as const` to keep its literal information. Neither hook validates
plain JSON Schema at runtime; validate in your handler when needed.

For direct browser registration, activate the Community Group's declarations. Install the version
the `@mcp-b/*` packages use so the project loads one copy of them:

```bash title="Install browser declarations" theme={null}
pnpm add -D webmcp-types@0.1.9
```

## Convert a Standard JSON Schema implementation outside React

For direct native or polyfilled `registerTool()` calls, convert the metadata and validate in the
callback. The MCP-B adapter lives at [`@mcp-b/webmcp-ts-sdk/schema`](/packages/webmcp-ts-sdk/reference#schema-boundary):

```bash title="Install the adapter and Zod" theme={null}
pnpm add @mcp-b/webmcp-polyfill @mcp-b/webmcp-ts-sdk zod@^4.2
```

```ts title="total-tool.ts" theme={null}
import { installWebMCP } from '@mcp-b/webmcp-polyfill';
import { normalizeInputSchema } from '@mcp-b/webmcp-ts-sdk/schema';
import { z } from 'zod';

const totalInput = z.object({
  count: z.string().regex(/^\d+$/).transform(Number),
  limit: z.number().default(10),
});

export async function registerTotalTool() {
  installWebMCP();
  const context = document.modelContext;
  if (!context) throw new Error('WebMCP is unavailable');
  const registration = new AbortController();

  await context.registerTool(
    {
      name: 'calculate_total',
      description: 'Add a numeric count to a limit, which defaults to 10',
      // Keep vendor validation in this handler, including when called through MCP.
      inputSchema: { ...normalizeInputSchema(totalInput).inputSchema },
      async execute(input) {
        const result = await totalInput['~standard'].validate(input);
        if (result.issues) {
          const text = result.issues.map((issue) => issue.message).join('; ');
          return { content: [{ type: 'text', text }], isError: true };
        }
        return { total: result.value.count + result.value.limit };
      },
    },
    { signal: registration.signal }
  );

  return () => registration.abort();
}
```

Call `registerTotalTool()` in browser setup, then call its returned cleanup function when the tool
is no longer needed. Keep the spread when copying the normalized schema: it passes only JSON metadata,
so MCP calls do not apply the vendor transforms a second time. The callback invokes the schema's own `validate()` method; no second validator
is needed. The standalone polyfill does not invoke that method for you. Invalid input returns an
error-flagged result instead of throwing because the polyfill reports every thrown error as the
generic `Tool execution failed`.

If you use [`BrowserMcpServer`](/packages/webmcp-ts-sdk/reference#schema-boundary), the official MCP
server runs a preserved Standard Schema validator on MCP client calls. Direct browser calls and
native mirrors bypass that MCP validation. Use the example above to validate in the browser callback with plain JSON metadata if both routes
can execute it, or use `@mcp-b/react-webmcp` when registering a Standard Schema from React.

## Add MCP-B output metadata

Add `outputSchema` when an MCP client needs typed structured data. The SDK descriptor type checks
the input and output when you declare the descriptor separately:

```ts title="search-summary-tool.ts" theme={null}
import '@mcp-b/global';
import type { JsonSchemaForInference, ToolDescriptorFromSchema } from '@mcp-b/webmcp-ts-sdk';

const inputSchema = {
  type: 'object',
  properties: {
    query: { type: 'string' },
    limit: { type: 'integer', minimum: 1 },
  },
  required: ['query'],
  additionalProperties: false,
} as const satisfies JsonSchemaForInference;

const outputSchema = {
  type: 'object',
  properties: {
    total: { type: 'integer' },
    items: { type: 'array', items: { type: 'string' } },
  },
  required: ['total'],
  additionalProperties: false,
} as const satisfies JsonSchemaForInference;

const tool = {
  name: 'search_summary',
  description: 'Search with summary',
  inputSchema,
  outputSchema,
  execute: ({ query }) => ({ total: 1, items: [query] }),
} satisfies ToolDescriptorFromSchema<typeof inputSchema, typeof outputSchema>;

const context = document.modelContext;
if (!context) throw new Error('WebMCP is unavailable');
await context.registerTool(tool);
```

`outputSchema` is MCP-B metadata for output inference and structured MCP responses. If your handler
constructs an MCP `CallToolResult` directly, include schema-compatible `structuredContent` as well
as human-readable `content`. The [`@mcp-b/webmcp-ts-sdk` reference](/packages/webmcp-ts-sdk/reference)
documents the adapter extensions and schema boundary.

`outputSchema` is enforced on calls through the official MCP server. `@mcp-b/react-webmcp` uses it
for inference and checks JSON serializability, but does not enforce the output schema on local or
direct browser calls. For React, the [MCP-B hook example](/packages/react-webmcp/overview) shows a complete
component with an output schema.

## Check the agent-facing schema

After registration, inspect the emitted schema in Chrome's [WebMCP DevTools
panel](https://developer.chrome.com/docs/devtools/application/webmcp). Then run the [Lighthouse
WebMCP schema validity
audit](https://developer.chrome.com/docs/lighthouse/agentic-browsing/webmcp-schema-validity).

Use Chrome's [WebMCP best
practices](https://developer.chrome.com/docs/ai/webmcp/best-practices) for names, descriptions, and
schema design. Use [WebMCP evals](https://developer.chrome.com/docs/ai/webmcp/evals) to test whether
agents select the tool and supply the expected arguments.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.