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:
executecannot 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’snavigate. 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)registersclear_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.