Step-by-step walkthrough of an MCP server for WooCommerce: tool definitions, Zod validation, schema.org alignment, and Cloudflare Workers deployment.
EN

Building an MCP server for WooCommerce: a practitioner's guide

4.70 /5 - (11 votes )
Last verified: May 1, 2026
8min read
Guide
500+ WP projects
AI integration

#Building an MCP server for WooCommerce: a practitioner’s guide

Model Context Protocol gives an AI agent a typed, introspectable surface to act on a store. WooCommerce already exposes a REST API under /wp-json/wc/v3/. Putting MCP in front turns that REST surface into something an LLM can call without guessing parameter names. This is the architecture I ship for clients who want their catalogue and order intent reachable from Claude, ChatGPT, or a custom agent runtime.

This article anchors to the MCP server development service and the Universal Commerce Protocol pillar.

#TL;DR

  • MCP sits in front of WooCommerce REST as a typed JSON-RPC surface.
  • Three tools cover most agent needs: catalogue.list, product.detail, order.intent.
  • Zod schemas define every input and output and run on every call.
  • Map WooCommerce fields to schema.org Product and Offer so agent answers stay consistent across surfaces.
  • Cloudflare Workers is the deployment target I default to for read-heavy MCP servers.

#What MCP actually is

Anthropic announced Model Context Protocol on 25 November 2024 (anthropic.com/news/model-context-protocol). It is an open protocol over JSON-RPC 2.0 that lets a client (an LLM host like Claude Desktop, an IDE, or a custom agent) talk to a server (your MCP implementation) over stdio or HTTP with Server-Sent Events.

The three primitives are:

  • Tools. Callable functions with typed inputs and outputs. The agent picks one and calls it.
  • Resources. Read-only references to documents or records the agent can pull into context.
  • Prompts. Server-supplied prompt templates the host can invoke.

For a WooCommerce store, tools carry most of the weight. Catalogue browsing is a tool call. Product detail is a tool call. Proposing an order is a tool call. Resources help when the agent needs the full text of a returns policy or a long product description; prompts help when you want to ship preset interactions (“recommend three products for this customer profile”).

#The intent inventory comes first

Before any code, I write down the intents an agent should be able to express against the store. For a typical WooCommerce build the list is short:

  1. Browse the catalogue with filters (catalogue.list).
  2. Fetch the full detail of one product (product.detail).
  3. Propose an order on behalf of a customer, returning a draft for human confirmation (order.intent).
  4. Look up the status of an existing order by number and email (order.status).
  5. Query stock for an SKU (inventory.check).

Each intent maps to exactly one tool. Resist the temptation to expose wc.products.get, wc.products.list, wc.orders.create as a one-to-one wrap of the REST surface. The agent does not benefit from REST verbs; it benefits from intent verbs.

#Tool definitions with Zod

The TypeScript SDK at @modelcontextprotocol/sdk uses Zod (zod.dev) for input and output schemas. Here is the contract for catalogue.list:

import { z } from "zod";

export const catalogueListInput = z.object({
  query: z.string().min(2).max(200).optional(),
  category: z.string().optional(),
  in_stock: z.boolean().optional(),
  price_min: z.number().nonnegative().optional(),
  price_max: z.number().nonnegative().optional(),
  page: z.number().int().min(1).max(100).default(1),
  per_page: z.number().int().min(1).max(50).default(12),
});

export const catalogueListOutput = z.object({
  results: z.array(
    z.object({
      sku: z.string(),
      name: z.string(),
      url: z.string().url(),
      price: z.object({
        amount: z.number().nonnegative(),
        currency: z.string().length(3),
      }),
      availability: z.enum(["InStock", "OutOfStock", "PreOrder"]),
      image: z.string().url().optional(),
    })
  ),
  total: z.number().int().nonnegative(),
  page: z.number().int(),
});

Two things matter here. First, the output uses schema.org vocabulary (InStock, OutOfStock, PreOrder are direct values from ItemAvailability). Second, the input rejects garbage at the protocol boundary; the handler never sees a query: "" or a negative price_min.

I declare every tool the same way: input schema, output schema, handler. The handler is a thin adapter over /wp-json/wc/v3/. Zod runs twice per call: once on input, once on output. The output check catches handler bugs that would otherwise leak malformed data to the agent.

#Bridging to the WooCommerce REST API

The handler for catalogue.list reads from /wp-json/wc/v3/products:

async function handleCatalogueList(input: z.infer<typeof catalogueListInput>) {
  const params = new URLSearchParams();
  if (input.query) params.set("search", input.query);
  if (input.category) params.set("category", input.category);
  if (input.in_stock) params.set("stock_status", "instock");
  if (input.price_min !== undefined) params.set("min_price", String(input.price_min));
  if (input.price_max !== undefined) params.set("max_price", String(input.price_max));
  params.set("page", String(input.page));
  params.set("per_page", String(input.per_page));

  const response = await fetch(
    `${WC_BASE}/wp-json/wc/v3/products?${params.toString()}`,
    { headers: { Authorization: `Basic ${WC_AUTH}` } }
  );

  const products = await response.json();
  const total = Number(response.headers.get("X-WP-Total") ?? 0);

  return catalogueListOutput.parse({
    results: products.map(mapWooProductToSchemaOrg),
    total,
    page: input.page,
  });
}

The mapWooProductToSchemaOrg helper converts WooCommerce field names (stock_status, regular_price, permalink) to the schema.org-aligned shape declared in the output schema. Field mapping in one place, called from one tool, called from one handler. Drift between WooCommerce upgrades and the agent contract gets caught by the output parse call, not in production.

For order.intent, I never call WooCommerce write endpoints from inside the handler. The tool returns a draft order object that the host displays to the user, the user confirms, and only then a separate authenticated path calls POST /wp-json/wc/v3/orders. Writing on speculation from an LLM is the wrong default.

#schema.org alignment is load-bearing

Two reasons to align tool outputs with schema.org vocabulary:

Agents reuse the same words across sources. When an agent answers “is this product in stock?” it stitches a Product entity from your MCP server, an Offer with availability, and possibly a Review. If your MCP server returns { "stock": "yes" } and the agent’s other source returns availability: "https://schema.org/InStock", the agent has to reconcile two vocabularies. With schema.org alignment on your end, it does not.

Your storefront’s structured data already speaks schema.org. Your /wp-content/themes/<theme>/single-product.php (or the headless equivalent) emits Product JSON-LD. Keeping the MCP output in the same shape means the same product page renders the same data to a Google crawler, an OpenAI crawler via the URL, and an agent calling your MCP tool directly.

The mapping for a typical WooCommerce product looks like this:

WooCommerce fieldschema.org field
skusku
namename
permalinkurl
regular_price + currencyoffers.price + priceCurrency
stock_status: "instock"availability: InStock
stock_status: "outofstock"availability: OutOfStock
images[0].srcimage
descriptiondescription

Variations need an extra layer (hasVariant array of Product), but the base shape is the same.

#Idempotency and side effects

Read tools (catalogue.list, product.detail, inventory.check, order.status) are naturally idempotent. Calling them three times yields the same answer.

order.intent is the trap. The agent can call it twice if the network glitches between the response and the host. The contract I ship:

  • order.intent accepts an optional idempotency_key (a UUID supplied by the host).
  • The handler stores the key in Workers KV with a 24-hour TTL alongside the resulting draft order ID.
  • A repeat call with the same key returns the same draft instead of creating a new one.

This pattern matches how Stripe handles idempotency on POST /v1/charges. It is well understood; agents and humans both benefit.

#Deploying to Cloudflare Workers

The MCP TypeScript SDK exports a Server class plus transport adapters. For Workers, I use the streamable HTTP transport. The Worker entry looks like this:

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    if (request.method !== "POST") {
      return new Response("Method not allowed", { status: 405 });
    }

    const auth = request.headers.get("Authorization");
    if (!isValidAgentToken(auth, env)) {
      return new Response("Unauthorized", { status: 401 });
    }

    const server = createWooCommerceMcpServer(env);
    const transport = new StreamableHTTPServerTransport(request);
    return server.connect(transport);
  },
};

isValidAgentToken checks against scoped tokens stored in Workers KV. Read-only tools go through with a less-privileged token; order.intent requires a token scoped to the orders:write capability. The auth strategy is one of the two patterns covered in the MCP authentication patterns guide.

wrangler.toml declares the WordPress origin URL, the WooCommerce consumer key and secret (as Worker secrets, never in code), and the KV namespace for idempotency. Deploy is wrangler deploy. The Worker is small (well under the 1 MB compressed bundle limit) because it is just JSON-RPC routing plus REST adapters.

#Observability is non-optional

Every tool call gets logged with:

  • The tool name.
  • A SHA-256 hash of the input (not the input itself; PII risk).
  • The latency.
  • Whether output validation passed or failed.
  • The agent token scope.

The logs go to Cloudflare Logpush into a long-term store. Two operational habits this enables:

Schema tightening. Six weeks after launch I review the validation failures. Every output failure is a handler bug or a missed WooCommerce field. Every input failure is either a malformed agent or a schema that is too strict. Both get fixed in the next release.

Cost attribution. Tool calls are billable load on the WooCommerce origin. The log tells me which agent token is generating the load and which tool. A single misbehaving agent that loops on catalogue.list is one log query away from being identified.

#Where this fits in the cluster

This article is the implementation walkthrough. For the typed-tool deep dive see writing typed catalogue tools with Zod for MCP. For the auth patterns see MCP authentication patterns: OAuth, tokens, and when to use each. For the protocol-level decision between MCP and REST see MCP vs REST: when each wins for AI agent integration. For the migration path from an existing API see migrating an existing WordPress API to MCP: a 4-week playbook. The pillar is MCP server development and the broader strategy sits under the Universal Commerce Protocol.

Pricing is individual because the scope depends on how many tools you need, the complexity of the WooCommerce extensions involved, and the agent runtimes you intend to support.

Next step

Turn the article into an actual implementation

This block strengthens internal linking and gives readers the most relevant next move instead of leaving them at a dead end.

Want this implemented on your site?

If visibility in Google and AI systems matters, I can build the content architecture, FAQ, schema, and internal linking needed for SEO, GEO, and AEO.

Related cluster

Explore other WordPress services and knowledge base

Strengthen your business with professional technical support in key areas of the WordPress ecosystem.

Article FAQ

Frequently Asked Questions

Practical answers to apply the topic in real execution.

SEO-ready GEO-ready AEO-ready 5 Q&A
What is MCP and why use it for WooCommerce?
Model Context Protocol is a JSON-RPC based protocol introduced by Anthropic on 25 November 2024 for connecting AI agents to data and tools. Used in front of WooCommerce, an MCP server gives an agent typed actions like catalogue.list and order.intent instead of forcing it to guess which /wp-json/wc/v3/ endpoint to call.
Does MCP replace the WooCommerce REST API?
No. MCP sits in front of the REST API. The tool handlers call /wp-json/wc/v3/ under the hood. REST stays the system of record. MCP is a typed surface that an LLM agent can introspect and call without bespoke prompt engineering.
Where do tool definitions live?
In code, alongside the server. Each tool has a name, a Zod input schema, a Zod output schema, and a handler. The MCP SDK exposes tools/list to clients so the agent can discover what is callable at runtime.
Why Cloudflare Workers rather than a Node server?
Workers gives global edge presence, a small attack surface, and a billing model that matches sporadic agent traffic. The MCP TypeScript SDK runs on the Web Platform APIs Workers exposes; no Node-only modules required.
How does the agent authenticate to the MCP server?
For read-only tools, an unauthenticated transport plus IP allow lists is often enough. For order.intent and any mutating tool, a scoped API token issued by your store and verified on every request. The full pattern is in the MCP authentication patterns guide.

Need an FAQ tailored to your industry and market? We can build one aligned with your business goals.

Let’s discuss

Related Articles

How to design Zod schemas for MCP tool inputs and outputs, including idempotency keys, error response shapes, and the typed agent contracts that prevent silent drift between the MCP server and a WooCommerce origin.
wordpress

Writing typed catalogue tools with Zod for MCP

How to design Zod schemas for MCP tool inputs and outputs, including idempotency keys, error response shapes, and the typed agent contracts that prevent silent drift between the MCP server and a WooCommerce origin.

A decision guide for picking between Model Context Protocol and a REST API when the consumer is an AI agent. Typed surface vs JSON shape inference, mutating actions, authentication, and the hybrid pattern that often beats both.
wordpress

MCP vs REST: when each wins for AI agent integration

A decision guide for picking between Model Context Protocol and a REST API when the consumer is an AI agent. Typed surface vs JSON shape inference, mutating actions, authentication, and the hybrid pattern that often beats both.

A four-week migration playbook for putting a Model Context Protocol server in front of an existing WordPress REST API. Endpoint audit, MCP scaffold, parallel-run, cutover, and the observability that makes the move safe.
wordpress

Migrating an existing WordPress API to MCP: a 4-week playbook

A four-week migration playbook for putting a Model Context Protocol server in front of an existing WordPress REST API. Endpoint audit, MCP scaffold, parallel-run, cutover, and the observability that makes the move safe.