Sunny Car Accessories

Source Code & WebMCP Architecture

WebMCP (W3C) navigator.modelContext Per-View Scoping Inspector Panel

About This Code Showcase

This curated code showcase walks through the WebMCP integration in Sunny Car Accessories — from how 7 typed tools are defined, to how the per-view tool surface swaps as the user navigates, to the Inspector Panel that proves the demo is genuinely active.

The entire app runs client-side in a single static HTML file. The WebMCP layer is purely additive — humans see a normal storefront; agents see a typed contract via navigator.modelContext.

Core: Tool Definitions (TOOLS object)

Each tool is a typed contract — name, human-readable description, JSON inputSchema for the agent to read, and an async execute() handler that runs the business logic and returns structured JSON.

index.html — checkFitment (the killer demo tool)
const TOOLS = { checkFitment: { name: 'checkFitment', description: 'Check whether a part fits a specific vehicle.', inputSchema: { type: 'object', properties: { sku: { type: 'string', description: 'Product SKU to check' }, vehicleMake: { type: 'string', enum: ['Honda','Toyota','Perodua','Proton'] }, vehicleModel: { type: 'string', description: 'e.g. Civic, Myvi, Saga' }, year: { type: 'number', description: 'Vehicle model year' } }, required: ['sku', 'vehicleMake'] }, execute: async ({ sku, vehicleMake, vehicleModel, year }) => { const p = CATALOG.find(x => x.sku === sku); if (!p) return { type: 'text', text: JSON.stringify({ error: 'SKU not found' }) }; if (p.fitment === 'universal') { return { type: 'text', text: JSON.stringify({ fits: true, reason: 'Universal fit' }) }; } const fits = Array.isArray(p.fitment) && p.fitment.includes(vehicleMake); return { type: 'text', text: JSON.stringify({ fits, sku, vehicleMake, compatibleMakes: p.fitment }) }; } } // ... 6 more tools: searchAccessories, getProductDetails, addToCart, getCart, removeFromCart, checkout };

Per-View Tool Scoping (the killer feature)

WebMCP's design choice: agents see only the 3–5 tools relevant to where they currently are. Tool surface morphs on every view change. provideContext() is called fresh per view.

index.html — VIEW_TOOLS map + updateWebMCPContext()
// Per-view tool surface — demonstrates per-page scoping const VIEW_TOOLS = { home: ['searchAccessories'], category: ['searchAccessories', 'getProductDetails'], product: ['getProductDetails', 'checkFitment', 'addToCart'], cart: ['getCart', 'removeFromCart'], checkout: ['getCart', 'checkout'], track: [] // post-purchase: nothing for agents to do }; // Hooked into showView() — fires on EVERY view transition function updateWebMCPContext() { const viewToolNames = VIEW_TOOLS[state.currentView] || []; const tools = viewToolNames.map(n => TOOLS[n]).filter(Boolean); webMCP.provideContext({ tools }); } function showView(viewName, params = {}) { state.currentView = viewName; // ... show/hide view DOM ... if (viewName === 'home') renderHome(); // ... other render branches ... updateWebMCPContext(); // ← swap tool surface here window.scrollTo(0, 0); }

WebMCP Wrapper (real API + compatibility fallback)

The webMCP wrapper uses navigator.modelContext when present, falls back to a local registry otherwise. This makes the Inspector Panel work identically across browsers — a real Chrome 146+ install vs a compatibility-mode browser show the same UI behavior.

index.html — webMCP wrapper
const webMCP = { tools: [], log: [], isSupported: () => 'modelContext' in navigator, provideContext({ tools }) { this.tools = tools; if (this.isSupported() && navigator.modelContext.provideContext) { try { navigator.modelContext.provideContext({ tools }); // real API } catch (e) { console.warn('WebMCP error:', e); } } updateInspectorPanel(); }, async invokeTool(name, params = {}) { const tool = this.tools.find(t => t.name === name); if (!tool) return { error: 'Tool not registered on this view' }; const entry = { ts: timeStamp(), name, params, result: null }; this.log.push(entry); updateInspectorPanel(); try { const result = await tool.execute(params); entry.result = result; updateInspectorPanel(); return result; } catch (e) { entry.result = { error: e.message }; return { error: e.message }; } }, async requestUserInteraction({ title, message }) { return new Promise(resolve => { // Show custom modal — Allow once / Deny — resolves the Promise document.getElementById('confirm-title').textContent = title; document.getElementById('confirm-message').textContent = message; document.getElementById('confirm-modal').classList.remove('hidden'); window._confirmResolve = resolve; }); } };

requestUserInteraction() — Human-in-the-Loop on Checkout

The destructive checkout tool gates execution behind a mandatory human confirmation. Even if an agent invokes it autonomously, the order cannot proceed without explicit user approval — Allow once / Deny.

index.html — checkout tool definition
checkout: { name: 'checkout', description: 'Place the order. Triggers requestUserInteraction() before charging.', inputSchema: { type: 'object', properties: { paymentMethod: { type: 'string', enum: ['DuitNow','FPX','COD'] } }, required: ['paymentMethod'] }, execute: async ({ paymentMethod }) => { if (state.cart.length === 0) return { type: 'text', text: JSON.stringify({ success: false, error: 'Cart is empty' }) }; const total = cartTotal(); // ← THE GATE — pauses execution, waits for user approval const confirmed = await webMCP.requestUserInteraction({ title: 'Confirm purchase', message: `Agent is about to place order: RM ${total.toFixed(0)} via ${paymentMethod}. Approve?` }); if (!confirmed) return { type: 'text', text: JSON.stringify({ success: false, reason: 'User declined' }) }; const result = placeOrder(null); return { type: 'text', text: JSON.stringify(result) }; } }

Inspector Panel — 4 Tabs of Live Introspection

The Inspector Panel is the proof layer that turns the invisible WebMCP API into a visible demonstration. Status badge updates per view; 4 tabs show live tools, run sample invocations, log results, and reveal the source code that produced the surface.

index.html — updateInspectorPanel() + tab switching
function updateInspectorPanel() { const status = document.getElementById('inspector-status'); if (webMCP.isSupported()) { status.innerHTML = `✅ WebMCP active · ${webMCP.tools.length} tool${webMCP.tools.length !== 1 ? 's' : ''} on <strong>${state.currentView}</strong> view`; } else { status.innerHTML = `⚠️ WebMCP not detected · running in compatibility mode · ${webMCP.tools.length} tools on ${state.currentView}`; } const activeTab = document.querySelector('.inspector-tab.active')?.dataset.tab || 'tools'; renderInspectorPane(activeTab); } function renderInspectorPane(tab) { const pane = document.querySelector(`[data-pane="${tab}"]`); if (tab === 'tools') { // Live list of registered tools — expandable to show inputSchema JSON pane.innerHTML = webMCP.tools.map(t => ` <details class="tool-entry"> <summary><strong>${t.name}</strong> — ${t.description}</summary> <pre>${JSON.stringify(t.inputSchema, null, 2)}</pre> </details>`).join(''); } else if (tab === 'try') { // Buttons that invoke tools with sample params — demonstrates tool calls work pane.innerHTML = webMCP.tools.map(t => { const sample = sampleParamsFor(t.name); return `<button onclick='runFromInspector("${t.name}", ${JSON.stringify(sample)})'> ▶ Run ${t.name}</button>`; }).join(''); } else if (tab === 'log') { // Timestamped log of every tool call — agent or human-triggered pane.innerHTML = webMCP.log.slice(-12).reverse().map(l => ` ${l.ts} ${l.name}(${JSON.stringify(l.params)}) → ${JSON.stringify(l.result)}`).join(''); } else if (tab === 'source') { // Shows the actual provideContext() code that produced this view's surface pane.innerHTML = `<pre>${getSourceForView(state.currentView)}</pre>`; } }

agentInvoked Detection

Per WebMCP spec slide 13: submitEvent.agentInvoked tells the form handler "this submission came from an agent, not a human." Useful for analytics splits (agent traffic vs human traffic) and merchant-side fraud / bot detection.

index.html — global form submit listener
// Detect agent-invoked form submissions document.addEventListener('submit', (e) => { if (e.agentInvoked) { console.log('[WebMCP] Form was submitted by an agent, not a human'); webMCP.log.push({ ts: timeStamp(), name: '(form submit)', params: { agentInvoked: true }, result: { detected: 'agent submission' } }); updateInspectorPanel(); // In production: also log to BigQuery / merchant analytics } });

Catalog Data Model

The 18-SKU catalog is a JS array. Each product has SKU, name, brand, category, price, image, description, variantType (color / size / brand / scent / null), variants array, fitment (universal | array of compatible vehicle makes), stock, rating, reviews. Same shape parameterizes any retail vertical via the WebMCP webstore template.

index.html — sample CATALOG entries
const CATALOG = [ { sku: 'INT-001', name: 'Premium Leather Seat Cover Set', brand: 'AutoLuxe', category: 'interior', price: 280, image: 'images/products/int-001-leather-seat-cover.jpg', description: '5-piece premium leather seat cover set...', variantType: 'color', variants: ['Black','Beige','Grey','Brown','Red'], fitment: 'universal', stock: 12, rating: 4.5, reviews: 87 }, { sku: 'INT-002', name: '3D Floor Mats', brand: 'TerraGuard', category: 'interior', price: 180, variantType: null, variants: [], fitment: ['Honda','Toyota','Perodua','Proton'], // model-specific! stock: 25, rating: 4.7, reviews: 156 } // ... 16 more SKUs ];

Key Design Decisions