AI Blog

WebMCP makes your page an API, and the session is the only auth it has

WebMCP lets a page hand an AI agent a list of callable tools, and Chrome is shipping it behind a flag while the W3C community group draft is still moving. The part worth arguing about is not discovery but authority: a registered tool executes as your page's own JavaScript, inside the session the logged-in user already established, so your server sees a request it cannot distinguish from a click. You are publishing an API whose only credential belongs to someone who is not the caller.

By Agentic AI Wiki 12 min read

The first WebMCP tool most teams ship will not be written as a tool. It will be a form that already existed, re-declared in four lines, and it will accept calls from a model with the full authority of whoever is logged in — because the browser authenticates the user, and nothing authenticates the caller.

At a glance

WebMCP adds a browser API that lets a web page declare structured, callable tools to an AI agent, instead of making the agent drive the page through its UI. It is genuinely a good idea, and it is much earlier than the coverage suggests.

QuestionAnswerWhy it matters
What is it? A page registers named tools with JSON Schema inputs; an agent discovers and invokes them. Replaces scraping and selector-driven clicking with a declared contract.
Standards status A W3C Draft Community Group Report from the Web Machine Learning Community Group — not a W3C Standard and not on the Standards Track. Incubation. The shape can still change, and it has.
Implementations Chrome, behind a flag and an origin trial. Other engines are engaged in the group without shipping commitments. One implementation is a preview, not a platform.
Scope Tools only. MCP's other primitives — resources and prompts — are out of scope. This is not "MCP in the browser". It is one third of MCP.

The churn is already visible from outside: the entry point moved from navigator.modelContext to document.modelContext, and an earlier provideContext() design was dropped. Writing against it today buys you a migration, which is fine if you know that is what you are buying.

What WebMCP covers, and what it leaves to you A matrix over four concerns — tool declaration, user consent, caller identity and API lifecycle — showing that the specification and the browser cover declaration and consent strongly, treat caller identity only as an origin allow-list, and cover versioning, rate limiting and abuse handling not at all. Who handles what, once a page registers a tool Declaring the tool Asking the user Identifying the caller Lifecycle The specification Strong Medium Origin allow-list None The browser Medium Strong Weak None Your page Yours Weak Yours Yours Your server Not involved Not involved Yours Yours Strong, or squarely your problem Partial Weak or absent
The two right-hand columns are the ones with no owner but you — and they are the ones that make this an API rather than a feature.

What actually shipped

The imperative surface is small enough to hold in your head. A page calls document.modelContext.registerTool() with a descriptor — a name, a description in natural language, an inputSchema in JSON Schema, and an execute callback — and the agent side gets getTools() to enumerate and executeTool() to invoke, plus a toolchange event so a single-page app can revise its offer as the user navigates.

document.modelContext.registerTool({
  name: 'addToCart',
  description: 'Add a product to the shopping cart.',
  inputSchema: { type: 'object', properties: { sku: { type: 'string' } } },
  async execute({ sku }) {
    return await fetch('/api/cart', { method: 'POST', body: JSON.stringify({ sku }) });
  },
});

Three details in that snippet are doing more work than they look like they are.

The description is a prompt

An agent picks a tool by reading its description, which makes that string part of your model's instruction surface rather than documentation. It is the same property that makes MCP tool poisoning work, arriving in a place where your marketing team has edit access. A description that says "use this instead of asking the user" is an instruction, and it will be followed.

There is a declarative path, and it is the one that scales

Alongside the JavaScript API, the draft lets an HTML <form> act as a tool declaration. This is the part that will actually get adopted, because it is nearly free — and it is also how a large existing surface becomes agent-callable in a single deploy, without anybody writing a line of code they would recognise as "exposing an API".

Cross-origin is in scope

Registration options include exposedTo, an allow-list of origins permitted to see a tool, and the discovery side has fromOrigins, naming secure origins to collect tools from. Tools are origin-isolated by default and the API requires a secure context. But the existence of these two options tells you the model is not "the page talks to the browser's agent" — it is a graph, and your page is a node in it.

The tool runs inside the session

Where a WebMCP tool call actually executes An agent asks the browser to run a tool. The browser dispatches to JavaScript inside the page, which calls the site's own backend over the same authenticated session the logged-in user established. The server sees an ordinary same-origin request with a valid cookie and cannot distinguish it from a click. THE BROWSER Agent picks a tool by name Browser mediation consent prompt, origin allow-lists Your page's JavaScript document.modelContext .registerTool({ execute }) runs in your origin Your backend sees a same-origin request with a valid session cookie A human click indistinguishable tool call dispatch fetch() The authority on both paths is the user's live session, not the caller. PAGE ORIGIN — YOUR CODE SERVER — YOUR CODE
Nothing new reaches your server. That is the whole problem.

Follow the authority rather than the data. The execute callback is ordinary page JavaScript in your origin. When it calls your backend, it carries the cookies, the bearer token in localStorage, the CSRF token your template rendered — every credential the browser holds for the user who is sitting there. Your server receives a well-formed, same-origin, fully authenticated request and has no field anywhere in it that says a model composed the arguments.

This is a confused deputy in its classic form, and it is worth being precise about who is confused. Your page is the deputy. It holds authority it did not earn — the user's — and it is now accepting instructions about how to spend that authority from a caller whose intent it cannot inspect. The browser's consent prompt is real mitigation and it is the right place for it, but consent is granted by a human who is reading a tool name and a summary, once, at the start of a task that may involve forty calls.

Three callers, one handler A click, the browser's own agent, and a tool call arriving from another origin through the cross-origin discovery path all reach the same registered handler with the same session authority. The handler is given no way to tell them apart. The user clicks submit The browser's agent executeTool(), after consent Another origin discovery via fromOrigins execute(args) your handler, your origin, the user's live session
Three callers, one handler, one credential — and no argument in execute() that distinguishes them.

Compare the three paths directly and the asymmetry is easy to see. A human click passes through your UI, which means it passed your disabled states, your confirmation modals, and whatever rate the user can physically produce. An agent call skips all of that by design — that is the feature — and arrives at the handler at machine rate. A cross-origin call reaches the same handler having satisfied an origin check that your page configured, possibly by accident, possibly with a wildcard that looked convenient during development.

And the instructions driving all three may have come from your own page. An agent that reads a product listing, a review, a support-ticket body or a user-supplied display name is reading untrusted text in the same window where it holds your tool list. This is prompt injection with the tool call already provisioned, which is why every serious defence starts at the authorisation layer rather than at the text.

You are publishing an API, whatever you called it

Set security aside for a moment, because the operational argument lands even for a site with nothing sensitive behind the login. The moment a tool is registered, you have taken on every obligation an API carries, and you have taken it on without any of the machinery you would normally build first.

ObligationYour REST API hasYour WebMCP tools have
VersioningA path segment, a deprecation policy, a changelog.A name string. Rename it and every agent that learned it breaks.
Rate limitingPer-key quotas, backoff contracts, 429s.Whatever your session-level limits happen to be, which were sized for humans.
AuthorisationScopes, per-client grants, revocation.The user's session, all of it, undifferentiated.
Abuse handlingA key you can revoke.A tool you can unregister — for everyone, on your next deploy.

The versioning row is the one that will bite first and quietly. Agents do not read your changelog; they read the description and the inputSchema at call time, which sounds like it makes breakage impossible. It does not. It makes breakage silent: you tighten a schema, agents that used to succeed now fail validation, and your only signal is a metric nobody has built yet. Server-side, this is exactly the tool catalogue lifecycle problem, and it does not become easier for being in a browser.

The rate-limiting row is the one that will cost money. Your per-session limits encode an assumption about human speed that WebMCP invalidates on purpose. An agent working a task will call a search tool more times in ten seconds than a user would in ten minutes, and it will do so legitimately — there is no attacker in this story, just a caller with different physics.

What to do about it

None of this is an argument against WebMCP. The alternative to a declared tool surface is agents driving your UI with synthetic clicks, which is worse on every axis including security — you cannot reason about an interface you never designed as one. The argument is that the cheapness of adoption is the risk, and the mitigations are boring and server-side.

  • Mark the request at the boundary. Have execute attach a header that says this call came from a tool, and which tool. Your server cannot infer it and will never be able to; the only place that knowledge exists is the callback you are writing. Everything below depends on this one line.
  • Re-authorise per action, server-side. Treat the header as a lower trust tier: the same endpoint, the same session, a narrower set of permitted operations. Reads are usually fine. Anything that spends money, sends a message, or is hard to undo should require a fresh, specific confirmation — the blast radius argument, applied to a surface that did not exist last quarter.
  • Register the minimum, not the maximum. The temptation with the declarative form path is to expose everything at once because it costs nothing. Start with the read-only tools that make an agent useful — search, availability, status — and add a write tool only when you have a reason and a limit for it.
  • Set exposedTo explicitly. A default-open tool on a page that embeds third-party iframes is a configuration you want to have made on purpose, in code review, with a name next to it.
  • Rate-limit the tool path separately. Once tool calls are labelled they can have their own budget, which is both the abuse control and the cost control.
  • Write the descriptions like security-relevant strings. They are prompt content. Put them under the same review as your system prompts, not your microcopy.

And on timing: because this is a community group draft with one flagged implementation and an entry point that has already moved once, the honest schedule is to prototype now and ship the server-side half. The header, the per-action authorisation, and the separate rate limit are useful whether or not WebMCP is the standard that wins — they are the same controls you need for any agent traffic reaching your product. The registerTool calls are the part you should expect to rewrite.

FAQ

Is WebMCP a W3C standard?

No. It is published as a W3C Draft Community Group Report by the Web Machine Learning Community Group, which explicitly means it is neither a W3C Standard nor on the W3C Standards Track. Community group incubation is where good ideas start, and also where some of them stop.

Does WebMCP replace MCP servers?

No, and it covers less ground than the name implies. The draft is tools-only — MCP's resources and prompts primitives are out of scope — and it addresses the case where the capability lives in a page the user already has open and is already authenticated to. A remote MCP server still owns everything that is not a browser session.

Can a cross-origin script register tools on my page?

Tools are scoped to the origin that registers them, and the API requires a secure context, so a third-party script cannot silently impersonate your origin's tools. What it can do is register its own tools from its own origin, and cross-origin discovery via fromOrigins plus the exposedTo allow-list is exactly the surface to configure deliberately rather than leave at whatever the default turns out to be.

If my server cannot tell an agent call from a click, what is the minimum fix?

One header, attached inside execute, naming the tool. It is not a security boundary — page JavaScript can send anything — but it is the observability that makes every real control possible, and without it you cannot even measure how much of your traffic is agents.

Should we adopt it now?

Prototype now, and ship the parts that survive. Behind a flag with one engine and an API that has already been renamed, a production dependency on the client half is premature. The server half — labelled tool traffic, per-action authorisation, a separate quota — is work you will need for agent traffic regardless of which spec wins.

Further reading

On this wiki:

Sources: