How to Implement WebMCP on Your Site in 2026

WebMCP turns actions on your page into named, typed tools that an AI agent in the same tab can call directly. This page gives you the declarative form markup, the imperative registerTool() code and the polyfill route, all checked against the current W3C Community Group draft.

CategoryAgent & AI protocols StatusChromium origin trial Maintained byW3C (Google + Microsoft) Glippy checkAgent Interactivity (category 10)

WebMCP (Web Model Context Protocol) is a browser API that lets a page register named tools, each with a JSON Schema for its inputs and a callback you wrote, so an AI agent in the same tab can invoke them instead of guessing at your UI. Tools run inside the user's existing session, so there is no separate server to stand up and no credentials to copy anywhere. The API lives at document.modelContext, and a parallel declarative form covers the common case with no JavaScript at all.

Why WebMCP matters for AI visibility

A browser agent working a page without WebMCP reads the accessibility tree, guesses which control is the submit button, and retries when a click lands somewhere unexpected. Every re-render, hashed class name and A/B variant breaks that guess, and the failure is silent: the agent reports success on an action that never happened. A registered tool replaces the guess with a contract. The agent receives a name, a typed input schema and a return value, and your code decides what actually runs.

That makes WebMCP the one agent-facing surface where you set the vocabulary rather than a crawler. Adoption is still thin, so the cost of being early is small and the worst case is renaming a method later. For the background on where the protocol came from and how it sits next to Anthropic's MCP, the conceptual explanation lives here. The rest of this page is implementation.

Where the spec lives

WebMCP has changed shape several times since the first preview, and secondary write-ups go stale within weeks. Check these before you ship anything.

Three ways to implement WebMCP

The declarative route annotates forms you already have and needs no JavaScript, which suits content sites and server-rendered apps. The imperative route registers tools from script, which is what you need when the set of available actions depends on application state. The polyfill route runs that same imperative code in browsers with no native implementation, which is how you ship to real traffic while the origin trials run.

01

Annotate an existing form (no JavaScript)

Four attributes turn a working HTML form into a tool. Use this first: if the form already has labelled, named controls it is most of the way there, and the markup degrades to an ordinary form everywhere else.

htmltemplates/search.html
<form toolname="search_catalogue"
      tooldescription="Search the product catalogue by keyword and category."
      toolautosubmit
      action="/search" method="get">

  <label for="q">Keywords</label>
  <input type="text" id="q" name="q" required
         toolparamdescription="Words to match against product titles and descriptions.">

  <label for="category">Category</label>
  <select id="category" name="category"
          toolparamdescription="Restrict results to one category.">
    <option value="">All categories</option>
    <option value="tents">Tents</option>
    <option value="sleeping-bags">Sleeping bags</option>
  </select>

  <button type="submit">Search</button>
</form>

<script>
  document.querySelector('form[toolname="search_catalogue"]')
    .addEventListener('submit', (event) => {
      if (!event.agentInvoked) return;   // a person clicked Search: let it navigate
      event.preventDefault();
      event.respondWith(runSearch(new FormData(event.target)));
    });
</script>

What this does: the browser derives the input schema from the named controls, so the agent sees a search_catalogue tool with a q string and a category enum. Without toolautosubmit the agent fills the form and waits for the user to press Search; with it, the form submits and the optional respondWith() handler returns a result to the agent instead of navigating.

02

Register a stateful tool with the imperative API

Reach for this when the tool is not a form: cart mutations, filters, canvas operations, anything where the available actions change as the user moves through the app. Registration is scoped to an AbortSignal, which is how you take a tool away again.

javascriptassets/js/product-tools.js
// ES module, loaded on product pages only.
if ('modelContext' in document) {
  const controller = new AbortController();

  await document.modelContext.registerTool(
    {
      name: 'add_to_cart',
      title: 'Add to cart',
      description: 'Add a product to the shopping cart. Returns the new cart total.',
      inputSchema: {
        type: 'object',
        properties: {
          sku: { type: 'string', description: 'Product SKU shown on the product page.' },
          quantity: { type: 'integer', minimum: 1, description: 'How many units to add.' }
        },
        required: ['sku'],
        additionalProperties: false
      },
      annotations: { readOnlyHint: false, untrustedContentHint: false },
      async execute({ sku, quantity = 1 }, { signal }) {
        const line = await cart.add(sku, quantity, { signal });
        return `Added ${quantity} x ${sku}. Cart holds ${line.count} items, total ${line.total}.`;
      }
    },
    { signal: controller.signal }
  );

  // Leaving the product view removes the tool without killing work in flight.
  router.on('leave', () => controller.abort());
}

What this does: the agent gets one unambiguous action with typed arguments, and the confirmation string tells it what changed so it does not have to re-read the page to verify. Aborting the controller fires a toolchange notification, so the agent stops offering an action that no longer exists.

03

Ship to every browser with the polyfill

Native WebMCP is Chromium only and still gated behind a flag or an origin trial, and no other engine implements it. The polyfill installs document.modelContext only when the browser has none, so one code path covers native and non-native visitors.

javascriptsrc/webmcp/bootstrap.js
// npm install @mcp-b/webmcp-polyfill
//     npm install -D @mcp-b/webmcp-types
import { initializeWebMCPPolyfill } from '@mcp-b/webmcp-polyfill';

// Idempotent: it never replaces a native document.modelContext.
initializeWebMCPPolyfill();

export async function registerArticleTools(article) {
  if (!('modelContext' in document)) return () => {};

  const controller = new AbortController();

  await document.modelContext.registerTool(
    {
      name: 'get_article_summary',
      description: 'Return the title, canonical URL and summary of the article on screen.',
      inputSchema: { type: 'object', properties: {} },
      annotations: { readOnlyHint: true, untrustedContentHint: true },
      execute: async () => ({
        title: article.title,
        canonical: document.querySelector('link[rel="canonical"]')?.href,
        summary: article.summary
      })
    },
    { signal: controller.signal }
  );

  return () => controller.abort();
}

What this does: visitors on a Chromium build with WebMCP enabled use the native implementation; everyone else gets the polyfill, which also picks up the declarative form attributes from example 01. Cross-document features such as non-empty exposedTo and fromOrigins reject under the polyfill, so keep those paths behind a native check.

Implementation guidelines

These are the things that break WebMCP in production rather than in a demo.

  1. Tie every registration to an AbortController. Register a tool when the action becomes possible and abort the signal when it stops being possible. A listed tool that fails on call is worse than no tool, because the agent has already told the user it will work.
  2. Stay inside the character budgets. Chrome's guidance is roughly 500 characters per tool description, 150 per parameter description, 30 for a name, and 1.5K for tool output. Longer strings get truncated or trip agent guardrails, and the agent silently drops the tool.
  3. Do not opt out of origin isolation. WebMCP is only exposed in origin-isolated documents. Chromium has been origin-keyed by default since Chrome 106, so the usual cause of a missing document.modelContext is a legacy Origin-Agent-Cluster: ?0 header kept around to make document.domain writable.
  4. Remember the tools Permissions Policy in iframes. The tools policy defaults to self, so a widget you embed cross-origin registers nothing until the parent page adds allow="tools" to the iframe element.
  5. Validate strictly in code, loosely in schema. Accept raw user input, declare enums and real types rather than opaque numeric IDs, and return a descriptive error string the model can correct against instead of throwing an opaque exception.
  6. Annotate honestly. Set readOnlyHint only on tools that change nothing, and set untrustedContentHint on any tool returning user-generated or third-party content so the agent can treat the payload as data rather than instructions.
  7. Keep the human path intact. Safari and Firefox have no implementation and Chromium support is still trial-gated, so every tool must map to something a person can still click. WebMCP is an addition to your UI, never a replacement for it.

Do this, not that

Do

  • Name tools with a verb that says what happens on call, such as create_booking, and keep it to 30 characters or fewer.
  • Feature-detect with 'modelContext' in document before touching the API, so older browsers do not throw on load.
  • Put toolparamdescription on the named form control itself, not the wrapping element, so it lands on the right schema property.
  • Keep the tool set small and non-overlapping; two tools that could both do the job make the agent pick wrong more often than a missing tool does.

Do not

  • Do not write navigator.modelContext. It is a deprecated compatibility alias for document.modelContext and Chromium logs a warning on first access.
  • Do not call provideContext() or clearContext(). Both were dropped from the draft; the current surface is registerTool() plus an AbortSignal.
  • Do not publish /.well-known/webmcp.json and expect discovery. There is no manifest in the spec: tools are registered at runtime and the browser aggregates them.
  • Do not mark a destructive tool readOnlyHint: true to get agents to call it without confirming. That is the annotation agents use to decide when to skip asking the user.

How Glippy checks this

Glippy scores WebMCP under Agent Interactivity (category 10). It looks for forms carrying toolname with a non-empty tooldescription and toolparamdescription on their inputs, a live document.modelContext object (falling back to the legacy navigator.modelContext), inline scripts that call registerTool, and a loaded WebMCP polyfill or SDK. It also reports tool description quality, form agent-readiness and whether your element IDs are stable rather than hashed per render, which is what an agent needs to reference a control twice. The category uses a bonus model at a low weight, so a missing implementation is surfaced as a tip and does not drag your score down. The full breakdown is on the WebMCP Checker page.

Check your WebMCP setup

Glippy runs 240+ checks across 16 categories on any page, including Agent Interactivity (category 10). No sign-up required.

Frequently asked questions

WebMCP is Chromium only. Chrome shipped an early preview in Chrome 146 behind chrome://flags/#enable-webmcp-testing, and a Chrome origin trial has been running from Chrome 149 so you can enable it for real users on a registered origin. Microsoft is running a matching Edge origin trial that is scheduled to run to 17 November 2026. Safari and Firefox have no implementation, which is why the polyfill route matters if you want coverage today.

No. The declarative API works from HTML alone: add toolname and tooldescription to a form element and toolparamdescription to its named controls, and the browser derives the JSON Schema from the form itself. By default the agent fills the form and the user submits it, which keeps a person in the loop. You only need script if you want toolautosubmit plus a custom result, which the SubmitEvent.respondWith() method provides.

No. The tool registration surface moved from Navigator to Document, so the canonical entry point is document.modelContext and navigator.modelContext survives only as a deprecated alias that logs a warning. Two earlier methods, provideContext() and clearContext(), were removed from the draft entirely and no shipping implementation exposes them. Current code uses document.modelContext.registerTool(tool, { signal }) and removes the tool by aborting that signal.

No, the two cover different lifetimes. An MCP server is persistent and headless: it exposes your data and actions to an agent anywhere, at any time, with its own authentication. WebMCP tools are ephemeral and bound to an open tab, running inside the session the user is already signed in to and acting on the state they can see. Most teams that do both keep business logic in MCP and use WebMCP to expose the in-page actions that only make sense with the user's current view.

Reviewed against the primary sources on . These standards move quickly, so check the linked specs before you ship.

Check your site for AI search readiness

Start free with the Glippy Chrome extension for instant page checks. Scaling up? Automate audits across many URLs and your whole sitemap with the Glippy MCP server.

Add Glippy to Chrome – free Automate with the MCP server →