Skip to content

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

  1. Open the domain's settings in the backoffice as a staff user.
  2. 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 → ApplicationWebMCP, 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 into main.tsx alongside 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 to document; Chrome deprecated the navigator location but its origin trial still ships it.
  • unregisterTool was removed from the draft in April 2026. The tool is withdrawn by aborting the AbortSignal passed 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 the get_widget_config allow-list — without that the RPC's inner join drops the whole row and the widget can never see enabled.
  • 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 carry untrustedContentHint: true, because the answer is retrieval output over crawled and client-supplied content — data for the agent, never instructions.
  • execute reuses 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's AbortSignal so a cancelled call stops the request.