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

# Write a tool

> Define a product capability once. Call it from MCP, a CLI or your messaging agent.

A tool is a TypeScript function with a typed input. It reads from your product or takes an
action, then returns data the caller can use.

Tools are what make a conversation more than an answer box. They let someone check availability,
move a booking or place an order against your real systems. Start with the task a person wants
to complete, then expose the operations needed to do it.

This guide adds `get_hours` to the starter. You will call it from the CLI, then make the same
capability available in your MCP server and messaging agent.

## Before you start

Create a project with the [quickstart](https://flow.engineer/docs/framework/quickstart.md). The starter already imports `tool` and `z`
in `src/tools.ts` and exports a `tools` array.

## Add a tool

Add this definition **inside the existing `tools` array** in `src/tools.ts`. Keep the other
starter tools so its existing commands and test scenarios continue to work.

```ts src/tools.ts — add to the tools array
tool({
  name: "get_hours",
  title: "Opening hours",
  description: "Get the opening and closing time and the business time zone.",
  input: z.object({}),
  output: z.object({
    opens: z.string(),
    closes: z.string(),
    timeZone: z.string(),
  }),
  signIn: "none",
  hints: { readOnly: true, openWorld: false },
  run: () => ({
    opens: "08:00",
    closes: "18:00",
    timeZone: "America/New_York",
  }),
}),
```

This example returns fixed data so you can try it immediately. In your product, `run` can call
your API or database and return its result.

## Call it

Restart the MCP server to load your change, then call the tool in another terminal:

```sh First terminal
npm run mcp
```

In the second terminal:

```sh Second terminal
npm run cli -- get-hours --json
```

**Check it worked:** the CLI returns the hours from your function.

```json Result
{
  "opens": "08:00",
  "closes": "18:00",
  "timeZone": "America/New_York"
}
```

No CLI configuration is needed for this new tool: unnamed commands use the tool's name with
underscores changed to dashes. The MCP server lists `get_hours` too. Restart `npm start` to use
it in the playground, then ask “What time do you open?”

## What each field does

| Field         | Purpose                                                              |
| ------------- | -------------------------------------------------------------------- |
| `name`        | The stable identifier used by the agent and MCP clients              |
| `description` | What the tool does and when a model should call it                   |
| `input`       | A Zod schema that validates the caller's arguments                   |
| `run`         | The function that performs the operation                             |
| `title`       | A short name shown to people                                         |
| `output`      | The result schema; MCP checks it and sends structured content        |
| `signIn`      | Whether the operation needs a verified account                       |
| `hints`       | Whether it reads, writes, can overwrite, or reaches external systems |

> [!NOTE]
>
> `signIn: "none"` makes this public information available without a login. Use the default
> `"required"` for account data and configure authentication on the [MCP server](https://flow.engineer/docs/framework/mcp.md) or
> messaging agent. Hints describe behavior to a host; your tool code still enforces permissions.

## Add input

Describe the input a person would naturally ask for. Zod validates it before `run` is called.
For a tool that looks up a product, the input could be:

```ts Input schema
input: z.object({
  product_id: z.string().describe("The product id returned by a search"),
  include_stock: z.boolean().default(false),
}),
```

Return the data a later tool call needs, such as an id or status. Keep credentials and internal
implementation details out of the returned result.

## Format tool results

In Framework 0.8.0, a tool can provide Markdown for people while retaining structured data for
models and scripts. Add these fields to a tool whose `run` returns the matching object:

```ts Tool result
output: z.object({ order_id: z.string(), ready_by: z.string() }),
format: (result) => `**Order ${result.order_id}** is ready by ${result.ready_by}.`,
```

**Expected result:** `{ order_id: "BL-1040", ready_by: "10:30" }` produces the text
`**Order BL-1040** is ready by 10:30.` MCP returns it in `content` alongside the data in
`structuredContent`; the [CLI](https://flow.engineer/docs/cli.md#format-results-for-people) prints it by default. Messaging
models still receive the data. `format` requires `output` and runs after output validation.

## Use the caller's account

`run` receives a second argument, `context`, supplied by the framework. Use its verified
identity to find the account, rather than asking a model to provide an account id.

| Value                    | Use it for                                                                                                                                                                  |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `context.key`            | Your verified account key, or `null` without one                                                                                                                            |
| `context.person`         | The stable person identity                                                                                                                                                  |
| `context.credentials`    | Server-side access to a configured backend                                                                                                                                  |
| `context.idempotencyKey` | Deduplicating a backend write on retries                                                                                                                                    |
| `context.signal`         | Cancelling work when the conversation closes                                                                                                                                |
| `context.records`        | The agent's own small records, kept across conversations ([records](https://flow.engineer/docs/records.md))                                                                 |
| `context.events`         | The agent's own business events side, once `events.attach(agent)` ran: save a person's watch there ([events](https://flow.engineer/docs/events.md#watches-raise-a-message)) |

The [authentication guide](https://github.com/flow-engineer/flow/blob/main/coding-agents/auth-skill.md)
shows how to connect your existing login and credentials provider.

## Share a status during a slow operation

Hosts that support status text can show a short line while a tool runs and when it finishes:

```ts Tool metadata
status: {
  running: "Checking availability…",
  done: "Availability checked",
},
```

For messaging, Flow handles acknowledgement and background execution. `deadlineMs` limits how
long a reply waits for one tool; it does not replace an API client's own timeout.

## Validate your change

```sh Terminal
npx @flow-engineer/flow check
```

This checks tool descriptors, skill references and CLI configuration. Exercise the tool as
well: a valid schema does not establish that your API call returns the right result.

> **[Teach a workflow](https://flow.engineer/docs/skills.md)**
>
> Explain when and how an agent should use your tools.

> **[Customize the CLI](https://flow.engineer/docs/cli.md)**
>
> Choose command names, arguments and output for your product.
