Inspiration

The WebMCP spec's own authors flagged the sharpest risk in agentic commerce in their own draft. Section 6.3.2.3 walks through an ambiguously named tool, finalizeCart, that an agent could call and silently complete a real purchase.

An agent shopping "on your behalf" today has two bad options: screen-scrape the UI (brittle, no ground truth), or call a private backend API directly (bypasses whatever safety rails the storefront's own UI enforces).

WebMCP offered a third path: let the agent call the exact tool the UI itself uses, on the exact same page state a human is looking at. We wanted to build a storefront that took the spec's own warning seriously enough to actually engineer around it, not just write a paragraph about "responsible AI" in a README.

What it does

Shopfront Copilot is a storefront where a shopper and their AI agent browse, compare, and buy together. The agent can:

  • Search & filter the catalog by price, category, and other bounded fields
  • Compare 2-4 products side by side in a real on-page comparison table
  • Check return policy for a product
  • Add / remove cart items, reversible either direction
  • Watch a product for price drops

All of it runs through the same WebMCP tools registered on document.modelContext that the storefront's own UI calls. A shopper also controls an Autonomy Dial (read-only vs. browse-and-cart) and an optional spending cap, both enforced with machine-readable refusal codes the agent can act on.

The checkout gate: the headline feature

Action Exposed to the agent? Can it complete a purchase?
checkout (WebMCP tool) Yes No. Always returns { status: "confirmation_required", cart_summary }. Never mutates the cart.
Confirm-purchase click Not a registered tool at all. Yes, this is the only thing that finalizes an order, and it's gated behind WebAuthn on a plain onClick handler an agent can't reach.

There is no tool an agent can call, no matter how it's worded, that completes a purchase.

How we built it

  • Stack: Next.js (App Router), TypeScript, React, Tailwind
  • State: client-side React context persisted to localStorage, no backend database
  • Tools: defined once in lib/webmcp/tools/, registered via document.modelContext.registerTool(), so a human's click and an agent's tool call run through the identical code path into the identical shared cart

The safety layer is factored out into its own framework-agnostic package, webmcp-guardrails:

  • Autonomy tiers & spending caps that gate mutating calls
  • Structural-absence check that proves confirm_purchase was never registered, rather than just asserting it in prose
  • Flight recorder that logs every tool call as structured, timestamped data
  • Intent-snapshot diffing that freezes what an agent saw when it called checkout and flags anything that changed by the time a human confirms

To prove the pattern holds against a real model, not just scripted scenarios, we added a live agent mode backed by Groq, driving the exact same tool registry and guardrails a simulated harness uses. Checkout confirmation is server-verified through WebAuthn with zero database, and everything degrades gracefully in browsers without document.modelContext support, which today is most of them.

Challenges we ran into

Making the checkout gate airtight meant resisting the temptation to "just add a permission check" to a confirm_purchase tool, since a runtime check is something an agent, or a future refactor, could route around. We made the property structural instead of declarative: the function that finalizes an order simply is never wired into the tool registry.

We also hit five places where the WebMCP spec doesn't yet define an answer:

  1. No standard classification for irreversible actions
  2. No agent identity to key a per-agent rate limit or trust tier on
  3. No way for a shopper's consent decision to persist and reach the agent before it fails once
  4. No push channel for telling an idle agent a watched price actually dropped
  5. No standard primitive for an agent to pause and ask a human a question

We wrote up each gap and our workaround as an RFC rather than silently papering over them.

Keeping the demo honest against a real model was its own challenge: a live Groq-backed agent will happily try creative phrasings to get a tool to do more than its contract allows, which made for a good adversarial test of whether our guardrails actually held.

Accomplishments that we're proud of

  • confirm_purchase cannot be reached by an agent under any circumstance, and we can prove it structurally, not just document it
  • webmcp-guardrails is genuinely reusable: pure TypeScript, no dependency on React or this app's domain
  • Live agent mode means the checkout gate is tested against a real model actively trying to complete a purchase, not just scripted scenarios

What we learned

The safest thing you can do with a dangerous capability isn't a well-worded permission check, it's not exposing the capability's implementation to the caller at all. We also came away convinced that several of the gaps we hit (agent identity, a standard human-question primitive) sit upstream of a lot of other trust problems in agentic web tooling, and are worth pushing at the spec level rather than solving per-site forever.

What's next for Shopfront Copilot

  • Publish webmcp-guardrails to npm so other WebMCP sites can adopt the structural-absence pattern without rebuilding it
  • Prototype an irreversibleHint annotation as a concrete proposal back to the WebMCP spec
  • Prototype a standard ask_human-shaped primitive for the same reason

Built With

Share this project:

Updates