> ## Documentation Index
> Fetch the complete documentation index at: https://docs.voiceos.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Defining tools

> Descriptions the agent routes on, schemas it fills, and handlers that behave.

Tools are the whole interface between the agent and your code. Each one is
declared in the manifest and implemented in your MCP server under the same name.
VoiceOS namespaces them per integration, so your `send_message` never collides
with anyone else's.

```json theme={null}
{
  "name": "top_stories",
  "title": "Top stories",
  "description": "Fetch the current Hacker News front page. Use when the user asks what's on Hacker News, tech news headlines, or what's trending on HN.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "count": { "type": "number", "description": "How many stories (1-8, default 5)" }
    }
  }
}
```

## Descriptions are routing rules

Every enabled integration's tools are offered to the agent on **every turn**,
and the `description` is **the only thing the model reads about your
integration** when deciding what to call. Write it in two parts:

1. **What it does** — one sentence.
2. **When to use it** — literally *"Use when the user asks …"*, with the
   phrasings people actually say.

<Warning>
  A vague description doesn't make your tool a fallback. It makes it invisible.
</Warning>

`name` must be `snake_case` starting with a letter. Keep `title` short — it's
what users see in the UI.

## Input schemas

`inputSchema` is standard JSON Schema (draft 2020-12).

* **Describe every property.** Descriptions steer the model's argument-filling
  the same way the tool description steers routing.
* Mark true requirements in `required`.
* **Validate again in your handler.** Treat arguments as user input.
* For acting tools, the user can **edit any bound argument** on the
  [confirmation card](/integrations/confirmations) before it reaches you.

## Handlers

Your server is a standard MCP server. Handlers receive the validated arguments
and return an MCP result:

```ts theme={null}
server.registerTool(
  "top_stories",
  { title, description, inputSchema: { count: z.number().min(1).max(8).optional() } },
  async ({ count }) => {
    const stories = await fetchTopStories(count ?? 5);
    return {
      content: [{
        type: "text",
        text: JSON.stringify({
          stories,                    // data — the model narrates from this
          ...glanceResult([...]),     // card — the user sees this
        }),
      }],
    };
  },
);
```

Three conventions:

* **Return data *and* a card.** The model reads the JSON; the user sees the
  [glance card](/integrations/result-cards). A result the user can't see isn't
  finished — and a card the model can't read from isn't either.
* **Configuration arrives as environment variables** named after your
  [setup fields](/integrations/setup-fields). There is no context object.
* **Throw on failure.** Never fabricate data or claim success. A tool error ends
  the agent's attempt and your message is what the model explains to the user,
  so make it actionable:
  `"ELEVENLABS_API_KEY was rejected (401). Check the key in Configure."`

## Sync vs background

| Mode             | Contract                                                                                                                                               | Use for                                            |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------- |
| `sync` (default) | Return within the turn — target well under 30 s. Put `AbortSignal.timeout(8000)` on your fetches so a slow API fails fast instead of hanging the turn. | Lookups, quick actions.                            |
| `background`     | Return a handle immediately. VoiceOS tracks the task in the side notch, then chimes and shows your glance card on completion.                          | Anything over \~20 s: exports, long jobs, polling. |

Background tools declare both the mode and the permission:

```json theme={null}
"execution": { "mode": "background", "estimatedDurationMs": 30000 },
...
"permissions": [{ "kind": "background" }]
```

Declaring the mode without the permission is a validation error.

## Network

Declare an egress allowlist and keep it minimal:

```json theme={null}
"permissions": [{ "kind": "network", "domains": ["api.elevenlabs.io"] }]
```

Permissions are shown to the user at install time — a short list reads as
trustworthy, a long one reads as a liability.

## Acting vs reading

The most important tool-design rule:

| Tool                                             | Rule                                                                               |
| ------------------------------------------------ | ---------------------------------------------------------------------------------- |
| **Acts** — send, create, delete, post, pay, book | **Must** declare a [`confirmation`](/integrations/confirmations).                  |
| **Reads** — get, list, search, fetch, check      | **Must not**. The agent runs these freely; that's what makes lookups feel instant. |

<Card title="Next: Result cards" icon="layout-grid" href="/integrations/result-cards">
  Make every result worth glancing at.
</Card>
