Repository navigation
Machvive WebMCP Polyfill Web Component Lib
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.
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:
const controller = new AbortController();
await document.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']
},
annotations: { consequentialHint: true },
execute: async ({ sku }) => {
await cart.add(sku);
return { added: sku, items: cart.count };
}
}, { signal: controller.signal });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.
Two things to know about that snippet. It is document.modelContext, not
navigator.modelContext — the navigator form appears throughout older
explainers and is stale. And the AbortController is not optional bookkeeping:
aborting it is the only way to withdraw a tool, so a registration made without
one lasts as long as the page.
The catch: most browsers do not ship WebMCP yet. Chrome 149 and Edge 150 have
it behind an origin trial (and about:flags#enable-webmcp-testing); everything
else has nothing. That is the gap this package fills — and when a browser does
ship it, the polyfill stands aside and your code is unchanged.
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.
| Component | Tag | Purpose |
|---|---|---|
| Polyfill | <machvive-webmcp-polyfill> |
Provides document.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.
npm install @machfivetechchicago/machvive-webmcp-aiImport 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.
machvive-webmcp-polyfill implements document.modelContext following the W3C
proposal: registerTool(tool, { signal }) to publish, getTools() to discover,
and executeTool(tool, input) to invoke — on an EventTarget that fires
toolchange when the set changes.
Withdrawal is by AbortSignal. The spec removed unregisterTool and
provideContext, so the controller you registered with is the only way to take a
tool back. When app state changes which tools make sense, give each group its own
controller and abort the ones that no longer apply.
Two further 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. Bridging that gap is getTools()
and executeTool(), both of which are spec surface — so bridge code written
against them keeps working on a native implementation.
executeTool resolves with the result JSON-serialized as a string and
rejects on failure. Versions before 7.0 added a synchronous tools accessor and
a callTool that never threw, because the spec then covered registration only;
both are gone.
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.
machvive-webmcp-analytics records every tool invocation — parameters, result,
duration, and errors — and lets you list, edit, export, replay, or forward them.
Analytics wraps each tool's execute handler at registration time. It does not
hook executeTool.
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 document.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 the descriptors
getTools()returns. 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 toolsimport { 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 parametersReplay 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.
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.
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.
Secure context required. The polyfill installs on HTTPS and localhost only.
A plain-HTTP staging box will silently have no document.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.
Report tool failures by throwing. There is no isError flag in the spec, so
a handler reports a problem by rejecting — and callers must wrap executeTool in
try/catch. Code carried over from 6.x that checked result.isError stops
handling failures entirely.
Registering a name twice is refused. A duplicate rejects with
InvalidStateError rather than silently replacing the existing tool.
- Package — npm
- Source — GitHub
- WebMCP proposal — W3C Web Machine Learning CG
Apache-2.0. Zero runtime dependencies.