WebMCP
Building block for the W3C WebMCP API exposed at document.modelContext. (For backward compatibility with the previous shape of the API, this package also reads from navigator.modelContext.) Register agent-callable tools from any framework; the package is vanilla TypeScript / DOM with an optional React adapter shipped as a subpath export. See useWebMCP for React.
Where the Built-in AI APIs make the browser an AI runtime, WebMCP makes your page an agent surface: instead of a visiting agent guessing what your interface elements do, the page declares structured tools the agent can discover and call.
import { registerTool } from "@web-ai-sdk/webmcp";
const cleanup = registerTool({ name: "add_to_cart", description: "Add a SKU to the user's cart", execute: async (input) => ({ ok: true, ...input }),});
// Later, when the tool should no longer be exposed:cleanup();To register many at once, map over the array and combine the cleanups:
const cleanups = tools.map(registerTool);const cleanup = () => cleanups.forEach((c) => c());interface Tool<TInput = unknown, TOutput = unknown> { name: string; description: string; inputSchema?: object; readOnly?: boolean; destructive?: boolean; annotations?: ToolAnnotations; execute: (input: TInput) => Promise<TOutput> | TOutput;}Returns
Section titled “Returns”registerTool(tool) returns a cleanup function: () => void. Calling it unregisters the tool. Calling it twice is a no-op. On browsers without document.modelContext (or the legacy navigator.modelContext), the cleanup is itself a no-op.
How it works
Section titled “How it works”document.modelContext.registerTool({...}, { signal }) is the spec entry point. (For backward compatibility with the previous shape of the API, this package also reads from navigator.modelContext.) The wrapper sits on top with three quality-of-life additions:
- Feature detection. On browsers without
document.modelContext(or the legacynavigator.modelContext), every entry point is a no-op so consumer code ships unchanged.isAvailable()returnsfalse. - Last-writer-wins on duplicate names. React effect cleanup is asynchronous; a fast re-render can attempt to register
"foo"before Chrome processed the priorabort(). The wrapper detects the resulting duplicate-name error, queues a microtask retry, and tracks ownership so a stale registration can’t squat the name. - One AbortController per call.
registerToolreturns a cleanup function that aborts the underlying controller and unregisters the tool.
Annotations
Section titled “Annotations”Annotations communicate intent to the agent. WebMCP exposes shorthand flags (readOnly, destructive) plus a raw annotations passthrough that merges on top:
registerTool({ name: "delete_account", description: "Permanently delete the user account", destructive: true, // → annotations.destructiveHint annotations: { idempotentHint: false, openWorldHint: true, }, execute: () => { /* ... */ },});Shorthand flags map to the spec hints (readOnlyHint, destructiveHint); pass annotations directly when you need idempotentHint or openWorldHint.
Tool design
Section titled “Tool design”A tool is a semantic interface, not just a function: the agent selects tools by reading names, descriptions, schemas, and annotations. A few rules of thumb:
- Action-oriented names.
add_to_cart,select_shipping_address,run_diagnostics— notdoAction,submit, orhandleClick. - Descriptions that say when to use the tool, not just what it does. Besides the schema, the description is the agent’s only routing signal.
- Precise
inputSchema. Markrequiredfields, describe every property, and use enums for closed sets so hosts can validate before dispatch. - Small, structured outputs. Return compact objects the agent can act on, and surface failures as structured error objects rather than thrown strings.
- Honest annotations.
readOnlyfor inspection tools,destructivefor anything irreversible or user-impacting — and still confirm destructive actions with the user; an annotation is not authorization, and neither is a tool. Never expose a tool that bypasses your normal authorization checks. - Lifecycle-scoped registration. Register a tool only while the UI state it acts on exists, and call the cleanup when that state goes away.
Errors and unavailability
Section titled “Errors and unavailability”The wrapper never throws on missing API; it’s a no-op. The vanilla functions return a no-op cleanup, so consumer code stays declarative:
import { registerTool, isAvailable } from "@web-ai-sdk/webmcp";
if (!isAvailable()) { console.log("WebMCP not available; tools will not be exposed to agents."); return;}
registerTool({ ... });Origin trial
Section titled “Origin trial”Chrome opened a public origin trial for WebMCP (Chrome 149), so you can test agent-callable tools with real traffic before the API stabilizes. Sign up on the Chrome origin trials page; for local development a flag works without a token — see Browser support for the current flag names.
Keep WebMCP optional either way. The wrapper already no-ops when the API is missing, so the same registration code ships to every browser — with or without the trial:
import { registerTool } from "@web-ai-sdk/webmcp";
registerTool({ name: "add_to_cart", description: "Add a product SKU to the user's cart", inputSchema: { type: "object", properties: { sku: { type: "string", description: "The product SKU to add" }, }, required: ["sku"], }, execute: async ({ sku }) => addToCart(sku),});Debugging in DevTools
Section titled “Debugging in DevTools”Chromium DevTools includes experimental WebMCP debugging in the Application panel (introduced in Chrome’s DevTools 149; Edge ships it too): inspect registered tools and their schemas, run a tool manually with custom parameters, track active and pending invocations, and inspect execution status and return payloads. Tools registered through registerTool show up there like any other document.modelContext registration.
The panel is off by default; enable both flags (under chrome://flags / edge://flags) and restart the browser:
#devtools-webmcp-support#enable-webmcp-testing
Testing and evals
Section titled “Testing and evals”Test WebMCP tools at two levels:
- Deterministic tests for
executeitself — plain unit tests, nothing WebMCP-specific. - Agent evals for tool selection — does an agent pick the right tool, with the right arguments, in the right order, from a natural-language prompt?
Chrome’s WebMCP evals tooling expresses the second kind as the call expected from a user prompt:
{ "messages": [{ "role": "user", "content": "Add this product to my cart." }], "expectedCall": [ { "functionName": "add_to_cart", "arguments": { "sku": "abc-123" } } ]}Multi-step journeys wrap call chains in ordered (must run sequentially) or unordered (any order). When an eval fails, the fix is usually in the tool’s name, description, or inputSchema — that is the interface the agent actually reads; see Tool design.