Bygge en MCP-server for WooCommerce: en praktisk guide
Model Context Protocol gir en KI-agent en typet, introspekterbar flate å handle mot butikken på. WooCommerce har allerede et REST API under /wp-json/wc/v3/. Setter du MCP foran det, blir denne flaten noe en LLM kan kalle uten å gjette parameternavn. Det er arkitekturen jeg ruller ut for kunder som vil ha katalogen og ordreintensjonen tilgjengelig fra Claude, ChatGPT eller sin egen agent-runtime.
Artikkelen hører til tjenesten utvikling av MCP-servere og søylen Universal Commerce Protocol.
TL;DR
- MCP ligger foran WooCommerce REST som en typet JSON-RPC-flate.
- Tre verktøy dekker det meste agenten trenger:
catalogue.list,product.detail,order.intent. - Zod-skjemaer definerer hver inn- og utdata og kjører ved hvert kall.
- Map WooCommerce-felter til
schema.orgProduct og Offer, slik at agentens svar blir konsistente på tvers av flater. - Cloudflare Workers er mitt standardvalg for utrulling av MCP-servere som domineres av lesing.
Hva MCP egentlig er
Anthropic lanserte Model Context Protocol 25. november 2024 (anthropic.com/news/model-context-protocol). Det er en åpen protokoll basert på JSON-RPC 2.0 som lar en klient (en LLM-vert som Claude Desktop, en IDE eller din egen agent-runtime) snakke med en server (din MCP-implementasjon) over stdio eller Streamable HTTP. Streamable HTTP erstattet den eldre HTTP med Server-Sent Events i spesifikasjonsversjonen 2025-03-26.
Tre primitiver:
- Tools. Kallbare funksjoner med typet inn- og utdata. Agenten velger én og kaller den.
- Resources. Lesbare referanser til dokumenter eller poster som agenten kan hente inn i konteksten.
- Prompts. Promptmaler levert av serveren, som verten kan kalle.
For en WooCommerce-butikk er det tools som bærer hovedtyngden. Å bla i katalogen er et verktøykall. Produktdetaljer er et verktøykall. Å foreslå en ordre er et verktøykall. Resources er nyttige når agenten trenger hele teksten i returvilkårene eller en lang produktbeskrivelse; prompts er nyttige når du vil levere ferdige interaksjoner (“foreslå tre produkter for denne kundeprofilen”).
Hva MCP-spesifikasjonen 2026-07-28 endrer for en WooCommerce-server
Gjeldende revisjon av spesifikasjonen er datert 28. juli 2026 (endringslogg). For en server som står foran en nettbutikk, betyr fire av endringene noe.
Protokollen har ikke lenger sesjoner. initialize-håndtrykket forsvinner, og i Streamable HTTP-transporten forsvinner også headeren Mcp-Session-Id. Hver forespørsel bærer protokollversjonen og klientens kapabiliteter i _meta. En Worker som bygger en ny server for hver forespørsel, passer til denne modellen, fordi det ikke finnes noen tilkoblingstilstand å ta vare på mellom kallene.
Tilstand reiser som argument. Trenger en server tilstand på tvers av kall, anbefaler spesifikasjonen å returnere et eksplisitt håndtak fra et opprettingsverktøy og ta imot det som argument i senere kall (verktøy med tilstand). Spesifikasjonens eget eksempel er en handlekurv med basket_id. Utkastet som order.intent returnerer, følger det samme mønsteret: utkast-ID-en er håndtaket. Den samme delen ber serveren sjekke autorisasjonen til den som kaller mot håndtaket ved hvert kall, oppgi levetiden i verktøybeskrivelsen (her 24 timer) og returnere en kjørefeil når et utløpt eller ukjent håndtak kommer inn, slik at modellen kan lage et nytt.
Serveren må implementere server/discover, metoden den bruker til å kunngjøre hvilke protokollversjoner den støtter, kapabilitetene sine og identiteten sin.
Lister kan caches. Resultater fra tools/list har nå ttlMs og cacheScope, og serveren bør returnere verktøyene i fast rekkefølge. Fem verktøy som bare endres ved en ny utrulling, tåler en lang ttlMs.
HTTP+SSE-transporten har vært foreldet siden versjon 2025-03-26, og nye servere bruker Streamable HTTP. Versjon 2 av TypeScript-SDK-en betjener begge protokollgenerasjonene fra samme inngangspunkt (protokollversjoner i SDK-dokumentasjonen), så en klient som fortsatt starter med initialize, fungerer videre.
Hvilke MCP-verktøy en WooCommerce-butikk trenger
Før jeg skriver en eneste kodelinje, lister jeg opp intensjonene agenten skal kunne uttrykke overfor butikken. For en typisk WooCommerce-butikk er listen kort:
- Bla i katalogen med filtre (
catalogue.list). - Fullstendige detaljer om ett produkt (
product.detail). - Foreslå en ordre på vegne av kunden, returnert som et utkast som et menneske bekrefter (
order.intent). - Sjekke statusen til en eksisterende ordre ut fra ordrenummer og e-postadresse (
order.status). - Spørre etter lagerbeholdning for en SKU (
inventory.check).
Hver intensjon tilsvarer nøyaktig ett verktøy. Motstå fristelsen til å eksponere wc.products.get, wc.products.list, wc.orders.create som én-til-én-innpakning av REST. Agenten har ingen nytte av REST-verb; den har nytte av intensjonsverb.
Slik definerer du MCP-verktøy i Zod
TypeScript-SDK-en (pakken @modelcontextprotocol/server i versjon 2; versjon 1 het @modelcontextprotocol/sdk) bruker Zod (zod.dev) til inn- og utdataskjemaer. Slik ser kontrakten for catalogue.list ut:
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(),
});To ting er viktige her. For det første bruker utdataene schema.org-vokabularet (InStock, OutOfStock, BackOrder, PreOrder er verdier rett fra ItemAvailability). For det andre avviser inndataene søppel ved protokollgrensen; handleren ser aldri query: "" eller en negativ price_min.
Hvert verktøy deklarerer jeg etter samme mønster: inndataskjema, utdataskjema, handler. Handleren er en tynn adapter over /wp-json/wc/v3/. Zod kjører to ganger per kall: én gang på inndata, én gang på utdata. Sjekken av utdata fanger handlerfeil som ellers ville lekket til agenten som misdannede data.
Slik registrerer du MCP-verktøy med annotasjoner og strukturert utdata
I versjon 2 av SDK-en heter pakken på serversiden @modelcontextprotocol/server, og hvert verktøy registreres med registerTool, som tar et navn, en konfigurasjon og en handler (verktøy i SDK-dokumentasjonen). Med skjemaene over ser catalogue.list slik ut:
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;
}Når et verktøy deklarerer outputSchema, krever spesifikasjonen at serveren returnerer structuredContent som samsvarer med skjemaet, og klienten bør validere det (verktøy i spesifikasjonen). SDK-en gjør denne valideringen før resultatet forlater serveren. Av hensyn til eldre klienter anbefaler spesifikasjonen å returnere den serialiserte JSON-en i en tekstblokk i tillegg, og det er content i eksempelet.
Annotasjoner beskriver hvordan verktøyet oppfører seg. readOnlyHint sier at det ikke endrer omgivelsene sine. destructiveHint (standard true) og idempotentHint (standard false) teller bare når readOnlyHint er false. openWorldHint (standard true) sier om verktøyet samhandler med en åpen verden av eksterne enheter. De fire leseverktøyene har readOnlyHint: true. order.intent har readOnlyHint: false og destructiveHint: false, fordi det lager et utkast og ikke sletter noe. idempotentHint lar jeg stå på false, fordi idempotency_key er valgfri, og uten den lager to like kall to utkast. Ifølge SDK-dokumentasjonen kan en vert godkjenne et skrivebeskyttet verktøy automatisk og be om bekreftelse før et destruktivt. Spesifikasjonen krever samtidig at klienter behandler annotasjoner som upålitelige med mindre de kommer fra en server de stoler på, så sjekken av tokenets scope blir værende på serveren.
Navn med punktum, som catalogue.list, følger anbefalingen i spesifikasjonen: 1 til 128 tegn, bare ASCII-bokstaver, sifre, understrek, bindestrek og punktum. Feil går gjennom to kanaler. Et ukjent verktøy eller en feilformet forespørsel er en protokollfeil og sendes som JSON-RPC-feil. En feil fra WooCommerce-API-et eller et argument utenfor gyldig område er en kjørefeil og ligger i resultatet med isError: true og en tekst modellen kan bruke til å rette seg selv. Et product.detail-kall med en SKU som ikke finnes, bør returnere den andre typen, med en melding om at SKU-en ikke finnes, slik at modellen kan endre argumentet og prøve igjen.
Slik kobler du MCP-serveren til WooCommerce REST API
Handleren for catalogue.list leser fra /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,
});
}Hjelpefunksjonen mapWooProductToSchemaOrg oversetter WooCommerce-feltnavn (stock_status, regular_price, permalink) til den schema.org-kompatible formen fra utdatadeklarasjonen. Feltmappingen ligger på ett sted, kalles fra ett verktøy, som kalles fra én handler. Avvik mellom WooCommerce-oppdateringer og agentkontrakten fanges av parse-kallet på utdata, ikke av produksjon.
I order.intent kaller jeg aldri WooCommerce sine skrivende endepunkter fra handleren. Verktøyet returnerer et ordreutkast som verten viser brukeren, brukeren bekrefter, og først da kaller en separat, autentisert vei POST /wp-json/wc/v3/orders. Å skrive i blinde på grunnlag av en LLM er en dårlig standard. Det passer også med GDPR art. 25 (innebygd personvern); utkastet lar deg tilpasse kjøpsknappen og markedsføringssamtykkene før operasjonen utføres.
Slik oppretter du WooCommerce REST API-nøkler for MCP-serveren
MCP-serveren snakker med WooCommerce med et par REST API-nøkler, Consumer Key og Consumer Secret. Ifølge den offisielle dokumentasjonen (autentisering i REST API-et) oppretter du dem i de avanserte WooCommerce-innstillingene, på REST API-skjermen (wp-admin/admin.php?page=wc-settings&tab=advanced§ion=keys). Tre egenskaper ved nøklene styrer hvordan serveren bygges.
Hver nøkkel tilhører en WordPress-bruker, og forespørsler med den følger brukerens roller og rettigheter. Slettes brukeren, slutter nøkkelen å virke, og den overføres ikke til en annen bruker. Derfor oppretter jeg en egen teknisk bruker for MCP-serveren i stedet for å henge nøkkelen på kontoen til en person som en dag slutter i firmaet.
Tilgangsnivået er lesing, skriving eller lesing og skriving. Leseverktøyene bruker en nøkkel med bare lesetilgang. Nøkkelen med skrivetilgang kjenner bare den separate ruten som bekrefter ordren med POST /wp-json/wc/v3/orders.
Over HTTPS skjer autentiseringen med HTTP Basic, med Consumer Key som brukernavn og Consumer Secret som passord. Det er Authorization: Basic-headeren i handleren for catalogue.list.
Pagineringen står også i dokumentasjonen (paginering). Som standard returnerer API-et 10 elementer per side, per_page og page endrer det, og headerne X-WP-Total og X-WP-TotalPages inneholder totalt antall ressurser og sider. Handleren leser X-WP-Total for å fylle ut total.
To detaljer ved produktendepunktet (liste alle produkter) påvirker skjemaet. Parameteren category filtrerer på kategori-ID, ikke på slug, så agenten trenger ID-er, eller serveren må oversette slugen før kallet. Og stock_status godtar instock, outofstock og onbackorder. For den tredje verdien har schema.org BackOrder, og derfor tar utdata-enumen til catalogue.list den med. Uten den ville et restordreprodukt stoppet i valideringen av utdata.
Oppdatering, : WooCommerce kunngjorde 21. oktober at versjon 11.3 fjerner sitt eget MCP-endepunkt, /wp-json/woocommerce/mcp, sammen med headeren X-MCP-API-Key som autentiserte det med en REST API-nøkkel. Serveren i denne veiledningen bruker ikke det endepunktet. Den kaller /wp-json/wc/v3/ med consumer-nøkler, og den stien blir værende. Har du også koblet en MCP-klient til det innebygde endepunktet, flytter du den før oppgraderingen: installer utvidelsen MCP Adapter, lag et applikasjonspassord for en bruker som bare har rettighetene klienten trenger, og pek klienten mot /wp-json/mcp/mcp-adapter-default-server. Mønsteret med en ekstern server kan flyttes til en Shopify-butikk eller en headless backend, fordi bare handlerne som spør butikken endres.
Oppdatering, : WooCommerce 11.3 legger også til et eksperimentelt API for utvidbare evner, extensible abilities (WooCommerce-utviklerbloggen, 22. oktober 2026). Utvidelser kan koble egne felt, spørringsfiltre og validatorer til evnene for produkter, ordrer og ordrelinjer, slik at en agent leser og endrer utvidelsens data gjennom evnene den allerede bruker. Funksjonen er av som standard, slås på under WooCommerce > Settings > Advanced > Features i den eksperimentelle delen, og WooCommerce ber utviklere om ikke å bruke den i produksjon ennå. For en ekstern server som denne endres ingenting. Det får betydning når data fra utvidelser, for eksempel egne produktfelt, skal nå agenter gjennom adapteren.
Slik mapper du WooCommerce-felter til schema.org
To grunner til å holde verktøyenes utdata i schema.org-vokabularet:
Agenter bruker de samme ordene på tvers av kilder. Når en agent svarer på “er dette produktet tilgjengelig?”, syr den sammen en Product-entitet fra MCP-serveren din, en Offer med availability og eventuelt en Review. Hvis MCP-serveren din returnerer { "stock": "ja" } og en annen kilde availability: "https://schema.org/InStock", må agenten forene to vokabularer. Med schema.org-samsvar på din side slipper den det.
Butikkens strukturerte data snakker uansett schema.org. Din /wp-content/themes/<theme>/single-product.php (eller den headless-ekvivalenten) sender ut JSON-LD av typen Product. Holder du MCP-utdataene i samme form, serverer den samme produktsiden de samme dataene til Googles crawler, til OpenAIs crawler via URL og til agenten som kaller MCP-verktøyet ditt direkte.
Mappingen av et typisk WooCommerce-produkt ser slik ut:
| WooCommerce-felt | schema.org-felt |
|---|---|
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 |
Varianter krever et ekstra lag (en hasVariant-liste av typen Product), men grunnformen er den samme.
Slik gjør du ordrer idempotente i MCP-serveren
Leseverktøyene (catalogue.list, product.detail, inventory.check, order.status) er naturlig idempotente. Tre kall gir samme svar.
order.intent er fellen. Agenten kan kalle to ganger hvis nettverket mellom svaret og verten hikker. Kontrakten jeg ruller ut:
order.intenttar imot en valgfriidempotency_key(UUID levert av verten).- Handleren lagrer nøkkelen i Workers KV med TTL på 24 timer, sammen med ID-en til ordreutkastet som ble opprettet.
- Et nytt kall med samme nøkkel returnerer det samme utkastet i stedet for å lage et nytt.
Mønsteret tilsvarer Stripes idempotens på POST /v1/charges. Det er godt forstått; agenter og mennesker har like stor nytte av det.
Slik ruller du ut MCP-serveren på Cloudflare Workers
Versjon 2 av TypeScript-SDK-en eksporterer createMcpHandler. Funksjonen returnerer et { fetch }-objekt i den formen Cloudflare Workers forventer som standardeksport, og serverer Streamable HTTP-transporten (web-standard-kjøremiljøer i SDK-dokumentasjonen). Inngangen til Workeren ser slik ut:
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 sjekker avgrensede tokens i Workers KV og returnerer tokendataene, som handlerne leser som ctx.http.authInfo. Skrivebeskyttede verktøy slipper gjennom med et token med lavere rettigheter; order.intent krever et token med scope orders:write. Autentiseringsstrategien er ett av to mønstre fra guiden om MCP-autentiseringsmønstre.
wrangler.toml deklarerer origin-URL-en til WordPress, forbrukernøkkelen og hemmeligheten for WooCommerce (som Worker-hemmeligheter, aldri i koden) og et KV-navnerom for idempotens. Utrulling skjer med wrangler deploy. Workeren holder seg liten, fordi den bare er JSON-RPC-ruting pluss REST-adaptere.
Slik sikrer du MCP-endepunktet med OAuth og Origin-validering
Autorisasjon er valgfritt i MCP. Når en HTTP-basert implementasjon støtter det, bør den følge autorisasjonsspesifikasjonen (autorisasjon). En stdio-server bør derimot ikke følge den, og henter legitimasjonen fra miljøet. Disse reglene gjelder for denne Workeren.
MCP-serveren fungerer som ressursserver etter OAuth 2.1 og må publisere metadata for beskyttede ressurser etter RFC 9728, slik at klienten finner ut hvilken autorisasjonsserver som utsteder tokenene.
Den må validere at tokenet er utstedt til nettopp den som målgruppe (audience), slik RFC 8707 definerer det. Et ugyldig eller utløpt token får HTTP 401.
Den må ikke godta eller sende videre noe annet token. For en nettbutikk betyr det at agentens token aldri når /wp-json/wc/v3/. Til WooCommerce går bare REST API-nøklene som ligger lagret som Worker-hemmeligheter.
Transporten legger til én plikt til (Streamable HTTP). Serveren må validere Origin-headeren på alle innkommende tilkoblinger for å hindre DNS rebinding-angrep, og svare med 403 når headeren finnes og er ugyldig. createMcpHandler gjør ikke denne sjekken selv. SDK-dokumentasjonen sier at hostHeaderValidationResponse og originValidationResponse skal ligge foran, og det er det Workeren over gjør. En forespørsel uten Origin-header slipper gjennom den sjekken, så tokenet er fortsatt hovedsperren.
En annen endring i transporten hjelper på loggingen i neste del. Hver POST har headerne Mcp-Method og Mcp-Name, og ved tools/call inneholder Mcp-Name verktøynavnet. En proxy eller Workeren selv kan dermed registrere hvilket verktøy som ble kalt, uten å parse JSON-kroppen.
Oppdatering, : Hver utvidelse som bringer MCP inn i WordPress, gir sin egen angrepsflate. Wordfence publiserte tre slike oppføringer 15. og 16. oktober 2026. AI Engine til og med 3.8.3 manglet en tilgangskontroll, så en innlogget bidragsyter (contributor) kunne utføre en uautorisert handling (CVE-2026-110888). MCP-verktøyet wsp_get_posts i WSP MCP til og med 2.9.5 lot enhver innlogget abonnent lese titler, forfattere og utdrag fra utkast og fra ventende og planlagte innlegg (CVE-2026-109382). Og xSpeed Cache til og med 1.4.2, en hurtigbufferutvidelse med MCP-funksjoner, tillot lagret cross-site scripting fra bidragsytere (CVE-2026-109985). Ingen av de tre krever en administratorkonto. Workeren i denne guiden kjører utenfor WordPress og når butikken bare med REST-API-nøklene sine, så en WordPress-rolle gir ikke tilgang til verktøyene. Den beskyttes av kontrollene av token og Origin-header som er beskrevet over.
Slik logger og overvåker du MCP-verktøykall
Hvert verktøykall logger jeg med:
- Navnet på verktøyet.
- En SHA-256-hash av inndataene (ikke selve inndataene; risiko for personopplysninger etter GDPR art. 32).
- Forsinkelse.
- Om valideringen av utdata gikk gjennom eller ikke.
- Scopet til agentens token.
Loggene går via Cloudflare Logpush til langtidslagring. To driftsvaner dette gjør mulig:
Innstramming av skjemaer. Seks uker etter lansering går jeg gjennom valideringsfeilene. Hver utdatafeil er en handlerfeil eller et WooCommerce-felt som ikke er mappet. Hver inndatafeil er enten en agent med en svakhet eller et for strengt skjema. Begge deler havner i neste utgivelse.
Kostnadsfordeling. Verktøykall er målbar belastning på WooCommerce-origin. Loggen viser hvilket agenttoken som skaper belastningen, og med hvilket verktøy. En enkelt agent som oppfører seg dårlig og går i loop på catalogue.list, er én loggspørring unna å bli identifisert.
Andre guider om MCP og WooCommerce
Denne artikkelen viser selve implementeringen. En feiltolerant arkitektur for ERP-synkronisering, Redis-køer og databasetransaksjoner er beskrevet i vår guide til arkitektur for integrasjon mellom WooCommerce og ERP 2026. Tjenestesiden er utvikling av MCP-servere. Den bredere strategien finner du i Universal Commerce Protocol.
Prisen er individuell, fordi omfanget avhenger av hvor mange verktøy du trenger, hvor komplekse WooCommerce-utvidelsene er og hvilke agent-runtimes du vil støtte.







