WebMCP · Lesson 2

Register a tool

The author's whole job: call document.modelContext.registerTool once, with four fields you already understand from MCP. We read each one, then wire it to real page code.

In Lesson 1 you placed WebMCP as the in-page sibling of MCP and CDP. Now you play the website author. Your entire surface is one method — registerTool — and its shape is the MCP tool descriptor you already know, with the handler being a local JavaScript function.chrome

1 · Anatomy of a tool

Here is a real registration, verbatim in shape from Chrome's imperative-API guide. Read it before the explanation — you can already guess most of it.

// somewhere in your page's own JavaScript, same origin await document.modelContext.registerTool({ name: "add_todo", // 1–128 chars, [A-Za-z0-9_-.] description: "Add a new item to the to-do list", // natural language, for the agent inputSchema: { // JSON Schema — same role as in MCP type: "object", properties: { text: { type: "string" } }, required: ["text"] }, execute: async ({ text }, { signal }) => { // YOUR handler — plain page code addTodoToDom(text); // do the real thing on the page return `Added to-do: ${text}`; // string the agent sees back } });
Field by field

name — the stable id the agent calls. description — natural language; this is the prompt the agent reads to decide whether to use the tool, so write it well. inputSchema — a JSON Schema; the browser can validate the agent's input against it before your code runs. execute — your async handler, receiving the validated input and an AbortSignal, doing the real work, and returning a result.spec

The contract (what the agent reads) name description inputSchema
▼ agent picks the tool & supplies input matching inputSchema
The handler (what you run) execute(input, {signal}) ▼ touches the DOM / app state, then returns return "…result string…"
The first three fields are a promise to the agent; execute is you keeping it. Description quality ≈ tool quality.
What execute returns

In current Chrome, execute returns a plain string (or a value that gets serialized to text) — the message the agent receives. Heads-up on churn: earlier WebMCP drafts used an MCP-style {content:[{type:"text",text:…}]}. Teach yourself the plain-string form Chrome ships today, and check the spec if you see the older shape.imperative-api

2 · Turning a tool off — AbortSignal

A tool should only exist while the page can actually do it. You unregister a tool by aborting the signal you passed at registration — no separate "unregister" method.spec

// register with a controller... const controller = new AbortController(); await document.modelContext.registerTool(addTodoTool, { signal: controller.signal }); // ...later, when the tool no longer applies (e.g. user logged out): controller.abort(); // the tool disappears from the registry

The other useful option is exposedTo — an array of origins allowed to see the tool. Combined with the Permissions-Policy "tools" feature, this is WebMCP's trust model. We give it a full lesson later; just know registration is where trust starts.

3 · The mindset shift for authors

You are used to building UI for humans — buttons, forms, layout. WebMCP asks you to also publish the same capabilities as a machine contract. The button and the tool can call the very same function; you're just giving the agent a labelled front door instead of making it hunt for the button.

Treat tool input as untrusted (Check Point instinct)

An execute handler runs on input chosen by an agent, which may be steered by untrusted page content or a malicious prompt. Validate and sanitize inside execute just as you would a network request handler — inputSchema checks shape, not intent. Never let a tool do more than the current user is allowed to do.

🔧 Try it yourself (≈ 5 minutes)

You don't need the origin trial to feel the shape. Open any page, turn on the flag (chrome://flags/#enable-webmcp-testing → Enabled → relaunch — full setup is Lesson 3), open DevTools Console, and paste:

await document.modelContext.registerTool({ name: "greet", description: "Return a friendly greeting for a given name", inputSchema: { type:"object", properties:{ name:{type:"string"} }, required:["name"] }, execute: async ({ name }) => `Hello, ${name}! 👋` }); console.log("registered:", (await document.modelContext.getTools()).map(t => t.name));

If document.modelContext is undefined, the flag isn't on yet — that's exactly what Lesson 3 fixes. Ask me if the console throws something unexpected.

Check yourself

Which field does the agent read to decide whether to use a tool?
How do you unregister a WebMCP tool?
In current Chrome, what does execute return to the agent?
Primary source — read this next

Chrome's Imperative API guide — the source of the examples above (pizza-layer + to-do). Read its full registerTool walkthrough, then the spec's tool dictionary for every optional field.