JIMDRURY.
Jim Drury
Bolted get_addresses / get_availability / create_basket plate on an engineering desk with a WebMCP tag
ai

WebMCP Solves MCP’s Discovery Problem (If You’re Already on the Site)

SEP 19, 20268 MIN READ

Confession

Classic MCP is powerful. Install is the burden.

WebMCP: tools on the page you already opened

mcp-handler will bridge the route you already have

Illustrative pattern (not a real VMO2 API)

app/api/mcp/route.ts (illustrative)
// ILLUSTRATIVE ONLY: pattern from mcp-handler docs, not a real VMO2 API
import { createMcpHandler } from "mcp-handler";
import { z } from "zod";

const handler = createMcpHandler(
  (server) => {
    server.registerTool(
      "get_addresses",
      {
        title: "Get addresses",
        description:
          "Given a UK postcode, return the official addresses and address ids for that postcode. Call this before availability.",
        inputSchema: z.object({
          postcode: z.string().min(2),
        }),
      },
      async ({ postcode }) => {
        const result = await lookupAddresses({ postcode });
        return {
          content: [{ type: "text", text: JSON.stringify(result) }],
        };
      },
    );

    server.registerTool(
      "get_availability",
      {
        title: "Get availability",
        description:
          "Given an official address id, return the products available at that premise. Call this instead of scraping a catalogue or a comparison site.",
        inputSchema: z.object({
          addressId: z.string().min(1),
        }),
      },
      async ({ addressId }) => {
        const result = await lookupAvailability({ addressId });
        return {
          content: [{ type: "text", text: JSON.stringify(result) }],
        };
      },
    );

    server.registerTool(
      "create_basket",
      {
        title: "Create basket",
        description:
          "Create a basket for a chosen product at an address id, so the human can be handed into checkout.",
        inputSchema: z.object({
          addressId: z.string().min(1),
          productId: z.string().min(1),
        }),
      },
      async ({ addressId, productId }) => {
        const result = await createBasket({ addressId, productId });
        return {
          content: [{ type: "text", text: JSON.stringify(result) }],
        };
      },
    );
  },
  {
    experimental_webMcp: {
      tools: ["get_addresses", "get_availability", "create_basket"],
    },
  },
);

export { handler as GET, handler as POST };
On the availability journey page
<script src="/api/mcp?webmcp-script" async></script>

Why this matters for GEO-gated products


Thoughts

Similar Articles