An AI shopping agent is comparing your trail jacket against three competitors right now, and your product page gives it a paragraph about "all-day comfort on the trail." It can't tell what the jacket weighs, whether the shell is waterproof, or whether a size M fits a 100 cm chest. So it ranks the competitor whose listing says weight: 380 g. Agentic commerce, where agents discover, evaluate, and buy on a shopper's behalf, puts a cost on that gap. Fixing the catalog now is cheaper than fixing it after agents have already learned to skip you.
This guide builds the content side of that readiness in Strapi 5. You get a typed Product Collection Type, components for specs, variants, and policies, publish-time completeness checks, and the Model Context Protocol (MCP) server scoped by Admin tokens so agents can query the catalog directly. Strapi manages the product content layer. Your commerce backend still owns cart, checkout, and payments, and nothing below changes that.
In Brief
Focus on these four points:
- Agents can't infer what human readers skim past. They need structured, complete, unambiguous product data, with identifiers, units, variants, and policies stored as fields.
- Model it once in Strapi with a Product Collection Type and reusable components for specs, variants, and shipping and return terms.
- Expose it through the Strapi MCP server, generally available (GA) since 5.49.0, using an Admin token per agent whose owner has read-only catalog permissions.
- Keep checkout, tax, and payment in your commerce engine. Strapi is the content layer beside it.
Together, these steps give agents reliable product data without moving transaction responsibilities into Strapi.
What Agentic Commerce Means for Product Content
Agents read your catalog at three points in the purchase loop, and each one punishes a different kind of missing data.
How Agents Discover, Evaluate, and Buy
Agentic commerce is shopping in which an AI agent discovers products, compares them, and completes the purchase on a person's behalf.
Discovery largely runs on feeds you publish before any conversation starts. OpenAI ingests a CSV or JSON feed carrying identifiers, descriptions, pricing, inventory, media, and fulfillment options. Google's AI Mode and Gemini draw on the Shopping Graph (60B+ listings) and reuse existing Merchant Center feeds.
Evaluation reads the same fields, and OpenAI ranks merchants by availability, price, quality, primary-seller status, and checkout enablement. Shopify's guidance tells merchants that agents parse structured data fields rather than product pages, and that details such as material, weight, and dimensions belong in those fields.
At the transaction step, ChatGPT calls your merchant endpoints under ACP to create a checkout session, and you validate the order, calculate tax, and charge through your own processor. Checkout relies on live data from your commerce engine, not the static feed.
Three protocols cover the transaction side, while OpenAI's product discovery update reflects its evolving shopping approach. ACP key concepts, from OpenAI and Stripe, cover checkout plus delegated payment. The Google-hosted UCP specification offers REST and MCP bindings and keeps you as Merchant of Record. The AP2 overview describes payment security through signed mandates. UCP leans on existing Merchant Center feeds and schemas, while ACP uses a ChatGPT-specific product feed specification rather than relying on an existing product schema, and AP2 has no product-data format at all.
Why Product Content Becomes the Bottleneck
Agents can't infer what a human skims past. A shopper reading "fits most" knows to open the size chart. An agent likely sees a string with no numeric value and moves on. The ShopSimulator benchmark (arXiv preprint) found GPT-5 reaching a 32% full-success rate on shopping tasks, with failures clustering on "fine-grained attribute and option requirements." Merchant Center guidance lists the same gaps among the common problems behind disapprovals: wrong or missing GTINs, missing item_group_id, color, and size on variants, and feed data that conflicts with the landing page. Ambiguous sizing, missing compatibility constraints, and a returns policy nobody modeled as a field each hand the agent a reason to pick the competitor it can parse.
Audit Your Product Content for Agent Readiness
Before modeling anything, read a few of your current product pages the way an agent does: as text with no layout, no images, and no intuition.
Where Agents Fail on Human-Written Copy
Four common content patterns cause most of the parsing failures:
- Prose-only specs: A description like "lightweight 20D ripstop shell with a roomy fit" contains a material and a hint about weight, but an agent has to bind "20D" to the right attribute.
- Vague sizing: "Fits most" fails for the same reason. Baymard's apparel research found 82% of apparel sites fail to provide sufficient sizing information and recommends numeric sizing in both inches and centimeters plus international conversions.
- Paragraph-based variants: Variants described in paragraphs ("available in black, navy, and olive, sizes S through XL") never become purchasable rows. The OpenAI feed spec requires one row per variant, with the variant reflected in the title and URL, and Google requires
item_group_idfor variants. - Unstructured return policies: If your returns policy exists only as an image or PDF, there's no structured field for an agent to read. OpenAI offers
accepts_returns,return_deadline_in_days, and areturn_policyURL, and Google reads schema.orgMerchantReturnPolicy.
Turning these details into discrete fields removes the interpretation step that causes agents to miss otherwise suitable products.
The Fields Agents Need
The cross-spec requirements from OpenAI's product feed spec, the Google Merchant Center product data spec, and schema.org Product reduce to seven attribute groups.
| Attribute | Why agents need it | Example |
|---|---|---|
| SKU (stock keeping unit) plus GTIN (Global Trade Item Number) or MPN | The matching key across every feed. Google disapproves incorrect GTINs and says not to guess | TRAIL-BLK-10, 09506000134352 |
| Typed specs with units | A value has to bind to an attribute and a unit | weight: 0.75 kg, dimensions: 30×20×12 cm |
| Variant matrix | One purchasable row per option, grouped under a parent ID | group_id, {"color":"Black","size":"10"} |
| Availability enum | Required by OpenAI, filtered and ranked everywhere | in_stock, out_of_stock, backorder |
| Shipping and return terms | Structured fields exist on OpenAI and Google. Prose and PDFs don't map | accepts_returns: true, return_deadline_in_days: 30 |
| Compatibility constraints | Agents over-generalize from partial matches without explicit socket, chipset, or form-factor data | schema.org isAccessoryOrSparePartFor |
| Plain-language summary | OpenAI requires plain, factual text, ≤5,000 characters | "Waterproof trail shoes with rubber outsole…" |
OpenAI's feed has no dedicated compatibility field, so map compatibility to Google's related_product or to schema.org relation properties. One spelling differs between the specs: OpenAI's native feed uses pre_order for the availability enum, while its Google-compatible profile uses preorder. The Strapi model below stores preorder, so map it to pre_order when you render the OpenAI feed.
Model Agent-Ready Product Content in Strapi
Every attribute in that table maps to a Strapi type that already exists, so agentic commerce readiness is mostly a matter of choosing typed fields over rich text and making completeness enforceable.
Create the Product Collection Type
Define the product at src/api/product/content-types/product/schema.json. The Content-type Builder will generate the same file, but seeing it whole makes the modeling choices explicit.
{
"kind": "collectionType",
"collectionName": "products",
"info": {
"singularName": "product",
"pluralName": "products",
"displayName": "Product"
},
"options": {
"draftAndPublish": true
},
"attributes": {
"name": { "type": "string", "required": true, "maxLength": 150 },
"slug": { "type": "uid", "targetField": "name" },
"sku": {
"type": "string",
"required": true,
"unique": true,
"column": { "unique": true }
},
"gtin": { "type": "string", "maxLength": 14 },
"summary": { "type": "text", "required": true, "maxLength": 5000 },
"price": { "type": "decimal", "required": true },
"currency": { "type": "string", "required": true, "default": "USD" },
"availability": {
"type": "enumeration",
"enum": ["in_stock", "out_of_stock", "preorder", "backorder"],
"required": true
},
"weight": { "type": "decimal" },
"weightUnit": { "type": "enumeration", "enum": ["g", "kg", "oz", "lb"] },
"waterproof": { "type": "boolean" },
"images": { "type": "media", "multiple": true, "allowedTypes": ["images"] },
"brand": {
"type": "relation",
"relation": "manyToOne",
"target": "api::brand.brand",
"inversedBy": "products"
},
"category": {
"type": "relation",
"relation": "manyToOne",
"target": "api::category.category",
"inversedBy": "products"
},
"specs": { "type": "component", "repeatable": true, "component": "product.spec" },
"variants": { "type": "component", "repeatable": true, "component": "product.variant" },
"policies": { "type": "component", "repeatable": false, "component": "product.policies" }
}
}A few choices deserve a note. sku is unique at the schema level and column: { unique: true } at the database level, per the Models documentation. gtin is a string so leading zeros survive, which OpenAI's spec calls out. summary caps at 5,000 characters to match OpenAI's plain-text limit, and name at 150 to match the title limit. weightUnit uses OpenAI's four accepted values. waterproof is a top-level boolean rather than a specs entry because the MCP list tool filters on scalar fields only, as the MCP section below explains. The listed price here is the price agents use for discovery and ranking; your commerce engine re-verifies price and stock at checkout.
Use Components for Specs, Variants, and Policies
Components live in src/components/<category>/. A product.spec component, saved as src/components/product/spec.json, has three fields and covers every typed specification agents choke on in prose:
{
"collectionName": "components_product_specs",
"info": { "displayName": "Spec" },
"attributes": {
"name": { "type": "string", "required": true },
"value": { "type": "string", "required": true },
"unit": { "type": "string" }
}
}Give product.variant its own sku, color, size, a sizeSystem enumeration (OpenAI accepts US, UK, EU, DE, FR, JP, CN, IT, BR, MEX, and AU), an availability enumeration, and an image. That produces one row per purchasable item when you render a feed. Give product.policies fields such as acceptsReturns (boolean), returnDeadlineDays (integer), returnPolicyUrl, and a repeatable shipping option with country, service, and price. The brand and category relations keep those values consistent across products instead of free-typed.
Skip a Dynamic Zone unless editors need to choose between section types, since filtering on one doesn't help an agent.
Typed fields beat a rich-text blob for three concrete reasons:
- The MCP
listtool can filter onweightoravailabilitybut not on a sentence. - Field-level token permissions let you hide a single typed field, such as a wholesale cost, without hiding the whole entry.
- Every downstream format, from OpenAI's
variant_dictto schema.org'shasMeasurement, expects discrete values you'd otherwise have to re-extract.
One limit shapes the model. The MCP list tool filters and sorts on scalar attributes only (strings, numbers, booleans, dates, and enumerations), so component and relation fields such as specs, variants, and category can't be filtered through MCP. Promote the attributes agents filter on most to top-level scalar fields, and keep components for the long tail. REST and GraphQL still filter on relations when you need them.
Enforce Completeness Before Publishing
The schema above enables Draft and Publish, and required-field validation blocks publishing: the publish dialog shows "Ready to publish" or red errors. A product missing summary, price, or availability stays a draft, and drafts stay out of results when an agent queries published content.
With Draft and Publish on, Strapi intentionally skips unique checks for drafts, so two drafts can share a SKU until one of them publishes and triggers the database constraint. You can catch the conflict earlier with a beforeCreate lifecycle hook that uses the Document Service API. Strapi 5 deprecated the Entity Service, so strapi.documents() is the right call, and the error handling docs tell you to throw from beforeX hooks rather than afterX.
// src/api/product/content-types/product/lifecycles.ts
import { errors } from '@strapi/utils';
export default {
async beforeCreate(event) {
const { sku, documentId } = event.params.data;
if (!sku) return;
const existing = await strapi
.documents('api::product.product')
.findFirst({
filters: {
sku: { $eq: sku },
// A plain create has no documentId yet; publish() passes the existing one
...(documentId ? { documentId: { $ne: documentId } } : {}),
},
});
if (existing) {
throw new errors.ApplicationError(`SKU ${sku} already exists`, { sku });
}
},
};Because publish() also triggers Create lifecycles, the documentId exclusion stops the hook from matching the entry's own draft. Add the same check in beforeUpdate so a changed SKU can't slip through.
Expose Product Content to Agents With the Strapi MCP Server
The MCP server turns the Product Collection Type into tools any MCP client can call, and the token you hand out decides which tools exist.
Enable the MCP Server
For agentic commerce, any agent that speaks MCP can query Strapi directly once you flip one config flag. The Strapi MCP server shipped as beta in 5.47.0 and went GA in 5.49.0. mcp.enabled is false by default:
// config/server.ts
import type { Core } from '@strapi/strapi';
const config = ({ env }: Core.Config.Shared.ConfigParams): Core.Config.Server => ({
host: env('HOST', '0.0.0.0'),
port: env.int('PORT', 1337),
app: {
keys: env.array('APP_KEYS'),
},
mcp: {
enabled: true,
},
});
export default config;Restart Strapi, and the server answers at /mcp over Streamable HTTP.
Scope Access With Admin Tokens
Only Admin tokens authenticate /mcp, so a Content API token won't work there. Create tokens at Settings > Administration Panel > Admin Tokens, and create one per agent or use case rather than sharing. Each token is bounded by its owner's permissions: removing a permission from the owner's role removes it from the token, and requests fail while the owner's account is deactivated.
Scoping works at four levels: Content-Type, action, field, and locale. A token with only read on Product exposes only the list and get tools. No update, delete, or publish tool appears at all. Field exclusions narrow the input and output schemas, so if the token excludes a field the client never sees it. With i18n, the locale parameter is narrowed per action. Strapi's MCP security best practices go deeper on token design.
For the read-only product token in this guide, enable read on Product and Category and nothing else. With no write actions granted, the agent can't touch price or anything else, and if you store an internal field such as a wholesale cost, untick it so it never leaves the server. Never place an Admin token in client-side code.
Connect an Agent and Test Product Queries
Use this one-line command from the Claude Code MCP setup:
claude mcp add --transport http strapi-mcp http://localhost:1337/mcp --header "Authorization: Bearer YOUR_ADMIN_TOKEN"For Cursor MCP configuration, add .cursor/mcp.json to the project. Cursor merges it with ~/.cursor/mcp.json, with the project configuration winning:
// .cursor/mcp.json
{
"mcpServers": {
"strapi-mcp": {
"type": "streamable-http",
"url": "http://localhost:1337/mcp",
"headers": {
"Authorization": "Bearer YOUR_ADMIN_TOKEN"
}
}
}
}Now ask the agent: "Find waterproof jackets under 400 g that are in stock." The agent calls the Product list tool with Strapi filters on scalar fields:
{
"filters": {
"name": { "$containsi": "jacket" },
"waterproof": { "$eq": true },
"weight": { "$lt": 400 },
"weightUnit": { "$eq": "g" },
"availability": { "$eq": "in_stock" }
},
"sort": "weight:asc",
"pageSize": 10
}A trimmed, illustrative response looks like this:
{
"data": [
{
"documentId": "...",
"sku": "TRAIL-BLK-10",
"name": "Trail Shell Jacket",
"weight": 380,
"weightUnit": "g",
"waterproof": true,
"availability": "in_stock"
}
]
}[TKTK: confirm the list tool's response shape and whether the list and get tools return component fields such as policies and specs; the MCP docs list no nested population support for relations.]
A second prompt, "Does TRAIL-BLK-10 accept returns, and within how many days?", is meant to resolve through policies.acceptsReturns and policies.returnDeadlineDays instead of a PDF, once the TKTK above is confirmed. A third, "Set the price of TRAIL-BLK-10 to 59.99," fails cleanly because no update tool was ever exposed.
Audit and Governance
From 5.52.0, Audit Logs record MCP entry actions with origin: "mcp", distinguishing them from "admin" panel actions. Audit Logs require the Enterprise plan, and read-only operations aren't logged. For agents that do write, the create tool produces a draft whenever Draft and Publish is on, so an agent-created product still passes the same required-field gate before it goes live. Token durations run 7, 30, or 90 days, or Unlimited, and only the token's owner sees the Regenerate button when you rotate. Replace tokens before offboarding their owner, because deleting an owner deletes their tokens with no recovery.
Serve Agents Beyond MCP With Structured Data and APIs
Not every agent speaks MCP. ChatGPT and Google read feeds and crawled pages, so the same Strapi fields need to reach three more surfaces from one source of truth.
Render JSON-LD First
Render a schema.org Product on each product page from the fields above: sku, gtin14, name, description from summary, brand, an offers block with price, priceCurrency, availability, and url, and hasMerchantReturnPolicy from the policies component. Google's structured data guidance requires both price and priceCurrency and needs one of review, aggregateRating, or offers on Product. Strapi's structured data guide covers the implementation side.
Use the Content API
The REST and GraphQL Content API is read-only when you issue a Read-only API token, which covers find and findOne. By default, Strapi doesn't populate components and relations, so request them explicitly when you build a feed:
GET /api/products?populate[specs]=true&populate[variants]=true&populate[policies]=true&populate[brand]=true&filters[availability][$eq]=in_stock HTTP/1.1The populate and select docs cover nesting, and the GraphQL plugin auto-resolves nested relations if you'd rather query by documentId.
Generate Feeds
Transform the same response into OpenAI's lowercase, underscore-separated columns (item_id, image_url, variant_dict) delivered by SFTP, upload, or hosted URL. Render Google's supported formats according to its feed format guidance: .txt, .xml, or .tsv. The Merchant Center documentation maps attributes to schema.org (title to name, item_group_id to inProductGroupWithID), so JSON-LD and the feed share the same Strapi fields. One schema, three outputs, and a field renamed in Strapi changes once. Strapi summary becomes JSON-LD description, Google description, and OpenAI description.
Build the Agentic Commerce Content Layer With Strapi
Strapi's Ecommerce CMS solution centers on consistent product data across web, mobile, social commerce, and marketplaces. That same model gives agents identical typed fields on every surface. Strapi is not the checkout engine. The headless commerce guide draws the boundary clearly: the CMS owns product storytelling, editorial content, media, and localization, while the commerce engine owns carts, pricing, and checkout. For the architecture view, see where your CMS fits in a headless commerce stack.
The Shopify integration draws the line the same way: Shopify handles inventory and transactions, while you use Strapi to manage rich content and custom experiences. BigCommerce and Medusa pairings follow the same split for agentic commerce.
You can enable Internationalization per Content-Type at no cost, so a summary in French and an EU sizeSystem live on the same document. MCP tokens can then restrict which locales an agent reads. For governance, you can combine role-based access control (RBAC) for editors, Admin tokens for agents, Content History for restoring a bad edit, and Audit Logs on Enterprise to trace which agent changed what.
Make Your Catalog Readable Before Agents Arrive
Agents skip products whose weight, size, compatibility, and return terms exist only in prose. The benchmarks and Merchant Center disapproval lists point to the same gaps. Modeling those attributes as typed fields and components in Strapi, gated by Draft and Publish and required fields, gives you one catalog that feeds MCP clients, JSON-LD, the Content API, and marketplace feeds without re-extraction. Checkout stays in your commerce engine, where the live price and stock already live. Start with the Strapi MCP server docs on a local project and point Claude Code or Cursor at your first read-only Admin token for the content MCP server, or try the live demo to explore a working Strapi project first.





