All articles
ProductOctober 10, 2026 · 6 min read

Introducing WebMCP Support

Websites can now give agents named tools instead of a page to click through. Hyperbrowser sessions discover and call those tools, and hand a form to a person when it needs their review.

Introducing WebMCP Support

Most browser agents use a website the way a person would. They read the page, work out which box is the search field, type, click, and read the page again to see whether it worked. That approach works on any site, but every step is a guess about an interface built for people, and a redesign can break it overnight.

WebMCP lets a website take the guessing out. The site publishes the actions it supports as tools, such as search_products or request_quote, each with a description and a schema for its inputs, and an agent calls them like functions. Starting today, Hyperbrowser sessions can discover and call WebMCP tools on any page that registers them.

What WebMCP is

WebMCP is a proposed web standard for exposing a page's actions to AI agents. It is being drafted in the W3C Web Machine Learning Community Group by editors from Google and Microsoft, and Chrome is already testing it. A page registers tools in one of two ways: in JavaScript, through document.modelContext.registerTool(), or by adding a few attributes to an HTML form it already has.

The tools run inside the page, with the page's own code and the visitor's signed-in session. That is the main difference from an MCP server, which a company runs as a separate service with its own authentication. The specification is still a draft and browser support is experimental, so expect the details to change.

Why it takes a browser

Because WebMCP tools live in the page, there is no endpoint to call them from. Something has to load the page in a real browser, keep the user signed in, and run each tool in the document that registered it. A Hyperbrowser session does that. Turn on WebMCP when you create the session, and you get:

  • Discovery across the session. One call lists the tools in every tab and frame, with each tool's description, input schema, hints such as read-only, and the page and frame that registered it.
  • Calls that reach the right page. Each tool reference belongs to the document that registered it. After a navigation you discover tools again, and an old reference never silently calls a tool with the same name on a different page.
  • Long calls without double runs. Start a call, check on it later, or cancel it. Checking on a call never runs the tool a second time.
  • A person when you need one. A form that needs a human to submit it waits in the session, and the customer finishes it through Live View.

Playwright or Puppeteer still handles navigation and sign-in. WebMCP gives your agent a direct path on the pages that offer one.

Try it

This script runs end to end with nothing but an API key. Install @hyperbrowser/sdk (1.2.0 or later) and playwright-core, and set HYPERBROWSER_API_KEY.

On a real store, the registerTool call lives in the site's own JavaScript, and execute calls the store's search API. Here the script registers a stand-in with a three-product catalog on example.com, so you can see the whole flow before you build anything.

import { Hyperbrowser } from "@hyperbrowser/sdk";
import { chromium } from "playwright-core";

const client = new Hyperbrowser(); // reads HYPERBROWSER_API_KEY
const session = await client.sessions.create({ enableWebMcp: true });

try {
  const browser = await chromium.connectOverCDP(session.wsEndpoint);
  const page = browser.contexts()[0].pages()[0];
  await page.goto("https://example.com");

  // A stand-in store: on a real site, the page's own script registers its tools.
  await page.evaluate(async () => {
    const catalog = [
      { name: "Arc desk lamp", category: "lighting", price: 89 },
      { name: "Clip-on reading lamp", category: "lighting", price: 34 },
      { name: "Oak standing desk", category: "furniture", price: 420 },
    ];
    await document.modelContext.registerTool({
      name: "search_products",
      description: "Search the store's catalog. Returns matching products with prices.",
      inputSchema: {
        type: "object",
        properties: {
          query: { type: "string", description: "What the shopper wants" },
          category: { type: "string", enum: ["lighting", "furniture"] },
        },
        required: ["query"],
      },
      annotations: { readOnlyHint: true },
      async execute({ query, category }) {
        return catalog.filter((product) =>
          product.name.toLowerCase().includes(query.toLowerCase()) &&
          (!category || product.category === category)
        );
      },
    });
  });

  // The agent's side: find the tool, then call it with input that fits its schema.
  const { tools } = await client.sessions.webmcp.listTools(session.id);
  const search = tools.find((tool) => tool.name === "search_products");
  if (!search) throw new Error("search_products is not registered");

  const result = await client.sessions.webmcp.invoke(session.id, {
    toolRef: search.toolRef,
    input: { query: "lamp", category: "lighting" },
  });
  console.log(result.status, result.output);
} finally {
  await client.sessions.stop(session.id);
}

Save it as search.mjs and run node search.mjs. It prints the two lamps:

completed [
  { name: 'Arc desk lamp', category: 'lighting', price: 89 },
  { name: 'Clip-on reading lamp', category: 'lighting', price: 34 }
]

The agent never touched the page's layout. It asked the session for tools, picked one by name, and sent input that matches the schema. When several tabs or frames register a tool with the same name, each tool's source tells you which page and frame it came from. Python and the REST API follow the same steps.

Hand the last step to a person

Some actions shouldn't be finished by an agent alone. Requesting a quote, booking a table, or anything else that commits the customer should get their yes first. WebMCP's form tools fit this case. A site adds toolname and tooldescription to a form it already has, and WebMCP builds the input schema from the form's fields:

<form id="quote" toolname="request_quote"
      tooldescription="Request a bulk quote. The customer reviews and submits it."
      action="/quotes" method="post">
  <input name="product" required>
  <input name="quantity" type="number" min="1" required>
  <input name="email" type="email" required>
  <button>Request quote</button>
</form>
<script>
  document.getElementById("quote").addEventListener("submit", (event) => {
    if (!event.agentInvoked) return;
    event.preventDefault();
    event.respondWith(
      fetch("/quotes", { method: "POST", body: new FormData(event.target) })
        .then((response) => response.json())
    );
  });
</script>

Without the toolautosubmit attribute, an agent can fill the form in but can't send it. The submit handler is how the form reports back: when a submission belongs to an agent's call, the handler passes the result to respondWith, and that value becomes the tool's output.

On the agent's side, use start instead of invoke. It returns a handle as soon as the call begins, so nothing has to block while the customer decides:

const { tools } = await client.sessions.webmcp.listTools(session.id);
const quote = tools.find((tool) => tool.name === "request_quote");
if (!quote) throw new Error("request_quote is not registered");

let call = await client.sessions.webmcp.start(session.id, {
  toolRef: quote.toolRef,
  input: { product: "Arc desk lamp", quantity: 40, email: "buyer@acme.test" },
  timeoutSeconds: 900,
});

// The form is filled in and waiting. Send the customer to it.
console.log("Review and submit:", session.liveUrl);

while (["running", "awaiting_submission"].includes(call.status)) {
  call = await client.sessions.webmcp.getResult(session.id, call.invocationId, { waitSeconds: 30 });
}
console.log(call.status, call.result?.output);

While the customer reviews, edits, and submits the form through Live View, which you can share as a link or embed in your app, the call reports awaiting_submission. Each getResult waits up to 30 seconds for news and never starts the tool again, and timeoutSeconds sets the budget for the whole call, up to an hour. Keep the session open until the call finishes. If the customer walks away, cancel the call; cancelling stops the wait but doesn't undo anything the page has already done.

The same start and getResult pair works for any tool that runs longer than a blocking invoke allows, which is two minutes.

Treat page tools as page content

A tool's name, description, schema, and output all come from the website, so treat them like anything else your agent reads on a page. Hints such as read-only or consequential are the site describing its own tool, not permission to run it. Keep a person involved for actions that spend money or change an account, and check each result's status instead of assuming success: a tool that fails still returns a normal response, with a status of error and the reason in errorText.

Get started

WebMCP is available today in the Node.js SDK (1.2.0 and later), the Python SDK (1.11.0 and later, with sync and async clients), and the REST API. Set enableWebMcp: true (enable_web_mcp in Python) when you create a session. If you run a website, a form with two extra attributes is enough to give agents their first tool.

Read the WebMCP docs →

WebMCPAI AgentsProduct Launch