navigator.modelContext Explained: The Complete API Reference
The Imperative API of WebMCP lives on navigator.modelContext. This is the full method reference with real code you can paste into a production app today.
navigator.modelContext is the browser-side object exposed to pages that want to describe their capabilities to AI agents. Where the Declarative API uses HTML attributes on forms, the Imperative API uses JavaScript — giving you dynamic, state-aware tool registration.
Surface area
The API has four main methods. Everything else is convenience:
navigator.modelContext.registerTool(toolDefinition)navigator.modelContext.unregisterTool(name)navigator.modelContext.provideContext(contextObject)navigator.modelContext.requestUserInteraction(options)
registerTool()
Declares a capability the agent can call. Returns a handle (or unsubscribe function depending on polyfill).
navigator.modelContext.registerTool({
name: "searchProducts",
description: "Search the product catalog by keyword, with optional category and price filters.",
inputSchema: {
type: "object",
properties: {
query: { type: "string", description: "Free-text search query." },
category: { type: "string", enum: ["shoes","shirts","pants","accessories"] },
maxPrice: { type: "number", minimum: 0 }
},
required: ["query"]
},
execute: async ({ query, category, maxPrice }) => {
const params = new URLSearchParams({ q: query });
if (category) params.set("cat", category);
if (maxPrice) params.set("max", String(maxPrice));
const res = await fetch(`/api/search?${params}`);
const data = await res.json();
return { results: data.items.slice(0, 20) };
}
});
Schema design matters more than you think
Agents pick tools by matching user intent against tool description first, then inputSchema. If your description is "Searches things," no agent will pick it over a competitor's "Search our running-shoe catalog by size, brand, and color." Be specific and use natural language.
unregisterTool()
Removes a tool. Critical for SPAs — never leave tools registered after their view unmounts:
// React example
useEffect(() => {
const handle = navigator.modelContext.registerTool({
name: "saveDraft",
description: "Save the current document as a draft.",
inputSchema: { type: "object" },
execute: () => saveDraftToServer()
});
return () => navigator.modelContext.unregisterTool("saveDraft");
}, []);
provideContext()
Tells the agent about the current page state. Call it on route change or when meaningful state shifts. Agents use this for disambiguation ("which product is the user looking at?") and for deciding whether a tool is currently relevant.
navigator.modelContext.provideContext({
type: "page",
data: {
route: "/products/sku-1234",
title: "Nike Pegasus 40",
sku: "sku-1234",
price: 129.99,
inStock: true,
userIsLoggedIn: true
}
});
requestUserInteraction()
The human-in-the-loop primitive. Use it when a tool call needs explicit confirmation — payments, deletes, legal actions. The browser shows a consent UI; the agent cannot bypass it.
const confirmed = await navigator.modelContext.requestUserInteraction({
type: "confirm",
title: "Complete purchase?",
description: "Your card ending 4242 will be charged $129.99.",
action: "Purchase"
});
if (confirmed) { /* proceed */ }
SubmitEvent.agentInvoked
When an agent submits a form, the browser sets event.agentInvoked = true. Use it for analytics (segment agent vs human traffic), UX adjustments (skip confirmation modals an agent already handled), and abuse tracking. Never use it for authorization — the user's session is the authority.
form.addEventListener("submit", (event) => {
if (event.agentInvoked) {
analytics.track("agent_form_submit", { form: form.id });
}
});
React integration pattern
function useWebMCPTool(tool) {
useEffect(() => {
if (!navigator.modelContext) return;
navigator.modelContext.registerTool(tool);
return () => navigator.modelContext.unregisterTool(tool.name);
}, [tool.name]);
}
Browser support and fallbacks
Chrome 146 Canary ships the native API behind the webmcp flag. For everything else, use the @mcp-b/global polyfill. Always feature-detect before calling:
if ("modelContext" in navigator) {
navigator.modelContext.registerTool({ /* ... */ });
}
Common gotchas
- Registering at module scope — tools get registered before the DOM is ready and may not map to visible UI.
- Leaky SPAs — missing
unregisterToolleaves ghost tools that agents can still call. - Over-broad schemas —
{ type: "object" }with no properties tells the agent nothing; be explicit. - Silent execute failures — wrap in try/catch and return a structured error; agents surface it to the user.
For the bigger picture, see the full implementation guide, the security model, and industry use cases.
Frequently Asked Questions
Is navigator.modelContext the same across browsers?
The spec is — the implementation is not. Chrome 146 Canary is the only native implementation as of April 2026. Other browsers will follow; in the meantime use the polyfill.
Can I register tools before the page is ready?
You can, but you usually shouldn't. Register tools when the UI they map to is mounted so the agent doesn't call actions the user can't see.
How do I test my tools without building an agent?
Open Chrome Canary DevTools → Application → WebMCP panel. You can see registered tools, inspect their schemas, and invoke them manually.
What happens if execute() throws?
The agent receives the error and typically surfaces it to the user. Always throw meaningful messages, and return structured errors (e.g., { success: false, reason: "out_of_stock" }) instead of raw exceptions when possible.
Can tools call other tools?
Yes, from within execute() you can call anything — including other registerTool handlers. Just be careful about infinite loops and about exposing internal tools you didn't mean to chain.
Is your site ready for AI agents?
Run a free WebMCP readiness audit in 15 seconds — no signup.
Run Free Audit →