Call a tool
tool.invoke calls a tool the way an agent would, with the cursor and confirmation. Use it for a "Try it" button, a keyboard shortcut, an onboarding tour or a test.
Every tool has one verb, invoke. It calls the tool the way an agent would, through the page’s WebMCP: the cursor
moves, fields are typed, confirmation applies, and you get the answer the agent would get. Use it for a “Try it”
button, a keyboard shortcut, a guided tour or an end-to-end test.
A complete example
A tool called from a button. Where invoke comes from depends on how the tool was written:
The hook returns invoke for the tool it registers, the same function on every render.
const addToCart = useWebTool({
id: "add_to_cart",
// …
});
<Button
variant="outline"
onClick={() => addToCart.invoke({ size: "M", quantity: 2 })}
>
Show me
</Button>A defined tool has invoke on it: import the tool and call it.
src/components/cart-summary.tsx
import { WebToolError } from "@webmcp-agents/react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { tools } from "@/tools";
export function CartSummary() {
const [summary, setSummary] = useState("");
const check = async () => {
try {
// The call an agent would make, with the same answer.
const { lines, total } = await tools.getCart.invoke();
setSummary(`Lines: ${lines.length}, total $${total.toFixed(2)}`);
} catch (err) {
// What the agent would have read: a code and a message.
if (!(err instanceof WebToolError)) throw err;
setSummary(`${err.code}: ${err.message}`);
}
};
return (
<div className="flex items-center gap-3">
<Button variant="outline" onClick={check}>
Check the cart
</Button>
<p>{summary}</p>
</div>
);
}The hook returns invoke for the tool it registers, the same function on every render.
const addToCart = useWebTool({
id: "add_to_cart",
// …
});
<Button
variant="outline"
onClick={() => addToCart.invoke({ size: "M", quantity: 2 })}
>
Show me
</Button>A defined tool has invoke on it: import the tool and call it.
components/cart-summary.tsx
"use client";
import { WebToolError } from "@webmcp-agents/react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { tools } from "@/tools";
export function CartSummary() {
const [summary, setSummary] = useState("");
const check = async () => {
try {
// The call an agent would make, with the same answer.
const { lines, total } = await tools.getCart.invoke();
setSummary(`Lines: ${lines.length}, total $${total.toFixed(2)}`);
} catch (err) {
// What the agent would have read: a code and a message.
if (!(err instanceof WebToolError)) throw err;
setSummary(`${err.code}: ${err.message}`);
}
};
return (
<div className="flex items-center gap-3">
<Button variant="outline" onClick={check}>
Check the cart
</Button>
<p>{summary}</p>
</div>
);
}The hook returns invoke for the tool it registers, the same function on every render.
const addToCart = useWebTool({
id: "add_to_cart",
// …
});
<Button
variant="outline"
onClick={() => addToCart.invoke({ size: "M", quantity: 2 })}
>
Show me
</Button>A defined tool has invoke on it: import the tool and call it.
src/components/cart-summary.tsx
import { WebToolError } from "@webmcp-agents/react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { tools } from "@/tools";
export function CartSummary() {
const [summary, setSummary] = useState("");
const check = async () => {
try {
// The call an agent would make, with the same answer.
const { lines, total } = await tools.getCart.invoke();
setSummary(`Lines: ${lines.length}, total $${total.toFixed(2)}`);
} catch (err) {
// What the agent would have read: a code and a message.
if (!(err instanceof WebToolError)) throw err;
setSummary(`${err.code}: ${err.message}`);
}
};
return (
<div className="flex items-center gap-3">
<Button variant="outline" onClick={check}>
Check the cart
</Button>
<p>{summary}</p>
</div>
);
}The hook returns invoke for the tool it registers, the same function on every render.
const addToCart = useWebTool({
id: "add_to_cart",
// …
});
<Button
variant="outline"
onClick={() => addToCart.invoke({ size: "M", quantity: 2 })}
>
Show me
</Button>A defined tool has invoke on it: import the tool and call it.
src/components/cart-summary.tsx
import { WebToolError } from "@webmcp-agents/react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { tools } from "@/tools";
export function CartSummary() {
const [summary, setSummary] = useState("");
const check = async () => {
try {
// The call an agent would make, with the same answer.
const { lines, total } = await tools.getCart.invoke();
setSummary(`Lines: ${lines.length}, total $${total.toFixed(2)}`);
} catch (err) {
// What the agent would have read: a code and a message.
if (!(err instanceof WebToolError)) throw err;
setSummary(`${err.code}: ${err.message}`);
}
};
return (
<div className="flex items-center gap-3">
<Button variant="outline" onClick={check}>
Check the cart
</Button>
<p>{summary}</p>
</div>
);
}invoke resolves to what execute returned, typed: for get_cart, { lines, total }.
It works only while the tool is on: a component’s tool while the component is mounted, a defined tool while its
match fits.
What it resolves and rejects with
- It resolves to what
executereturned, orundefinedwhen the call navigated the page away. The common tools’ readers return MCP results,{ content, structuredContent }. - It rejects with a
WebToolErrorcarrying the code an agent would have read:invalid_inputfor input that fails the schema,unavailablefor a confirmation with nothing to click,failedwhenexecutethrows, and the others in Errors. It also rejects when the tool is not on the page right now. - A confirm-gated tool waits for the person’s click, like an agent’s call. Pass a
signalto stop waiting:invoke(input, { signal })rejects with anAbortErrorwhen it aborts.
For a unit test, use run instead: it validates and executes with no page, no cursor and no confirmation.
const orders = await listOrders.run(
{ limit: 1 },
{ signal: new AbortController().signal, source: "test" },
);
From the console
In development, the devtools put the same call on window.agent, by name:
await agent.invoke("get_cart");
await agent.list();
End-to-end tests drive tools the same way: the example apps’ Playwright tests call window.agent.invoke.
Try it
Render <CartSummary /> and click Check the cart: it shows “Lines: 1, total $28.00”. Then, in the console, call the tool
by name:
await agent.invoke("get_cart");
Options you’ll use most
invoke(input, options) |
What it is for |
|---|---|
input |
The tool’s parameters, typed by its schema. Optional when it takes none. |
options.signal |
Abort the call, including a wait for the person’s click. |
Reference: defineWebTool (the WebTool it returns, with invoke and run) and
useWebTool (what the hook returns).