All posts
EducationEngineeringBest practices

A practical WebMCP demo: register tools and run them with a real agent

Follow a WebMCP demo end‑to‑end: add two concrete tools, run them with a supporting agent today, and learn what works—and what breaks—in real production. Keyword: webmcp demo.

Agent LayerOctober 16, 202610 min read

You came here for a WebMCP demo. Good news: you can run one today with a supporting agent and a couple of short scripts. By the end, you’ll have a page that exposes two WebMCP tools—search_products and add_to_cart—plus a guarded start_checkout. You’ll see how an agent discovers and calls them, what JSON Schemas agents expect, and the sharp edges to avoid. This is a practical webmcp demo, not a pitch.

Two caveats up front. WebMCP support is early and inconsistent across agents and browsers; check the live status on the public WebMCP ecosystem tracker. Also, you can get far without any platform: plain WebMCP works in your codebase today. Agent Layer only shows up where it removes maintenance work in production.

TL;DR

  • Register tools with feature detection: use document.modelContext first, then navigator.modelContext as a fallback.
  • Keep tools small and explicit: clear description, tight JSON Schema, small JSON result.
  • Sensitive actions (checkout, account, payments) must ask the user for explicit confirmation in‑page before executing.
  • You can demo this today with agents that call page tools (e.g. ChatGPT desktop app, Stagehand, Vercel agent‑browser). See the ecosystem support list.
  • Agent Layer adds guardrails, drift checks, analytics and test scenarios once you move beyond a demo.

What you’ll build

We’ll add three tools to a typical storefront page:

  • search_products: full‑text search returning a compact result list.
  • add_to_cart: add a SKU with a quantity to the user’s current cart.
  • start_checkout: begin checkout—but only after explicit user confirmation.

These tools run in the user’s existing session. No backend changes or keys are needed for the agent.

Basic page setup

We’ll assume you can call your own endpoints (e.g. /api/search, /api/cart) with fetch. If you don’t have endpoints, bind to your existing client functions or form submissions—WebMCP doesn’t care how you implement execute, only that it returns a small JSON object.

Add this feature‑detection snippet once per page template where the tools should exist:

<!-- In your layout head or at the end of body -->
<script>
  // Shared helper: returns the current WebMCP context or null.
  function getModelContext() {
    return (typeof document !== 'undefined' && document.modelContext)
      || (typeof navigator !== 'undefined' && navigator.modelContext)
      || null;
  }
</script>

Tool 1: search_products

Search should be side‑effect free and return compact data an agent can reason about.

<script type="module">
  const mc = getModelContext();
  if (mc) {
    mc.registerTool({
      name: "search_products",
      description: "Search catalog by text. Use to find products before adding to cart. Returns top matches with sku, title, price, and url. No side effects.",
      inputSchema: {
        type: "object",
        additionalProperties: false,
        properties: {
          query: { type: "string", minLength: 2, description: "Search text" },
          limit: { type: "integer", minimum: 1, maximum: 20, default: 5 }
        },
        required: ["query"]
      },
      execute: async ({ query, limit = 5 }) => {
        const res = await fetch(`/api/search?q=${encodeURIComponent(query)}&limit=${limit}`);
        if (!res.ok) throw new Error("search_failed");
        const items = await res.json(); // Expect [{sku,title,price,url}]
        // Return only fields agents need; keep it small.
        return {
          query,
          count: items.length,
          items: items.map(x => ({ sku: x.sku, title: x.title, price: x.price, url: x.url }))
        };
      }
    });
  }
</script>

Notes:

  • Good descriptions tell the agent when to use the tool, preconditions, and side effects (or lack of them).
  • Keep results small and JSON‑serialisable. Avoid raw HTML.

Tool 2: add_to_cart with guardrails

This modifies state, so require minimal but explicit user confirmation in the page.

<script type="module">
  const mc = getModelContext();
  if (mc) {
    mc.registerTool({
      name: "add_to_cart",
      description: "Add a product to cart by sku and quantity. Use only after selecting a product. Side effect: updates the user's cart.",
      inputSchema: {
        type: "object",
        additionalProperties: false,
        properties: {
          sku: { type: "string", minLength: 1 },
          quantity: { type: "integer", minimum: 1, default: 1 }
        },
        required: ["sku"]
      },
      execute: async ({ sku, quantity = 1 }) => {
        // Require explicit user confirmation for the side effect.
        const ok = window.confirm(`Add ${quantity} of ${sku} to your cart?`);
        if (!ok) return { status: "cancelled", reason: "user_declined" };
        const res = await fetch('/api/cart', {
          method: 'POST', headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ sku, quantity })
        });
        if (!res.ok) throw new Error("add_to_cart_failed");
        const cart = await res.json(); // Expect {lines:[{sku,qty}], total}
        return { status: "added", sku, quantity, line_count: cart.lines.length, total: cart.total };
      }
    });
  }
</script>

For production UX, replace window.confirm with your site’s modal that requires an explicit click. The point is the same: sensitive actions should not occur silently on the user’s behalf.

Tool 3: start_checkout with explicit intent

Checkout must be opt‑in. Keep the result small—describe what happened rather than returning a whole DOM snapshot.

<script type="module">
  const mc = getModelContext();
  if (mc) {
    mc.registerTool({
      name: "start_checkout",
      description: "Begin checkout. Use only if the user has a non‑empty cart and has confirmed they want to checkout. Side effect: navigates to checkout.",
      inputSchema: { type: "object", properties: {}, additionalProperties: false },
      execute: async () => {
        const ok = window.confirm("Proceed to checkout now?");
        if (!ok) return { status: "cancelled", reason: "user_declined" };
        // Navigate or call your session API; keep return JSON small.
        window.location.assign('/checkout');
        return { status: "started", next: "/checkout" };
      }
    });
  }
</script>

How to run the demo today

You have a few practical options right now. The ecosystem is moving; verify current support on the ecosystem status page.

  • ChatGPT desktop app: “site tools” are on by default and, in our testing, call page tools. Open your site in the desktop app, then ask it to “search for running shoes and add two pairs to cart.”
  • Stagehand (TypeScript, launches a browser): Works today and will call tools exposed in the page it drives.
  • Vercel agent‑browser: On by default in the Chrome it launches; calls page tools.
  • Browser Use (hosted): Announced support; works on their hosted agents.

Some agents currently do not call WebMCP tools (e.g. Claude for Chrome). Chrome and Edge have origin trials for the browser‑side plumbing, which helps frameworks that launch a browser. Safari and Firefox do not support the API today. Details change; the public tracker is the source of truth.

If you prefer a structured walk‑through on setup, see the step‑by‑step WebMCP tutorial on setup, tools, and deployment. If you’re building for Chrome specifically, read WebMCP in Chrome: what works today and how to build for it.

Why tools beat DOM clicks for this demo

Agents that call tools get semantics and guardrails, not pixels. Here’s a quick side‑by‑side:

Task Without WebMCP With WebMCP
Discover search Guess input selector, type, press Enter Call search_products with query
Handle cart Click a moving “Add” button Call add_to_cart with sku, quantity
Confirm checkout Risk silent checkout or brittle flows Ask user, call start_checkout
Portability Breaks on tiny UI changes Contracts stay stable; UI can change underneath

For a deeper comparison, see WebMCP vs Traditional APIs: Choosing the Right Interface for AI Agents. Short version: APIs are great for backend integrations; WebMCP is great for an agent acting in the user’s session with the site you already shipped.

Demo flow at a glance

The sequence below shows how an agent discovers and calls tools in‑page.

Agent discovers and calls page tools via WebMCP

Writing tool descriptions agents actually use

Agents pick tools by description. Treat it like a tight function docstring:

  • When to use it: “Use to find products before adding to cart.”
  • Preconditions: “Only after selecting a product.” “Only if cart is non‑empty.”
  • Side effects: “Updates the user’s cart.” “Navigates to checkout.”

Avoid fluff. State exactly what the tool does and does not do.

Returning results the agent can act on

  • Prefer lists of small objects with stable keys (sku, title, price, url).
  • Avoid raw HTML, innerText dumps, or whole cart payloads.
  • Include minimal context (query, count) when it helps planning.
  • Throw structured errors (Error(“add_to_cart_failed”)) rather than swallowing them; agents can branch on that.

Extending the demo safely

Once the core works, add more tools along the same lines:

  • get_cart_summary: read‑only breakdown of lines and totals.
  • apply_discount_code: requires explicit confirmation, returns whether it applied and the new total.
  • update_shipping_address: strongly guarded; confirmation in the page.

If you need a checklist for coverage, use the WebMCP form coverage checklist. It keeps you honest about edge cases and sensitive actions.

What breaks in production

A demo is easy; production is not. The common failure modes we see:

  • Missing feature detection: Only using document.modelContext or only navigator.modelContext. Use both.
  • Vague tool descriptions: Agents mis‑pick tools if you skip preconditions or side effects.
  • Oversized results: Returning full HTML, images, or large nested JSON makes agents slow and error‑prone.
  • Silent side effects: Adding to cart or starting checkout without an explicit user confirmation is risky.
  • UI drift: You bind execute to brittle selectors. When the UI changes, the tool silently fails or does the wrong thing.
  • Network coupling: Tools assume specific response shapes; errors bubble as unhelpful strings.
  • Missing observability: You cannot tell which agent called what or why a tool failed.

If you want help with the production side:

  • Agent Layer detects drift when UI changes break a tool and suggests updated selectors.
  • Built‑in guardrails help you require confirmations for sensitive actions.
  • Analytics show which agents called which tools, with success or error and duration.
  • Test scenarios let you write “intent → expected tool” checks and run them before deploy.

None of this is required for the demo, but it pays for itself once other teams touch the UI.

Troubleshooting the demo

  • The agent does not list tools: Verify getModelContext returns a non‑null object in the page. Some agents only support it in specific browsers or environments—check the support tracker.
  • Tools never get called: Tighten descriptions and schemas. If multiple tools overlap (“search”, “lookup”, “find”), agents may choose poorly.
  • Calls succeed but nothing changes: Confirm your execute logic hits real endpoints or UI bindings and that CORS, cookies, and sessions work in the environment the agent launches.
  • Checkout navigations ignored: Some agents block window.location changes; return a JSON directive like {status:“started”, next:“/checkout”} and let the agent navigate.

Security and guardrails recap

  • Never pass secrets, API keys, or payment tokens through tool parameters.
  • Always require explicit user confirmation in the page for payments, checkout, account changes, or destructive actions.
  • Keep side effects narrow and described in the tool’s description.

For a broader primer on hardening agentic interfaces, read our guidance on preventing prompt injection in agentic web interfaces.

Where this fits with your stack

  • If you already expose a public API: keep it. Agents acting as users in the browser still benefit from WebMCP because they inherit auth, pricing, locale, and A/B context from the current session.
  • If you run on Shopify or WooCommerce: you can ship these exact patterns with platform‑specific bindings. See the practical guides for WebMCP on Shopify and WebMCP for WooCommerce.

A quick architecture view

The following flowchart shows where WebMCP sits relative to your app and an agent.

Beyond the demo

Once tools are useful, you’ll want versioning, observability, and resilience to UI changes:

  • Versioned configs per page template: ship changes safely and roll back.
  • Health checks: flag when a selector or flow drifts.
  • Analytics and test scenarios: see which agents succeed where, and catch regressions.

That’s the gap Agent Layer fills while staying close to the WebMCP standard. You can keep the simple tool code in your site while delegating maintenance heavy‑lifting to the platform.

Common pitfalls checklist

  • You forgot the navigator.modelContext fallback.
  • Tool descriptions don’t mention preconditions or side effects.
  • You return raw HTML or huge payloads.
  • You perform checkout without explicit user confirmation.
  • You hard‑coded DOM selectors inside execute that drift.
  • No way to see which agents called what.

Try it

Ship the three tools above on a staging page and run them with a supporting agent from the ecosystem list. If you want a quick sanity check across machine interface, semantics, executability, and access, run the free agent‑readiness audit at https://agentlayer.tech/. It flags missing tools, invalid JSON‑LD, unlabeled inputs, and more, and suggests concrete fixes.

Get started

Don't Be Invisible to Tomorrow's Buyers.

Make your platform executable for every AI assistant your customers already use. No engineering, no lock-in, no breakage.