---
title: How to add tools to your eve agent
description: Add tools to an eve agent by creating a TypeScript file under agent/tools/ with defineTool, and gate sensitive ones on human approval with needsApproval.
url: /kb/guide/how-to-add-eve-tools
canonical_url: "https://vercel.com/kb/guide/how-to-add-eve-tools"
last_updated: 2026-07-29
authors: Ben Sabic
related: []
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---

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:

### Agent prompt

```txt
I want to add a custom tool to my eve agent. Use this guide as your reference: https://vercel.com/kb/guide/how-to-add-eve-tools.

Before writing any code, ask me what the tool should do. Find out:

- The action it performs (an API it calls, a query it runs, a file it writes)
- Its inputs and their types
- What it returns, and whether any of that is sensitive
- Whether the action has side effects or is hard to undo (sending email, charging a card, deleting data), so we can decide whether it needs human approval

Once you have my answers, create the tool as one TypeScript file and follow these conventions:

- Put it under `agent/tools/`. The filename is the name the model sees, so use snake_case ASCII (a file named `get_weather.ts` is exposed as `get_weather`).
- Author it with `defineTool` from `eve/tools`.
- Write a clear description aimed at the model, since that's what it reads to decide when to call the tool.
- Define an `inputSchema` with Zod. This is required. For a tool that takes no input, use `z.object({})`.
- Write the `execute(input, ctx)` function, sync or async.
- If the tool returns structured data, add an `outputSchema`. When the full return is richer than the model needs, use `toModelOutput` to shape what the model sees.
- Don't return secrets, credentials, or unneeded personal data. Filter and redact first, because the model and any connected channel can read whatever you return.

If the action is sensitive or hard to undo, gate it on human approval. Set `needsApproval` with the helpers from `eve/tools/approval`: `never()` (the default), `once()`, or `always()`. When the decision depends on the input, pass a predicate that receives `{ toolName, toolInput, approvedTools }` and returns a boolean, and guard against `toolInput` being undefined.

One distinction to keep straight: use `needsApproval` when you've decided what to do but a person should sign off first. If instead the tool is missing information it shouldn't guess, eve's built-in `ask_question` already handles that, so you don't need to build it.

When the file is ready, tell me how to test it: start the dev server with `eve dev`, send a request that should trigger the tool, then open Agent Runs in the Vercel dashboard to confirm it ran.

If any of my requirements are unclear, ask before you generate the file rather than guessing.
```

## 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