# How to add tools to your eve agent

**Author:** Ben Sabic

---

Tools give an [eve](https://vercel.com/eve) agent typed actions it can call, such as hitting an API, running a query, or writing a file. With them, the agent does the real work rather than just describing it. eve exposes any tool you place under `agent/tools/`, and the filename becomes the tool name the model sees. Unlike skills, there's no separate CLI to install tools: you author each one as a TypeScript file with `defineTool`, and eve discovers it with no registration step.

This guide walks you through writing a basic tool, gating a sensitive one on human approval, and then confirming that your agent calls the tool for the right requests.

## Prerequisites

Before you begin, you need an eve project and Node.js installed.

- To create a new project, run `npx eve@latest init my-agent`.
  
- To add eve to an existing app, follow the [quickstart steps](https://eve.dev/docs/getting-started#quick-start).
  

## Quick start with an AI coding agent

If you're using an AI coding agent like Claude Code or Cursor, give it this prompt, and it'll help you add a tool to your eve agent:

## How eve loads tools

eve discovers the files under `agent/tools/` and advertises each tool's name, description, and input schema to the model. During this discovery step the model sees only those descriptors, never your implementation. When the model decides a tool fits the task, it calls the tool with arguments that match your `inputSchema`, and eve runs your `execute` function in your app runtime with full access to `process.env` and shared code in `lib/`, not in the [sandbox](https://eve.dev/docs/sandbox).

eve never runs a tool during discovery. Only what the model actually calls gets executed, and completed steps don't re-run, so your tools stay predictable across a session. There's no separate registry to keep in sync: add the file and eve discovers it, move or rename it and its identity moves with it.

## Add a tool by creating a file

The smallest tool is a single TypeScript file under `agent/tools/`. Its name comes from the filename, which must be snake\_case ASCII, so a file at `agent/tools/get_weather.ts` is exposed to the model as `get_weather`.

Create `agent/tools/get_weather.ts`:

`import { defineTool } from "eve/tools"; import { z } from "zod"; export default defineTool({ description: "Get the current weather for a city.", inputSchema: z.object({ city: z.string().min(1) }), async execute({ city }, ctx) { return { city, condition: "Sunny", temperatureF: 72 }; }, });`

Every tool definition needs four things:

- **Filename slug** under `agent/tools/`: the model-facing name.
  
- `**description**`: what the tool does, written for the model to read.
  
- `**inputSchema**`: a Zod schema (or any Standard Schema, or a plain JSON Schema object). This is required. For a tool that takes no input, pass `z.object({})`. Zod and Standard Schema infer the `input` type in `execute`.
  
- `**execute(input, ctx)**`: the implementation, which can be sync or async. The `ctx` argument carries runtime accessors like `ctx.session`, `ctx.getSandbox()`, and `ctx.getSkill(id)`.
  

When a tool returns structured data, add an optional `outputSchema`, which also types the `execute` return when you use Zod or Standard Schema. If the full return is richer than the model needs, project it down with `toModelOutput`, which receives the typed `execute` return and shapes only what the model sees. Channel handlers and hooks still receive the full output, so a channel can render rich platform output that the model never reads:

``toModelOutput(output) { return { type: "text", value: `Report for ${output.domain}: score ${output.score}.` }; },``

Don't return secrets, credentials, unnecessary personal data, or unbounded sensitive content from a tool. Filter, minimize, and redact tool outputs before returning them, because the model and any connected channel can see what you return.

## Gate a tool on human approval

Some actions shouldn't run without a person signing off, such as issuing a refund, sending an email, or anything irreversible. A tool can require approval before it runs: set `needsApproval` with the helpers from `eve/tools/approval`.

Create `agent/tools/refund_charge.ts`:

`import { defineTool } from "eve/tools"; import { always } from "eve/tools/approval"; import { z } from "zod"; export default defineTool({ description: "Refund a charge.", inputSchema: z.object({ chargeId: z.string(), amount: z.number() }), needsApproval: always(), // or once() / never() / a predicate async execute(input) { return refund(input); }, });`

The helpers cover the common cases:

| Helper     | Behavior                                                                     |
| ---------- | ---------------------------------------------------------------------------- |
| `never()`  | Never require approval. This is the default when you omit `needsApproval`.   |
| `once()`   | Require approval the first time the tool runs in a session, then auto-allow. |
| `always()` | Require approval before every call.                                          |

When the decision depends on the input, pass your own predicate instead of a helper. It receives `{ toolName, toolInput, approvedTools }` and returns a boolean. Guard the access, since `toolInput` can be undefined. To require approval only when an amount crosses a threshold:

`needsApproval: ({ toolInput }) => (toolInput?.amount ?? 0) > 1000,`

Because an omitted `needsApproval` behaves like `never()`, tool calls can run without human approval by default. Require approval or another safeguard for sensitive, irreversible, regulated, financial, healthcare, employment, housing, legal, safety-impacting, user-impacting, or external side-effecting actions. Gating a side effect this way also keeps non-idempotent work safe: a charge or email behind `always()` can't fire from a re-run step without a fresh human decision.

When a gated tool is called, the run parks durably at `session.waiting` until a person answers, then picks back up exactly where it left off. The pause survives restarts, since nothing is held in memory while it waits.

Here's how a waiting run resolves:

- eve emits an `input.requested` stream event carrying the pending approval.
  
- The client answers with structured `inputResponses` keyed by request ID, or with a normal follow-up message.
  
- Follow-up text that matches an option label automatically resolves the request.
  

Approval is one of two ways a tool can pause for a person. The other is the built-in `ask_question` tool, which lets the agent ask the user a clarifying question using the same protocol. It's part of the default harness, so you don't define it yourself. The model calls it with:

- `prompt`: the question to put to the user.
  
- `options` (optional): choices that channels render as buttons or a select menu.
  
- `allowFreeform`: whether the user can answer with free text.
  

The rule of thumb: reach for `ask_question` when the model is missing information it shouldn't guess at, and `needsApproval` when the model has decided what to do, but a person should sign off first.

## Verify the tool runs

Start your agent and send a request that needs the tool. If you scaffolded with `init`, the dev server is already running; otherwise start it with `eve dev`. When a request matches, the model calls the tool with arguments from your `inputSchema`, and eve runs your `execute` function and returns the result to the model.

To see which tools ran, along with timing and token usage, open [Agent Runs](https://vercel.com/d?to=%2F%5Bteam%5D%2F%5Bproject%5D%2Fobservability%2Fagent-runs) in the Vercel dashboard.

## Resources and next steps

- [Tools in eve](https://eve.dev/docs/tools): the complete `defineTool` API, `ctx`, `outputSchema`, and `toModelOutput`.
  
- [Human-in-the-loop](https://eve.dev/docs/human-in-the-loop): gate a tool on approval, or have the agent ask the user a question.
  
- [Default harness](https://eve.dev/docs/concepts/default-harness): the built-in file, shell, web, and delegation tools, and how to override or disable them.
  
- [Skills in eve](https://eve.dev/docs/skills): on-demand procedures the model loads when a task calls for them, for guidance rather than actions.
  
- [Dynamic capabilities](https://eve.dev/docs/guides/dynamic-capabilities): resolve a tool set per session with `defineDynamic`.
  

* * *

## Learn more about building with eve

---

[View full KB sitemap](/kb/sitemap.md)
