# WebMCP document.modelContext - AI Agent Directive **Purpose**: Register real WebMCP tools on `document.modelContext` for a live SPA / 3D configurator, prove them with `getTools()`, and tear them down safely with `AbortSignal`. **Target Audience**: Frontend builders shipping agent-ready product surfaces in Chrome origin trial, not server-side MCP integrators. **Estimated Time**: 30-45 minutes (includes origin trial token and one live page test) **Prerequisites**: HTTPS page (or localhost), Chrome with WebMCP origin trial enabled, a SPA or configurator you control, basic TypeScript or JavaScript. --- ## What This Directive Does Half of 2026 tutorials still paste `navigator.modelContext`. Chrome moved the getter to `document.modelContext` (W3C draft May 27 2026). Chromium 150 deprecated the old name with a silent alias - so copy-paste "works" until your unregister path or docs drift. **Problem**: Agents screenshot-click a configurator because the page exposes zero tools. Builders register on the wrong global, skip `AbortSignal` cleanup, or never call `getTools()` to prove registration. **Solution**: Feature-detect, register three configurator tools (`get_config`, `set_option`, `list_options`) on `document.modelContext`, pass `signal` through `execute`, unregister with `controller.abort()`, verify with `getTools()`, and run a kill-switch abort test. **Outcome**: An agent calls structured tools instead of guessing from pixels. You have a repeatable playbook and console proof before production traffic hits the page. --- ## How to Use This Directive ### Option 1: Copy to Your AI Agent (Recommended) 1. Copy the entire "DIRECTIVE START" to "DIRECTIVE END" section below 2. Paste into Cursor, Claude, or your agent of choice 3. Execute step by step - do not skip verification gates 4. Stop if origin trial or HTTPS prerequisites fail ### Option 2: Manual Execution Follow each phase in your SPA bootstrap or configurator shell. --- ## DIRECTIVE START ```text AI Agent Directive: WebMCP document.modelContext Configurator Tools Version: 1.0 Context: Live SPA / real-time 3D configurator. In-browser WebMCP only. NOT bernhardrieder.com. HTTPS required. OBJECTIVE: Register get_config, set_option, and list_options on document.modelContext, prove registration with getTools(), implement AbortSignal unregister, and verify kill-switch abort without breaking in-flight work (Chrome 153 behavior). PREREQUISITES CHECK: Before proceeding, verify: - [ ] Page is served over HTTPS (or localhost dev exception per Chrome guidance) - [ ] Chrome build with WebMCP origin trial token applied (see Chrome Imperative API docs) - [ ] You have a config state object or store the tools can read/write - [ ] You understand navigator.modelContext is deprecated - use document.modelContext PHASE 1: Feature Detect modelContext Action: 1. At app bootstrap (before registering tools), resolve the API: const modelContext = document.modelContext ?? navigator.modelContext; 2. If modelContext is undefined, log once and skip WebMCP - do not throw in production 3. Gate all registration behind this check Verification: Console shows modelContext object on trial-enabled Chrome; undefined on unsupported browsers (graceful no-op). Failure Mode: If undefined, confirm origin trial token, HTTPS, and Chrome version - do not register on a dead reference. PHASE 2: Create AbortController for Tool Lifecycle Action: 1. const controller = new AbortController(); 2. Store controller on your app shell so teardown can call controller.abort() 3. Pass controller.signal into every registerTool call Verification: controller.signal.aborted is false at startup. Failure Mode: If you register without a signal, you cannot unregister cleanly later. PHASE 3: Register get_config Action: registerTool on modelContext with: - name: "get_config" - description: "Return current configurator state as JSON" - inputSchema: { type: "object", properties: {}, additionalProperties: false } - signal: controller.signal - execute: async (input, { signal }) => { if (signal?.aborted) throw new DOMException("Aborted", "AbortError"); return { content: [{ type: "text", text: JSON.stringify(getConfigState()) }] }; } Verification: After registration, modelContext.getTools() includes get_config for same origin. Failure Mode: Empty getTools() - wrong global (navigator only), wrong origin, or registration threw. PHASE 4: Register set_option Action: registerTool with: - name: "set_option" - description: "Set one configurator option by key and value" - inputSchema: { type: "object", properties: { key: { type: "string" }, value: { type: "string" } }, required: ["key", "value"], additionalProperties: false } - signal: controller.signal - execute: async (input, { signal }) => { if (signal?.aborted) throw new DOMException("Aborted", "AbortError"); const { key, value } = input; applyOption(key, value); // your store update return { content: [{ type: "text", text: JSON.stringify({ ok: true, key, value }) }] }; } Verification: Manual executeTool("set_option", JSON.stringify({ key: "color", value: "blue" })) updates UI state. Failure Mode: Schema rejection - fix inputSchema types to match agent payloads. PHASE 5: Register list_options Action: registerTool with: - name: "list_options" - description: "List available configurator option keys and allowed values" - inputSchema: { type: "object", properties: {}, additionalProperties: false } - signal: controller.signal - execute: async (_input, { signal }) => { if (signal?.aborted) throw new DOMException("Aborted", "AbortError"); return { content: [{ type: "text", text: JSON.stringify(listOptionCatalog()) }] }; } Verification: getTools() lists three tools; list_options returns catalog JSON. Failure Mode: Duplicate name error - unregister previous registration or use unique names per session. PHASE 6: Prove Tools with getTools() Action: 1. const tools = modelContext.getTools(); 2. Log tool names: tools.map(t => t.name) 3. Optional cross-origin check only if iframe exposes tools: modelContext.getTools({ fromOrigins: ["https://your-embed.example"] }) Verification: Console shows ["get_config", "set_option", "list_options"] (order may vary). Failure Mode: Empty array - registration ran on navigator only, or page not same-origin with tool host. PHASE 7: Listen for toolchange (Optional but Recommended) Action: document.modelContext?.addEventListener("toolchange", (event) => { console.info("[WebMCP] toolchange", event); }); Verification: Registering or aborting tools emits toolchange. Failure Mode: No event - confirm listener attached to document.modelContext, not window. PHASE 8: Kill-Switch - AbortSignal Unregister Action: 1. On route leave, modal close, or admin kill-switch button: controller.abort() 2. Call getTools() again - registered tools should be gone 3. As of Chrome 153, in-flight execute calls are NOT cancelled by unregister - only new calls fail Verification: After abort, getTools() empty; in-flight fetch passed signal still completes or aborts per your execute body. Failure Mode: Tools still listed - registration used a different controller or re-register ran after abort. PHASE 9: executeTool Smoke Test Action: 1. modelContext.executeTool("get_config", "{}") 2. modelContext.executeTool("set_option", JSON.stringify({ key: "trim", value: "sport" })) 3. modelContext.executeTool("list_options", "{}") Verification: Each call returns structured content; UI reflects set_option. Failure Mode: Tool not found - name mismatch or tools unregistered. PHASE 10: Cross-Origin iframe (Only If Needed) Action: If tools live in iframe: parent iframe allow="tools" Permissions Policy; registerTool with exposedTo origins array per Chrome docs. Verification: Parent getTools({ fromOrigins: [childOrigin] }) sees child tools. Failure Mode: Policy block - check allow attribute and exposedTo list. VALIDATION CHECKLIST: - [ ] document.modelContext used (not navigator-only) - [ ] Three tools registered with AbortSignal - [ ] getTools() proves same-origin registration - [ ] controller.abort() removes tools from registry - [ ] execute receives { signal } and passes to async work - [ ] executeTool smoke tests pass - [ ] Graceful no-op when modelContext missing TROUBLESHOOTING: - navigator.modelContext "works" but docs say document - migrate to document.modelContext; alias may disappear in future Chromium. - provideContext / clearContext / unregisterTool() - removed; use registerTool + AbortSignal only. - Agent still screenshots UI - tools not registered, wrong origin, or agent runtime does not call WebMCP yet. REFERENCES: - Chrome Imperative API: https://developer.chrome.com/docs/ai/webmcp/imperative-api - W3C WebMCP draft (modelContext on document) DIRECTIVE END ``` --- ## Post-Execution Notes - **WebMCP vs MCP**: Server MCP (Cursor, Unreal) runs outside the page. WebMCP runs inside the tab on `document.modelContext`. You need both layers for different surfaces. - **Origin trial**: Chrome ships WebMCP through origin trial until wider release. Do not assume Edge/Firefox parity without checking their release notes. - **Do not** register experimental tools on domains you do not control. This directive targets your own SPA / configurator codebase.