---
title: Browsers without WebMCP
description: The provider loads a polyfill of document.modelContext where the browser has none, so tools, the cursor and the chat work in any browser, in development and in production.
sidebar:
  label: Polyfill
related:
  - /installation
  - /concepts/tool-lifecycle
  - /reference/core/load-webmcp-polyfill
---

Native WebMCP is only in Chrome, behind an origin trial. Everywhere else, a polyfill gives the page the same
`document.modelContext`, so your tools, the agent cursor, the devtools and the chat work in any browser. It ships in
`@webmcp-agents/core`, and you do not load it yourself: `<WebAgentsProvider>` does, only where the browser needs it.

## The provider loads it

When the provider mounts in a browser without `document.modelContext`, it loads the polyfill with a dynamic
`import()` and installs it. Nothing waits for it but the tools:

- **The page renders at once**, on the server too. There is no "ready" state to keep and nothing to load first.
- **Tools registered meanwhile wait.** The cursor installs and the catalog and every `useWebTool` register once
  `document.modelContext` exists, a few milliseconds later. A tool released before then never registers.
- **Browsers with WebMCP never download it.** The polyfill is its own chunk (about 2.5 KB min+gz), reached only by
  that dynamic import. With native WebMCP the tools register at once.

`createWebAgents`, the function the provider wraps, does the same for a page without React
([No framework](/docs/installation/no-framework)).

## Native WebMCP only: `polyfill={false}`

To leave browsers without WebMCP alone, turn it off. Nothing is loaded, and a tool registered where the browser has
no WebMCP is not registered: the first one logs a warning
(`document.modelContext is unavailable: tools are not registered`), and the page works as before.

```tsx
<WebAgentsProvider tools={tools} polyfill={false}>
  {children}
</WebAgentsProvider>
```

Without React the option is `createWebAgents({ tools, polyfill: false })`.

## In production

The default loads it in production too, and that is on purpose: a visitor in Firefox or Safari gets the same tools,
cursor and chat as one in Chrome. The polyfill is for your own page's agents, the chat and the devtools; a browser agent
that reads native WebMCP only does not see tools a polyfill registered. A site that wants native WebMCP only passes
`polyfill={false}`, as above, or chooses per environment with its own flag.

## What it covers

- **Tools.** `registerTool(tool, { signal })`, `unregisterTool(name)`, `getTools()` and
  `executeTool(nameOrTool, args, { signal })`. `executeTool` accepts both its own `(name, object)` shape and Chrome's
  `(tool, JSON string)`.
- **Lifecycle events** on `document.modelContext`: `toolchange`, `toolactivated` and `toolcancel`.
- **[Declarative forms](/docs/more/declarative-forms)**, found with a MutationObserver: the input schema from the controls,
  `toolautosubmit`, and the confirmation pause. Tools you write with `useWebTool` need none of this: they register
  through `registerTool`.
- **`SubmitEvent.agentInvoked`** and `respondWith(promise)`.
- **`cancelTool(name)`**, a non-standard extra, so the devtools can cancel a call they did not start.

## Differences from Chrome

| | Chrome | Polyfill |
|---|---|---|
| Form states | `:tool-form-active`, `:tool-submit-active` | `[data-tool-form-active]`, `[data-tool-submit-active]` (a script cannot define pseudo-classes) |
| Event order | fill, `submit`, then `toolactivated` | `toolactivated`, then fill, then `submit` |
| Cancelling another caller's call | not possible | `cancelTool(name)` |
| Unregistering a tool with a call in flight | cancels the call | the call finishes |

`@webmcp-agents/core` handles both orders, and its cursor shows both kinds of state, so the same code works on either.
`cursor.css` styles the polyfill's attributes; the native pseudo-classes have their own optional stylesheet,
[`cursor-native.css`](/docs/reference/core/cursor-css#cursor-nativecss).

## Without the provider

A page that registers tools some other way (declarative forms only, or `registerTool` by hand) loads it itself,
before its tools: `await loadWebMCPPolyfill()` from `@webmcp-agents/core` loads it only where it is needed, and
`import "@webmcp-agents/core/polyfill/auto"` installs it from your main bundle. See
[`loadWebMCPPolyfill`](/docs/reference/core/load-webmcp-polyfill) and [the polyfill reference](/docs/reference/polyfill).

`@webmcp-agents/polyfill`, the package that used to hold it, is deprecated: it re-exports these entries, so existing
imports keep working. Uninstall it and delete the `import()` and the "ready" state that waited for it.
