Skip to main content
A file at tools/<name>.ts that default-exports defineTool({ ... }) registers as the MCP tool <name>. Rename the file and the tool renames with it. .ts, .tsx and .mts are picked up.
tools/check-eligibility.ts

Fields

string
required
Shown to humans in connector UIs.
string
required
LLM-facing. The only thing the model reads before deciding to call this, so it says when to call it and what not to do instead. A tool with no description fails the build check.
Shape
A Zod shape written as a plain object, { amount: z.number() } rather than z.object({ ... }). .describe() on a field reaches the model as that argument’s description. run receives the parsed value, typed off this shape.
Shape
The structured output schema, also a plain Zod shape. The runtime advertises it in tools/list and validates what run returns against it.
ToolHints
Behavioural hints for the host LLM. The runtime translates them into MCP annotations and always fills in the title annotation that Claude’s Connectors Directory requires.
(input) => ToolResult | Promise<ToolResult>
required
The handler. It may be async.

What run can return

A run that throws returns an error envelope telling the host to offer a retry rather than invent a result, so exceptions are safe to let propagate.

Writing the description

The description is what the model sees at the moment it is choosing a tool, so the starter template’s version tells the model when to call it and what to avoid:
How the tools fit together across a conversation goes in the app’s overview instead, which the host reads once at connect.

Tool or endpoint

A tool appears in tools/list and costs a turn, and the model can reason about what came back. An endpoint under api/ is invisible to the model and is called by the widget itself from the browser. Reach for a tool when the model is the caller.