What is WebMCP? When and How to Use WebMCP in a Browser Agent?

MCP & Agent ProtocolsGaurav Dadhich2026-09-2314 min read
What is WebMCP?  When and How to Use WebMCP in a Browser Agent?
On this page
  1. What WebMCP is
  2. When it is worth reaching for
  3. Start with the part that is already stale
  4. The four calls you need
  5. Per step, not per site
  6. The list moves under you
  7. Getting it to run at all
  8. Whose hint is it
  9. Is it worth it
  10. What breaks next

Published 23 September 2026

WebMCP lets a web page hand an agent a list of named, typed actions instead of making it work them out from the rendered page. If you already run a scraper or a browser agent, the thing worth knowing before anything else is that the decision to use it is per step, not per site. A page can expose a clean search tool and leave its account settings as ordinary DOM controls, so an agent that picks one mode per domain gets the worst of both. Your existing reader stays primary, WebMCP becomes a fast path in front of it, and the arbitration happens at each step of the task.

That is the whole architecture. Everything below is what the parts cost you, starting with the one that will eat an afternoon if you take it from a guide written three weeks ago.

What WebMCP is

A page calls document.modelContext.registerTool() with a name, a plain-language description and a JSON Schema for the inputs. Any agent running in that tab can then list what is registered and call it directly. The specification puts it plainly: a page using WebMCP can be thought of as a Model Context Protocol server that implements its tools in client-side script rather than on a backend.

What it replaces is guessing. An agent today works a site by screenshotting it, reading the DOM or the accessibility tree, inferring which control does what, and simulating clicks; rename a CSS class and the sequence breaks. A registered tool states the contract instead, so search_products takes a query string and hands back a result, rather than a button somewhere on the page that might be the search button.

Two properties matter more than the API surface. Tools live in the tab and die on navigation, so they are scoped to the page rather than to your session. They also execute inside the user's existing signed-in session. That is how they reach a cart, a filter state or a half-finished form that a server-side integration would need its own authentication to touch.

When it is worth reaching for

Lifetime decides most of it. WebMCP does not replace an MCP server: its tools exist only while the page is open, so anything that must run without a live tab belongs in a server-side capability. Chrome frames the inversion well when it says that instead of your application being a guest inside an agent, the agent becomes a guest on your platform.

Within that boundary, reach for WebMCP when the action belongs to the page the user already has open and depends on live tab state, which covers a cart, an applied filter, a document being edited, a dashboard date range. Use your existing reader everywhere else, and everywhere else is still most of the web.

How often the fast path actually fires is genuinely unknown, which is worth settling before you plan around it. Largest published figure is a directory count rather than a crawl, at 462 live sites and demos as of 5 September. Against that, Shopify switched tools on for every Liquid storefront effective 21 August, a very large installed base arriving in one step, and Cloudflare's edge bridge lets a site enable it without touching its origin. Nobody has a reliable denominator.

Start with the part that is already stale

Every article-format guide currently published shows this call:

await document.modelContext.executeTool(tool, JSON.stringify(args));

That shape no longer validates. A Draft Community Group Report dated 17 September 2026 defines the second argument as optional any inputObject and specifies that if it is not an Object, the call returns a promise rejected with a TypeError; the browser serializes internally. Chrome's imperative API documentation, last updated 11 September, now documents only the object form and carries a deprecation note on the other: JSON stringified input arguments are deprecated from Chrome 155.

So the current call passes an object:

const result = await document.modelContext.executeTool(tool, { query: "mechanical keyboard" });

None of those guides were wrong when they published. freshman.tech and flaviocopes, both updated 9 September, even flag the divergence and tell you to use whichever form your browser supports; they predate the resolution by about a week. That is the real lesson, and it is worth more than the API detail: published integration guidance in this corner of the platform has a shelf life measured in days, and anything you hardcode from an article inherits it.

The four calls you need

Detection first, because two separate gates produce a silent undefined even on a browser that supports the feature. The page has to be origin-isolated, so a document opting out through document.domain or an Origin-Agent-Cluster: ?0 response header does not get the API at all, and registration is governed by the tools Permissions Policy with a default allowlist of self, which means cross-origin iframes are excluded unless the parent delegates with allow="tools".

if (!document.modelContext) { /* use your reader */ }

Global names have moved too. It was navigator.modelContext, an earlier draft carried a provideContext() method since removed, and document.modelContext is current. If a sample uses either older name, check its date before you trust anything else in it.

Listing is await document.modelContext.getTools(). Note the scope carefully, because it decides which API you actually write against: getTools() is a discovery surface for agents running inside the page, and a browser-integrated agent never calls it, since the browser hands it tools through an internal channel. If you drive a browser from outside, you use your driver instead. Stagehand exposes page.listWebMCPTools() and page.invokeWebMCPTool(); agent-browser exposes webmcp list and webmcp invoke; Puppeteer documents page.webmcp. Cross-origin listing takes an options bag, getTools({ fromOrigins: ['https://shop.example'] }), and it only returns anything if the registering page also named your origin in registerTool(tool, { exposedTo: [...] }). Both keys have to turn.

Calling is executeTool(tool, inputObject), passing the tool object itself rather than its name, and the result comes back as a JSON string even though the input is an object.

Cancellation is two different mechanisms that are easy to confuse. Registration lifetime rides on an AbortSignal handed to registerTool(tool, { signal }), so aborting the controller unregisters the tool. Execution cancellation is separate: execute receives a signal as its second argument, which you pass through to fetch(). freshman.tech notes the gap between them, and it is the kind of thing that surfaces in a live demo: as of Chrome 153, unregistering a tool does not cancel an execution already in flight.

Per step, not per site

Here is the gap in the published material. Every article-format treatment decides per site or at design time. The one project with genuine per-step arbitration, gui-agent, hands the model a single merged tool list of registered WebMCP tools plus synthesized DOM tools, instructs it to prefer the page's tools, and resolves name collisions in favour of the app tool; its decision node reads "Is there a purpose-built WebMCP tool for this step?" That is exactly right, and it is scoped to applications you own, which leaves the reader driving somebody else's site without a published answer.

Generalising it is not complicated. For each step, match against the tool's name, description and input schema, then fall through to your reader for that step when nothing fits or when you cannot fill the schema from data you already hold. Four conditions send you to the fallback and only one of them is "this site has no WebMCP": no API, no matching tool, a schema you cannot satisfy, or a call that throws. Treat them identically, including when a tool vanishes mid-task, which is normal rather than exceptional given that registrations follow routes and permissions.

Aident comes closest among the articles, with a per-action preference order running from a structured tool contract owned by the application, to a direct API or MCP capability for durable external work, to browser automation for whatever interface remains uninstrumented. Their caveat deserves repeating, because it cuts against the obvious reading: that order is not a universal reliability ranking, and a poorly designed tool can be more dangerous than careful automation.

For the fallback itself, accessibility-tree-with-refs is the strongest documented default. agent-browser marks its accessibility-tree snapshot as the best option for AI and treats annotated screenshots as optional and secondary, with change thresholds to control token spend; gui-agent builds a compact text snapshot carrying roles, labels, values and stable refs, and states plainly that it needs no screenshots and no multimodal model. Vision costs the most context and is the least stable under redesign.

The list moves under you

Tools follow routes, permissions and authentication state, so a catalogue read at page load goes stale quickly. The current spec defines three events, not one: toolchange when the list changes, toolactivated when an execution begins, and toolcancel when one is cancelled. Published guidance covers the first and has not caught up with the other two.

How you observe changes depends again on where you sit. In-page agents subscribe. External drivers re-list, and Stagehand makes this a guarantee rather than a convention: listWebMCPTools() returns a fresh snapshot on every call and never reuses tools from a previous page or a previous call. agent-browser pushes catalogue changes into context through a data.webmcp field and warns about the case that quietly breaks caching, which is that a full record can change while the brief description stays identical, so a previously fetched schema needs refreshing after any catalogue update.

Annotations are worth reading properly, because there are four and the circulating summaries list three. readOnlyHint says the tool only reads. untrustedContentHint says the output contains data the registering author does not trust. consequentialHint marks actions that are significant, real-world or non-reversible, with booking a flight and transferring money as the spec's own examples. The fourth, debugging, marks tools meant for developer tooling rather than end-user interaction, and it appears in none of the guides I read. It is spelled debugging, not debuggingHint.

One genuinely open question: whether tools registered declaratively through HTML form attributes show up in getTools() is undefined today. Section 4.3 of the spec reads, in full, that the section is entirely a TODO. The declarative explainer addresses the exact question and leaves it open, saying that declarative tools should almost certainly be invokable from that interface but the details are still to be determined. Chrome's declarative documentation, the stalest WebMCP page at 18 May, documents the attributes without stating the answer. Plan for imperative registration and treat declarative discovery as unavailable until the TODO closes.

Getting it to run at all

For local development, enable chrome://flags/#enable-webmcp-testing and relaunch. For production traffic, the origin trial received approval for Chrome M149 through M156 inclusive, with shipping estimated at 157; Edge runs a parallel trial from 150 that expires 17 November 2026. If you drive Chrome externally over DevTools you also need launch features, and Stagehand is the only source that prints them: Chrome or Chromium newer than 149, launched with --enable-features=WebMCPTesting,DevToolsWebMCPSupport.

Skip ChromeStatus as a source on current state; that record was last edited 12 August and still reports the feature as proposed with no origin trial and no engine signals, which the live trial and the filed positions both contradict.

Headless agents cannot reach the API directly, since it lives in a live tab, and Chrome's limitations list says headless is not the design target. Two routes work well. Run a hosted browser and evaluate inside the session, which is what Firecrawl documents through its scrape-then-interact endpoints, or drive Chrome over CDP with Stagehand, agent-browser, Browser Use (which added native support on 6 September) or Cloudflare Browser Run. Running in-page with @mcp-b/webmcp-polyfill is a third option that needs no flags at all, because it installs the global itself.

Client support today, per the implementation tracker: Chrome from 149, Edge from 150, Brave experimentally in Leo, and ChatGPT Desktop, which requires GPT-5.6 Sol or Terra and is unavailable in Enterprise and Edu workspaces. No Firefox, no Safari. Engine positions are now formally filed rather than merely under discussion, and secondary coverage gets this wrong in both directions: WebKit's recorded position is oppose, with concerns spanning API design, duplication, internationalization, privacy, security, venue, use cases and portability, while Mozilla's is neutral.

Chrome ships a Model Context Tool Inspector extension for debugging, which lists tools, executes one against JSON input and shows structured output. If document.modelContext is undefined after you enable the flag, check origin isolation before anything else, and resist shipping your own shim at that global, which mostly hides the fact that you have stopped testing the browser's implementation.

Whose hint is it

Tool names, descriptions, schemas and results are attacker-controlled input. agent-browser states the position as bluntly as anyone has: all page-provided names, descriptions, schemas, annotations and results are untrusted data, the provenance labels are cues rather than a prompt-injection boundary, and website text does not get promoted into system instructions, does not authorise shell commands, and does not constitute a claim of user consent.

Which sets up the thing nobody names. Two shipping libraries take opposite positions on readOnlyHint, and both are correct.

gui-agent trusts it. Any tool without annotations.readOnlyHint routes through a confirmation callback before running, and marking a tool read-only skips the prompt. agent-browser refuses it, stating that page-provided readOnlyHint or untrustedContentHint claims cannot bypass host controls, and that domain filters do not prevent a page from lying about what a tool does.

That is not a disagreement about security. gui-agent runs inside an application you own, where the annotation is your own assertion about your own code; agent-browser drives arbitrary third-party sites, where the identical field is a claim by a stranger. OpenAI puts the third-party case in one line, which is that a tool's name or its claim to only read data is not proof of what it does. Copy the wrong pattern into the wrong threat model and you have built a confirmation gate any page can switch off by setting a boolean.

Schemas do not help here either. Ego ran the Chrome Labs hotel demo on 11 September in Chrome 152.0.7977.76 and found an out-of-enum value passing schema validation and reaching the application route. A schema shapes input; it does not prove the request is authorised, and it never will, because it is advisory to whoever is calling.

What to actually build, drawing on Chrome's agent security guidance: cap inbound tool-response size and reject oversized payloads rather than truncating them into context; restrict callable origins to those relevant to the current task; wrap tool output so the model reads it as data rather than instruction; require confirmation for consequential actions by your own policy rather than the page's annotation; keep credentials and personal data out of tool arguments unless the tool genuinely needs them; and log origin, tool name, arguments and whether each step came from WebMCP or the fallback, because after an incident the question you will need to answer is which page told you to do that.

Is it worth it

One public benchmark exists, and the disclosure belongs before the numbers. WindTunnel is published by nekuda, which sells WebMCP products including AgentLane, a WebMCP Kit, a Chrome extension and an SDK, and which also operates the site hosting the leaderboard. The benchmark measures the interface its product line depends on.

Their method is better than that framing might suggest. The harness is public under Apache-2.0, covering 49 tasks across 8 self-hosted open-source applications pinned to upstream commits, three attempts per task scored by majority verdict, with agent models including Claude Sonnet 5, Claude Opus 5, GPT-5.6 Luna, GPT-5.6 Sol, GPT-6 Astra and Gemini 3.6 Flash. No judge model is involved; scoring is a code check against real post-run application state. Current board is version 1.2, dated 18 September, at 21 configurations and 3,087 attempt rows.

What it shows now is narrower than what gets quoted. Ten configurations solve 49 of 49: all nine WebMCP ones, plus GPT-6 Astra on code execution, which is a screen-driving mode. Take the median of the twelve non-WebMCP rows on the published board and it comes to 44, though the README does not print that figure itself. nekuda's own text concedes the shift: raw task-solve rate does not separate WebMCP from the best screen-driving configuration, and cost and time do. The same README discloses that projected per-thousand-run cost ranges overlap, that two drivers cannot report the model snapshot served, and that an independent review caught a merge built before corrected predicates were applied, requiring a re-score that produced nineteen false-negative promotions.

Two figures in wide circulation should stop circulating. "48 of 49 against a median of 43" is the August board, retired on 6 September when a missing checkout tool in one demo store was fixed; by nekuda's own account that gap made 48 a ceiling by construction, a limitation of one store's tool surface rather than of the approach. And the pairing of 7.8 seconds against 28.1, at 0.6 cents against 5.5, matches no table currently published on either the original post or the live board. I could not source it, so I am not repeating it as fact.

A quieter argument is the token bill, and exactly one project treats it seriously. agent-browser withholds schemas by default, requiring a two-stage fetch where the agent picks from a summary and then requests the single schema it needs, and it hard-budgets those summaries to sixteen tools and four kilobytes with descriptions cut to a hundred and sixty bytes. Sites can register a lot of tools; on the fallback side, a page dump or a screenshot per step is precisely what makes screen-driving expensive.

What breaks next

The spec is a Community Group draft. It is not a W3C Standard and it is not on the Standards Track. Since February the global has been renamed, a method has been removed, the argument shape of executeTool has changed, and the declarative section sits unwritten. Tool outputs, dynamic definitions, long-running execution and cross-document discovery are all listed by the spec itself as still moving, which is four more renames waiting to happen.

Which is the argument for keeping the volatile surface in one adapter that detects, lists, calls and normalises the result, and letting the rest of your agent talk only to that adapter. When the surface moves, and it will, you change one file rather than auditing every call site. The same reasoning applies to any integration instructions you hand a coding agent: point it at the spec and at Chrome's documentation rather than hardcoding method names, because a prompt containing a method name goes stale at exactly the speed an article does.

Getting the arbitration right pays off in a way that has little to do with whether any particular site adopts the standard. Your agent stops paying for a screenshot on the steps where a page has already told it what it can do, and keeps working normally everywhere else. That is the only version of this that survives the next rename. It is also the version that shows up on the inference bill.

From the team at Maximem

Stop rebuilding agent memory from scratch

Maximem Synap is the context management layer we built after hitting every problem in this post ourselves. Persistent recall across sessions, entity resolution and conscious forgetting, in Python, TypeScript and REST.

Related posts