WebMCP
Your page registers typed functions and the browser passes them to its agent, which calls your code instead of clicking your buttons.
The problem it solves
Every other approach has the agent operating your interface: read the page, find the control, click it, read the page again. That works, but it is brittle in a specific way, because the agent is reverse-engineering your intent from your markup at every step.
WebMCP removes the guesswork. You already have a function that applies a filter or moves a task between columns. Hand that over and there is nothing left to misread.
What it looks like
// Your app already has this function. WebMCP just hands it over.
await document.modelContext.registerTool(
{
name: 'apply_filter',
description:
'Filter the visible orders. Use it instead of clicking the filter UI.',
inputSchema: {
type: 'object',
properties: {
status: { type: 'string', enum: ['open', 'shipped', 'cancelled'] },
since: { type: 'string', description: 'ISO date' },
},
required: ['status'],
},
annotations: { readOnlyHint: true },
execute: async ({ status, since }) => {
const orders = await store.filterOrders({ status, since });
return `${orders.length} orders match. Oldest: ${orders.at(-1)?.id}`;
},
},
{ signal: controller.signal },
);The schema is ordinary JSON Schema, so the agent knows the shape of the arguments before it calls anything. execute returns a string, which means you decide what the agent learns from the call instead of leaving it to infer something from a re-render. The AbortController is how you unregister, and that matters more than it sounds in a single-page app where the available tools change with the route.
Where it is worth the effort
- When the DOM is not really your model. A canvas, a map, a diagram editor, a virtualised table of ten thousand rows. Clicking cannot express what your app does, and reading the page will never find a row that was never rendered.
- When an operation takes several steps but should happen as one. "Move this task to done and reassign it" is a single function call, or four fragile clicks.
- When you want to limit what is possible at all. A function with an enum parameter cannot be talked into a fifth option, whereas a user interface can be clicked anywhere.
Where it is not worth it
A mostly static site does not need it. If your page is content and a form, semantic HTML plus a web skill gets you most of the value for a fraction of the work, and it keeps working in browsers that have never heard of WebMCP.
It also requires the page to be open. If an agent should be able to check an order status without a browser, that is a remote MCP server, not this.
Where the standard actually stands
WebMCP is contested. Google is championing it and it ships in Chromium, but WebKit has objected on security and usability grounds, so Safari is not implementing it any time soon. The API has already moved once: navigator.modelContext was deprecated in favour of document.modelContext.
In practice that means you feature-detect rather than assume, and you treat WebMCP as an enhancement on top of a site that already works without it. Do not make it the only way to use your app.
This page has WebMCP tools
Every page here registers a few, including a ping so you can check whether your own browser passes them through. In a browser without the API the registration quietly does nothing, which is the behaviour worth copying.
- ping_skills_directory is it working
- search_skills search the directory
- list_skills_for_host what exists for a hostname
- open_skill_page navigate, after checking the page exists
- read_current_skill on a skill page only
Reading
- The WebMCP proposalW3C Web Machine Learning community group
- Chrome's WebMCP documentationthe imperative and declarative APIs