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

# Tools

> Design and register TypeScript MCP tools with validated inputs, model-friendly descriptions, and useful results.

Tools are callable actions on your MCP server. Use a tool when the client should look up data, run a workflow, mutate server-side state, or return an MCP App widget.

This guide focuses on tool design and common implementation patterns. Use the [Tools API reference](/typescript/api-reference/server/tools) for `ToolDefinition`, callback signatures, type inference, defaults, and return types.

## Start with a model-friendly tool

A good tool is narrow enough for a model to choose correctly. Give the tool a clear name, describe when to use it, validate every input with Zod, and return the smallest useful result.

```typescript theme={null}
import { MCPServer } from "mcp-use";
import { z } from "zod";

const server = new MCPServer({
  name: "inventory-server",
  version: "1.0.0",
});

export const getInventoryItem = server.tool(
  {
    name: "get_inventory_item",
    description: "Look up one inventory item by SKU.",
    inputSchema: z.object({
      sku: z.string().describe("Inventory SKU, such as SKU-1234"),
    }),
    annotations: {
      readOnlyHint: true,
      destructiveHint: false,
      openWorldHint: true,
    },
  },
  async ({ sku }) => {
    const item = await inventory.getItem(sku);
    return {
      content: [{ type: "text", text: JSON.stringify(item) }],
      structuredContent: item,
    };
  },
);
```

Use names that describe the user task, not the internal API method. `get_inventory_item` is easier for a model to choose than `inventoryFindByPrimaryKey`.

<Warning>
  Assign every statically declared tool to an exported constant. The generated `mcp-env.d.ts` derives view types from exported tool refs, and `useCallTool("name")` fails type checking when the matching ref is not exported. Use `useDynamicTool<Args, Result>("name")` only for tools registered from runtime data, loops, OpenAPI documents, or other dynamic sources.
</Warning>

## Describe inputs with Zod

Use Zod schemas for all tool inputs. The server validates the incoming arguments before your handler runs, and TypeScript infers the handler parameter type from the schema.

```typescript theme={null}
export const searchOrders = server.tool(
  {
    name: "search_orders",
    description: "Search customer orders by status and date range.",
    inputSchema: z.object({
      status: z.enum(["open", "fulfilled", "cancelled"]).optional(),
      fromDate: z.string().date().describe("Start date in YYYY-MM-DD format"),
      toDate: z.string().date().describe("End date in YYYY-MM-DD format"),
      limit: z.number().int().min(1).max(50).default(10),
    }),
    annotations: {
      readOnlyHint: true,
      destructiveHint: false,
      openWorldHint: true,
    },
  },
  async ({ status, fromDate, toDate, limit }) => {
    const orders = await orders.search({ status, fromDate, toDate, limit });
    return {
      content: [{ type: "text", text: JSON.stringify({ orders }) }],
      structuredContent: { orders },
    };
  },
);
```

Prefer descriptive field names and `.describe()` text that tells the model what value to provide. Use defaults for ordinary behavior, but keep defaults visible when they affect cost, safety, or output size.

## Add tool annotations

Tool annotations tell clients and models how risky a tool is. Set the main behavior hints explicitly, especially for tools exposed to ChatGPT or MCP catalogs.

```typescript theme={null}
annotations: {
  readOnlyHint: true,      // The tool does not change server or external state.
  destructiveHint: false,  // The tool does not delete or overwrite data.
  openWorldHint: true,     // The tool reads from external systems or live data.
}
```

Use `readOnlyHint: false` for tools that create, update, delete, send, purchase, deploy, or otherwise change state. Use `destructiveHint: true` when the change can remove data or is hard to undo.

Add `idempotentHint` when retry behavior matters. Set it to `true` only when repeating the same call has the same effect as running it once.

## Return the right kind of result

Prefer raw MCP `CallToolResult` shapes. Deprecated [response helpers](/typescript/server/response-helpers) still work for upgrades, but new code should return the wire envelopes directly.

```typescript theme={null}
export const reserveInventory = server.tool(
  {
    name: "reserve_inventory",
    description: "Reserve inventory for one order.",
    inputSchema: z.object({
      orderId: z.string(),
      sku: z.string(),
      quantity: z.number().int().positive(),
    }),
    annotations: {
      readOnlyHint: false,
      destructiveHint: false,
      openWorldHint: true,
    },
  },
  async ({ orderId, sku, quantity }) => {
    const reservation = await inventory.reserve({ orderId, sku, quantity });

    if (!reservation.ok) {
      return {
        isError: true,
        content: [{ type: "text", text: reservation.reason }],
      };
    }

    const data = {
      reservationId: reservation.id,
      status: "reserved",
    };
    return {
      content: [{ type: "text", text: JSON.stringify(data) }],
      structuredContent: data,
    };
  },
);
```

Use:

| Use case                | Return                                                                                 |
| ----------------------- | -------------------------------------------------------------------------------------- |
| Human-readable output   | `{ content: [{ type: "text", text }] }`                                                |
| Structured JSON         | `{ content: [{ type: "text", text: JSON.stringify(data) }], structuredContent: data }` |
| Error                   | `{ isError: true, content: [{ type: "text", text: message }] }`                        |
| Multiple content blocks | `{ content: [/* ContentBlock… */] }`                                                   |
| View-bound tool         | `view: { name }` + `{ content, structuredContent }`                                    |

See [Response Helpers](/typescript/server/response-helpers) for the deprecated-helper migration table and [Response helpers API reference](/typescript/api-reference/server/response-helpers) for signatures.

## Return views from tools

Bind a view on the tool definition, then return a plain `CallToolResult` with view props in `structuredContent` and model-facing text in `content`. The deprecated `widget()` helper builds the same envelope.

The `view.name` value must match a view under `resources/` (or your views directory).

```typescript theme={null}
export const searchProducts = server.tool(
  {
    name: "search_products",
    description: "Search products and display matching results.",
    inputSchema: z.object({
      query: z.string().describe("Product search query"),
    }),
    outputSchema: z.object({
      query: z.string(),
      results: z.array(z.object({ id: z.string(), name: z.string() })),
    }),
    annotations: {
      readOnlyHint: true,
      destructiveHint: false,
      openWorldHint: true,
    },
    view: {
      name: "product-search-result",
    },
  },
  async ({ query }) => {
    const results = await searchProducts(query);
    const data = { query, results };

    return {
      content: [
        {
          type: "text",
          text: `Found ${results.length} products for "${query}"`,
        },
      ],
      structuredContent: data,
    };
  },
);
```

The model sees `content`. The view reads `structuredContent` via `useToolContext()`. See [MCP Apps](/typescript/mcp-apps) for view workflow guidance.

## Use `ctx` for request-aware tools

The second callback argument, usually named `ctx`, exposes per-call authentication, request-scoped client metadata and capability checks, elicitation, progress, and logging.

```typescript theme={null}
export const privateProfile = server.tool(
  {
    name: "private_profile",
    description: "Return the authenticated user's profile.",
    annotations: {
      readOnlyHint: true,
      destructiveHint: false,
      openWorldHint: false,
    },
  },
  async (_params, ctx) => {
    if (!ctx.auth) {
      return {
        isError: true,
        content: [{ type: "text", text: "Unauthorized" }],
      };
    }

    const data = {
      userId: ctx.auth.user.userId,
      email: ctx.auth.user.email,
    };
    return {
      content: [{ type: "text", text: JSON.stringify(data) }],
      structuredContent: data,
    };
  },
);
```

Use `ctx.auth` only for verified identity from server authentication. Values returned by `ctx.client.info()`, `ctx.client.capabilities()`, `ctx.client.extension()`, and `ctx.client.user()` are self-reported by the client for the current request. `user()` normalizes optional OpenAI-specific `_meta` hints; even its subject, conversation, and organization identifiers are unverified and must never be used for access control.

See the [Tool context API reference](/typescript/api-reference/server/tool-context) for every `ctx` method, field, capability check, log level, and return type.

## Handle tool failures deliberately

Return `{ isError: true, content: […] }` when the tool ran but the requested operation could not complete. Throw only for unexpected failures that should be treated as server errors.

```typescript theme={null}
export const fetchInvoice = server.tool(
  {
    name: "fetch_invoice",
    description: "Fetch one invoice by invoice ID.",
    inputSchema: z.object({ invoiceId: z.string() }),
    annotations: {
      readOnlyHint: true,
      destructiveHint: false,
      openWorldHint: true,
    },
  },
  async ({ invoiceId }) => {
    const invoice = await billing.findInvoice(invoiceId);

    if (!invoice) {
      return {
        isError: true,
        content: [
          { type: "text", text: `Invoice ${invoiceId} was not found.` },
        ],
      };
    }

    return {
      content: [{ type: "text", text: JSON.stringify(invoice) }],
      structuredContent: invoice,
    };
  },
);
```

Make error messages actionable. Tell the model or user what failed and which input caused the failure.

## Test a tool locally

Run the development server and call the tool from the Inspector.

```bash theme={null}
npm run dev
```

Then open `http://localhost:3000/mcp/inspector`, select the tool, enter test arguments, and verify both success and failure cases.

## Next steps

<CardGroup cols={2}>
  <Card title="Tools API reference" icon="terminal" href="/typescript/api-reference/server/tools">
    Look up tool definitions, callback signatures, inferred types, and return
    shapes.
  </Card>

  <Card title="Response Helpers" icon="drill" href="/typescript/server/response-helpers">
    Choose the right helper for text, JSON, errors, resources, media, and
    widgets.
  </Card>

  <Card title="MCP Apps" icon="blocks" href="/typescript/mcp-apps">
    Return interactive widgets from tool results.
  </Card>

  <Card title="Authentication" icon="shield-check" href="/typescript/server/authentication/index">
    Add verified user identity to protected tools.
  </Card>
</CardGroup>
