Skip to content
Scott C. Krause edited this page Sep 18, 2026 · 4 revisions

machvive-webmcp-ai

Vanilla Web Components that make a web page usable by an AI agent.

@machfivetechchicago/machvive-webmcp-ai is a small, dependency-free toolkit for building and debugging agent-ready web pages. It gives your page a way to publish tools an AI agent can call, plus the instruments to see what those tools do when something actually calls them.

No framework. No build step. No runtime dependencies. Standards-based custom elements that work anywhere customElements does.


Why this exists

Agents interacting with websites today mostly do it by pretending to be a person: reading the rendered page, guessing which element is the "Add to cart" button, and clicking it. That is slow, brittle, and breaks every time the markup changes.

WebMCP is a W3C Web Machine Learning Community Group proposal that takes the opposite approach. Instead of an agent reverse-engineering your UI, the page declares what it can do:

navigator.modelContext.registerTool({
  name: 'add_to_cart',
  description: 'Add a product to the shopping cart',
  inputSchema: {
    type: 'object',
    properties: { sku: { type: 'string', description: 'Product SKU' } },
    required: ['sku']
  },
  execute: async ({ sku }) => {
    await cart.add(sku);
    return { content: [{ type: 'text', text: `Added ${sku} to cart.` }] };
  }
});

The agent now has a named, documented, schema-validated function to call. Your cart logic runs your code — not a simulated click on a button that might have moved. If the page changes, the tool keeps working.

The catch: no browser ships navigator.modelContext yet. That is the gap this package fills.


Business Accelerators

WebMCP empowers agents by giving them access to your core business functionality within a safe, semantic sandbox. That matters most where your page is already doing commercial work.

A Business Accelerator is a targeted, authoritative, conversion-optimized landing page with automated onboarding that facilitates a non-linear, multi-touch sales cycle. Exposing your Business Accelerators to agents makes perfect sense: it gives agents access to interactive experiences that deliver real business value — not just content. An agent that can call your onboarding flow, quote builder, or availability check is doing something categorically different from one that scraped your marketing copy.

But if agents are becoming part of your customer journey, you need to understand how they engage with your site. That is what the analytics component is for: record agent sessions and play them back to see exactly how they navigate, interact, and use your Business Accelerators. The result is detailed insight into agent behaviour — and into the broader ecosystem of agent-driven engagement.

Business Accelerators are a MachFiveTech Chicago practice. Learn more at machfivemagnet.com.


What's in the box

Component Tag Purpose
Polyfill <machvive-webmcp-polyfill> Provides navigator.modelContext so you can build against WebMCP today
Inspector <machvive-webmcp-inspect> Lists your tools, builds a form from each schema, runs them
Analytics <machvive-webmcp-analytics> Captures every call for listing, editing, export, replay, and dataLayer
Lorum Ipsum <machvive-lorum-ipsum> Placeholder copy (unrelated utility, included for convenience)

They compose: the polyfill provides the registry, the inspector drives it, and analytics observes everything flowing through it.


Install

npm install @machfivetechchicago/machvive-webmcp-ai

Import only what you need, or everything at once:

// Cherry-pick (smaller bundles)
import '@machfivetechchicago/machvive-webmcp-ai/webmcp-polyfill';
import '@machfivetechchicago/machvive-webmcp-ai/webmcp-inspect';
import '@machfivetechchicago/machvive-webmcp-ai/webmcp-analytics';

// Or everything
import '@machfivetechchicago/machvive-webmcp-ai';

Importing a module registers its custom element automatically — there is nothing to call. Then use the tags in your markup.

TypeScript declarations ship with the package; no @types/* needed.


The polyfill

machvive-webmcp-polyfill implements navigator.modelContext following the W3C proposal: registerTool, unregisterTool, and provideContext (which replaces the whole toolset at once, for when app state changes what's available).

Two behaviors worth understanding:

It never overwrites a native implementation. If the browser ships WebMCP, the polyfill steps aside and your page talks to the real thing. Your code does not change when that day arrives.

It requires a secure context. The native API is [SecureContext], so the polyfill matches — it installs on HTTPS and on localhost, and declines elsewhere with a console warning. This is deliberate: it stops you developing against a surface the browser will never hand you on a plain-HTTP page.

The polyfill is a registry, not a transport. It holds your tools and can invoke them; it does not connect to an agent. Two non-standard additions exist so your own code can bridge that gap: navigator.modelContext.tools (the descriptors an agent would discover, minus the handlers) and navigator.modelContext.callTool(name, params). Both are clearly marked as extensions — the spec defines registration only.


The inspector

You have declared some tools. Do they work?

machvive-webmcp-inspect reads each tool's inputSchema and builds a form from it — text inputs, number fields, checkboxes, dropdowns for enums, JSON textareas for object and array parameters. Fill it in, press Execute, see the result.

The important detail is type fidelity. An HTML number input hands you the string "3". A tool that declared { type: 'integer' } expects 3. The inspector coerces every value to the type its schema declares before calling, so what you test by hand is what an agent will send. Required fields are enforced before anything runs, and malformed JSON is reported on the field rather than thrown at the tool.

<!-- inline: renders where you place it -->
<machvive-webmcp-inspect></machvive-webmcp-inspect>

<!-- floating: docks as an overlay with a toggle, no layout impact -->
<machvive-webmcp-inspect floating></machvive-webmcp-inspect>

Use inline in a docs page or admin panel; use floating to debug a live app without disturbing it. The tool list stays live as tools are registered and removed.


Analytics

machvive-webmcp-analytics records every tool invocation — parameters, result, duration, and errors — and lets you list, edit, export, replay, or forward them.

How capture works, and why it matters

Analytics wraps each tool's execute handler at registration time. It does not hook callTool.

That distinction is the whole design. Hooking the caller would only see calls made through this library. Wrapping the handler sees every invocation — including ones from a native navigator.modelContext and from real agents, neither of which route through our code.

The consequence you must plan for:

Import analytics before you register any tools. Tools registered earlier cannot be instrumented, because the polyfill deliberately hides handlers from tools. The component warns in the console when it detects this rather than pretending it captured everything.

// analytics first
import '@machfivetechchicago/machvive-webmcp-ai/webmcp-analytics';
import '@machfivetechchicago/machvive-webmcp-ai/webmcp-inspect';
// then register tools

What you can do with a captured call

import { callLog } from '@machfivetechchicago/machvive-webmcp-ai/webmcp-analytics';

await callLog.ready;            // restoring from IndexedDB is async

callLog.entries;                // captured calls, oldest first
callLog.toJSON();               // export
callLog.import(json);           // merge a previously exported log
callLog.update(id, { params }); // edit before replaying
await callLog.replay(id);                   // re-run exactly as captured
await callLog.replay(id, { sku: 'OTHER' }); // re-run with edited parameters

Replay with edited parameters is the feature to reach for when reproducing an agent's mistake: capture what it actually sent, change one field, run it again.

Calls persist to IndexedDB, so a log survives reloads and is not bound by the ~5 MB localStorage ceiling. Where IndexedDB is unavailable, capture degrades to memory-only rather than failing. The default cap is 500 entries, oldest evicted first.

Recording is strictly best-effort. A failure inside the log — a throwing subscriber, an unwritable store — can never change a tool's result or make a successful call appear to have errored. An observability tool that corrupts the thing it observes is worse than no tool at all.

Google Tag Manager

Pushing to window.dataLayer is off unless you opt in, so importing the component never emits tracking traffic on its own:

<machvive-webmcp-analytics datalayer></machvive-webmcp-analytics>

Each call then pushes { event: 'webmcp_tool_call', webmcp_tool, webmcp_status, webmcp_duration_ms, webmcp_params }. Without the attribute, push individual entries on demand with callLog.pushToDataLayer(id) or the per-entry button.


Theming

The UI components follow the viewer's OS colour preference automatically. Set theme to override:

<machvive-webmcp-analytics theme="dark"></machvive-webmcp-analytics>

theme is also a reflected property: el.theme = 'dark', or null to go back to following the OS.

Colours are CSS custom properties on the host. Custom properties inherit through shadow boundaries where ordinary styles do not, so you can restyle the components from your own stylesheet without ::part or !important:

machvive-webmcp-inspect,
machvive-webmcp-analytics {
  --mv-accent: #7c3aed;
  --mv-bg: #ffffff;
  --mv-fg: #111827;
}

Every combination of OS preference and theme is verified to meet WCAG AA (≥ 4.5:1). If you override tokens, re-check your own contrast.


Constraints to know before you adopt

Secure context required. The polyfill installs on HTTPS and localhost only. A plain-HTTP staging box will silently have no navigator.modelContext.

Browser-only — these break under SSR. Every component extends HTMLElement at module load, so importing any entry point during a server render throws ReferenceError: HTMLElement is not defined. This never happens in a browser-only setup like Vite. In Next.js, Nuxt, Astro, SvelteKit, or Remix, import from a client-only path:

useEffect(() => { import('@machfivetechchicago/machvive-webmcp-ai'); }, []);

A static top-level import will not work in those frameworks — the module is evaluated during the server render, before any browser-only lifecycle hook runs.

Import order matters for analytics. See above; this is the single most common way to end up with an empty log.

The inspector needs the polyfill's extensions. tools and callTool are non-standard additions. Against a future native implementation that lacks them, the inspector detects this and explains itself rather than throwing.


Where things live

Apache-2.0. Zero runtime dependencies.