> ## Documentation Index
> Fetch the complete documentation index at: https://docs.flow.engineer/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://docs.flow.engineer/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 theme={null}
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 theme={null}
npm run mcp
```

In the second terminal:

```sh Second terminal theme={null}
npm run cli -- get-hours --json
```

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

```json Result theme={null}
{
  "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://docs.flow.engineer/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 theme={null}
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 theme={null}
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://docs.flow.engineer/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://docs.flow.engineer/records.md)) |
| `context.events` | The agent's own business events side, once `events.attach(agent)` ran: save a person's watch there ([events](https://docs.flow.engineer/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 theme={null}
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 theme={null}
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://docs.flow.engineer/skills.md)**
>
> Explain when and how an agent should use your tools.

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


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.