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

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 execute returned, or undefined when the call navigated the page away. The common tools’ readers return MCP results, { content, structuredContent }.
  • It rejects with a WebToolError carrying the code an agent would have read: invalid_input for input that fails the schema, unavailable for a confirmation with nothing to click, failed when execute throws, 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 signal to stop waiting: invoke(input, { signal }) rejects with an AbortError when 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).

Was this page helpful?