WebMCP: site tools for in-browser AI agents
What WebMCP (the document.modelContext browser API) is, how it differs from a remote MCP server, a minimal registerTool example, mistakes, and how to verify.
Updated:
What it is
WebMCP is a browser JavaScript API from a W3C Web Machine Learning Community Group
draft (authors from Microsoft and Google). A page registers “tools” — functions with a
name, a description and a JSON Schema for their input — via
document.modelContext.registerTool(). The browser’s AI agent (or an agent in an
iframe or extension) calls these functions directly instead of clicking through the DOM.
Tool code runs in the page itself, under the user’s session: the same cookies, the
same app state. WebMCP shares vocabulary with MCP (tools, inputSchema), but it is
not the MCP wire protocol: WebMCP has no server endpoint, no transport and no URL
to connect to.
Don’t confuse it with a remote MCP server (the Streamable HTTP / SSE transport at a
path like /mcp) — that is a separate mechanism, discovered via an
MCP Server Card.
Why it matters for AI agents
An agent that drives a site through screenshots and clicks is slow and brittle: the layout changes and the flow breaks. WebMCP gives it an explicit contract: “these are the actions available here, with these parameters”. Benefits:
- Reliability — a function call instead of guessing which button to press.
- No separate backend for agents — the tool reuses your client code and the user’s authorization.
- User in the loop — the result shows up in the UI they already have open.
Status as of October 2026: origin trial in Chrome 149 and Edge 150 (local development
via the about:flags#enable-webmcp-testing flag), supported in ChatGPT Desktop,
experimental in Brave; Firefox and Safari have no position yet. The declarative
<form> variant was removed from the spec on October 8, 2026 — the imperative API
remains.
Minimal working example
<script type="module">
if ('modelContext' in document) {
const controller = new AbortController();
await document.modelContext.registerTool({
name: 'add-to-cart',
description: 'Adds a product to the cart by SKU',
inputSchema: {
type: 'object',
properties: {
sku: { type: 'string', description: 'Product SKU' },
qty: { type: 'integer', minimum: 1, description: 'Quantity' },
},
required: ['sku'],
},
async execute({ sku, qty = 1 }) {
// Same function as the "Add to cart" button: checks, session, UI update
const item = await addToCart(sku, qty);
return { content: [{ type: 'text', text: `In cart: ${item.title} × ${qty}` }] };
},
}, { signal: controller.signal });
// controller.abort() unregisters the tool — e.g. once it no longer applies
}
</script>
The API is available only in a secure context (HTTPS). Agents in a cross-origin iframe
see the tools only with allow="tools" on the iframe and an explicit origin list in
the exposedTo option at registration. Changes to the tool set fire a toolchange
event.
Right vs wrong
| Right | Wrong |
|---|---|
| A few tools, each with a single responsibility | Dozens of catch-all tools that eat the model’s context |
Input, authorization and preconditions checked inside execute | Trusting the agent’s arguments — a schema is not a safeguard |
| The UI updates after a call just as it would after a click | The action happens “invisibly”, the UI shows stale state |
Tools registered and removed with app state (AbortSignal) | Every tool always registered, even when it doesn’t apply |
A 'modelContext' in document check before calling | Errors in browsers without WebMCP |
Common mistakes
- Confusing WebMCP with a remote MCP server — a
/mcpendpoint and the SSE transport have nothing to do with WebMCP. - The old
navigator.modelContextAPI — in the current spec the object lives ondocument. - Declarative
<form>tools — removed from the spec, don’t build on them. - No checks in
execute— an agent can pass anything; validate it like user input. - A page served over HTTP — outside a secure context
document.modelContextis unavailable.
How to verify
The webmcp check in the scanner is informational (weight 0, doesn’t affect the
score) and isn’t run yet: tools are registered from JavaScript, and the scanner doesn’t
execute page scripts. A cheap static signal is possible — a modelContext.registerTool
call in the page’s scripts — but it only shows the code exists, not that the tools work.
Manually:
- Chrome 149+ with the
about:flags#enable-webmcp-testingflag (or an origin trial on your domain). - Open the page over HTTPS and run
'modelContext' in documentin the DevTools console — it should betrue. - Per the spec,
await document.modelContext.getTools()returns the tools registered on the page — yours should be in the list.