WebMCP (in-page tools for AI browsers)¶
Staff-only, off by default
WebMCP is a W3C Community Group draft, not a ratified standard, and no mainstream AI agent calls these tools at volume yet. The feature is hidden from clients in the backoffice and disabled for every domain until a staff member turns it on.
What it is¶
WebMCP lets a web page publish callable tools to whatever AI agent is browsing it. Instead of reading the page and driving the UI with synthetic clicks, the agent calls a described function and gets a structured answer back.
When enabled for a domain, the Rose widget registers one tool on the client's page:
| Tool | Input | Behaviour |
|---|---|---|
ask_sales_agent |
{ question: string } |
Routes the question to that client's website agent and returns the answer. Read-only (readOnlyHint: true), so agents may call it without asking the visitor first. |
The tool name is deliberately brand-neutral — the widget is white-labelled, so
nothing on a client page may say "Rose". The client's company name and
description come from the identity config and go into the tool description,
which is what the agent actually reads when choosing a tool.
How it relates to the hosted Rose MCP server¶
Rose has two agent-to-agent surfaces, and they are not alternatives:
Hosted MCP (backend/apps/mcp) |
WebMCP (this page) | |
|---|---|---|
| Where it runs | Cloud Run, /mcp over Streamable HTTP |
The client's own web page |
| How an agent finds it | The user connects to it explicitly | Automatically, on visiting the site |
| Brand selection | ask_brand_agent(brand, …), fuzzy-matched |
Implicit — the page is the brand |
| Enabled per client | No (any enabled client is reachable) | Yes, staff toggle per domain |
Enabling it for a domain¶
- Open the domain's settings in the backoffice as a staff user.
- Find the WebMCP section and switch it on.
The slug has no settings of its own — the feature is the toggle. Turning it off
withdraws the tool on the next page load. The slug is staff-only both in the UI
(x-access: staff) and in the database (a restrictive RLS policy on
config.client_configs), so a customer admin cannot enable it for themselves.
Chrome additionally needs the origin trial
Enabling the feature is enough for Edge and for ChatGPT's built-in browser. Chrome ships WebMCP behind an origin trial through Chrome 156, so on Chrome the API is absent for ordinary visitors unless the page also carries an origin-trial token. See Browser support below.
Where the turns show up¶
Tool calls go through the same chat API as the visible widget, with the same
session id, so they land in the client's normal conversation thread. Requests
carry messageSource: 'webmcp', which is how you tell an agent-driven turn from
a visitor-typed one.
Browser support¶
| Browser / agent | Status | Needs anything? |
|---|---|---|
| ChatGPT desktop app's built-in browser | Supported in ChatGPT Work and Codex | GPT-5.6 Sol or Terra (Luna has it disabled), latest desktop app, Settings → Browser → Permissions → Enable site tools. Not available on Enterprise or Edu workspaces — the setting is simply absent there. |
| Microsoft Edge 147+ | Native | Nothing |
| Chrome 149–156 | Origin trial | A token. For local development, chrome://flags/#enable-webmcp-testing → Enabled. For real visitors, register the origin at Chrome's Origin Trials dashboard and serve the token in a <meta http-equiv="origin-trial" content="…"> tag. Without one, document.modelContext is undefined for ordinary Chrome users even with the feature enabled. |
| Firefox, Safari | Participating in the spec | No shipping commitment |
Everywhere else, document.modelContext is undefined and the widget registers
nothing.
To check a page by hand, open it in a browser that supports WebMCP and use DevTools → Application → WebMCP, or from the console:
await document.modelContext.getTools();
const mc = document.modelContext;
const tool = (await mc.getTools()).find((t) => t.name === 'ask_sales_agent');
// Chrome takes the arguments as a JSON **string**. Passing an object throws
// `UnknownError: Failed to parse input arguments`.
await mc.executeTool(tool, '{"question":"What does Rose do?"}');
Check typeof document.modelContext before trusting a browser: the DevTools
WebMCP panel ships in the DevTools frontend and renders even where the API is
absent (Brave 150 does exactly this). The Playwright Chromium behind
agent-browser does not expose the API either, so automated checks need an
injected stub — real-API verification means Chrome stable with
chrome://flags/#enable-webmcp-testing.
Two limits worth knowing, both from ChatGPT's implementation:
- Tools synthesised from HTML form attributes (the declarative half of the spec) are ignored. Only the JavaScript API works.
- Tools registered inside an iframe are not discovered. This is fine for us — the widget already refuses to run in an iframe and registers on the top-level document.
Implementation notes¶
- Module:
frontend/widget/src/webmcp/, wired intomain.tsxalongside AI Sections. It is a no-op unless the browser exposes the API and the feature is on. - The entry point is read as
document.modelContext ?? navigator.modelContext. The draft moved it todocument; Chrome deprecated thenavigatorlocation but its origin trial still ships it. unregisterToolwas removed from the draft in April 2026. The tool is withdrawn by aborting theAbortSignalpassed at registration, which happens when the widget is destroyed.- Config slug:
webmcp(schemas/configs/website/webmcp.schema.json),x-feature: true,x-access: staff,enabled_by_default: false. It is also listed in theget_widget_configallow-list — without that the RPC's inner join drops the whole row and the widget can never seeenabled. - The tool deliberately carries no
readOnlyHint: a call persists a conversation turn, updates visitor profile state, and a message containing an email fires the client's lead-capture webhook. It does carryuntrustedContentHint: true, because the answer is retrieval output over crawled and client-supplied content — data for the agent, never instructions. executereuses the widget's own chat service rather than building a second one, so it shares the resolved API endpoint and the live session id, and it forwards the agent'sAbortSignalso a cancelled call stops the request.