WebMCP is experimental. A correct implementation begins with the current specification, feature detection, and a narrow action contract. Do not start from older examples that use navigator.modelContext or treat form annotations as a stable declarative API.
This guide follows the July 8, 2026 Community Group draft, which exposes the imperative API through document.modelContext. Recheck the specification and browser documentation before using the code in production.
Step 0: Define one bounded workflow
Write down five things before registering a tool:
- the current application state the action needs;
- the user permission that authorizes it;
- the input the agent must supply;
- the state change or result the action should produce;
- the observation that will verify success.
If those boundaries are unclear, the tool contract is not ready.
Step 1: Check the environment
WebMCP requires a supporting browser implementation and a secure context. Chrome announced an origin trial for Chrome 149 on June 9, 2026, but trial and browser status can change.
Feature-detect the current API and keep the human-facing application functional when it is absent:
function supportsWebMCP() {
return "modelContext" in document;
}
if (!supportsWebMCP()) {
console.info("WebMCP is not available in this environment.");
}
Avoid a polyfill claim unless you have verified which agent or browser consumes the polyfilled transport. Making document.modelContext exist in JavaScript does not by itself make every browser agent discover the tools.
Step 2: Register a read-only tool
Start with a low-risk query whose authorization already exists in the application:
const controller = new AbortController();
if (supportsWebMCP()) {
document.modelContext.registerTool(
{
name: "search_records",
title: "Search records",
description:
"Search records available to the signed-in user. " +
"Returns identifiers and display names for matching records.",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
minLength: 1,
maxLength: 120,
description: "Text to match against record names."
}
},
required: ["query"],
additionalProperties: false
},
annotations: {
readOnlyHint: true
},
async execute({ query }) {
return api.records.search({ query });
}
},
{ signal: controller.signal }
);
}
The AbortSignal scopes the registration. Aborting it removes the tool when the page context changes or the component unmounts.
Step 3: Add a state-changing tool carefully
A mutation needs a stricter contract than a query:
- reuse the application’s authorization checks;
- reject unsupported state transitions;
- require clear user intent for risky or ambiguous actions;
- return enough information to inspect the resulting state;
- fail closed when the result cannot be confirmed.
document.modelContext.registerTool({
name: "set_record_status",
title: "Set record status",
description:
"Change the status of the record currently open in the interface. " +
"Only transitions allowed for the signed-in user are accepted.",
inputSchema: {
type: "object",
properties: {
status: {
type: "string",
enum: ["active", "paused", "archived"]
}
},
required: ["status"],
additionalProperties: false
},
annotations: {
readOnlyHint: false
},
async execute({ status }) {
const before = app.currentRecord();
await api.records.setStatus(before.id, status);
const after = await api.records.get(before.id);
if (after.status !== status) {
throw new Error("The requested state was not observed after the update.");
}
return {
recordId: after.id,
previousStatus: before.status,
currentStatus: after.status,
verified: true
};
}
});
The important step is not the success message. It is the post-action observation.
Step 4: Scope tools to the current page
Register only actions that make sense in the connected document. A tool for the record currently open should not stay registered after navigation to a different record.
In React, use an AbortController for lifecycle cleanup:
import { useEffect } from "react";
function RecordPage({ recordId }) {
useEffect(() => {
if (!("modelContext" in document)) return;
const controller = new AbortController();
document.modelContext.registerTool(
{
name: "get_current_record",
title: "Get current record",
description: "Return the record currently open in this page.",
inputSchema: {
type: "object",
properties: {},
additionalProperties: false
},
annotations: {
readOnlyHint: true
},
async execute() {
return api.records.get(recordId);
}
},
{ signal: controller.signal }
);
return () => controller.abort();
}, [recordId]);
return <RecordView recordId={recordId} />;
}
Step 5: Test the contract, not only the handler
Test at four levels:
- Schema: valid inputs pass and malformed or extra inputs fail.
- Authorization: the tool cannot exceed the signed-in user’s permissions.
- Selection: representative prompts lead an agent to the right tool and irrelevant prompts do not.
- Verification: the observed post-action state matches the requested postcondition.
Because model behavior is probabilistic, repeat the selection tests across a fixed prompt set. Record the model, date, prompt, selected tool, supplied arguments, and outcome.
A bounded ecommerce example
The following is hypothetical. It does not claim a kn8 commerce integration.
Suppose a connected collection page exposes:
- the currently visible product count;
- a visible price-filter target;
- a host-supported action named apply_price_filter;
- a refreshed page observation after the filter runs.
An agent can guide the shopper to the visible control, invoke the supported action with the requested range, and verify that the resulting interface reports the expected filter and a new result count. It should not infer inventory, pricing, or checkout capabilities the host did not expose.
Production review checklist
Capability boundary
- Every tool is necessary for the current page or workflow
- The description names the relevant context and limits
- Inputs reject unknown fields and invalid values
- Tools disappear when their page context is gone
Security
- Existing server-side authorization still applies
- Consequential actions require appropriate user intent or confirmation
- Untrusted output is labeled and handled
- Tool output does not expose secrets or unnecessary personal data
Reliability
- Errors are explicit and do not imply success
- Mutations define an observable postcondition
- The result is re-read or otherwise verified
- The application still works when WebMCP is unavailable
Freshness
- The implementation matches the current specification
- Browser and trial requirements were checked on the publication date
- No deprecated navigator.modelContext or unverified declarative API remains
kn8 product boundary
kn8 is not built on WebMCP. Its current code-proven behavior uses state and visible targets supplied by a connected host, guides through the interface already on screen, invokes supported actions, and verifies the result. The ecommerce example above is a design pattern, not proof of a catalog, inventory, cart, or checkout integration.