---
title: Tools across your app
description: "Put app-wide tools in one catalog the provider registers, then choose the pages each one is on with match, and which ones an agent may use with availableTools. No router setup."
sidebar:
  label: 4. Tools across your app
---

A tool in a component is on while that component is on the page. A tool for the **whole app** (read the cart, list
orders, go to a page) lives in one object, the **catalog**, written with `defineWebTool`. You hand it to the provider
once, and each tool's **`match`** chooses the pages it is on.

## A complete example

The catalog, then one prop on the provider you added at installation. The highlighted lines are the ones you add.

**Vite**

```ts title="src/tools/index.ts"
import {
  getLocation,
  getPage,
  getTime,
  goTo,
  setTheme,
} from "@webmcp-agents/tools";

import { getCart } from "./get-cart";
import { listOrders } from "./list-orders";
import { trackOrder } from "./track-order";

// Every tool the app has, by name. The provider registers them, and
// each one's `match` says where it is on.
export const tools = {
  // Common tools, ready-made.
  getTime: getTime(),
  getLocation: getLocation(),
  getPage: getPage(),
  goTo: goTo({
    pages: { home: "/", cart: "/cart", orders: "/orders" },
  }),
  setTheme: setTheme(),

  // Your own.
  getCart,
  listOrders,
  trackOrder,
};
```

```tsx title="src/main.tsx" {7,12}
import { WebAgentsProvider } from "@webmcp-agents/react";
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";

import "./index.css";
import "@webmcp-agents/core/cursor.css";
import { tools } from "@/tools";
import { App } from "./App";

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <WebAgentsProvider tools={tools}>
      <App />
    </WebAgentsProvider>
  </StrictMode>,
);
```

**Next.js**

```ts title="tools/index.ts"
import {
  getLocation,
  getPage,
  getTime,
  goTo,
  setTheme,
} from "@webmcp-agents/tools";

import { getCart } from "./get-cart";
import { listOrders } from "./list-orders";
import { trackOrder } from "./track-order";

// Every tool the app has, by name. The provider registers them, and
// each one's `match` says where it is on.
export const tools = {
  // Common tools, ready-made.
  getTime: getTime(),
  getLocation: getLocation(),
  getPage: getPage(),
  goTo: goTo({
    pages: { home: "/", cart: "/cart", orders: "/orders" },
  }),
  setTheme: setTheme(),

  // Your own.
  getCart,
  listOrders,
  trackOrder,
};
```

```tsx title="app/providers.tsx" {6,10}
"use client";

import { WebAgentsProvider } from "@webmcp-agents/react";
import type { ReactNode } from "react";

import { tools } from "@/tools";

export function Providers({ children }: { children: ReactNode }) {
  return (
    <WebAgentsProvider tools={tools}>{children}</WebAgentsProvider>
  );
}
```

**TanStack Start**

```ts title="src/tools/index.ts"
import {
  getLocation,
  getPage,
  getTime,
  goTo,
  setTheme,
} from "@webmcp-agents/tools";

import { getCart } from "./get-cart";
import { listOrders } from "./list-orders";
import { trackOrder } from "./track-order";

// Every tool the app has, by name. The provider registers them, and
// each one's `match` says where it is on.
export const tools = {
  // Common tools, ready-made.
  getTime: getTime(),
  getLocation: getLocation(),
  getPage: getPage(),
  goTo: goTo({
    pages: { home: "/", cart: "/cart", orders: "/orders" },
  }),
  setTheme: setTheme(),

  // Your own.
  getCart,
  listOrders,
  trackOrder,
};
```

```tsx title="src/components/providers.tsx" {4,8}
import { WebAgentsProvider } from "@webmcp-agents/react";
import type { ReactNode } from "react";

import { tools } from "@/tools";

export function Providers({ children }: { children: ReactNode }) {
  return (
    <WebAgentsProvider tools={tools}>{children}</WebAgentsProvider>
  );
}
```

**Astro**

```ts title="src/tools/index.ts"
import {
  getLocation,
  getPage,
  getTime,
  goTo,
  setTheme,
} from "@webmcp-agents/tools";

import { getCart } from "./get-cart";
import { listOrders } from "./list-orders";
import { trackOrder } from "./track-order";

// Every tool the app has, by name. The provider registers them, and
// each one's `match` says where it is on.
export const tools = {
  // Common tools, ready-made.
  getTime: getTime(),
  getLocation: getLocation(),
  getPage: getPage(),
  goTo: goTo({
    pages: { home: "/", cart: "/cart", orders: "/orders" },
  }),
  setTheme: setTheme(),

  // Your own.
  getCart,
  listOrders,
  trackOrder,
};
```

```tsx title="src/components/providers.tsx" {4,8}
import { WebAgentsProvider } from "@webmcp-agents/react";
import type { ReactNode } from "react";

import { tools } from "@/tools";

export function Providers({ children }: { children: ReactNode }) {
  return (
    <WebAgentsProvider tools={tools}>{children}</WebAgentsProvider>
  );
}
```

The common tools at the top come ready-made ([Common tools](/docs/build/common-tools), next). `getCart`, `listOrders` and
`trackOrder` are yours, one file each. An object means a tool cannot be listed twice, two tools with one id fail at
startup naming both keys, and everything else refers to a tool by reference: `tools.getCart.invoke()`,
`availableTools={[tools.getCart]}`, `defineWebAgent({ tools: [tools.getCart] })`.

## `match`: on while the path fits

A catalog tool with no `match` is on everywhere. With `match`, it is on while the URL's path fits: one pattern, several,
or a regular expression.

**Vite**

```ts title="src/tools/track-order.ts" {16-17}
import { defineWebTool } from "@webmcp-agents/core";
import { z } from "zod";

export const trackOrder = defineWebTool({
  id: "track_order",
  title: "Track order",
  description:
    "Where an order is: its status and delivery date. On the " +
    "orders pages only: go_to orders first. Take the id from " +
    "list_orders.",
  inputSchema: z.object({
    orderId: z.string().describe("The order's id, e.g. 1002"),
  }),

  readOnly: true,
  // On /orders and on an order's own page, /orders/1002.
  match: ["/orders", "/orders/:id"],

  execute: ({ orderId }) => ({
    orderId,
    status: "shipped",
    arrives: "2026-11-12",
  }),
});
```

**Next.js**

```ts title="tools/track-order.ts" {16-17}
import { defineWebTool } from "@webmcp-agents/core";
import { z } from "zod";

export const trackOrder = defineWebTool({
  id: "track_order",
  title: "Track order",
  description:
    "Where an order is: its status and delivery date. On the " +
    "orders pages only: go_to orders first. Take the id from " +
    "list_orders.",
  inputSchema: z.object({
    orderId: z.string().describe("The order's id, e.g. 1002"),
  }),

  readOnly: true,
  // On /orders and on an order's own page, /orders/1002.
  match: ["/orders", "/orders/:id"],

  execute: ({ orderId }) => ({
    orderId,
    status: "shipped",
    arrives: "2026-11-12",
  }),
});
```

**TanStack Start**

```ts title="src/tools/track-order.ts" {16-17}
import { defineWebTool } from "@webmcp-agents/core";
import { z } from "zod";

export const trackOrder = defineWebTool({
  id: "track_order",
  title: "Track order",
  description:
    "Where an order is: its status and delivery date. On the " +
    "orders pages only: go_to orders first. Take the id from " +
    "list_orders.",
  inputSchema: z.object({
    orderId: z.string().describe("The order's id, e.g. 1002"),
  }),

  readOnly: true,
  // On /orders and on an order's own page, /orders/1002.
  match: ["/orders", "/orders/:id"],

  execute: ({ orderId }) => ({
    orderId,
    status: "shipped",
    arrives: "2026-11-12",
  }),
});
```

**Astro**

```ts title="src/tools/track-order.ts" {16-17}
import { defineWebTool } from "@webmcp-agents/core";
import { z } from "zod";

export const trackOrder = defineWebTool({
  id: "track_order",
  title: "Track order",
  description:
    "Where an order is: its status and delivery date. On the " +
    "orders pages only: go_to orders first. Take the id from " +
    "list_orders.",
  inputSchema: z.object({
    orderId: z.string().describe("The order's id, e.g. 1002"),
  }),

  readOnly: true,
  // On /orders and on an order's own page, /orders/1002.
  match: ["/orders", "/orders/:id"],

  execute: ({ orderId }) => ({
    orderId,
    status: "shipped",
    arrives: "2026-11-12",
  }),
});
```

```ts
match: "/orders" // one path
match: ["/orders", "/orders/:id"] // any of these
match: /^\/orders\/\d+(\/tracking)?$/ // a RegExp, tested against the path
```

A pattern is literal segments, `:param` for exactly one segment, and a trailing `*` for the rest: `/orders`,
`/product/:slug`, `/flights/*`. It is the URL the visitor sees, in every framework: Next.js's
`app/product/[slug]/page.tsx` is `"/product/:slug"`. With a hash router, `#/orders` counts as the path `/orders`.

**No router wiring.** The library follows the browser's URL itself, so the provider needs nothing from React Router,
TanStack Router, Next.js or Astro. A tool turns on and off as the visitor navigates, client-side or by a full page
load. A tool that turns off in the middle of a call finishes that call first.

:::warning[A `match` tool turns on as soon as the URL changes]
That is before the new page has rendered. Do not write a `match` tool that reads state the page sets when it mounts:
read the URL, or a store that is set before the navigation. Skyline's results tools read the search, so its
`search_flights` stores the search first and navigates second. URL changes are also seen just after the navigation
call returns (Next.js changes the URL inside a React effect), so a tool turns on a moment after `router.push`, not
during it.
:::

## The three scopes

| Scope | How | Example |
|---|---|---|
| **Everywhere** | A catalog tool with no `match` | `get_cart`, `get_time` |
| **Per path** | A catalog tool with `match` | `list_orders` on `/orders` |
| **Per component** | `useWebTool`, on while the component is mounted (`{ enabled }` to switch it by state): a config written in the component, or a tool you defined in a file | `add_to_cart` on a product's page, `clear_cart` while the cart is open |

A tool in a component rarely needs `match`: it is on while the component is. `useWebTool` ignores `match`.

## A defined tool, on with a component

A tool written with `defineWebTool` does not have to go in the catalog. Hand it to `useWebTool` instead, and it is on
while that component is mounted, like a tool written in the component. Here `clear_cart`, the definition from
[Define a tool](/docs/build/define-a-tool), is on only while the cart panel is open.

**Vite**

```ts title="src/tools/clear-cart.ts"
import { defineWebTool } from "@webmcp-agents/core";

import { cart } from "../lib/cart";

// An agent calls this.
export const clearCart = defineWebTool({
  id: "clear_cart",
  title: "Clear cart",
  description:
    "Empty the cart. Use when the user asks to start over. " +
    "The user confirms on the page.",
  destructive: true,
  requireConfirmation: true,
  execute: () => {
    cart.clear();
    return "The cart is empty.";
  },
});
```

```tsx title="src/components/cart-panel.tsx" {10}
import { useWebTool } from "@webmcp-agents/react";

import { Button } from "@/components/ui/button";
import { cart } from "@/lib/cart";
import { clearCart } from "@/tools/clear-cart";

export function CartPanel({ open }: { open: boolean }) {
  // An agent calls this: the tool from tools/clear-cart.ts,
  // on while this panel is mounted and open.
  useWebTool(clearCart, { enabled: open });

  if (!open) return null;

  // A person clicks this.
  return (
    <aside className="flex flex-col gap-3">
      <p>Total: ${cart.total().toFixed(2)}</p>

      <Button variant="destructive" onClick={() => cart.clear()}>
        Clear cart
      </Button>
    </aside>
  );
}
```

**Next.js**

```ts title="tools/clear-cart.ts"
import { defineWebTool } from "@webmcp-agents/core";

import { cart } from "../lib/cart";

// An agent calls this.
export const clearCart = defineWebTool({
  id: "clear_cart",
  title: "Clear cart",
  description:
    "Empty the cart. Use when the user asks to start over. " +
    "The user confirms on the page.",
  destructive: true,
  requireConfirmation: true,
  execute: () => {
    cart.clear();
    return "The cart is empty.";
  },
});
```

```tsx title="components/cart-panel.tsx" {12}
"use client";

import { useWebTool } from "@webmcp-agents/react";

import { Button } from "@/components/ui/button";
import { cart } from "@/lib/cart";
import { clearCart } from "@/tools/clear-cart";

export function CartPanel({ open }: { open: boolean }) {
  // An agent calls this: the tool from tools/clear-cart.ts,
  // on while this panel is mounted and open.
  useWebTool(clearCart, { enabled: open });

  if (!open) return null;

  // A person clicks this.
  return (
    <aside className="flex flex-col gap-3">
      <p>Total: ${cart.total().toFixed(2)}</p>

      <Button variant="destructive" onClick={() => cart.clear()}>
        Clear cart
      </Button>
    </aside>
  );
}
```

**TanStack Start**

```ts title="src/tools/clear-cart.ts"
import { defineWebTool } from "@webmcp-agents/core";

import { cart } from "../lib/cart";

// An agent calls this.
export const clearCart = defineWebTool({
  id: "clear_cart",
  title: "Clear cart",
  description:
    "Empty the cart. Use when the user asks to start over. " +
    "The user confirms on the page.",
  destructive: true,
  requireConfirmation: true,
  execute: () => {
    cart.clear();
    return "The cart is empty.";
  },
});
```

```tsx title="src/components/cart-panel.tsx" {10}
import { useWebTool } from "@webmcp-agents/react";

import { Button } from "@/components/ui/button";
import { cart } from "@/lib/cart";
import { clearCart } from "@/tools/clear-cart";

export function CartPanel({ open }: { open: boolean }) {
  // An agent calls this: the tool from tools/clear-cart.ts,
  // on while this panel is mounted and open.
  useWebTool(clearCart, { enabled: open });

  if (!open) return null;

  // A person clicks this.
  return (
    <aside className="flex flex-col gap-3">
      <p>Total: ${cart.total().toFixed(2)}</p>

      <Button variant="destructive" onClick={() => cart.clear()}>
        Clear cart
      </Button>
    </aside>
  );
}
```

**Astro**

```ts title="src/tools/clear-cart.ts"
import { defineWebTool } from "@webmcp-agents/core";

import { cart } from "../lib/cart";

// An agent calls this.
export const clearCart = defineWebTool({
  id: "clear_cart",
  title: "Clear cart",
  description:
    "Empty the cart. Use when the user asks to start over. " +
    "The user confirms on the page.",
  destructive: true,
  requireConfirmation: true,
  execute: () => {
    cart.clear();
    return "The cart is empty.";
  },
});
```

```tsx title="src/components/cart-panel.tsx" {10}
import { useWebTool } from "@webmcp-agents/react";

import { Button } from "@/components/ui/button";
import { cart } from "@/lib/cart";
import { clearCart } from "@/tools/clear-cart";

export function CartPanel({ open }: { open: boolean }) {
  // An agent calls this: the tool from tools/clear-cart.ts,
  // on while this panel is mounted and open.
  useWebTool(clearCart, { enabled: open });

  if (!open) return null;

  // A person clicks this.
  return (
    <aside className="flex flex-col gap-3">
      <p>Total: ${cart.total().toFixed(2)}</p>

      <Button variant="destructive" onClick={() => cart.clear()}>
        Clear cart
      </Button>
    </aside>
  );
}
```

The tool keeps the best of both: it lives in a file of its own, so an agent lists it by reference
(`defineWebAgent({ tools: [clearCart] })`) and a server file can import it, and it is on only where its control is.
`enabled` switches it by state, as for any component tool. What to know:

- **`execute` cannot see the component.** A defined tool is made once, outside any component, so it reads only its
  input and module state: a store, the router's `navigate`. A tool that needs the component's props, state or form is
  written in the component ([Define a tool](/docs/build/define-a-tool)).
- **One place per tool.** Put a tool in the catalog or hand it to `useWebTool`, not both: they would register the same
  id twice, and the newest registration wins.
- **One component at a time.** Every mounted component that calls `useWebTool(clearCart)` registers `clear_cart`. For a
  list (a card per product), call it once in the component that owns the list, or write the tool in the card with
  its own id per item (`add_to_cart_mug-01`).

## Only some of them: `availableTools`

`tools` is everything the app has. `availableTools` is what an agent may use right now, decided by your own state;
left out, it is all of them. It filters the catalog, not the tools components own.

```tsx
<WebAgentsProvider
  tools={tools}
  availableTools={
    user
      ? [tools.getCart, tools.listOrders, tools.trackOrder]
      : [tools.getCart]
  }
>
```

Catalog files use `defineWebTool` from `@webmcp-agents/core`, the same function `@webmcp-agents/react` re-exports: an
agent lists catalog tools by reference, and your `/api/chat` route imports the agent on the server
([Add the chat](/docs/build/add-the-chat)), so a file it reaches imports no React.

## Try it

Open `/orders` and, in the browser console:

```js
await agent.invoke("list_orders", { limit: 1 });
```

It answers with the newest order. Go back to `/`: in the devtools panel, `list_orders` and `track_order` are listed
**off here**, and the same call rejects because the tool is not on.

## Options you'll use most

| Where | Option | What it is for |
|---|---|---|
| `defineWebTool` | `match` | The paths a catalog tool is on: a pattern, several, or a RegExp. Default: everywhere. |
| `<WebAgentsProvider>` | `tools` | The catalog: every app-wide tool, by name. |
| `<WebAgentsProvider>` | `availableTools` | The tools an agent may use now, by reference. Default: all of them. |
| `useWebTool` | `enabled` (second argument) | Switch a component's tool by state. |

Every option: [`defineWebTool`](/docs/reference/core/define-web-tool), [`<WebAgentsProvider>`](/docs/reference/react/web-agents-provider)
and [`matchPath`](/docs/reference/core/match-path) for the pattern syntax.
