---
description: "Puts your app's tools on the page and installs the agent cursor (and, in development, the devtools): tools is everything the app has, availableTools what an agent may use right now, and each tool's own match says on which pages it is on."
---

# `WebAgentsProvider`

Puts your app's tools on the page and installs the agent cursor (and, in development, the [devtools](/docs/reference/devtools) panel): `tools` is everything the app has, `availableTools` what an agent may use right now, and each tool's own `match` says on which pages it is on. Reach for it once, near the root of a React app. It is a thin wrapper over [`createWebAgents`](/docs/reference/core/create-web-agents); the library follows the browser's URL itself, and loads the WebMCP polyfill itself where the browser has none, so the same line works in Vite, TanStack Start, Next.js and Astro, with nothing wired to a router and nothing to load first.

## Import

```ts
import { WebAgentsProvider } from "@webmcp-agents/react";
import type { WebAgentsProviderProps } from "@webmcp-agents/react";
```

## Example

A catalog (see [`defineWebTool`](/docs/reference/core/define-web-tool) and [common tools](/docs/reference/tools)), narrowed by the person's state. The provider installs the cursor before the first tool registers, so every tool is animated without any ordering rule.

```tsx title="components/providers.tsx"
import { WebAgentsProvider } from "@webmcp-agents/react";
import type { ReactNode } from "react";

import { tools } from "@/tools";

// Every tool is in `tools`; which ones an agent may use right now
// follows your own state.
export function Providers({
  signedIn,
  children,
}: {
  signedIn: boolean;
  children: ReactNode;
}) {
  return (
    <WebAgentsProvider
      tools={tools}
      availableTools={
        signedIn
          ? [tools.getTime, tools.getCart, tools.listOrders]
          : [tools.getTime, tools.getCart]
      }
      visuals={{ spotlight: true }}
    >
      {children}
    </WebAgentsProvider>
  );
}
```

Without `availableTools`, every tool is available, each where its `match` fits:

```tsx
<WebAgentsProvider tools={tools}>
  <App />
</WebAgentsProvider>
```

## Props

| Name | Type | Default | Description |
|---|---|---|---|
| `tools` | `WebToolCatalog` | `{}` | Every tool the app has, as an object: `{ getCart, listOrders }`. All of them are on, each where its `match` fits, unless `availableTools` says otherwise. Define it at module level or memoise it: a new object re-checks every tool, and only tools whose definition changed re-register. Two tools with one id throw at startup, naming both keys. An array is the [earlier form](#earlier-form). |
| `availableTools` | `AvailableTools` | every tool of an object catalog | What an agent may use right now, decided by your own state: tools by reference (`tools.getCart`) or by their key in `tools`. A tool that leaves is released once its call in flight is answered. An inline array is fine: it is compared by the tools' ids. |
| `visuals` | `AgentVisualsOptions \| false` | `{}` | Options for [`installAgentVisuals`](/docs/reference/core/install-agent-visuals), read when the cursor installs. `spotlight` and `yieldToUser` also apply when they change. `false` installs nothing: tools still register, without animation, and [`useWebAgents`](/docs/reference/react/use-web-agents) gives `visuals: null`. |
| `polyfill` | `"auto" \| false` | `"auto"` | Where the browser has no `document.modelContext` (Firefox, Safari, Chrome without WebMCP), `"auto"` loads the [polyfill](/docs/reference/core/load-webmcp-polyfill), a chunk of its own that browsers with WebMCP never download. The children render meanwhile, and their tools register once it is there. In production too, so every visitor gets the tools and the chat. `false` never loads it: tools register only where the browser has WebMCP. Read once, on mount. |
| `devtools` | `boolean \| ProviderDevtoolsOptions` | `true` | The [devtools](/docs/build/devtools) panel (launcher bottom-right, `Alt+Shift+A`, and `window.agent`), wired to this provider's cursor so its Settings switches work. Development only: installed when `process.env.NODE_ENV` is `"development"` (`vite dev`, `next dev`, webpack's development mode), never in a production build, whose bundler drops the `import()` of `@webmcp-agents/devtools`, nor under `NODE_ENV=test`. `false` turns it off (to [place it yourself](/docs/build/devtools#place-it-yourself)); an object turns it on with panel options: [`DevtoolsOptions`](/docs/reference/devtools/install-web-agents-devtools#options) without `visuals` (`initialOpen`, `hotkey`, `initialSettings`, `eventClient`). Read once, on mount. |
| `children` | `ReactNode` | none | The app. |
| `global` | `readonly string[]` | `[]` | Earlier form: tool ids enabled on every page. |
| `routes` | `Readonly<Record<string, readonly string[]>>` | `{}` | Earlier form: tool ids per route pattern (`/orders`, `/product/:slug`, `/flights/*`, `*`). Every matching pattern contributes. Today a tool says this itself, with `match`. |
| `pathname` | `string` | the browser's URL | Earlier form: the path `routes` and the tools' `match` are tested against, when a router's idea of it should win. By default the library tracks the URL itself (see [`usePathname`](/docs/reference/react/use-pathname)). |
| `context` | `RegisteredContext` | none | Earlier form: app state handed to every catalog tool's `execute` as `ctx.context`, read when a tool runs, so it is the latest render's value. Typed through the `Register` interface. New code closes over its state, or reads its store, instead. |

### Earlier form

These props predate tools that carry their own `match` and close over their own state. They keep working for existing apps; new code does not need them. An **array** catalog keeps its earlier meaning too: its tools are off until `global`, `routes`, a mounted component ([`useEnableTools`](/docs/reference/react/use-enable-tools)) or `availableTools` turns them on.

```tsx
<WebAgentsProvider
  tools={catalog}
  global={["get_cart"]}
  routes={{ "/orders": ["list_orders"] }}
  context={cart}
>
  <App />
</WebAgentsProvider>
```

Route patterns use [`matchPath`](/docs/reference/core/match-path). Unknown ids in `global`, `routes` or `EnableTools` warn (outside `NODE_ENV=production`) with the catalog's ids instead of throwing, and register if a later `tools` update adds them.

## Returns

A context provider. Descendants read it with [`useWebAgents`](/docs/reference/react/use-web-agents).

## Notes

- Three scopes: **everywhere** (a catalog tool with no `match`), **per path** (a catalog tool with a `match`) and **per component** ([`useWebTool`](/docs/reference/react/use-web-tool), on while mounted).
- `match` turns a tool on as soon as the URL changes, which can be before the new route has rendered: a tool must not assume its page's state is there yet. See [`createWebAgents`](/docs/reference/core/create-web-agents#notes).
- Nothing is unregistered synchronously. Chrome cancels a running call when its tool's definition changes or the tool is unregistered, so releases wait a macrotask (StrictMode's mount, unmount, mount, or a route swap that keeps a tool, never touches WebMCP), then until the tool's calls have settled, plus 500 ms (`RESPONSE_GRACE_MS`) for the answer to reach the agent. A tool that turns itself off still answers.
- The cursor is kept across StrictMode's remount the same way: its teardown is deferred a macrotask, so tools registered in the first mount stay animated.
- A changed definition (id, title, description, schema, flags, `requireConfirmation`, a non-function `target`) re-registers at once. A changed `execute` does not: calls always go to the definition the catalog holds now.
- `visuals` options other than `spotlight` and `yieldToUser` are only read at install. Inline objects are fine for those two: they are compared by value.
- On the server it renders its children and does nothing else. In the browser it renders them at once too: there is nothing to wait for before mounting it, and no "ready" state to keep.
- In development it loads `@webmcp-agents/devtools` (a dependency of this package: nothing to install) and calls [`installWebAgentsDevtools`](/docs/reference/devtools/install-web-agents-devtools) with its own cursor. One panel per page however many providers mount; the last to unmount removes it, a macrotask later, so StrictMode's remount keeps it and its timeline. The check is literally `process.env.NODE_ENV === "development"` around the `import()`, which Vite, Next.js (webpack and Turbopack) and Astro replace, so a production build has no devtools chunk. A page loaded without a bundler (no `process`) gets no panel.
- Where the browser has no WebMCP it loads the polyfill when it mounts (unless `polyfill={false}`). The cursor installs and the tools register once `document.modelContext` exists, including those of [`useWebTool`](/docs/reference/react/use-web-tool) components that mounted meanwhile. With native WebMCP nothing is loaded.

## See also

- [Tools across your app](/docs/build/tools-across-your-app), the page that teaches `tools`, `match` and `availableTools`
- [`createWebAgents`](/docs/reference/core/create-web-agents), the same without React
- [Browsers without WebMCP](/docs/more/polyfill), what `polyfill` loads
- [Devtools](/docs/build/devtools), what `devtools` installs
- [`useWebTool`](/docs/reference/react/use-web-tool), a tool that lives in a component
- [`useWebAgents`](/docs/reference/react/use-web-agents), read the provider
- [`defineWebTool`](/docs/reference/core/define-web-tool)
- [`installAgentVisuals`](/docs/reference/core/install-agent-visuals)
