Skip to main content
Tools are the primary way your agent interacts with the outside world — fetching data, querying databases, calling APIs, and performing computations. Each tool you pass to defineAgent is advertised to the model, and the runtime handles the full call-validate-execute cycle automatically.

The AgentTool interface

Every tool you define implements this interface:
string
required
A unique identifier for the tool. The model uses this name when requesting a call. Use snake_case — most model providers require it.
string
required
A plain-English description of what the tool does. The model reads this to decide when to call the tool, so write it from the model’s perspective: what information does the tool provide, and when should it be used?
z.ZodType<Input>
A Zod schema for the tool’s input. Preferred over inputSchema — the runtime converts it to JSON Schema automatically (to advertise to the model) and validates the model’s input against it before calling execute. If validation fails, the model receives a descriptive error result instead of a crash.
JsonSchema
A raw JSON Schema for the tool’s input. Use this if you cannot express the input as a Zod schema. If both zodSchema and inputSchema are present, zodSchema is used for validation and inputSchema is ignored.
boolean
Annotates the tool as one that mutates external state (sends an email, writes to a database, calls a payment API, etc.). This is a documentation signal — use it to communicate intent to other developers reading the agent definition.
(input: Input, meta: ToolExecMeta) => Promise<unknown> | unknown
required
The async function that runs when the model calls this tool. Return any serializable value — it becomes the tool result fed back to the model. Throw an error to return an error result.

The ToolExecMeta argument

execute receives a second meta argument with runtime context:
string
The unique ID for this tool invocation, assigned by the model. Useful for correlating logs and traces.
AbortSignal | undefined
The request’s AbortSignal. Pass it to any fetch calls or async work inside the tool so that a client disconnect cancels in-flight I/O.
(payload?: unknown) => never
Throw this to suspend the run and request human approval before proceeding (human-in-the-loop). The run is checkpointed and the client receives a suspended event. Resume with resumeAgent after the operator approves.

Complete tool example

Input validation

The runtime validates tool inputs against zodSchema before calling execute. You never need to validate inside the function body:
If the model sends invalid input (a missing required field, a number where a string is expected), the runtime returns a descriptive Zod error message to the model as the tool result. The model can then correct its call on the next iteration.
Always use zodSchema for user-facing tools. Without it, malformed model output reaches execute unvalidated. Raw inputSchema provides no runtime protection — it only shapes what the model is told.

Preventing infinite loops with maxIterations

By default the agent runtime caps the model↔tool loop at 10 iterations. Each call to the model counts as one iteration, regardless of how many tools it calls in that turn. Set maxIterations on defineAgent to tighten or loosen this limit:
When the cap is reached the runtime emits a final event with whatever text the model produced last. The stoppedAtCap flag in AgentRunResult tells you whether the run ended this way.
Set maxIterations to a small value (2–3) for tools that should complete in one or two steps, and only raise it for complex multi-step workflows. Smaller caps reduce latency and cost when a runaway tool loop occurs.

Standalone tools and anvil mcp

Tools defined inline in defineAgent are only available to that agent route. If you also want a tool served via the MCP protocol (for use with Claude Desktop or other MCP clients), define it as a standalone tool in server/tools/:
Running anvil mcp serves both server/tools/*.ts files and any MCP-exposed route-derived tools from the same endpoint. Route-level tools defined in defineAgent are not automatically MCP-exposed — only server/tools/ files and routes with meta.mcp.expose: true appear there.