Crear un servidor MCP para WooCommerce: guía práctica
Model Context Protocol da a un agente de IA una superficie tipada e introspeccionable para actuar sobre la tienda. WooCommerce ya ofrece una REST API en /wp-json/wc/v3/. Poner MCP delante convierte esa superficie en algo que un LLM puede llamar sin adivinar nombres de parámetros. Esta es la arquitectura que implemento para clientes que quieren el catálogo y la intención de pedido accesibles desde Claude, ChatGPT o su propio runtime de agentes.
El artículo forma parte del servicio de desarrollo de servidores MCP y del pilar Universal Commerce Protocol.
TL;DR
- MCP se sitúa delante del REST de WooCommerce como una superficie JSON-RPC tipada.
- Tres herramientas cubren la mayor parte de lo que necesita el agente:
catalogue.list,product.detail,order.intent. - Los esquemas Zod definen cada entrada y salida y se ejecutan en cada llamada.
- Mapea los campos de WooCommerce a
schema.orgProduct y Offer para que las respuestas del agente sean coherentes entre superficies. - Cloudflare Workers es mi destino de despliegue por defecto para servidores MCP dominados por lecturas.
Qué es realmente MCP
Anthropic anunció Model Context Protocol el 25 de noviembre de 2024 (anthropic.com/news/model-context-protocol). Es un protocolo abierto basado en JSON-RPC 2.0 que permite a un cliente (un host LLM como Claude Desktop, un IDE o tu propio runtime de agentes) hablar con un servidor (tu implementación MCP) por stdio o HTTP con Server-Sent Events.
Tres primitivas:
- Tools. Funciones invocables con entrada y salida tipadas. El agente elige una y la llama.
- Resources. Referencias legibles a documentos o registros que el agente puede traer al contexto.
- Prompts. Plantillas de prompt que proporciona el servidor y que el host puede invocar.
En una tienda WooCommerce, las tools cargan con casi todo el trabajo. Recorrer el catálogo es una llamada a una herramienta. Los detalles de un producto son una llamada a una herramienta. Proponer un pedido es una llamada a una herramienta. Los resources sirven cuando el agente necesita el texto completo de la política de devoluciones o una descripción larga de producto; los prompts sirven cuando quieres ofrecer interacciones listas (“propón tres productos para este perfil de cliente”).
Qué herramientas MCP necesita una tienda WooCommerce
Antes de escribir una línea de código, anoto las intenciones que el agente debe poder expresar frente a la tienda. Para una tienda WooCommerce típica, la lista es corta:
- Recorrer el catálogo con filtros (
catalogue.list). - Detalles completos de un producto (
product.detail). - Proponer un pedido en nombre del cliente, devuelto como borrador para que lo confirme una persona (
order.intent). - Consultar el estado de un pedido existente por número y dirección de correo electrónico (
order.status). - Consultar el stock de un SKU (
inventory.check).
Cada intención corresponde exactamente a una herramienta. Resiste la tentación de exponer wc.products.get, wc.products.list, wc.orders.create como un wrapper uno a uno del REST. Al agente no le aportan nada los verbos REST; le aportan los verbos de intención.
Cómo definir herramientas MCP en Zod
El SDK de TypeScript en @modelcontextprotocol/sdk usa Zod (zod.dev) para los esquemas de entrada y salida. Así es el contrato de 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(),
});Aquí importan dos cosas. Primero, la salida usa el vocabulario de schema.org (InStock, OutOfStock, PreOrder son valores tomados directamente de ItemAvailability). Segundo, la entrada rechaza la basura en la frontera del protocolo; el handler nunca verá query: "" ni un price_min negativo.
Declaro todas las herramientas con el mismo patrón: esquema de entrada, esquema de salida, handler. El handler es un adaptador fino sobre /wp-json/wc/v3/. Zod se ejecuta dos veces por llamada: una en la entrada y otra en la salida. La comprobación de la salida detecta errores del handler que, si no, llegarían al agente como datos mal formados.
Cómo conectar el servidor MCP con la REST API de WooCommerce
El handler de catalogue.list lee de /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,
});
}El helper mapWooProductToSchemaOrg traduce los nombres de campo de WooCommerce (stock_status, regular_price, permalink) a la forma compatible con schema.org de la declaración de salida. El mapeo de campos vive en un solo sitio, llamado desde una herramienta, llamada desde un handler. Las desviaciones entre las actualizaciones de WooCommerce y el contrato del agente las detecta la llamada parse en la salida, no producción.
En order.intent nunca llamo desde el handler a los endpoints de escritura de WooCommerce. La herramienta devuelve un borrador de pedido que el host muestra al usuario, el usuario lo confirma y solo entonces una ruta separada y autenticada llama a POST /wp-json/wc/v3/orders. Escribir a ciegas a partir de un LLM es un mal comportamiento por defecto. Además encaja con el art. 25 del RGPD (protección de datos desde el diseño); el borrador permite ajustar el botón de compra y los consentimientos de marketing antes de ejecutar la operación.
Cómo mapear campos de WooCommerce a schema.org
Dos razones para mantener las salidas de las herramientas en el vocabulario de schema.org:
Los agentes usan las mismas palabras entre fuentes. Cuando un agente responde a “¿está disponible este producto?”, combina una entidad Product de tu servidor MCP, una Offer con availability y quizá una Review. Si tu servidor MCP devuelve { "stock": "sí" } y otra fuente availability: "https://schema.org/InStock", el agente tiene que conciliar dos vocabularios. Con la alineación a schema.org de tu lado, no hace falta.
Los datos estructurados de tu tienda ya hablan schema.org. Tu /wp-content/themes/<theme>/single-product.php (o su equivalente headless) emite JSON-LD de tipo Product. Mantener la salida de MCP con la misma forma hace que la misma página de producto sirva los mismos datos al rastreador de Google, al rastreador de OpenAI por URL y al agente que llama directamente a tu herramienta MCP.
El mapeo de un producto WooCommerce típico queda así:
| Campo de WooCommerce | Campo de schema.org |
|---|---|
sku | sku |
name | name |
permalink | url |
regular_price + currency | offers.price + priceCurrency |
stock_status: "instock" | availability: InStock |
stock_status: "outofstock" | availability: OutOfStock |
images[0].src | image |
description | description |
Las variaciones requieren una capa adicional (una lista hasVariant de tipo Product), pero la forma básica es la misma.
Cómo hacer idempotentes los pedidos en el servidor MCP
Las herramientas de lectura (catalogue.list, product.detail, inventory.check, order.status) son idempotentes por naturaleza. Tres llamadas devuelven la misma respuesta.
order.intent es la trampa. El agente puede llamar dos veces si la red entre la respuesta y el host falla un instante. El contrato que implemento:
order.intentacepta unaidempotency_keyopcional (UUID proporcionado por el host).- El handler guarda la clave en Workers KV con un TTL de 24 horas junto al ID del borrador de pedido creado.
- Una nueva llamada con la misma clave devuelve el mismo borrador en lugar de crear otro.
El patrón corresponde a la idempotencia de Stripe en POST /v1/charges. Es bien conocido; agentes y personas se benefician por igual.
Cómo desplegar el servidor MCP en Cloudflare Workers
El SDK de TypeScript para MCP exporta la clase Server y adaptadores de transporte. En Workers uso el transporte HTTP en streaming. La entrada del Worker es esta:
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 comprueba tokens con alcance limitado en Workers KV. Las herramientas de solo lectura pasan con un token de permisos más bajos; order.intent exige un token con el alcance orders:write. La estrategia de autenticación es uno de los dos patrones de autenticación MCP.
wrangler.toml declara la URL de origen de WordPress, la clave de consumidor y el secreto de WooCommerce (como secretos del Worker, nunca en el código) y un namespace de KV para la idempotencia. El despliegue se hace con wrangler deploy. El Worker sigue siendo pequeño (muy por debajo del límite de 1 MB de bundle comprimido), porque solo es enrutamiento JSON-RPC más adaptadores REST.
Cómo registrar y monitorizar las llamadas a herramientas MCP
Registro cada llamada a una herramienta con:
- El nombre de la herramienta.
- Un hash SHA-256 de la entrada (no la entrada en sí; riesgo de datos personales según el art. 32 del RGPD).
- La latencia.
- Si la validación de la salida pasó o no.
- El alcance del token del agente.
Los registros van por Cloudflare Logpush a un almacenamiento a largo plazo. Dos hábitos operativos que esto hace posibles:
Ajustar los esquemas. Seis semanas después del lanzamiento reviso los fallos de validación. Cada fallo de salida es un bug del handler o un campo de WooCommerce sin mapear. Cada fallo de entrada es o un agente defectuoso o un esquema demasiado restrictivo. Ambos entran en la siguiente versión.
Atribución de costes. Las llamadas a herramientas son carga medible sobre el origen de WooCommerce. El registro muestra qué token de agente genera la carga y con qué herramienta. Un único agente que se porta mal y entra en bucle con catalogue.list está a una consulta al registro de quedar identificado.
Otras guías sobre MCP y WooCommerce
Este artículo muestra el recorrido de implementación. Una arquitectura tolerante a fallos para la sincronización con ERP, colas Redis y transacciones de base de datos se describe en nuestra guía de arquitectura de integración de WooCommerce con ERP 2026. La página del servicio es desarrollo de servidores MCP. La estrategia más amplia está en Universal Commerce Protocol.
El presupuesto es individual, porque el alcance depende del número de herramientas que necesites, de la complejidad de las extensiones de WooCommerce y de los runtimes de agentes que quieras soportar.







