Skip to main content
Development 2026-07-05

How to Add WebMCP to a Website: Expose Your Site's Actions to AI Agents

MCP Trail

MCP Trail Team

Developer Relations

How to Add WebMCP to a Website: Expose Your Site's Actions to AI Agents

How to Add WebMCP to a Website: Expose Your Site’s Actions to AI Agents

Short answer: To add WebMCP to a website, register each of your site’s actions as a tool with window.navigator.modelContext.registerTool, giving each tool a name, a description, an inputSchema (JSON Schema), and an async execute function that runs in the page as the logged-in user. Then test it behind the Chrome WebMCP flag by calling await document.modelContext.getTools() in DevTools. Ship one registerTool call per action, keep schemas tight, and guard anything destructive.

WebMCP is an experimental browser API that lets a website publish its own actions as tools an in-browser AI agent can call directly. Instead of scraping your UI or reverse-engineering your REST API, an agent asks the page “what can you do?” and gets a structured list of tools it can execute on behalf of the signed-in user. This WebMCP tutorial walks through a real WebMCP implementation from the first flag toggle to production-ready best practices. If you want the conceptual background first, read What is WebMCP.

1. Enable the flag and check prerequisites

WebMCP is available in Chrome 150 and later behind an experimental flag. Before you can add WebMCP to a website and see anything, enable it:

# 1. Open this URL in Chrome 150+
chrome://flags/#enable-webmcp-testing

# 2. Set it to "Enabled"
# 3. Restart Chrome

After restarting, confirm the API exists on any page you control. Open DevTools and run:

// In the DevTools console on your own page
console.log(typeof navigator.modelContext);       // publisher side: "object"
console.log(typeof document.modelContext);         // consumer side: "object"

If both print object, WebMCP is live and you can start registering tools. The publisher side lives on navigator.modelContext (your site registers tools here). The consumer side lives on document.modelContext (this is how an agent, or you during testing, discovers and runs tools). You do not need any package, build step, or server to get started, just the flag and a page you serve.

2. Register your first WebMCP tool

The core of any WebMCP implementation is registerTool. Each tool maps to one real action your page can already perform. Here is a minimal example that exposes “add to cart”:

<script>
window.navigator.modelContext.registerTool({
  name: "add_to_cart",
  description: "Add a product to the shopping cart",
  inputSchema: {
    type: "object",
    properties: {
      productId: { type: "string" },
      quantity: { type: "number" }
    },
    required: ["productId"]
  },
  async execute({ productId, quantity = 1 }) {
    await cart.add(productId, quantity);
    return {
      content: [
        { type: "text", text: `Added ${quantity} × ${productId} to the cart.` }
      ]
    };
  }
});
</script>

Three things make this work. First, navigator.modelContext.registerTool publishes the tool to the current tab. Second, execute runs in the page with the same cookies and session as the human user, so cart.add behaves exactly as if the person clicked the button. Third, the return value is an MCP content envelope: an object with a content array of { type: "text", text } blocks that the agent reads back. Call registerTool once per tool; register your whole toolset by calling it several times.

3. Add an input schema and validation

The inputSchema field is standard JSON Schema, and it is the contract between your site and the agent. A loose schema means the agent guesses; a tight schema means it calls your tool correctly the first time. Use required, constrain types, and reach for enum when only a fixed set of values is valid:

window.navigator.modelContext.registerTool({
  name: "search_orders",
  description: "Search the current user's orders by status and date range",
  inputSchema: {
    type: "object",
    properties: {
      status: {
        type: "string",
        enum: ["open", "shipped", "delivered", "cancelled"]
      },
      since: { type: "string", description: "ISO 8601 date, e.g. 2026-01-01" },
      limit: { type: "number", minimum: 1, maximum: 50 }
    },
    required: ["status"]
  },
  async execute({ status, since, limit = 20 }) {
    // Always validate again inside execute — never trust the caller.
    if (limit > 50) limit = 50;
    const orders = await api.orders.search({ status, since, limit });
    return {
      content: [{ type: "text", text: JSON.stringify(orders, null, 2) }]
    };
  }
});

JSON Schema constrains the shape, but execute still runs untrusted input in your page. Re-validate inside execute, clamp numbers, and reject anything unexpected. Treat every argument as if it came from the network, because effectively it did.

4. Register multiple tools: read-only vs mutating

A real site exposes several tools. Group them by intent and mark which ones only read data versus which ones change state. WebMCP supports a readOnlyHint annotation so agents (and any governance layer in front of them) can reason about risk before calling:

// Read-only: safe to call freely, no side effects
window.navigator.modelContext.registerTool({
  name: "get_cart_total",
  description: "Return the current cart subtotal and item count",
  inputSchema: { type: "object", properties: {} },
  annotations: { readOnlyHint: true },
  async execute() {
    const { subtotal, count } = await cart.summary();
    return { content: [{ type: "text", text: `${count} items, $${subtotal}` }] };
  }
});

// Mutating: changes state, should be idempotent and guarded
window.navigator.modelContext.registerTool({
  name: "create_invoice",
  description: "Create a draft invoice for the given order",
  inputSchema: {
    type: "object",
    properties: { orderId: { type: "string" } },
    required: ["orderId"]
  },
  annotations: { readOnlyHint: false },
  async execute({ orderId }) {
    // Idempotent: reuse an existing draft instead of creating duplicates.
    const invoice = await invoices.upsertDraftForOrder(orderId);
    return { content: [{ type: "text", text: `Draft invoice ${invoice.id} ready.` }] };
  }
});

Name tools like verbs the agent can reason about: create_invoice, search_orders, get_cart_total. Make mutating tools idempotent where you can, so a retried call does not create duplicate invoices or double-charge a cart.

Here is how a WebMCP tool compares to a traditional REST endpoint:

AspectWebMCP toolREST endpoint
DiscoverySelf-describing via getTools()Out-of-band docs / OpenAPI
Where it runsIn the browser tab, as the logged-in userOn your server, needs its own auth
AuthInherits the user’s session cookiesYou issue and verify tokens
ConsumerIn-browser AI agentAny HTTP client
ContractinputSchema (JSON Schema) + descriptionURL, method, body schema
AvailabilityOnly while the tab is openAlways, from anywhere

The key difference: a WebMCP tool is only callable while the tab is open, and it runs with whatever session the human already has. That is powerful and also the source of the governance gap we cover in step 7. For a deeper contrast with how models call functions generally, see MCP vs function calling.

5. Test it with getTools in DevTools

Once your tools are registered, verify them from the consumer side. With the flag enabled and your page loaded, open DevTools and run:

// List every tool the current tab exposes
const tools = await document.modelContext.getTools();
console.table(tools.map(t => ({ name: t.name, description: t.description })));

// Run one tool directly, passing arguments as JSON
const result = await document.modelContext.executeTool(
  "add_to_cart",
  JSON.stringify({ productId: "sku-123", quantity: 2 })
);
console.log(result);

getTools() returns your registered toolset exactly as an agent would see it. executeTool(name, argsJson) runs a single tool and returns its content envelope, which is the fastest way to smoke-test execute end to end. If you register or unregister tools dynamically, listen for changes:

document.modelContext.ontoolchange = () => {
  console.log("Toolset changed, re-fetching...");
};

The tab must stay open for tools to be callable; there is no background worker keeping them alive. If getTools() returns an empty array, confirm the flag is enabled, the page reloaded after your registerTool calls ran, and no exception was thrown during registration.

6. WebMCP best practices

A working WebMCP implementation is easy; a good one takes discipline. The table below contrasts weak and strong tool design:

Bad tool designGood tool design
doStuffcreate_invoice (verb + object)
“Handles orders""Search the current user’s orders by status and date range”
Free-form string inputsTyped schema with enum and required
Silent delete_accountGuarded action requiring explicit confirmation
Non-idempotent writesIdempotent upserts safe to retry

Concretely:

  • Name tools like verbs. search_orders, not orders. The name is the agent’s first signal.
  • Write descriptions an agent can reason about. Say what the tool does, what it needs, and any constraints.
  • Keep the input schema tight. Required fields, enums, and min/max bounds prevent malformed calls.
  • Make execute idempotent where possible. Retries are common; duplicate side effects are not forgivable.
  • Mark read-only tools with readOnlyHint and reserve false for mutations.
  • Never expose destructive actions without a guard. Deleting an account, refunding money, or emptying a cart should require a confirmation step, not a single unguarded execute.
  • Map tools to real user actions. If the page cannot already do it, do not invent a tool for it.

7. The missing piece: auth, logging, and approvals

Here is the honest part of any WebMCP tutorial. Every tool you register runs in the browser as the logged-in user, with real side effects, and the spec gives you no built-in authentication, no audit log, and no approval step. An agent that gains access to the tab can call create_invoice or add_to_cart as many times as it likes, and you will have no record of who did what, or any chance to say “wait, confirm that first.”

That governance gap is exactly what MCP Trail closes. You keep your WebMCP tools as the source of truth and put a guarded layer in front of them so every call is authenticated, logged, and (where you want it) approved by a human before it runs:

Add the tools first, then add the guardrails. Both matter.

One caveat to keep in mind: WebMCP is an evolving experimental spec. The exact API shape, including annotation names and the modelContext surface, may change between Chrome versions. Test everything behind the flag, and keep an eye on the spec as it stabilizes.

FAQ

What browsers support WebMCP?

WebMCP is currently experimental in Chrome 150 and later, enabled via chrome://flags/#enable-webmcp-testing. It is not yet available in stable channels or other browsers. Because it is behind a flag, treat it as a preview: build and test now, but expect the API to change before it ships broadly.

Is WebMCP the same as MCP?

No. MCP (Model Context Protocol) is the broader standard for connecting AI models to tools and data, usually over a client-server transport. WebMCP is a browser-specific way to publish tools from a web page via navigator.modelContext, so an in-browser agent can call them as the logged-in user. WebMCP tools speak the same content-envelope shape as MCP, but they run in a tab instead of a server. See MCP vs function calling for how these ideas relate.

Do WebMCP tools work without a server?

Yes. That is one of the main draws. Your execute function runs entirely in the browser using the user’s existing session, so you can add WebMCP to a website with no backend changes at all. The trade-off is that tools are only callable while the tab is open, and you get no server-side auth or logging by default, which is why a guard layer matters for anything sensitive.

How do I secure WebMCP tools?

The spec itself provides no auth, audit, or approval, so you add those in front of your tools. At minimum: mark read-only tools, guard destructive actions behind confirmation, and re-validate every input inside execute. For production, put an authenticated, logged, approval-gated layer between agents and your tools. Turn a WebMCP site into a guarded MCP server with auth and logs and Human-in-the-loop approvals for WebMCP walk through exactly that.

Explore features · Use cases · Open MCP Trail

Share this article