Exposing Your Site's Actions to AI Agents With WebMCP
WebMCP shows how to register site tools with document.modelContext, set annotations, and secure actions agents can run in a signed-in browser session.
WebMCP reverses the direction of the Model Context Protocol. Instead of an agent connecting out to a server you host, your page registers its own tools in JavaScript with document.modelContext.registerTool(), and an agent that already has the page open calls those declared actions directly, rather than clicking through your interface and guessing at your form fields.
If you have already wired an MCP server to a coding agent, the server-side model is familiar: a process, a transport, a tool list, a client that connects. The browser case is the awkward one. Agent traffic arrives on a rendered page with a logged-in session, and until now the only way through was actuation: read the DOM, infer what the buttons do, hope the checkout step does not re-render mid-click.
This article covers the mechanics that matter to someone who owns a real application: what a correct tool registration looks like, what the three annotation hints actually change about agent behaviour, where site tools run today, and the security consequence of a tool running inside the user’s signed-in session.
Key Takeaways
- Tools are registered with
document.modelContext.registerTool(), which requires a name, a description and aninputSchema;navigator.modelContextwas the earlier namespace and still appears in stale snippets. - Three annotation hints change agent behaviour:
readOnlyHint,consequentialHintanduntrustedContentHint. - A registered tool executes in the live page under the user’s signed-in session, so every capability you expose is one an agent can exercise with that user’s authority.
- ChatGPT’s built-in browser does not support the declarative HTML-form API and does not discover tools inside iframes, so register imperatively on the top-level document.
- WebMCP is not a discovery channel: Chrome lists tool discoverability as an open limitation, because nothing advertises a site’s tools until an agent loads the page.
The Inversion: What Makes WebMCP Different?
A server-side MCP server is something an agent connects out to, configured once and reachable independently of any open page. WebMCP runs the other way. OpenAI’s site-tools documentation draws the line at where the tools live. MCP points an AI application at a server, local or remote, that sits outside the page and works whether or not a browser is open. A WebMCP site hands over its own capabilities as a ready-made set of tools that an agent finds on arrival, and there is nothing for the user to install.
The payoff is precision. Chrome’s WebMCP documentation puts the difference in terms of who decides what a control means: with a tool, the site says so outright, and the agent has nothing left to deduce. Actuation gives it a chain of steps and a judgement call at each one. An agent calling search_orders({ status: "open" }) against a schema you wrote cannot mis-click a filter dropdown, and it cannot break because you renamed a CSS class.
How Do You Register a Tool With document.modelContext.registerTool()?
Tool registration takes an object with a name, a description and an inputSchema; Chrome’s imperative API reference treats those three as the required fields, with annotations and an execute function carrying the behaviour. Feature-detect before calling, exactly as OpenAI’s own example does, because the API is not present in most browsers yet.
async function registerAgentTools() {
if (typeof document.modelContext?.registerTool !== "function") return;
await document.modelContext.registerTool({
name: "list_orders",
description:
"List the signed-in customer's orders, newest first, optionally filtered by fulfilment status. Returns order number, placed date, status and total.",
inputSchema: {
type: "object",
properties: {
status: {
type: "string",
enum: ["open", "shipped", "delivered", "cancelled"],
description: "Fulfilment status to filter by. Omit for all orders.",
},
limit: {
type: "integer",
minimum: 1,
maximum: 20,
description: "Maximum orders to return. Defaults to 10.",
},
},
required: [],
additionalProperties: false,
},
annotations: { readOnlyHint: true },
// Same function the orders table calls. The API still checks the session.
execute: async ({ status, limit = 10 }) => fetchOrders({ status, limit }),
});
}
The description is the only thing the model reads when deciding whether this tool fits the request, so it carries as much weight as the code behind it. Name the return shape, name the filter, and say what the tool does not cover.
Note what execute does here: it delegates. The tool calls the same data-fetching function the UI calls, and the server behind it applies the same authorisation it already applies. OpenAI’s guidance points developers at their existing authentication and authorisation rather than a parallel path, and the engineering reason is plain: two code paths to the same capability will drift, and the one without a UI in front of it is the one nobody notices drifting.
What Do the Three Annotation Hints Change?
Annotations are metadata that tell an agent how to treat a tool before it calls it. Chrome documents three, and Chrome’s tool security guidance re-frames each one in terms of the risk it signals.
| Hint | Set it when | Effect on the agent |
|---|---|---|
readOnlyHint | The tool only reads and changes nothing | Lets the agent judge whether a confirmation is needed at all |
consequentialHint | The action lands in the real world and cannot be taken back: a payment, a transfer, a booking | Tells the agent or browser to get the user’s confirmation first |
untrustedContentHint | Output contains user-generated or external data | Marks the payload as untrusted, so the agent handles it with extra care |
Set them per tool, deliberately. A cancel_subscription tool with no consequentialHint is a tool an agent may fire without pausing, and a review-fetching tool without untrustedContentHint hands the model a block of stranger-authored text with no flag on it.
Where Does WebMCP Run Today?
OpenAI’s site-tools page sets out where ChatGPT will honour a registered tool: in the built-in browser of the ChatGPT desktop app, kept up to date, where ChatGPT Work and Codex can find and call whatever the page offers. The model matters too, with GPT-5.6 Sol and GPT-5.6 Terra supported and WebMCP disabled on GPT-5.6 Luna. Enterprise and Edu workspaces are left out, and whether the feature shows up at all still turns on rollout and on what the open page registers. An arrow in the address bar lists the tools a page provides, and the whole feature can be switched off under Browser permissions.
Chrome’s implementation is still a preview. Chrome documents WebMCP behind the chrome://flags/#enable-webmcp-testing flag for local development, set to Enabled with a relaunch, alongside an origin trial you can join from Chrome 149. It is not on by default in stable, and both vendors’ support statements move, so read the source pages before you ship against them.
What ChatGPT’s Browser Does Not Support
OpenAI’s documentation is clear that the built-in browser covers only part of WebMCP, and it names two gaps. Tools defined through HTML form attributes do not become site tools, and tools registered inside an iframe are not discovered, same-origin iframes included. The practical instruction is short: register imperatively, on the top-level document, and do not rely on anything exotic.
That iframe restriction is ChatGPT’s, not the standard’s. Chrome puts both APIs behind the tools Permissions Policy, which starts at self. On that default the top-level document and same-origin frames can register tools, and a cross-origin iframe cannot. An embedded widget on another origin can register tools if the frame is granted the tools policy, the tool passes exposedTo listing authorised origins, and the caller passes fromOrigins to getTools(). Chrome also restricts WebMCP to origin-isolated documents, so a page using document.domain gets no API at all.
Your Tool Runs as the Logged-In User
A registered tool executes inside the live page under the user’s signed-in session, which means every capability you expose is a capability an agent can exercise with that user’s authority. The scoping question is not what would be convenient to automate. It is what you would accept being invoked without a click.
Chrome’s security guidance is unusually blunt about why that matters. A model takes instructions and data as one continuous run of tokens, with no line between them. Safety cannot be guaranteed inside something probabilistic. Prompt injection has already worked, repeatably, against agent systems running the best models available, and the number of such attacks on the web keeps climbing. OpenAI says much the same about the tools themselves: in its site-tools documentation, a website’s tool definitions and the results they return both count as untrusted content.
Three concrete controls follow. Tool visibility starts closed, since other sites and cross-origin iframes cannot see your tools until you name their origins in exposedTo; apply the same care to read-only tools that reveal user data as to write tools. Chrome also notes an access path you did not open: extensions can query and execute your tools from a content script, and one holding host_permission for your site can already run its own JavaScript on the page in any case. And keep text small. Chrome recommends 500 characters for a tool description, 150 per parameter description, 30 for tool and parameter names, and 1.5K per tool output, describing all four as recommendations that may change with ecosystem feedback and may later be formalised. Work on consent management continues, including a spec-draft requestUserInteraction() for asking the user something mid-execution, which has not shipped.
WebMCP Is Not an SEO Play
Registering site tools changes what an agent can do once it arrives on your page. It does nothing to make it arrive. Chrome’s own limitations list names tool discoverability as an open problem: a client or a browser only finds out that a site has callable tools by going there. There is no crawl, no index, no feed of registered tools. Read alongside that mechanism, the conclusion is straightforward, though it is our reading rather than a vendor statement: WebMCP is a conversion-path surface, not a ranking or citation lever, and treating a tool description as meta-description copy misunderstands who reads it.
Pick one action your users already complete on your site, register it read-only first, and put the real work into the description and the schema. That is where an agent either understands your application or does not, and it is the part no browser rollout is going to fix for you.
FAQs
How do I unregister a WebMCP tool when the user navigates away?
There is no unregisterTool method. Pass an AbortSignal in the options object of document.modelContext.registerTool, then abort that controller when the tool no longer applies, such as on component unmount or an SPA route change. Chrome's best practices put this in terms of page state: register a tool while it is useful, and unregister it once it is not. Tying the abort to your page transitions is the practical way to do that, and it keeps a stale tool from lingering or from colliding with a fresh registration of the same name. Agents observe the change through the toolchange event on document.modelContext.
What is the difference between the declarative and imperative WebMCP APIs?
The declarative API turns an existing HTML form into a tool: add toolname and tooldescription attributes to the form element, plus toolparamdescription on individual fields, and the browser derives a structured representation from the form. Removing either attribute unregisters the tool. The imperative API, document.modelContext.registerTool, suits dynamic tools and complex logic. ChatGPT's built-in browser supports only the imperative path.
Is there React or Angular support for registering WebMCP tools?
Both exist and both are experimental. Chrome Labs maintains the useWebMCP hook in the use-webmcp-tool package, which registers a tool on mount, unregisters it on unmount, requires React 18 or later, and degrades to a no-op where the API is absent. Angular exposes provideExperimentalWebMcpTools from its core package, tying tool lifetime to an injector, with route or application providers the recommended placement.
Does the browser validate the arguments an agent passes against my inputSchema?
Do not assume it does. Treat the input reaching execute as unvalidated and check it in code before acting on it. Chrome's WebMCP guidance tells developers to validate constraints and return descriptive errors so the agent can retry, and Angular says plainly that it does not check agent-supplied arguments against the JSON schema you declared. Server-side authorisation checks still apply on top.