# Tools (/docs/tools)

> Write typed tools in TypeScript with defineTool.

Tools are loaded from their `.tool.ts` files. They can import sibling TypeScript
modules and npm packages installed in the project. `create-plico` includes a
`package.json` with `type: "module"` for ESM tool imports.

Wrap a tool in `defineTool` from `@plico-ai/sdk` to get editor types. `defineTool` infers the
handler `input` from `inputSchema` and returns its argument unchanged.

```ts title="tools/add.tool.ts"
import { defineTool } from "@plico-ai/sdk";
import { z } from "zod";

export default defineTool({
  name: "add",
  description: "Add two numbers.",
  inputSchema: z.object({ a: z.number(), b: z.number().default(0) }),
  capabilities: [],
  approval: { required: false },
  handler: ({ a, b }) => a + b,
});
```

## Fields

| Field | Meaning |
| --- | --- |
| `name` | The name the model calls. Unique across the project; `list_files`, `read_file`, `write_file`, `edit_file` and `load_skill` are reserved. |
| `description` | Tells the model when to use the tool. |
| `inputSchema` | Zod or JSON Schema for the arguments. |
| `capabilities` | Required list of labels for what the tool touches, such as `["ticket:write"]`. Plico records it in `tools.discovered` and does not enforce it. |
| `approval` | Required. `{ required: true, reason }` holds each call for a person. See [Approvals](/docs/approvals). |
| `handler` | `(input, ctx)`. `input` is the parsed arguments; `ctx` has `artifacts`, `context`, `signal` and an idempotency key. |

A tool must export its definition as the file default.

## Input schemas

Declare `inputSchema` with Zod 3, Zod 4, or JSON Schema. Plico validates tool calls
with that schema and sends JSON Schema to the model. Parsed Zod values, including
defaults and transforms, reach the handler.

> **Note:** Add `zod` to the project dependencies before importing it from a tool.

Validation rejects duplicate names declared across `.tool.ts` files, and readiness
reports the duplicate under tool discovery.

## When a tool call fails

In a run with a real model, a bad tool call does not end the run at once. An unknown tool
name, arguments that fail `inputSchema`, or a handler that throws is returned to the model as
the tool result, `{ "error": "<message>" }`, and the event log records it as a `tool.result`.
The model can then correct the call. This matters most for small local models, which often
send numbers as strings.

A run allows 2 such failures. The third ends the run with the original error, in the same
`run.error` event as before. Scripted runs (`plico run --dry --script`) and approval denials
still fail immediately.

## Writing artifacts

A tool can read, write, and list artifacts with `ctx.artifacts`: `read(path)`,
`write(path, content, { mimeType })`, and `list()`. See [Artifacts](/docs/artifacts).

## Host context

When a host app starts a run with a `context` (see [Threads](/docs/threads#host-data-metadata-and-context)),
the handler receives it as `ctx.context`, such as `{ userId: "u-1" }`. It is `undefined` when
the host passed none, and it is the same value after a restart. Plico records it in the run's
event log, so keep secrets out of it and look them up inside the tool.