Tensor LabsTENSORLABS

Teaching your website to answer agents

On May 19, 2026, at Google I/O, Chrome announced WebMCP, and as of Chrome 149 it ships as an origin trial. WebMCP lets a web page expose structured tools to a browser-based AI agent, so the agent calls a function you defined instead of guessing its way through your DOM

June 8, 20267 min read10 sectionsBy Tensor Labs
Teaching your website to answer agents

Introduction

On May 19, 2026, at Google I/O, Chrome announced WebMCP, and as of Chrome 149 it ships as an origin trial. WebMCP lets a web page expose structured tools to a browser-based AI agent, so the agent calls a function you defined instead of guessing its way through your DOM. If you have ever watched an agent try to "click the blue button," this is the fix. This tutorial builds a working storefront page that an agent can search and add to a cart through real WebMCP tools, using both the imperative JavaScript API and the declarative HTML form approach.

What WebMCP actually is

WebMCP is the Model Context Protocol, brought to the browser. MCP already lets servers expose tools to models like Anthropic's Claude or OpenAI's GPT. WebMCP moves that contract to the front end: your page registers tools on navigator.modelContext, and any agent running in the browser (a Chrome extension, an Agentic Mode, a future navigator.ai caller) can discover and invoke them.

The point is reliability. Screen-scraping a page is a guess that breaks the moment you ship a redesign. A registered tool is a promise. The agent gets a name, a description, and a typed input schema, and it calls the function. Your CSS class names stop being load-bearing.

  • Imperative API: register tools with standard JavaScript for anything (search, navigation, state changes).
  • Declarative API: annotate an existing HTML <form> and Chrome turns it into a tool for free.

Getting it running in Chrome 149

WebMCP is behind a flag and not in stable Chrome yet. You need Chrome Canary 146.0.7672.0 or higher (the origin trial opens it up more broadly in Chrome 149)

code
 # 1. Install Chrome Canary (macOS example)
brew install --cask google-chrome-canary
# 2. Launch it, then in the address bar go to:
# chrome://flags/#enable-webmcp-testing
# Set "WebMCP for testing" to Enabled, click Relaunch.
# 3. Serve any local page over http (WebMCP needs a real origin, not file://)
python3 -m http.server 8000

Open http://localhost:8000 in Canary with the flag on, and navigator.modelContext exists. That object is your whole API surface

Registering your first tool

Here is the imperative API in full. The descriptor takes a name, a description the agent reads to decide when to call it, an inputSchema in JSON Schema form, and an execute handler that returns content the agent can read back.

javascript
 // store.js - runs on your product page
const cart = [];
navigator.modelContext.registerTool({
name: "add_to_cart",
description: "Add a product to the shopping cart by its SKU and quantity.",
inputSchema: {
type: "object",
properties: {
sku: { type: "string", description: "The product SKU, e.g. 'TS-114'" },
quantity: { type: "integer", description: "How many to add", minimum: 1 }
},
required: ["sku"]
},
execute: ({ sku, quantity = 1 }) => {
cart.push({ sku, quantity });
renderCart();
return {
content: [{
type: "text",
text: `Added ${quantity} x ${sku}. Cart now has ${cart.length} line(s).`
}]
};
}
});

The execute return shape matters. WebMCP expects { content: [{ type: "text", text }] }, the same content envelope MCP servers return. The agent does not see your renderCart() side effect. It sees the text you hand back, so write that text as the honest result of the action, not a hopeful one.

Tools that talk to your backend

Real tools are not synchronous array filters over a hardcoded list. They call an API. The execute handler can return a promise, and WebMCP awaits it, so an async function that hits your backend behaves exactly like the synchronous ones above. This is where most production tools will live

javascript
 navigator.modelContext.registerTool({
name: "check_stock",
description: "Check live inventory for a SKU. Returns the quantity in stock.",
inputSchema: {
type: "object",
properties: { sku: { type: "string", description: "Product SKU" } },
required: ["sku"]
},
execute: async ({ sku }) => {
try {
const res = await fetch(`/api/inventory/${encodeURIComponent(sku)}`);
if (!res.ok) {
return { content: [{ type: "text", text: `Stock lookup failed (${res.status}).` }] };
}
const { quantity } = await res.json();
return { content: [{ type: "text", text: `${sku}: ${quantity} in stock.` }] };
} catch (err) {
return { content: [{ type: "text", text: "Inventory service unreachable. Try again." }] };
}
}
});

Two things matter here. The handler is async, and WebMCP waits for the promise to resolve before handing the result back, so calling your real /api/inventory endpoint needs no special treatment. And every failure path still returns the { content: [...] } envelope, never a thrown exception. An agent cannot read a JavaScript stack trace. If your tool throws, the agent sees nothing, falls back to guessing, and you are back to the scraping behavior WebMCP exists to remove. Catch the error and hand the agent a sentence it can reason about

Testing a tool before an agent ever sees it

You do not need a live agent to develop against WebMCP. The registration runs in the page, so you can confirm the API is present from the DevTools console and exercise the underlying logic while you build the UI

code
 // In the Chrome DevTools console, on your page with the flag enabled:
"modelContext" in navigator; // -> true when the WebMCP flag is on
// Your tools register on page load. Drive the same functions your
// execute handlers call (renderCart, the catalog filter, the fetch)
// and confirm the side effects before wiring up an agent to call them

Build the tool, confirm the API is present, exercise the handler's real work by hand, and only then let an agent drive it. The agent is the last step, not the debugger. (If your tool only works when an agent calls it, you do not have a tool, you have a coincidence.)

The declarative shortcut

Most teams already have a checkout form. WebMCP can lift an existing <form> into a tool with annotations, no JavaScript handler required. Chrome reads the form fields and the agent submits through them.

html
 <form id="newsletter"
data-tool-name="subscribe_newsletter"
data-tool-description="Subscribe an email address to the store newsletter.">
<label>
Email
<input name="email" type="email" required
data-tool-param-description="The subscriber's email address" />
</label>
<button type="submit">Subscribe</button>
</form>

The data-tool-name and data-tool-description attributes register the form as a WebMCP tool. Each input's data-tool-param-description tells the agent what the field wants. Submit still works for human users exactly as before. You added an agent interface and changed nothing about the human one

Cleaning up

Tools registered on a single-page app should be removed when the view unmounts, or the agent will see stale tools that no longer have a backing UI.

code
 // When leaving the product page
navigator.modelContext.unregisterTool("add_to_cart");
navigator.modelContext.unregisterTool("search_products");

When to wire this up, and when to wait

Reach for WebMCP when your site has real actions an agent should take: search, filter, add to cart, book, configure. Anything where today an agent would scrape and misclick is a candidate, and exposing the tool is strictly more reliable than hoping the layout holds.

Hold off when your page is static content with nothing to do, or when your audience is not running agentic browsers yet. This is an origin trial, not a stable API, and the surface can still change before it lands for everyone. (Shipping a flag-gated feature to production users who do not have the flag is a great way to debug nothing for a week.) Treat it as a progressive enhancement: register the tools when navigator.modelContext exists, and lose nothing when it does not

code
 if ("modelContext" in navigator) {
registerStoreTools();
}

The web spent twenty years optimizing pages for humans who read and click. The next interface is a function call from something that does neither. WebMCP is the first standard that lets you answer it on purpose instead of by accident