Skip to content
WebMCP Agents
Esc
↑↓navigate↵open⌘Jpreview
On this page

Tools across your app

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.

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.

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,
};
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>,
);
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,
};
"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>
  );
}
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,
};
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>
  );
}
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,
};
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, 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.

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",
  }),
});
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",
  }),
});
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",
  }),
});
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",
  }),
});
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.

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, is on only while the cart panel is open.

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.";
  },
});
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>
  );
}
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.";
  },
});
"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>
  );
}
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.";
  },
});
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>
  );
}
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.";
  },
});
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).
  • 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.

<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), so a file it reaches imports no React.

Try it

Open /orders and, in the browser console:

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, <WebAgentsProvider> and matchPath for the pattern syntax.

Was this page helpful?