navigator.modelContext vs document.modelContext: how WebMCP tools are exposed
navigator.modelContext is an older address. Use feature detection and prefer document.modelContext to expose WebMCP tools that agents can call instead of scraping.
You searched for navigator.modelContext. Here’s the short answer: the current WebMCP address is document.modelContext. Some early agent/browser builds still look at navigator.modelContext. You should feature‑detect both, register tools once, and move on. By the end, you’ll have copy‑paste code that safely exposes WebMCP tools, plus guardrails and tests that hold up in production.
TL;DR
- WebMCP today: use document.modelContext. Keep a fallback to navigator.modelContext for older agents.
- Register tools with a clear description, a JSON Schema input, and an async execute that returns a small JSON result.
- Tools run in the page with the user’s session—no extra API keys or backends needed for the agent.
- Real support is uneven. ChatGPT desktop and Work call page tools today; Chrome and Edge are in origin trials. Track status on the public WebMCP ecosystem tracker.
- Production tips: guard sensitive actions with explicit user confirmation, version your configs, and watch for UI drift.
What is navigator.modelContext?
JavaScript is case‑sensitive: it’s navigator.modelContext, not navigator.modelcontext. Early experiments exposed WebMCP at navigator.modelContext. The current address is document.modelContext. Because some agents and niche browsers still probe the old place, you should feature‑detect both.
WebMCP itself is simple: a page exposes a small set of typed “tools” that an agent can discover and call. No DOM scraping. No image clicking. Each tool:
- Has a name (snake_case), a precise description, and a JSON Schema input.
- Runs inside the page’s JS context (the user’s real session).
- Returns a small JSON payload describing what happened.
If you want a broader framing of how this differs from browser automation and agent browsers, see WebMCP vs Agent Browsers: What Each Actually Does and What is the Difference Between MCP and WebMCP?.
Minimal, correct feature detection
Use document.modelContext first, then fall back to navigator.modelContext. If neither exists, do nothing.
// Feature detection covering current and older implementations
const mc = (typeof document !== 'undefined' && document.modelContext)
?? (typeof navigator !== 'undefined' && navigator.modelContext);
if (mc) {
mc.registerTool({
name: 'ping',
description: 'Use to check that tools are reachable in this page. Returns a timestamp.',
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
execute: async () => ({ ok: true, ts: Date.now() })
});
}
That pattern is safe to ship. It won’t throw in non‑supporting browsers, and it makes your tools discoverable where WebMCP is wired up.
Where WebMCP works today
Support is real but patchy. Based on hands‑on verification:
- Agents: ChatGPT desktop app and ChatGPT Work call page tools today. Claude for Chrome and Gemini in Chrome do not. Brave Leo has a Nightly flag. Several extensions have no clear signal. See the ecosystem tracker for dates and notes.
- Browsers: Chrome and Edge run WebMCP under origin trials. Brave Nightly has a flag. Firefox and Safari currently show no support.
- Frameworks: Vercel agent-browser and Stagehand work today; Puppeteer has a flagged path; Playwright declined for now. Details again on the ecosystem tracker.
If you want to see an end‑to‑end setup with a real agent calling your page tool, read the practical WebMCP demo.
Good tool design: precise descriptions and typed inputs
Agents choose tools by reading your descriptions and schemas. Be explicit about when to use a tool, its preconditions, and side effects. Keep the result compact and serialisable.
Example: a “add_to_cart” tool on a product page.
const mc = document.modelContext ?? navigator.modelContext;
if (mc) {
mc.registerTool({
name: 'add_to_cart',
description: [
'Add the currently viewed product variant to the cart.',
'Use only when the page shows a purchasable product and a selected variant.',
'Side effect: modifies the cart in this session.'
].join(' '),
inputSchema: {
type: 'object',
properties: {
quantity: { type: 'integer', minimum: 1, default: 1 },
variant_id: { type: 'string', description: 'Variant identifier visible on the page' }
},
required: ['variant_id'],
additionalProperties: false
},
execute: async ({ quantity = 1, variant_id }) => {
// Example binding: simulate a click or call your app code
// Keep this short and side‑effectful only as described
const form = document.querySelector('[data-cart-form]');
if (!form) return { ok: false, error: 'no_cart_form' };
const inputVariant = form.querySelector('input[name="variant_id"]');
const inputQty = form.querySelector('input[name="quantity"]');
if (!inputVariant || !inputQty) return { ok: false, error: 'missing_fields' };
inputVariant.value = String(variant_id);
inputQty.value = String(quantity);
form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
return { ok: true, added: { variant_id, quantity } };
}
});
}
This example binds to your existing UI. WebMCP runs inside the page; no API keys needed for the agent.
For more form patterns and coverage ideas, see WebMCP forms: model your forms as agent‑callable tools and the form coverage checklist.
Guardrails for sensitive actions
Payments, account changes, and destructive actions should require an explicit, human confirmation in the page before proceeding. Do not accept raw payment tokens or secrets via tool parameters.
Example: tool that starts checkout but blocks until the user confirms a dialog.
const mc = document.modelContext ?? navigator.modelContext;
if (mc) {
mc.registerTool({
name: 'start_checkout',
description: [
'Begin checkout for the current cart.',
'Requires user confirmation in the page before proceeding to payment.'
].join(' '),
inputSchema: {
type: 'object',
properties: { note: { type: 'string', description: 'Optional order note' } },
additionalProperties: false
},
execute: async ({ note }) => {
// Show a modal to the human, return only after confirmation
const ok = await new Promise(resolve => {
const confirmed = window.confirm(
'An agent requested to start checkout. Proceed to payment?'
);
resolve(confirmed);
});
if (!ok) return { ok: false, error: 'user_declined' };
if (note) {
const noteEl = document.querySelector('[name="order_note"]');
if (noteEl) noteEl.value = note;
}
const btn = document.querySelector('[data-checkout-button]');
if (!btn) return { ok: false, error: 'no_checkout_button' };
btn.dispatchEvent(new Event('click', { bubbles: true }));
return { ok: true, started: true };
}
});
}
This keeps the final say with the human and avoids passing sensitive data through the tool interface.
Tool discovery and call flow
Here’s the typical flow when an agent supports WebMCP.
Agents don’t click pixels. They read your tool descriptions and inputs, decide which tool to call, and pass structured JSON. Your code executes in the page context and returns a compact result.
document.modelContext vs navigator.modelContext
Use this table to decide how to address the object in production.
| Address | Status | Why you still care |
|---|---|---|
| document.modelContext | Current | Preferred in active trials and modern builds. Use first. |
| navigator.modelContext | Older | Some agents and niche browsers still probe here. Keep a fallback. |
The point: don’t pick one. Feature‑detect both, register once, and keep the rest of your code the same.
A practical pattern for real sites
On production sites, you’ll usually:
- Register a small, well‑named set of tools per page template (product, cart, search, account).
- Keep input schemas tight, with required fields and clear descriptions.
- Return small, stable JSON shapes. Avoid dumping the DOM.
- Add confirmation for sensitive actions.
- Log and test calls against real intents.
If you want a guided setup plus drift‑resistant bindings, the Agent Layer platform adds:
- Template detection (product, cart, search), tool definitions as versioned configs, and one‑click rollback.
- Health checks that flag when a CSS selector or binding broke after a UI change and suggest a fix.
- Guardrails for sensitive actions and analytics that show which agents actually called a tool, with duration and errors.
- Test scenarios where you write an intent and the expected tool, then verify.
You can implement WebMCP directly yourself (examples above), and many teams do for first trials. Agent Layer removes the maintenance work when UI changes and lets you see what agents are actually doing.
Example: search, then add to cart
Two tools that often pair: one to run a site search, one to add to cart from a product page.
const mc = document.modelContext ?? navigator.modelContext;
if (mc) {
mc.registerTool({
name: 'site_search',
description: 'Search products by keyword on the current site and show results to the user.',
inputSchema: {
type: 'object',
properties: { q: { type: 'string', minLength: 2 } },
required: ['q'],
additionalProperties: false
},
execute: async ({ q }) => {
const input = document.querySelector('input[type="search"], [name="q"]');
if (!input) return { ok: false, error: 'no_search_input' };
input.value = q;
input.dispatchEvent(new Event('input', { bubbles: true }));
const form = input.form || document.querySelector('form[role="search"]');
if (form) form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
return { ok: true, query: q };
}
});
mc.registerTool({
name: 'add_to_cart',
description: 'Add the current product variant to cart. Requires visible product with variant selected.',
inputSchema: {
type: 'object',
properties: {
variant_id: { type: 'string' },
quantity: { type: 'integer', minimum: 1, default: 1 }
},
required: ['variant_id'],
additionalProperties: false
},
execute: async ({ variant_id, quantity = 1 }) => {
const form = document.querySelector('[data-cart-form]');
if (!form) return { ok: false, error: 'no_cart_form' };
const idEl = form.querySelector('[name="variant_id"]');
const qtyEl = form.querySelector('[name="quantity"]');
if (!idEl || !qtyEl) return { ok: false, error: 'missing_fields' };
idEl.value = String(variant_id);
qtyEl.value = String(quantity);
form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
return { ok: true, added: { variant_id, quantity } };
}
});
}
This keeps side effects local to the intended UI flows and allows agents to chain actions via results.
Architecture at a glance
A small diagram to anchor how this sits in your stack.
Agents call tools, tools bind to your existing UI and session, and your usual backend handles the real work.
What breaks in production
A few sharp edges we see most often:
- Missing feature detection: hard‑coding navigator.modelContext only. Result: nothing happens in current trials that expose document.modelContext. Always probe both.
- Vague descriptions: agents choose poorly or skip your tool. State preconditions and side effects plainly.
- Over‑broad schemas: tools accept junk. Use required, minLength, enums, and additionalProperties: false.
- Large return payloads: time and token waste. Return a compact summary and let the agent decide the next tool call.
- Sensitive data in params: never accept secrets or payment tokens via tools. Gate with a human confirmation for payments, account changes, or deletion.
- UI drift: a renamed selector breaks bindings quietly. If you roll your own, add tests that exercise tools on key templates. Agent Layer’s health checks catch and suggest fixes when selectors change.
- Assuming universal support: shipping tools doesn’t guarantee calls. Track support and test with agents that actually call tools (e.g., ChatGPT desktop). See the ecosystem status.
How far can you go without a platform?
You can register tools by hand and wire them to your UI, as shown. For small sites and experiments, that’s fine. As soon as you have more than a handful of tools across multiple templates, the maintenance hits: drift, unknown agent behavior, and untested edge cases.
Agent Layer exists to reduce that surface area without changing the WebMCP model:
- Define tools per template, publish as versioned configs, and roll back safely.
- Health checks detect selector drift and suggest corrections.
- Guardrails for sensitive actions.
- Analytics log each tool call (which agent, success, duration, errors).
- Test scenarios to validate that a user intent triggers the tool you expect.
If you’re evaluating scope, compare this path with traditional APIs in WebMCP vs Traditional APIs: Choosing the Right Interface for AI Agents, and see concrete site patterns in WebMCP example: concrete uses that work today.
FAQ
- Should I still expose navigator.modelContext on new code? Yes, as a fallback via feature detection. Prefer document.modelContext.
- Do I need a backend key or MCP server? No. WebMCP tools run in‑page in the user’s session. WebMCP is distinct from backend MCP servers.
- Which agents call tools today? ChatGPT desktop and ChatGPT Work have been observed calling page tools. Others are in trials or unknown; check the ecosystem tracker.
Try it
Run the free agent‑readiness audit on your site. It flags missing WebMCP tools, unlabeled forms, invalid JSON‑LD, blocked crawlers, and more, with page‑by‑page fixes. Start at the Agent Layer audit. If you’re deciding when to ship tools based on browser and agent support, watch the ecosystem tracker.


