---
description: "Registers a tool that lives in a component and closes over its state, while the component is mounted."
---

# `useWebTool`

Registers a tool that lives in a component and closes over its state, while the component is mounted. Pass the definition directly, as you would to [`defineWebTool`](/docs/reference/core/define-web-tool), or a tool that is already defined. Nothing is added to the JSX for the cursor: it finds the control by role and accessible name (the tool's `title`, or its id read as words) and types each parameter into the field with that `name` or label.

## Import

```ts
import { useWebTool } from "@webmcp-agents/react";
import type { UseWebToolOptions, WebToolState } from "@webmcp-agents/react";
```

## Example

The action is one function with two callers. When an agent calls `clear_cart`, the cursor finds the button named "Clear cart", parks on it, and `clear` runs only after the person's own click.

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

import { Button } from "@/components/ui/button";

export function ClearCart() {
  const [items, setItems] = useState(["Ocean Tee", "Canvas Tote"]);

  const clear = () => {
    setItems([]);
    return "The cart is empty.";
  };

  // An agent calls this.
  useWebTool({
    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: clear,
  });

  // A person clicks this.
  return (
    <div className="flex flex-col items-start gap-3">
      <p>
        {items.length > 0 ? items.join(", ") : "The cart is empty."}
      </p>

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

In Next.js the file starts with `"use client"`. A button of your own that runs the tool the way an agent would:

```tsx
const clearCart = useWebTool({ id: "clear_cart", /* … */ });

<Button onClick={() => clearCart.invoke()}>Try it</Button>;
```

A tool already made with [`defineWebTool`](/docs/reference/core/define-web-tool) in a file of its own is passed as it is, and is
on while the component is mounted (and `enabled`). Its `execute` reads only its input and module state, never the
component's:

```tsx title="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>
  );
}
```

## Options

`useWebTool(tool, options?)`. The first argument is the definition; the second holds the hook's own options.

| Name | Type | Default | Description |
|---|---|---|---|
| `tool` | `WebToolConfig \| WebTool` | required | The definition, with every option of [`defineWebTool`](/docs/reference/core/define-web-tool#options): `id`, `title`, `description`, `inputSchema`, `execute`, and optionally `target`, `fields`, `fill`, `requireConfirmation` and the flags. One difference: `match` is ignored here, because the tool is on while the component is. A tool already made with `defineWebTool` is used as it is. |
| `enabled` | `boolean` | `true` | Register only while true, to switch the tool by state: `useWebTool(tool, { enabled: cart.items.length > 0 })`. While false the tool is released like an unmount releases it. |

## Returns

A `WebToolState`:

| Name | Type | Description |
|---|---|---|
| `invoke` | `WebTool["invoke"]` | Call the tool the way an agent would: the cursor moves, fields are typed, confirmation applies. Resolves to what `execute` returned; rejects when the call fails or the tool is not registered. The same function on every render, so it is safe in dependency arrays. |
| `registered` | `boolean` | The tool is registered with WebMCP. False until the effect runs, while `enabled` is false, and on pages without WebMCP. |
| `supported` | `boolean` | The page has `document.modelContext` (native, or the polyfill once it has loaded). False during server rendering. |

## Notes

- Calls always reach the latest render's `execute`, `target`, `fields` and `fill`, so new state never needs a re-registration (a `target` that closes over `cart` sees the current cart).
- It re-registers only when what the agent sees changes: id, title, description, input schema, flags and annotations, `requireConfirmation`, a non-function `target` (the [`webToolSignature`](/docs/reference/core/web-tool-signature)). A new definition replaces the old one at once, since Chrome would cancel its calls anyway.
- Disabling or unmounting releases the tool like the catalog does: a macrotask later (StrictMode's remount or a quick re-enable cancels it), and only after in-flight calls settle, plus 500 ms for the answer to reach the agent. A tool that disables itself (`clear_cart` emptying the cart) still answers.
- The signature is computed during render, so a bad schema throws at the component, not inside an effect.
- Works without a provider. Inside a [`<WebAgentsProvider>`](/docs/reference/react/web-agents-provider) it installs the provider's cursor before registering, so the tool is animated, and passes the provider's earlier-form `context` as `ctx.context`. Without one, nothing installs the cursor.
- Registering an id that is already registered replaces that registration (the newest wins). Do not give a component's tool the same id as a catalog tool.
- Inside a [`<WebAgentsProvider>`](/docs/reference/react/web-agents-provider) in a browser without WebMCP, the tool waits for the polyfill the provider loads and registers once it is there; `registered` turns true then. Without WebMCP and with no polyfill coming (no provider, or `polyfill={false}`), registration warns once (`document.modelContext is unavailable`) and does nothing, so the app still renders.
- A form is a tool with an `inputSchema`: React Hook Form and TanStack Form inputs hear the cursor's typing, and `fill: (values) => form.reset(values)` shows values in widgets it cannot type into. See [Tools with input](/docs/build/tools-with-input).
- With `requireConfirmation`, a trusted click on the tool's control runs the tool instead of the control's own `onClick`, so the action happens once. That includes a React Aria `Button`'s `onPress` and a `Link`'s navigation: the click React Aria re-fires is swallowed too. It needs exactly one matching control: give the tool a `title` that names one button, or a `target`.
- Icon-only buttons need an `aria-label`; a translated UI uses a function `target` returning the translated name.

## See also

- [Define a tool](/docs/build/define-a-tool), the page that teaches it
- [`defineWebTool`](/docs/reference/core/define-web-tool), every option of the definition
- [`locate`](/docs/reference/core/locate), how the control and the fields are found
- [`<WebAgentsProvider>`](/docs/reference/react/web-agents-provider), the cursor and the catalog
- [`deferRelease`](/docs/reference/core/defer-release), why unmounting never cancels a call
- [Tools with input](/docs/build/tools-with-input) and [Call a tool](/docs/build/call-a-tool)
