WebMCP · 9 min read

How to Add WebMCP to a Website

Add catalog search and a quote-request form to a supplier website. Includes code, browser setup, permission checks, and recorded test results.

By Permadyn AIPublished September 16, 2026Updated September 20, 2026

This guide adds two WebMCP tools to a supplier website: catalog search and quote-request preparation. The tools use the existing application, and a person reviews the quote before sending it.

The catalog is fictional. You can adapt the pattern to a customer portal or internal web application.

Technical guidance checked September 4, 2026 (UTC). WebMCP is still a proposed standard. Verify the current browser version and trial conditions before deploying. Start with the WebMCP specification if you need the normative API details.

1. Define the catalog task

Suppose a maintenance buyer needs 20 replacement filter cartridges. They know the filtration rating but not your part number. A good first workflow lets their agent find matching catalog entries, return the specifications that support the match, and prepare an inquiry.

Name the tools for the work they perform: search_products and prepare_quote_request. These names describe the intended operation and help distinguish the tools.

This example searches published products and prepares a request. It does not check machine compatibility, stock, or pricing, and it cannot place an order.

Keep the catalog small for the first test so you can inspect every returned record. Google's tool design guidance is a useful reference for naming and separating capabilities.

2. Enable WebMCP in the browser

For local development, Chrome documents the flag chrome://flags/#enable-webmcp-testing. Enable it and relaunch a compatible Chrome build. The published origin trial begins with Chrome 149, but that does not make every subsequent API addition available in every trial version. Chrome's setup instructions

For a hosted trial, register your own origin and supply its token on eligible pages, either as an HTTP header or a head element:

<meta http-equiv="origin-trial" content="YOUR_ORIGIN_TRIAL_TOKEN">

The token must match the relevant origin and remain valid. A token for your production domain does not enable an unrelated localhost origin. Check acceptance in DevTools, not merely whether the token appears in the HTML. Track expiration as a release dependency. Origin-trial setup and diagnostics

WebMCP also depends on origin isolation and the tools permissions policy. Check these when the API is missing; do not broadly loosen iframe permissions to make a test pass. Feature detection still belongs in your code. Everyone without WebMCP should retain the ordinary website.

3. Choose HTML annotations or JavaScript

Use the declarative API when the task already maps cleanly to an HTML form. Its fields become the inputs, and the browser presents the form for review.

Use the imperative API when the task needs application logic: searching records, returning a structured comparison, changing filters, or coordinating an existing component.

They can coexist. Our example uses JavaScript for catalog search and HTML annotations for the quote form. Neither requires a new chatbot or an AI subscription just to register the tools. The compatible agent is a separate part of the experience.

4. Build a discovery tool that returns verifiable answers

Save this complete example as catalog.html and serve it over localhost. It uses fictional records and no external libraries. Replace the product paths with your real canonical product URLs before adapting it to a live site.

<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Fictional supplier catalog</title>
<main>
  <h1>Fictional supplier catalog</h1>
  <p>Demo records only. No stock or compatibility promises.</p>
  <form id="search">
    <label for="query">Product or specification</label>
    <input id="query" name="query" required minlength="2" maxlength="80">
    <button>Search catalog</button>
  </form>
  <p id="status" role="status"></p>
  <ul id="results"></ul>
</main>
<script type="module">
  const catalog = [
    { sku: "SF-10", name: "10 micron cartridge", micron: 10,
      lengthMm: 250, path: "/products/sf-10" },
    { sku: "SF-25", name: "25 micron cartridge", micron: 25,
      lengthMm: 250, path: "/products/sf-25" }
  ];
  const form = document.querySelector("#search");
  const list = document.querySelector("#results");
  const status = document.querySelector("#status");

  function searchProducts(input) {
    const query = typeof input?.query === "string"
      ? input.query.trim() : "";
    if (!input || Object.keys(input).some(key => key !== "query") ||
        query.length < 2 || query.length > 80) {
      status.textContent = "Enter a query between 2 and 80 characters.";
      return { ok: false, error: status.textContent };
    }
    const matches = catalog.filter(product =>
      `${product.sku} ${product.name}`.toLowerCase()
        .includes(query.toLowerCase())
    ).map(({ path, ...product }) => ({
      ...product, url: new URL(path, location.origin).href
    }));
    list.replaceChildren(...matches.map(product => {
      const item = document.createElement("li");
      const link = document.createElement("a");
      link.href = product.url;
      link.textContent = `${product.sku}: ${product.name}`;
      item.append(link, `; ${product.lengthMm} mm long.`);
      return item;
    }));
    status.textContent = matches.length
      ? `${matches.length} catalog match(es). Verify fit before ordering.`
      : "No catalog matches. Try a part number or filtration rating.";
    return { ok: true, results: matches };
  }

  form.addEventListener("submit", event => {
    event.preventDefault();
    searchProducts({ query: new FormData(form).get("query") });
  });

  let registration;
  async function registerSearch() {
    if (!document.modelContext) return;
    registration?.abort();
    registration = new AbortController();
    try {
      await document.modelContext.registerTool({
        name: "search_products",
        description: "Search the fictional supplier catalog by part " +
          "number or filtration rating. Returns published specs and source links.",
        inputSchema: {
          type: "object",
          properties: { query: { type: "string" } },
          required: ["query"], additionalProperties: false
        },
        annotations: { readOnlyHint: true },
        execute: async input => JSON.stringify(searchProducts(input))
      }, { signal: registration.signal });
    } catch {
      status.textContent = "Agent search is unavailable. Use the search form.";
    }
  }
  await registerSearch();
  addEventListener("pagehide", () => registration?.abort());
  addEventListener("pageshow", event => {
    if (event.persisted) void registerSearch();
  });
</script>
</html>

The search form and tool call the same function, so both return the same records and update the visible results. Each result includes its product URL for inspection.

A search with no matches returns an empty result list. Invalid input returns an error explaining the allowed query length. Product text is inserted with textContent.

The registration uses the current document.modelContext interface and an abort signal for cleanup. In a component framework, connect the same registration lifetime to the component or route rather than registering on every render. Check version-specific behavior in the imperative API documentation.

5. Add the quote form

On your quote page, annotate the existing form:

<form action="/quote-requests" method="post"
      toolname="prepare_quote_request"
      tooldescription="Prepare a supplier quote request for the customer to review and send.">
  <p>Review the part, quantity, and email before sending this request to the supplier.</p>
  <label for="sku">Part number</label>
  <select id="sku" name="sku" required>
    <option value="">Choose a part</option>
    <option value="SF-10">SF-10: 10 micron cartridge</option>
    <option value="SF-25">SF-25: 25 micron cartridge</option>
  </select>
  <label for="quantity">Quantity</label>
  <input id="quantity" name="quantity" type="number"
         min="1" max="1000" step="1" required>
  <label for="email">Work email</label>
  <input id="email" name="email" type="email" required>
  <button type="submit">Send quote request</button>
</form>

There is deliberately no toolautosubmit. Chrome's declarative API distinguishes preparing a visible form from automatically submitting one. The customer can correct the fields or abandon the request. Declarative form behavior

This form assumes your application already handles POST /quote-requests. It is not a backend implementation. That handler must validate the SKU, integer quantity, email, applicable permissions, and abuse protections. Return confirmation only after the request is actually accepted. Do not label a populated form “Quote sent.”

For payments or other consequential changes, add an application-controlled review step bound to the exact transaction. A tool description asking for permission is not an authorization mechanism.

6. Preserve the application rules

Apply the same authentication, authorization, CSRF defenses, rate limits, and validation to tool calls as to ordinary form submissions.

Do not send private catalog pricing to a user who could not otherwise view it. Do not overwrite a person's unsent request when an agent offers a new draft. Show the proposed replacement and let them choose.

Treat descriptions and returned customer content as untrusted input to the agent. readOnlyHint describes behavior; it does not prevent data exposure or enforce permission. Avoid cross-origin exposure unless there is a specific, reviewed need. Google's tool security guidance

If a task needs to run after the tab closes, reconsider the architecture. A conventional API or backend MCP connection may be the appropriate interface instead. When to use WebMCP and MCP

7. Test discovery, failure, and human review

Use Chrome's Model Context Tool Inspector, linked from its WebMCP overview, to inspect registrations and invoke tools. Seeing a tool name is only the first check.

Test a realistic prompt: “Find the 10 micron cartridge and prepare a quote for 20.” Then verify the returned SKU and specifications against the actual record. Check that no request reaches the server before review.

Also test an unknown part, blank or oversized input, invalid quantity, an edited draft, cancellation, a repeated call, server rejection, and navigation away and back. Run the normal form in a browser without WebMCP. For asynchronous tools, test cancellation while the request is still pending; a canceled interface must not quietly report a completed transaction.

Record the browser build, date, and tested paths. A successful inspector call is evidence of that tool path, not proof that every assistant will choose or use it correctly.

Example verification, September 4, 2026: JavaScript tool registration, invocation, invalid inputs, and navigation recovery were tested in the Codex in-app browser, reporting Chromium 152. The ordinary search and quote form were also tested in Safari 26.5.2 without WebMCP. The test host did not expose the annotated form as a tool, so that agent path still needs verification in a compatible Chrome build. The form syntax follows Chrome's documentation; its manual submission path was tested separately.

8. Ship one workflow and measure the result

Before release, confirm that your tools return accurate source links, visible state agrees with tool results, private routes expose nothing, and the manual path still works. Assign responsibility for browser changes and token renewal.

Measure outcomes at the workflow level. Did the buyer find the right record? Was the inquiry complete? How many attempts ended in an error or abandonment? Compare against the ordinary journey where possible. Count completed requests and errors alongside tool calls.

What if the tools do not appear?

  • API missing: check the actual browser build, feature setting, origin isolation, and permissions policy.
  • Token rejected: check origin, expiration, placement, and DevTools acceptance. Do not reuse another site's token.
  • Unexpected submission: inspect toolautosubmit, submit handlers, and any tool that calls a mutation directly.
  • Stale or duplicate tools: check route cleanup, repeated component registration, and restored page state.

Does WebMCP help with SEO or ChatGPT discovery?

Tool discovery happens when a compatible client reaches the site. There is no universal WebMCP index to submit to. Getting the page found is a separate task.

Keep important content crawlable, readable in HTML, accurately titled, and linked from relevant pages. Use structured data that matches the visible content. Google says its AI search features do not require special AI files or schema. Google Search guidance

For ChatGPT search, check access for OAI-SearchBot, including hosting protections that might block it. GPTBot concerns model training and is controlled separately; enabling training access is not a prerequisite for search eligibility. Neither setting guarantees a citation or ranking. OpenAI crawler documentation

For an introduction, read what WebMCP does. For help choosing the first workflow, see WebMCP modernization or the agent-ready software assessment.

Working on something similar?