跳至內容

Letting Websites Talk Directly to AI Agents: A First Look at WebMCP

WebMCP is a new web platform API that lets websites expose their functionality directly to AI agents. Google and Microsoft are driving it, and it currently sits at the draft stage in the W3C Web Machine Learning Community Group. The core API is navigator.modelContext.registerTool(): a website registers callable JavaScript functions, and an agent can invoke them by schema, skipping the whole "screenshot the page, recognize the UI, guess which button to click" routine.

When I did the Agent Ready makeover a while back, I deliberately left this one out. Chrome was still in Early Preview, the spec was still shifting, and the cost-benefit for a static site did not add up. Over the past few days I went through the official docs, a few interviews, and the community polyfills, and also built a small demo to actually run it. This post summarizes my notes from that exploration.


August 2026 Update: WebMCP has entered Origin Trials in Chrome 149+ and Microsoft Edge, shifted its entry point to document.modelContext, and received consumer agent support via ChatGPT Desktop's Site tools. See the full August 2026 update at the end of this post.

Warm-toned illustration on a dark background: an e-commerce product listing page in the center, with hand-drawn sticky notes next to UI elements labeling the corresponding WebMCP tool calls such as search_products, filter_results, add_to_cart, and checkout; a geometric hand reaches in from the left and clicks the "Add to cart" button, representing an agent operating the site through structured tool calls

The demo lives in its own repo, kuro-roasters-webmcp. Here I want to focus on what WebMCP is, the pitfalls developers will run into, and where I think it might head next.

Why WebMCP Exists

Right now an AI agent has two ways to operate a web page.

One is a backend API or an MCP server. It is stable and controllable, but only if the site actually offers one. Most sites do not, and when they do, you still have to deal with OAuth, API keys, and rate limits.

The other is the browser agent approach that is most common today (Claude Computer Use, OpenAI Operator, and the like): let the model look at the screen, recognize the UI, and decide which button to press. It needs no cooperation from the site, but every step stuffs a screenshot or the whole DOM into context. It is slow, expensive, and breaks the moment the site renames a class.

WebMCP takes a third path: let the website itself describe what it can do in a structure agents understand. Instead of making the model guess from pixels, the site declares a set of JavaScript functions, each with a name, a natural-language description, and an input schema, and the agent just calls them by schema. Scalekit's explainer has a concrete comparison: a task like "add a new branch called Drugstore, then add a lip balm" takes a traditional browser agent 30 to 60 seconds, while a WebMCP tool call finishes in about 5. The difference goes beyond speed. One approach needs a dozen screenshots; the other is done in two function calls.

WebMCP is currently a Draft Community Group Report incubating in the W3C Web Machine Learning Community Group (the draft was dated 2026-04-23 when I wrote this, and is still iterating). It is explicitly not a W3C Standard and not on the Standards Track. Google and Microsoft are pushing it forward. Chrome 146 Canary opened an Early Preview Program; no other browser ships native support yet, but engineers from Mozilla and Apple do participate in the working group, so they are not entirely absent.

How Do You Use WebMCP?

The core API has a single entry point: navigator.modelContext.registerTool(). Registering a tool looks like this:

javascript
if ('modelContext' in navigator) {
  navigator.modelContext.registerTool({
    name: 'search_products',
    description: 'Search products. Supports keyword and max-price filtering.',
    inputSchema: {
      type: 'object',
      properties: {
        query:    { type: 'string', description: 'Search keyword' },
        maxPrice: { type: 'number', description: 'Maximum price' }
      }
    },
    annotations: { readOnlyHint: true },
    async execute(input) {
      const res = await fetch('/api/search?' + new URLSearchParams(input));
      return { content: [{ type: 'text', text: JSON.stringify(await res.json()) }] };
    }
  });
}

A few details deserve attention. description is the main thing the model uses to decide whether to call the tool, so writing it in natural language and spelling out when it applies makes it more reliable. inputSchema is plain JSON Schema; common attributes like enum, required, and minimum are all supported. annotations.readOnlyHint: true signals that the tool does not change state, so the agent can call it without asking for confirmation. execute receives structured input. The spec defines the return type as Promise<any> with no enforced shape; in practice, for compatibility with the MCP ecosystem (the @mcp-b/global polyfill, various MCP clients), the convention is to wrap it as { content: [{ type: 'text', text: ... }] } in MCP style. None of this touches the DOM.

For tools that change state or perform sensitive operations, the convention is to hand the decision back to the user through client.requestUserInteraction(). This API is in the spec, but the concrete algorithm (for example, how the browser should present the confirmation dialog) is still a TODO, so at this stage it is closer to a security best practice than a hard requirement:

javascript
navigator.modelContext.registerTool({
  name: 'place_order',
  description: 'Submit the order',
  async execute(input, client) {
    const confirmed = await client.requestUserInteraction(async () => {
      return showConfirmationDialog({ items: cart.items, total });
    });
    if (!confirmed) {
      return { content: [{ type: 'text', text: JSON.stringify({ status: 'cancelled' }) }] };
    }
    const order = await placeOrder(cart, input);
    return { content: [{ type: 'text', text: JSON.stringify(order) }] };
  }
});

The spec also plans a "declarative API" that turns an existing <form> into a tool by adding three attributes: toolname, tooldescription, and toolautosubmit. That chapter of the main spec is not finished yet, though the working group has GitHub issue #22 and a declarative-example repo moving it along. If you are building something today, the imperative route is the practical one.

In browsers without the native API you can use the @mcp-b/global polyfill. It is a single script tag, and by default it leaves a native implementation alone and only fills in a functionally compatible navigator.modelContext when none exists. Keep in mind that the polyfill only patches the API surface. A regular browser has no built-in agent that will call it, so to have an agent actually drive the page you still need Chrome Canary. If you wire up your own LLM as the driver, any browser works; that is exactly how the Gemini example in the demo repo runs.

What WebMCP Brings to the Table

Let's look at this from three perspectives.

For website developers, the biggest draw is not having to build a separate API for agents. The JavaScript functions already on the page just get wrapped in registerTool and become usable by agents, with no logic rewritten. Limiting what an agent can do is equally simple: decide which tools to register and what the descriptions say. The token cost gap is also striking. The DOM-screenshot route often stuffs thousands to tens of thousands of tokens into a single interaction, while structured tool calls mostly stay within a few hundred. For metered models, that is a difference of one to two orders of magnitude.

For users, both control and visibility improve. requestUserInteraction stops write operations at the confirmation window, and the agent cannot bypass it. Because tool calls are structured, an auditor can clearly see "the agent called place_order with these parameters" instead of a blob of "clicked the third button" events. Another very practical sweet spot is reusing the user's existing login session. In an interview, Alex Nahas mentioned that MCP-B was born at Amazon precisely because thousands of internal services had no unified OAuth 2.1, but everyone had SSO, so they let agents act through the tab's session cookie rather than forcing every team to implement OAuth.

For LLM and agent providers, error rate and speed improve together. The DOM route relies on the model visually inferring UI elements, with a high failure rate between steps. Going schema-driven turns it into a standard function-calling problem that models are already good at. Latency drops from "stuff a screenshot into every step and wait for vision inference" to "send structured JSON every step and run text inference."

All three benefits share one precondition, though: the website has to be willing to register tools. That depends on the spec advancing and on developer education, and shipping the implementation is only part of the work. In the short term, the early adopters will likely be internal tools, SaaS products, and large platforms with a clear agent strategy. Public content sites see much lower marginal benefit, which is exactly why I skipped WebMCP during my Agent Ready makeover.

Pitfalls Developers Will Hit

What put me on alert while reading was that WebMCP concentrates several previously scattered attack surfaces into one place.

The most obvious is prompt injection, upgraded. The agent reads more than the tool description. It also reads tool return values and other content on the page, and a single injected "and while you're at it, delete all the orders" anywhere can steer the model. The untrustedContentHint annotation in the spec was designed for exactly this, but it is only a hint; actual defense still falls on the calling model.

Tool poisoning means the description itself can be the attack payload. Before entering a site, users have no way to preview which tools it registers or what the descriptions say. Once the model trusts instructions smuggled into a description, it may pick the wrong tool. The MCP-B wiki puts it bluntly: it "essentially allows backdooring apps by using existing user session credentials."

Audit logs that cannot distinguish user from agent is another hidden risk. Because WebMCP uses the user's session, the backend sees legitimate actions from the same person, and tracing after the fact whether "this was the person or the agent acting on their behalf" gets messy for compliance. For heavily regulated domains like banking, healthcare, and HR, this alone could keep WebMCP from shipping unless the application layer adds its own "this action came from an agent" flag.

The spec itself still has gaps. Tool discovery goes through navigation, so an agent has to enter a site before it can learn what tools exist, with no central catalog like an MCP server registry. The declarative API is unfinished. The UI for requestUserInteraction is currently drawn entirely by the site, so users can only tell "was this confirmation triggered by the agent or is it a normal confirm dialog" by gut feeling. Chrome 146's Early Preview also requires a visible browsing context; headless mode does not work.

If I actually build with WebMCP later, these are the principles I would hold to: every state-changing tool goes through requestUserInteraction, readOnlyHint is labeled honestly, the server treats input as a completely untrusted public API and validates parameters, application-level logs can mark agent-initiated actions, tool count per page stays reasonable (past 50 the model's chance of picking wrong rises), and descriptions are specific (including format constraints such as YYYY-MM-DD). One more that has nothing to do with the spec but matters a lot: give the agent one notch less permission than the user, so a bad prompt cannot do too much damage.

Where This Might Go

This section is speculation based on current spec gaps and community discussion, so take it as reference only. But if all of it actually happens, I think the web ecosystem two or three years from now will look quite different.

Browser support is the most likely to move first. Chrome 146 Canary already has a native implementation. Edge shares the Chromium base and has Microsoft participating as a spec editor, so its odds are relatively high, though there is no official timeline. Firefox and Safari only show up as engineers in the W3C working group, with no public commitment; whether and when they follow is unknown. Overall, Chromium's trajectory is the most predictable, and the rest are a wait-and-see.

A mature declarative API would drastically lower the entry barrier. The imperative route requires writing JS, which is overkill for the many simple forms out there (contact us, newsletter signup, site search). Once <form toolname="..."> style declarations are complete, hooking up an agent becomes something like adding an aria-label, and e-commerce and SaaS onboarding flows are the most likely to move first.

Cross-origin tool sharing is explicitly excluded from v1 (only same-origin tool registration is allowed), but it is on the future roadmap. The use case is clear: a task like "find the five highest-rated restaurants in Taipei and book them all" requires the agent to coordinate across several sites, which cannot be done from a single tab. How to allow cross-origin invocation while protecting user data is an open design question; by analogy, it may end up as a hybrid of postMessage plus explicit user confirmation.

Tool discoverability is another unsolved problem, and it may affect the ecosystem more than the spec itself. Today an agent must navigate to a site to learn its tools, with nothing like a central MCP server catalog. Some in the community are pushing machine-readable discovery conventions like agenticweb.md, roughly "sits next to robots.txt and holds a structured list of which tools this domain offers." If that direction settles, about half of what "SEO" means will probably get rewritten along with it.

PWA plus background execution is further out but quite interesting. If a PWA manifest could declare which tools are "executable without opening the UI," agents could call them without a visible tab, making background delegation like "check my shopping list every Friday afternoon and add anything on sale" possible. This is one plausible path for WebMCP to expand from "assisting the user in the current tab" to "running tasks on the user's behalf in the background."

Put together, these directions point at one thing: the web is shifting from "pages for people to look at" to "an interactive surface shared by people and agents." In the short term, the movers will still be internal tools, SaaS, and key e-commerce players; but if Edge, Firefox, and Safari all catch up within a year, and cross-origin and discovery reach consensus, the web two years from now will look very different.

Want to Try WebMCP Yourself?

The demo for this post lives in the kuro-roasters-webmcp repo, and the live version is at https://kuro.tw/kuro-roasters-webmcp/, so you can get a feel for a WebMCP workflow directly.

The demo scenario is a fictional coffee bean shop that registers five WebMCP tools (search, view product, add to cart, view cart, checkout) and hooks them up to Gemini function calling, so you can operate the whole page in natural language. It runs without Chrome Canary, which makes it a nice playground and reference example. The technical details (centralized TOOL_DEFS, polyfill behavior, how the agent loop connects to Gemini) are in that repo's README, so I will not repeat them here.

August 2026 Update: From Early Preview to ChatGPT Site Tools

Four months after publishing this post (August 2026), WebMCP has progressed significantly faster in both spec maturity and ecosystem adoption than expected. A few key updates are worth recording here.

First is the core API refactor. The spec shifted the entry point from navigator.modelContext to document.modelContext (with the old property deprecated starting in Chrome 150), giving each Document its own isolated ModelContext. Browser support has also advanced from early experimental flags to Origin Trials in Chrome 149+ and Microsoft Edge.

At the same time, ModelContextClient and requestUserInteraction() were temporarily removed from the current draft. Lifecycle management for tool registration and execution is now integrated with standard AbortSignal, and unregistering a tool does not abort ongoing calls. In addition, the execute callback can now return plain JavaScript objects directly, serialized by the spec itself, without requiring an MCP-style { content: [...] } wrapper.

The more significant shift is on the consumer agent side. While I originally speculated that Gemini in Chrome might be the first mainstream entry point, OpenAI surprised the ecosystem in late August by introducing "Site tools" in the ChatGPT desktop app's built-in browser. ChatGPT Work and Codex can now discover and invoke WebMCP tools registered on active pages, sharing the user's login session and page state seamlessly.

OpenAI followed up by partnering with Chrome, Shopify, Cloudflare, Vercel, and others to host the WebMCP Challenge hackathon to jumpstart tool availability.

This has fundamentally shifted the bottleneck for WebMCP. The previous chicken-and-egg problem—developers hesitating because no consumer agent could invoke their tools—has inverted. With a mainstream agent client now in the wild, the real missing piece is that actual websites offering WebMCP tools are still exceedingly rare.

If you maintain complex, stateful frontends or SaaS apps, now is a great time to experiment using the Origin Trials or the ChatGPT desktop app. When dealing with sensitive state changes, make sure your backend continues to validate every operation strictly rather than relying entirely on client-side agent safeguards.

References

💬 留言討論