HomeAPIregisterTool

registerTool

Registers a tool, its schemas, and the view that renders its result.

registerTool declares one tool on the server. It returns the server, therefore you can chain the calls.

import { z } from "zod";

server.registerTool(
  {
    name: "create_poll",
    title: "Create poll",
    description: "Create a poll and render it as an interactive view.",
    inputSchema: z.object({ question: z.string().min(1) }),
    outputSchema: z.object({ pollId: z.string(), total: z.number() }),
    view: { component: "poll" },
  },
  (args) => ({
    content: `Created poll "${args.question}".`,
    structuredContent: { pollId: "1", total: 0 },
  }),
);

Config

Field Type Purpose
name string The identifier of the tool. The model uses it.
title string, optional A label for people.
description string, optional Tells the model when to call the tool.
inputSchema Standard Schema, optional The arguments. The server validates them and infers the type of args.
outputSchema Standard Schema, optional The shape of structuredContent.
annotations ToolAnnotations, optional The standard MCP annotations.
icons Icon[], optional Icons for the tool.
view ViewConfig, optional The view that renders the result.
securitySchemes SecurityScheme[], optional The auth schemes that the tool supports.
_meta ToolMeta, optional More metadata for the host.

Schemas

A schema field takes any Standard Schema that can also produce JSON Schema. In practice that is a zod v4 z.object({ … }); ArkType and Valibot also qualify. The raw zod shape of 0.2.x ({ question: z.string() }) is not accepted. Wrap the fields.

Omit inputSchema for a tool that takes no arguments. Its handler then gets {} as args.

view

Set view.component to the name of a registered view. The value is checked against the ViewNameRegistry interface, which the view generator keeps current.

The other fields of view control the presentation and the CSP of the iframe. See Content Security Policy.

view.cacheHint tells the host how long it may cache the view resource. Your fields are merged over the computed default, field by field.

The view resource is Default hint
A production build with a content hash in its URI { ttlMs: 3_600_000, cacheScope: "private" }
A development shell { ttlMs: 0, cacheScope: "private" }
view: { component: "poll", cacheHint: { ttlMs: 60_000 } }

Both view resources of a tool, the apps-sdk one and the mcp-app one, get the merged hint. For a hint on a whole operation, use the cacheHints option of McpServer.

securitySchemes

securitySchemes: [{ type: "noauth" }, { type: "oauth2", scopes: ["read"] }];

The list tells a client which tools need a sign-in. If you list noauth and oauth2 together, the tool works for an anonymous caller, and it gives more to a caller who signed in.

Handler

(args, ctx) => result | Promise<result>

args holds the parsed input. Its type comes from inputSchema.

ctx is the request context of the SDK, with the _meta field widened by the client hints of an Apps SDK host.

Field Contents
ctx.mcpReq._meta The metadata of the request, including the client hints.
ctx.mcpReq.envelope The reserved envelope keys of the request, such as the client capabilities.
ctx.mcpReq.inputResponses The answers of an elicitation round.
ctx.http?.req The original web Request. Read a header with ctx.http?.req?.headers.get("x-foo").
ctx.http?.authInfo The verified identity. See requireBearerAuth.
ctx.state The sealed state helpers. Present when the server has a state option.

The result

A handler returns one of two things.

Return Meaning
An object with content, and optional structuredContent and _meta The tool completed.
inputRequired({ … }) The tool needs more input first. See elicitation.

content accepts a plain string, one content block, or an array of content blocks. The server normalizes each form. See content helpers.

Always return content

A view renders only on a host that supports MCP Apps or the Apps SDK. Every other client, and the model reading its own transcript, sees content and nothing else. Therefore a tool with a view must still return a short text summary of what the view shows.

return {
  content: `Created poll "${args.question}" with 4 options.`,
  structuredContent: poll,
};

Outside production the server logs a warning once for each view tool that returns no content.

Types accumulate

Each call adds the input, output and _meta shape of that tool to the type of the server. Therefore typeof server carries enough information for injectAppHelpers to give you typed, tool-name-narrowed helpers in the view.

For this to work, export the server type from the same module:

export function createMcpServer() {
  return new McpServer(/* … */).registerTool(/* … */);
}
export type AppServer = ReturnType<typeof createMcpServer>;