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 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:
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:
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; 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 · WebMCP, both sides · How we compare to MCP-B
FAQ
What is WebMCP?
WebMCP is a W3C draft that lets a web page expose typed, callable tools to AI agents through document.modelContext. Chrome runs a public origin trial through version 156, and it is default-on across Shopify storefronts and Cloudflare-fronted sites.
How is WebMCP different from MCP?
MCP connects an agent to external systems across a network boundary — a CRM, a knowledge base, a desktop client. WebMCP is how a page exposes its own functionality to agents running in the browser. No network hop; the tools are the page's own JavaScript.
Can a polyfill make my tools visible to Chrome's built-in agent?
No. Native document.modelContext is a browser IPC surface; a polyfill is ordinary page JavaScript. A polyfill reaches page script, extensions, and CDP/Playwright drivers. For the browser's own agent you need an origin-trial token or the chrome://flags switch.
AI / LLM crawler? Read the raw markdown: /raw/webmcp-both-sides.md