# WebMCP has two sides, and everyone is building one
WebMCP is shipping in Chrome and default-on at Shopify. But every implementation so far picks a side — publish tools, or consume them. Here's why both matters, and what it takes.
**Published:** 2026-09-04  
**Tags:** webmcp, standards, agents, browser, mcp  
**Cluster:** cornerstone  
---
There is a decent chance an AI agent visited your site this week and had to guess its way
around. It read your DOM, found something button-shaped, and clicked it. Then you shipped a
redesign and it broke.

[WebMCP](https://github.com/webmachinelearning/webmcp) is the W3C draft that fixes this. A
page declares what it can *do* — typed tools with JSON Schemas, registered on
`document.modelContext` — and agents call those instead of hunting for selectors.

It is not speculative any more. Chrome runs a public origin trial from 149 through 156, with
a WebMCP panel in DevTools. It is default-on across Shopify storefronts and Cloudflare-fronted
sites. The getter moved from `Navigator` to `Document` in May and Chrome 150 deprecated the
old alias — the kind of churn you only get on a standard people are actually shipping.

## The two sides

Here is the thing that took me a while to see clearly. WebMCP is not one capability. It is
two, and they are almost unrelated in practice.

**The provider side** is publishing: your page registers tools so *something else* can drive
it. This is what Shopify is doing. It is what you do when you add `toolname` to a form.

**The consumer side** is calling: an agent discovers the tools on a page and invokes them.
This is what Chrome's built-in agent does.

Now look at who is building what:

| Who | Their side | What they can't do |
|---|---|---|
| Shopify, Cloudflare, any site adding `<form toolname>` | provider | nothing calls those tools from inside the page |
| Chrome's built-in agent | consumer | you can't build on it, or ship it |
| MCP-B (`@mcp-b/*`) | provider plumbing — polyfill, transports, relay | it has no agent of its own |
| CopilotKit, Stagehand, Vercel AI SDK | neither | not on the standard at all |

Everyone picked a side. Which is reasonable — they are different problems, and the standard
is young.

But the interesting position is *both*, in the same page. An agent that can call the tools
the page already publishes, and publish its own so outside agents can drive the page. That is
the thing nobody had, so we built it.

## Consuming: the page's tools become the agent's tools

If you are running an agent in the browser, the page's WebMCP registry is the highest-quality
context you will ever get. It is not a DOM snapshot you have to interpret. It is a list of
operations with names, descriptions, and argument schemas, written by the people who built
the page.

In Dornick, adopting them is the default:

```ts
const dornick = new Dornick({ config, transport });
dornick.attach(form);

// The page registered "add_to_cart".
// The model now has a tool called webmcp__add_to_cart, and calls it directly.
```

Two design decisions turned out to matter more than expected.

**Trust is a property of the channel, not a claim.** A tool descriptor can carry annotations,
including hints about whether its output is trustworthy. We ignore that for anything adopted
from the page or a remote server, and mark it untrusted regardless. The page is not
adversarial in the normal case, but "the page says its own output is safe" is not evidence,
and the failure mode — prompt injection straight into the model — is bad enough to warrant
the paranoia. Adopted output gets fenced in `<untrusted-tool-output>` and screened before it
reaches the model.

**Side effects need a human.** A read-only tool can run freely. A tool that charges a card
cannot. Consequential tools from external origins are denied unless the integrator supplies
a confirmation handler. Deny-by-default is annoying exactly once, which is the right number
of times.

## Publishing: the config you already wrote is the schema

The provider side is where the effort usually is. Every tool means a name, a description, a
JSON Schema, and an execute callback. For a form with twelve fields that is a lot of typing
to describe something you already described.

Except — you *did* already describe it. If you configured an agent to collect an email with
a validation pattern and a set of allowed values, that is a JSON Schema in all but syntax:

```ts
new Dornick({ config, transport, webmcp: { publish: true } });

// dornick_set_email, dornick_set_plan, … one per configured dataPoint,
// each schema derived from the type / pattern / allowedValues already in your config,
// plus the read-only workspace verbs and dornick_ask.
```

That is not a better polyfill. It is a different starting point. Because the runtime already
knows what your form is *for*, the tool surface falls out of the configuration rather than
being authored a second time.

Writes route through the same virtual filesystem the agent uses, so existing validation hooks
and access-control rules apply to an outside agent exactly as they do to the in-page one. The
provider surface is not a back door.

## The part everyone should say out loud

We ship a polyfill. It implements `document.modelContext` in every modern browser, in about
7 KB, and it never clobbers — if the browser has a native implementation, or another vendor's
polyfill got there first, it stands down and uses theirs.

Here is what it does **not** do, and what no polyfill can:

| Environment | Who can see your tools |
|---|---|
| Chrome 149+, origin trial or flag | the browser's own agent, extensions, DevTools, page script |
| Anything else (polyfilled) | extensions, CDP/Playwright drivers, the MCP relay, page script |

Native `document.modelContext` is a **browser IPC surface**. A polyfill is ordinary page
JavaScript. Chrome's built-in agent talks to the browser, not to your page's variables, so it
will never see polyfilled tools no matter how conformant they are.

This distinction is easy to blur in marketing copy and I would rather not. What a polyfill
buys you is one authoring API that works everywhere today, plus reach from extensions and
automation drivers. What it does not buy is browser-agent reach. If that is your goal, get an
[origin-trial token](https://developer.chrome.com/blog/ai-webmcp-origin-trial); the polyfill
is what covers everything else in the meantime.

## Where this goes

The bet underneath all of this is that the web gets a second audience. Pages have been
designed for humans, then retrofitted for crawlers. Agents are a third reader, and unlike
crawlers they want to *act*, not just read.

You can serve that reader by leaving your DOM as a puzzle and hoping the model is clever
enough. Or you can tell it what your page does.

WebMCP is the standard for telling it. Both sides of it are worth building.

---

*Dornick is MIT and runs entirely in the browser.
[Make your page agent-ready](/use-cases/agent-ready-page) ·
[WebMCP, both sides](/features/webmcp) ·
[How we compare to MCP-B](/compare/mcp-b)*