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.
