Construir um servidor MCP para WooCommerce: guia prático
O Model Context Protocol dá a um agente de IA uma superfície tipada e introspetável para agir sobre a loja. O WooCommerce já disponibiliza uma REST API em /wp-json/wc/v3/. Colocar o MCP à frente dela transforma essa superfície em algo que um LLM consegue chamar sem adivinhar nomes de parâmetros. É esta a arquitetura que implemento para clientes que querem o catálogo e a intenção de encomenda acessíveis a partir do Claude, do ChatGPT ou do seu próprio runtime de agentes.
O artigo integra-se no serviço de desenvolvimento de servidores MCP e no pilar Universal Commerce Protocol.
TL;DR
- O MCP fica à frente do REST do WooCommerce como uma superfície JSON-RPC tipada.
- Três ferramentas cobrem a maior parte das necessidades do agente:
catalogue.list,product.detail,order.intent. - Os esquemas Zod definem cada entrada e saída e correm em cada chamada.
- Mapeie os campos do WooCommerce para
schema.orgProduct e Offer, para que as respostas do agente sejam coerentes entre superfícies. - O Cloudflare Workers é o meu destino de implementação por defeito para servidores MCP dominados por leituras.
O que é, afinal, o MCP
A Anthropic anunciou o Model Context Protocol a 25 de novembro de 2024 (anthropic.com/news/model-context-protocol). É um protocolo aberto baseado em JSON-RPC 2.0 que permite a um cliente (um host LLM como o Claude Desktop, um IDE ou o seu próprio runtime de agentes) falar com um servidor (a sua implementação MCP) por stdio ou por Streamable HTTP, o transporte HTTP que desde a versão 2025-03-26 da especificação substitui o antigo HTTP com Server-Sent Events.
Três primitivas:
- Tools. Funções invocáveis com entrada e saída tipadas. O agente escolhe uma e chama-a.
- Resources. Referências legíveis a documentos ou registos que o agente pode trazer para o contexto.
- Prompts. Modelos de prompt fornecidos pelo servidor, que o host pode invocar.
Numa loja WooCommerce, são as tools que carregam o grosso do trabalho. Percorrer o catálogo é uma chamada de ferramenta. Os detalhes de um produto são uma chamada de ferramenta. Propor uma encomenda é uma chamada de ferramenta. Os resources são úteis quando o agente precisa do texto integral da política de devoluções ou de uma descrição longa de produto; os prompts são úteis quando quer fornecer interações prontas (“propõe três produtos para este perfil de cliente”).
O que muda a especificação MCP 2026-07-28 num servidor para WooCommerce
A revisão em vigor da especificação é a de 28 de julho de 2026 (registo de alterações). Para um servidor que fica à frente de uma loja, contam quatro alterações.
O protocolo deixa de ter sessões. Desaparece o aperto de mão initialize e, no transporte Streamable HTTP, também o cabeçalho Mcp-Session-Id. Cada pedido leva a sua versão de protocolo e as capacidades do cliente em _meta. Um Worker que constrói um servidor novo para cada pedido encaixa neste modelo, porque não há estado de ligação a guardar entre chamadas.
O estado viaja como argumento. Se um servidor precisa de estado entre chamadas, a especificação recomenda devolver um identificador explícito a partir de uma ferramenta de criação e aceitá-lo como argumento nas chamadas seguintes (ferramentas com estado). O exemplo da própria especificação é um cesto de compras com basket_id. O rascunho devolvido por order.intent segue esse padrão: o ID do rascunho é o handle. A mesma secção pede que o servidor verifique a autorização de quem chama contra o handle em cada chamada, que indique a validade na descrição da ferramenta (aqui, 24 horas) e que devolva um erro de execução quando chega um handle expirado ou desconhecido, para que o modelo crie um novo.
O servidor tem de implementar server/discover, o método com que anuncia as versões de protocolo que suporta, as suas capacidades e a sua identidade.
As listas podem ser guardadas em cache. Os resultados de tools/list trazem agora ttlMs e cacheScope, e o servidor deve devolver as ferramentas por uma ordem determinística. Cinco ferramentas que só mudam a cada implementação aguentam um ttlMs longo.
O transporte HTTP+SSE está obsoleto desde a versão 2025-03-26 e os servidores novos usam Streamable HTTP. A versão 2 do SDK TypeScript serve as duas gerações do protocolo a partir do mesmo ponto de entrada (versões de protocolo na documentação do SDK), por isso um cliente que ainda começa com initialize continua a funcionar.
De que ferramentas MCP precisa uma loja WooCommerce
Antes de escrever uma linha de código, listo as intenções que o agente deve conseguir exprimir perante a loja. Para uma loja WooCommerce típica, a lista é curta:
- Percorrer o catálogo com filtros (
catalogue.list). - Detalhes completos de um produto (
product.detail). - Propor uma encomenda em nome do cliente, devolvida como rascunho para confirmação humana (
order.intent). - Verificar o estado de uma encomenda existente pelo número e pelo endereço de e-mail (
order.status). - Consultar o stock de um SKU (
inventory.check).
Cada intenção corresponde exatamente a uma ferramenta. Resista à tentação de expor wc.products.get, wc.products.list, wc.orders.create como um wrapper um-para-um do REST. O agente não ganha nada com verbos REST; ganha com verbos de intenção.
Como definir ferramentas MCP em Zod
O SDK TypeScript (pacote @modelcontextprotocol/server na versão 2; na versão 1 era @modelcontextprotocol/sdk) usa Zod (zod.dev) para os esquemas de entrada e saída. Este é o contrato para 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", "BackOrder", "PreOrder"]),
image: z.string().url().optional(),
})
),
total: z.number().int().nonnegative(),
page: z.number().int(),
});Há aqui duas coisas importantes. Primeiro, a saída usa o vocabulário schema.org (InStock, OutOfStock, BackOrder, PreOrder são valores tirados diretamente de ItemAvailability). Segundo, a entrada rejeita lixo na fronteira do protocolo; o handler nunca verá query: "" nem um price_min negativo.
Declaro todas as ferramentas com o mesmo padrão: esquema de entrada, esquema de saída, handler. O handler é um adaptador fino sobre /wp-json/wc/v3/. O Zod corre duas vezes por chamada: uma na entrada, outra na saída. A verificação da saída apanha erros do handler que, de outro modo, chegariam ao agente como dados malformados.
Como registar ferramentas MCP com anotações e saída estruturada
Na versão 2 do SDK, o pacote do lado do servidor é @modelcontextprotocol/server e cada ferramenta é registada com registerTool, que recebe o nome, a configuração e o handler (ferramentas na documentação do SDK). Com os esquemas acima, catalogue.list fica assim:
import { McpServer } from "@modelcontextprotocol/server";
export function createWooCommerceMcpServer(env: Env): McpServer {
const server = new McpServer({ name: "woocommerce", version: "1.0.0" });
server.registerTool(
"catalogue.list",
{
title: "Browse the catalogue",
description: "List store products with filters and pagination",
inputSchema: catalogueListInput,
outputSchema: catalogueListOutput,
annotations: { readOnlyHint: true },
},
async (input) => {
const output = await handleCatalogueList(input);
return {
content: [{ type: "text", text: JSON.stringify(output) }],
structuredContent: output,
};
}
);
// product.detail, inventory.check, order.status, order.intent
return server;
}Quando uma ferramenta declara outputSchema, a especificação obriga o servidor a devolver structuredContent conforme esse esquema, e o cliente deve validá-lo (ferramentas na especificação). O SDK faz essa validação antes de o resultado sair do servidor. Por compatibilidade com clientes antigos, a especificação recomenda devolver também o JSON serializado num bloco de texto, que é o content do exemplo.
As anotações descrevem o comportamento da ferramenta. readOnlyHint indica que não altera o seu ambiente. destructiveHint (por omissão true) e idempotentHint (por omissão false) só contam quando readOnlyHint é false. openWorldHint (por omissão true) diz se a ferramenta interage com um mundo aberto de entidades externas. As quatro ferramentas de leitura levam readOnlyHint: true. order.intent leva readOnlyHint: false e destructiveHint: false, porque cria um rascunho e não apaga nada. Deixo idempotentHint em false, porque a idempotency_key é opcional e, sem ela, duas chamadas iguais criam dois rascunhos. Segundo a documentação do SDK, um host pode aprovar automaticamente uma ferramenta só de leitura e pedir confirmação antes de uma destrutiva. Mas a especificação obriga os clientes a tratar as anotações como não fiáveis, a menos que venham de um servidor de confiança, por isso a verificação do âmbito do token fica no servidor.
Os nomes com ponto, como catalogue.list, seguem a recomendação da especificação: entre 1 e 128 caracteres, apenas letras ASCII, dígitos, sublinhado, hífen e ponto. As falhas seguem por dois canais. Uma ferramenta desconhecida ou um pedido malformado é um erro de protocolo e viaja como erro JSON-RPC. Uma falha da API do WooCommerce ou um argumento fora do intervalo é um erro de execução e viaja dentro do resultado, com isError: true e um texto que o modelo pode usar para se corrigir. Um product.detail com um SKU que não existe deve devolver o segundo tipo, com uma mensagem a dizer que o SKU não existe, para que o modelo mude o argumento e tente de novo.
Como ligar o servidor MCP à REST API do WooCommerce
O handler de catalogue.list lê 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,
});
}O helper mapWooProductToSchemaOrg traduz os nomes de campos do WooCommerce (stock_status, regular_price, permalink) para a forma compatível com schema.org da declaração de saída. O mapeamento de campos fica num só sítio, chamado a partir de uma ferramenta, chamada a partir de um handler. Os desvios entre atualizações do WooCommerce e o contrato do agente são apanhados pela chamada parse na saída, não pela produção.
Em order.intent, nunca chamo os endpoints de escrita do WooCommerce a partir do handler. A ferramenta devolve um rascunho de encomenda que o host mostra ao utilizador, o utilizador confirma, e só então um caminho separado e autenticado chama POST /wp-json/wc/v3/orders. Escrever às cegas com base num LLM é um mau padrão. Isto encaixa também no RGPD art. 25 (proteção de dados desde a conceção); o rascunho permite ajustar o botão de compra e os consentimentos de marketing antes de a operação ser executada.
Como criar as chaves da REST API do WooCommerce para o servidor MCP
O servidor MCP fala com o WooCommerce através de um par de chaves da REST API, a Consumer Key e a Consumer Secret. Segundo a documentação oficial (autenticação da REST API), são geradas nas definições avançadas do WooCommerce, no ecrã da REST API (wp-admin/admin.php?page=wc-settings&tab=advanced§ion=keys). Três propriedades dessas chaves condicionam o desenho do servidor.
Cada chave pertence a um utilizador do WordPress, e os pedidos feitos com ela respeitam as funções e permissões desse utilizador. Se o utilizador for apagado, a chave deixa de funcionar e não passa para outro utilizador. Por isso crio um utilizador técnico dedicado ao servidor MCP, em vez de pendurar a chave na conta de uma pessoa que um dia sairá da empresa.
O nível de acesso é leitura, escrita ou leitura e escrita. As ferramentas de leitura usam uma chave só de leitura. A chave com escrita só é conhecida pela rota separada que confirma a encomenda com POST /wp-json/wc/v3/orders.
Sobre HTTPS, a autenticação é HTTP Basic, com a Consumer Key como nome de utilizador e a Consumer Secret como palavra-passe. É esse o Authorization: Basic do handler de catalogue.list.
A paginação também vem da documentação (paginação). Por omissão, a API devolve 10 elementos por página, per_page e page alteram isso, e os cabeçalhos X-WP-Total e X-WP-TotalPages trazem o número total de recursos e de páginas. O handler lê X-WP-Total para preencher total.
Dois pormenores do endpoint de produtos (listar todos os produtos) afetam o esquema. O parâmetro category filtra pelo ID da categoria, não pelo slug, por isso o agente precisa de IDs ou o servidor tem de traduzir o slug antes de chamar. E stock_status aceita instock, outofstock e onbackorder. Para o terceiro valor, o schema.org tem BackOrder, e é por isso que o enum de saída de catalogue.list o inclui. Sem ele, um produto com encomendas em espera (backorder) falharia a validação de saída.
Atualização, : o WooCommerce anunciou a 21 de outubro que a versão 11.3 remove o seu próprio endpoint MCP, /wp-json/woocommerce/mcp, juntamente com o cabeçalho X-MCP-API-Key com que se autenticava através de uma chave da REST API. O servidor deste guia não usa esse endpoint. Chama /wp-json/wc/v3/ com chaves consumer, e esse caminho mantém-se. Se também ligou um cliente MCP ao endpoint integrado, mude-o antes de atualizar: instale o plugin MCP Adapter, crie uma palavra-passe de aplicação para um utilizador que tenha apenas as permissões de que o cliente precisa e aponte o cliente para /wp-json/mcp/mcp-adapter-default-server. O padrão de servidor externo serve também para uma loja em Shopify ou um backend headless, porque só mudam os handlers que consultam a loja.
Atualização, : o WooCommerce 11.3 acrescenta ainda uma API experimental de abilities extensíveis (blog de programadores do WooCommerce, 22 de outubro de 2026). As extensões podem associar campos próprios, filtros de consulta e validadores às abilities de produtos, encomendas e itens de encomenda, para que um agente leia e altere os dados da extensão através das abilities que já usa. A funcionalidade vem desligada por predefinição, ativa-se em WooCommerce > Settings > Advanced > Features, na secção experimental, e o WooCommerce pede que ainda não seja usada em produção. Para um servidor externo como este nada muda. Passa a importar quando dados de extensões, como campos de produto próprios, tiverem de chegar aos agentes através do adaptador.
Como mapear campos do WooCommerce para schema.org
Duas razões para manter as saídas das ferramentas no vocabulário schema.org:
Os agentes usam as mesmas palavras entre fontes. Quando um agente responde a “este produto está disponível?”, junta uma entidade Product do seu servidor MCP, uma Offer com availability e eventualmente uma Review. Se o seu servidor MCP devolver { "stock": "sim" } e outra fonte availability: "https://schema.org/InStock", o agente tem de conciliar dois vocabulários. Com o alinhamento a schema.org do seu lado, não precisa.
Os dados estruturados da sua loja já falam schema.org. O seu /wp-content/themes/<theme>/single-product.php (ou o equivalente headless) emite JSON-LD do tipo Product. Manter a saída do MCP com a mesma forma faz com que a mesma página de produto sirva os mesmos dados ao crawler da Google, ao crawler da OpenAI por URL e ao agente que chama diretamente a sua ferramenta MCP.
O mapeamento de um produto WooCommerce típico é este:
| Campo WooCommerce | Campo schema.org |
|---|---|
sku | sku |
name | name |
permalink | url |
regular_price + currency | offers.price + priceCurrency |
stock_status: "instock" | availability: InStock |
stock_status: "outofstock" | availability: OutOfStock |
stock_status: "onbackorder" | availability: BackOrder |
images[0].src | image |
description | description |
As variações exigem uma camada adicional (uma lista hasVariant do tipo Product), mas a forma base é a mesma.
Como tornar as encomendas idempotentes no servidor MCP
As ferramentas de leitura (catalogue.list, product.detail, inventory.check, order.status) são naturalmente idempotentes. Três chamadas devolvem a mesma resposta.
order.intent é a armadilha. O agente pode chamar duas vezes se a rede entre a resposta e o host falhar por instantes. O contrato que implemento:
order.intentaceita umaidempotency_keyopcional (UUID fornecido pelo host).- O handler guarda a chave no Workers KV com TTL de 24 horas, junto com o ID do rascunho de encomenda criado.
- Uma nova chamada com a mesma chave devolve o mesmo rascunho, em vez de criar um novo.
O padrão corresponde à idempotência da Stripe em POST /v1/charges. É bem conhecido; agentes e pessoas beneficiam por igual.
Como implementar o servidor MCP no Cloudflare Workers
A versão 2 do SDK TypeScript exporta createMcpHandler, que devolve um objeto { fetch } com a forma que o Cloudflare Workers espera como exportação por omissão e serve o transporte Streamable HTTP (runtimes web-standard na documentação do SDK). A entrada do Worker é esta:
import {
createMcpHandler,
hostHeaderValidationResponse,
originValidationResponse,
} from "@modelcontextprotocol/server";
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const rejected =
hostHeaderValidationResponse(request, [env.MCP_HOSTNAME]) ??
originValidationResponse(request, [env.SHOP_HOSTNAME]);
if (rejected) return rejected;
const authInfo = await verifyAgentToken(request, env);
if (!authInfo) {
return new Response("Unauthorized", { status: 401 });
}
const handler = createMcpHandler(() => createWooCommerceMcpServer(env));
return handler.fetch(request, { authInfo });
},
};verifyAgentToken verifica tokens com âmbito limitado no Workers KV e devolve os dados do token, que os handlers leem como ctx.http.authInfo. As ferramentas só de leitura passam com um token de permissões mais baixas; order.intent exige um token com o âmbito orders:write. A estratégia de autenticação é um dos dois padrões descritos no guia de padrões de autenticação MCP.
O wrangler.toml declara o URL de origem do WordPress, a chave de consumidor e o segredo do WooCommerce (como segredos do Worker, nunca no código) e um namespace KV para a idempotência. A implementação faz-se com wrangler deploy. O Worker mantém-se pequeno, porque é apenas encaminhamento JSON-RPC mais adaptadores REST.
Como proteger o endpoint MCP com OAuth e validação de Origin
A autorização é opcional no MCP. Quando uma implementação sobre HTTP a suporta, deve seguir a especificação de autorização (autorização); um servidor stdio, pelo contrário, não a deve seguir e vai buscar as credenciais ao ambiente. Estas são as regras que afetam este Worker.
O servidor MCP funciona como servidor de recursos OAuth 2.1 e tem de publicar os metadados de recurso protegido da RFC 9728, para que o cliente descubra que servidor de autorização emite os seus tokens.
Tem de validar que o token foi emitido para ele como audiência, conforme a RFC 8707. Um token inválido ou expirado recebe um HTTP 401.
Não pode aceitar nem reencaminhar nenhum outro token. Numa loja, isso significa que o token do agente nunca chega a /wp-json/wc/v3/. Para o WooCommerce só seguem as chaves da REST API guardadas como segredos do Worker.
O transporte acrescenta outra obrigação (Streamable HTTP): o servidor tem de validar o cabeçalho Origin em todas as ligações para evitar ataques de DNS rebinding, e responder 403 se o cabeçalho estiver presente e não for válido. createMcpHandler não faz essa verificação sozinho. A documentação do SDK indica colocar à frente hostHeaderValidationResponse e originValidationResponse, que é o que o Worker acima faz. Um pedido sem cabeçalho Origin passa essa verificação, por isso o token continua a ser a barreira principal.
Outra novidade do transporte ajuda no registo da secção seguinte. Cada POST leva os cabeçalhos Mcp-Method e Mcp-Name, e em tools/call o Mcp-Name contém o nome da ferramenta. Um proxy ou o próprio Worker pode assim registar que ferramenta foi chamada sem analisar o corpo JSON.
Atualização, : cada plugin que traz o MCP para dentro do WordPress acrescenta superfície de ataque própria. A Wordfence publicou três entradas deste tipo a 15 e 16 de outubro de 2026: no AI Engine até à versão 3.8.3 faltava uma verificação de permissões, pelo que um colaborador (contributor) com sessão iniciada podia executar uma ação não autorizada (CVE-2026-110888); a ferramenta MCP wsp_get_posts do WSP MCP até à versão 2.9.5 permitia a qualquer subscritor com sessão iniciada ler títulos, autores e excertos de rascunhos e de artigos pendentes ou agendados (CVE-2026-109382); e o xSpeed Cache até à versão 1.4.2, um plugin de cache com funções MCP, permitia cross-site scripting armazenado por colaboradores (CVE-2026-109985). Nenhuma das três exige uma conta de administrador. O Worker deste guia corre fora do WordPress e só chega à loja com as suas chaves da REST API, por isso uma função do WordPress não dá acesso às suas ferramentas. Protegem-no as verificações do token e do cabeçalho Origin descritas acima.
Como registar e monitorizar chamadas de ferramentas MCP
Registo cada chamada de ferramenta com:
- O nome da ferramenta.
- Um hash SHA-256 da entrada (não a entrada em si; risco de dados pessoais nos termos do RGPD art. 32).
- A latência.
- Se a validação da saída passou ou não.
- O âmbito do token do agente.
Os registos seguem pelo Cloudflare Logpush para armazenamento de longo prazo. Dois hábitos operacionais que isto torna possíveis:
Apertar os esquemas. Seis semanas após o arranque, reviso as falhas de validação. Cada falha de saída é um bug do handler ou um campo do WooCommerce por mapear. Cada falha de entrada é um agente com defeito ou um esquema demasiado restritivo. Ambos entram na versão seguinte.
Atribuição de custos. As chamadas de ferramentas são carga mensurável sobre a origem do WooCommerce. O registo mostra que token de agente gera a carga e com que ferramenta. Um único agente com mau comportamento, preso em ciclo em catalogue.list, fica a uma consulta ao registo de ser identificado.
Outros guias sobre MCP e WooCommerce
Este artigo mostra o percurso de implementação. Uma arquitetura resistente a falhas para sincronização com ERP, filas Redis e transações de base de dados está descrita no nosso guia de arquitetura de integração WooCommerce com ERP 2026. A página do serviço é desenvolvimento de servidores MCP. A estratégia mais ampla está no Universal Commerce Protocol.
O orçamento é individual, porque o âmbito depende do número de ferramentas necessárias, da complexidade das extensões do WooCommerce e dos runtimes de agentes que pretende suportar.







