§1 · The Weekend Reality
Four layers. Two days. One operational store.
Shopify is the friendliest platform on earth for agent commerce, and that is not marketing. Every storefront already emits a baseline Product Schema.org block from Dawn, every store has a working Storefront API at /api/2026-04/graphql.json, every store on the rollout cohort gets a native MCP server at /api/mcp with no install, and every eligible US merchant is enrolled in the UCP catalog that powers ChatGPT, Microsoft Copilot, and the Jan 11, 2026 NRF Google co-launch (re-verify per plan). The four agent-readiness layers — structured data, API endpoint, MCP, UCP — are already wired. Your weekend job is to extend, verify, and test each one. By Sunday night you will have a Liquid snippet that fills the 20-field MVP, a Storefront access token that returns real JSON, a curl test against the native MCP endpoint, a custom Python MCP wrapper for the tools the native server does not cover, and a Claude Desktop session that completes a real cart-to-checkout flow against your store.
§2 · The 4-Layer Model on Shopify
What you build on top of what Shopify already gives you.
The full agent-readiness model is documented on the AgentMall Roadmap. On Shopify, three of the four layers ship in the box. Your weekend is about closing the gaps inside each layer — not building the layers from scratch.
Layer 1 · Schema
Structured Data
Dawn auto-emits Product + Offer. You extend with AggregateRating, GTIN, hasMerchantReturnPolicy, shippingDetails via Liquid + metafields. See Product Data for the field reference.
Layer 2 · API
Storefront API + Admin API
Storefront API at /api/2026-04/graphql.json covers eight canonical agent endpoints. Admin API is server-side for orders + inventory writes. See API Endpoint for the canonical surface.
Layer 3 · MCP
Native /api/mcp + Custom Wrapper
Shopify ships a native Storefront MCP at /api/mcp. Tool set is fixed. You add a custom Python MCP server for store-specific tools. See MCP for the protocol overview.
Layer 4 · UCP
Catalog + Checkout
Automatic Shopify Catalog enrollment feeds the UCP /api/ucp/mcp endpoint. Eight-step checkout state machine. See UCP for the protocol detail.
Why This Order Matters
Schema is the discovery layer — without it, agents cannot find or rank your products. The API is the transaction layer — without it, they cannot buy. MCP is the negotiation layer — without it, the agent has to write your API integration itself. UCP is the standardization layer — without it, every agent platform demands a custom integration. Build them in order, test each one, then test them together.
Layer 1 · Structured Data
Schema.org on Shopify: extend the Liquid, fill the metafields.
Dawn (and most modern themes built on Online Store 2.0) auto-emits a Product or ProductGroup JSON-LD block via the {{ product | structured_data }} Liquid filter. It covers name, description, image, brand, sku, and per-variant Offer with price, priceCurrency, and availability. That is the floor — not the ceiling. Google AI Overviews and most major shopping agents treat the absence of hasMerchantReturnPolicy and shippingDetails as a disqualifying signal. The fix is a single extended Liquid block plus matching metafield definitions.
What Shopify auto-emits
Open Dawn's sections/main-product.liquid or snippets/product-schema.liquid and you will find the filter call. The output is valid JSON-LD but minimal: no rating, no return policy, no shipping details, no GTIN, no MPN. Validate it once at Google's Rich Results Test and you will see exactly which fields Google complains about.
The 20-field MVP — what to add
| Field | Source | Shopify Location | Status |
@type | Schema.org | Liquid literal | Auto |
name | product.title | Product detail | Auto |
description | product.description | Product detail | Auto |
image | product.featured_image | Product media | Auto |
brand | product.vendor | Product detail | Auto |
sku | variant.sku | Variant | Auto |
price | variant.price | Variant | Auto |
priceCurrency | shop.currency | Shop settings | Auto |
availability | variant.available | Variant inventory | Auto |
url | shop.url + product.url | Computed | Auto |
gtin13 / gtin14 | variant.barcode | Variant barcode | Populate |
mpn | Metafield | custom.mpn | Populate |
aggregateRating.ratingValue | Review app metafield | reviews.rating | Add |
aggregateRating.reviewCount | Review app metafield | reviews.rating_count | Add |
hasMerchantReturnPolicy.returnPolicyCategory | Liquid literal | Snippet | Add |
hasMerchantReturnPolicy.merchantReturnDays | Liquid literal | Snippet | Add |
shippingDetails.shippingRate | Liquid literal | Snippet | Add |
shippingDetails.shippingDestination | Liquid literal | Snippet | Add |
shippingDetails.deliveryTime | Liquid literal | Snippet | Add |
itemCondition | Liquid literal | Snippet | Add |
The extended Liquid snippet
Replace the auto-emitted block in sections/main-product.liquid (or your theme's equivalent) with this. It pulls auto fields where Shopify exposes them and uses metafields plus literals for the rest. Wire your review app's rating into the reviews.rating and reviews.rating_count metafields first (Judge.me, Yotpo, Loox, and Stamped all support custom metafield sync).
<script type="application/ld+json">
{
"@context": "https://schema.org/",
"@type": "Product",
"name": {{ product.title | json }},
"description": {{ product.description | strip_html | json }},
"image": [{% for img in product.images %}{{ img | image_url: width: 1500 | prepend: 'https:' | json }}{% unless forloop.last %},{% endunless %}{% endfor %}],
"brand": { "@type": "Brand", "name": {{ product.vendor | json }} },
"sku": {{ product.selected_or_first_available_variant.sku | json }},
{% if product.selected_or_first_available_variant.barcode %}
"gtin13": {{ product.selected_or_first_available_variant.barcode | json }},
{% endif %}
{% if product.metafields.custom.mpn %}
"mpn": {{ product.metafields.custom.mpn | json }},
{% endif %}
"itemCondition": "https://schema.org/NewCondition",
{% if product.metafields.reviews.rating_count != blank and product.metafields.reviews.rating_count.value > 0 %}
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": {{ product.metafields.reviews.rating.value.rating | json }},
"reviewCount": {{ product.metafields.reviews.rating_count.value | json }},
"bestRating": "5",
"worstRating": "1"
},
{% endif %}
"offers": {
"@type": "AggregateOffer",
"priceCurrency": {{ shop.currency | json }},
"lowPrice": {{ product.price_min | money_without_currency | strip | json }},
"highPrice": {{ product.price_max | money_without_currency | strip | json }},
"offerCount": {{ product.variants.size }},
"availability": "{% if product.available %}https://schema.org/InStock{% else %}https://schema.org/OutOfStock{% endif %}",
"url": {{ shop.url | append: product.url | json }},
"hasMerchantReturnPolicy": {
"@type": "MerchantReturnPolicy",
"applicableCountry": "US",
"returnPolicyCategory": "https://schema.org/MerchantReturnFiniteReturnWindow",
"merchantReturnDays": 30,
"returnMethod": "https://schema.org/ReturnByMail",
"returnFees": "https://schema.org/FreeReturn"
},
"shippingDetails": {
"@type": "OfferShippingDetails",
"shippingRate": {
"@type": "MonetaryAmount",
"value": "5.99",
"currency": "USD"
},
"shippingDestination": {
"@type": "DefinedRegion",
"addressCountry": "US"
},
"deliveryTime": {
"@type": "ShippingDeliveryTime",
"handlingTime": { "@type": "QuantitativeValue", "minValue": 0, "maxValue": 1, "unitCode": "DAY" },
"transitTime": { "@type": "QuantitativeValue", "minValue": 2, "maxValue": 5, "unitCode": "DAY" }
}
}
}
}
</script>
Critical · Re-verify Before Launch
The literal shipping rate of $5.99 and the 30-day return window in the snippet are placeholders — replace both with your store's actual policy. Both values must match your published shipping and returns pages or Google will flag a structured-data-vs-content mismatch. The shipping schema also supports per-region rates via an array of OfferShippingDetails — extend the snippet if your store ships to multiple countries with different policies.
Metafield definitions to create
In Shopify Admin → Settings → Custom data → Products, create three metafield definitions. Use these exact namespaces and keys or update the Liquid snippet to match.
| Namespace.Key | Type | Source | Notes |
custom.mpn | Single line text | Manual or CSV import | Manufacturer part number; not always required if GTIN present |
reviews.rating | Rating (Shopify type) | Review app sync | 0.0–5.0 scale; Judge.me, Yotpo, Loox, Stamped all support this |
reviews.rating_count | Integer | Review app sync | Total count of reviews aggregated for ratingValue |
Schema apps if you do not want to edit Liquid
| App | Approach | Price (re-verify before launch) |
| Schema Plus for SEO | Auto-extends product, breadcrumb, FAQ schema; full ecosystem of types | Tiered — free starter through paid plans; re-verify on the App Store listing before launch |
| JSON-LD for SEO | Adds Article, Recipe, Event, FAQ, and review schema on top of product | Tiered subscription — re-verify before launch |
| Smart SEO | Bundles meta tags + schema + sitemaps in one suite | Tiered subscription — re-verify before launch |
| SEO Manager | Schema plus meta tag and broken link auditing | Tiered subscription — re-verify before launch |
For the field-by-field reference and the cross-platform 20-field MVP that this Liquid snippet implements, see the Product Data spoke. For Schema-only validation tactics, run every change through the Rich Results Test and Schema.org validator before deploying to production.
Layer 2 · API Endpoint
Storefront API for the buyer flow. Admin API for the back office.
Shopify exposes two GraphQL APIs at distinct privilege tiers. For an agent operating on behalf of a shopper, the Storefront API is the right surface for product search, cart, and checkout handoff. The Admin API is the right surface for order status, returns, and inventory writes — but it should be proxied through your own authenticated backend, never called by the agent with raw credentials. REST Admin API was marked legacy on October 1, 2024; new agent integrations should be GraphQL-only.
Storefront vs Admin at a glance
| Property | Storefront API | Admin API |
| Surface | /api/2026-04/graphql.json | /admin/api/2026-04/graphql.json |
| Protocol | GraphQL only | GraphQL preferred; REST legacy as of Oct 1, 2024 |
| Auth | Storefront access token (low privilege, public) | Admin access token (high privilege, server-only) |
| Who uses it | Buyer-facing apps, themes, AI agents directly | Backend services, scripts, never the agent directly |
| Rate limit | Calls-per-IP throttle; varies by app/store class (re-verify before launch) | Calculated cost model — points per query (re-verify before launch) |
| Typical agent use | list_products, search, cart, checkoutUrl handoff | order status, returns, inventory adjustments (proxied) |
| Cart vs Checkout | Cart API: cartCreate → cartLinesAdd → cart.checkoutUrl | Order API for post-checkout state |
Critical · checkoutCreate Is Dead
The legacy Storefront checkoutCreate mutation was permanently shut down on April 1, 2025. Any agent integration still calling it returns 410 Gone. The current path is the Cart API: cartCreate → cartLinesAdd → optional cartBuyerIdentityUpdate → hand off cart.checkoutUrl. Audit every wrapper, every saved prompt, every internal doc for checkoutCreate and migrate now.
Creating your Storefront access token
- Shopify Admin → Apps → Develop apps → Create an app.
- Configuration → Storefront API access → enable scopes:
unauthenticated_read_product_listings, unauthenticated_read_product_inventory, unauthenticated_read_product_pickup_locations, unauthenticated_write_checkouts.
- Install the app to your store and copy the Storefront access token from the API credentials tab.
- Set it as
SHOPIFY_STOREFRONT_TOKEN in your local .env and in any deployed environment. Never expose the Admin access token to client-side or agent code.
The eight canonical agent endpoints — mapped to Shopify
The API spoke defines eight canonical endpoints that cover the full agent purchase flow. Shopify implements all eight through Storefront GraphQL and Admin GraphQL. Map yours from this table.
| Canonical Endpoint | Shopify API | Operation |
| list_products | Storefront | products(first: N) query |
| get_product_details | Storefront | product(handle: $h) query |
| search_products | Storefront | products(query: $q) with search syntax |
| check_availability | Storefront | productVariant.availableForSale + quantityAvailable |
| initiate_checkout | Storefront | cartCreate mutation → return cart.checkoutUrl |
| get_order_status | Admin (proxy) | order(id: $id) query with fulfillmentStatus |
| webhook_order_updates | Admin Webhooks | ORDERS_UPDATED, FULFILLMENTS_CREATE topics |
| get_manifest | Native MCP | tools/list via /api/mcp |
list_products — Storefront GraphQL
curl -X POST "https://{your-store}.myshopify.com/api/2026-04/graphql.json" \
-H "Content-Type: application/json" \
-H "X-Shopify-Storefront-Access-Token: $SHOPIFY_STOREFRONT_TOKEN" \
-d '{
"query": "query ListProducts($n: Int!) { products(first: $n) { edges { node { id handle title vendor productType description featuredImage { url altText } priceRange { minVariantPrice { amount currencyCode } maxVariantPrice { amount currencyCode } } variants(first: 10) { edges { node { id sku barcode availableForSale quantityAvailable price { amount currencyCode } selectedOptions { name value } } } } } } } }",
"variables": { "n": 20 }
}'
search_products — Storefront query syntax
curl -X POST "https://{your-store}.myshopify.com/api/2026-04/graphql.json" \
-H "Content-Type: application/json" \
-H "X-Shopify-Storefront-Access-Token: $SHOPIFY_STOREFRONT_TOKEN" \
-d '{
"query": "query Search($q: String!) { products(first: 20, query: $q) { edges { node { handle title vendor priceRange { minVariantPrice { amount currencyCode } } featuredImage { url } } } } }",
"variables": { "q": "title:wireless OR product_type:headphones available_for_sale:true" }
}'
initiate_checkout — the Cart API path
Two mutations: cartCreate with a buyerIdentity + lines, then read cart.checkoutUrl from the response. Hand the URL to the buyer (or to a Checkout MCP partner-preview agent) for payment completion.
curl -X POST "https://{your-store}.myshopify.com/api/2026-04/graphql.json" \
-H "Content-Type: application/json" \
-H "X-Shopify-Storefront-Access-Token: $SHOPIFY_STOREFRONT_TOKEN" \
-d '{
"query": "mutation CartCreate($input: CartInput!) { cartCreate(input: $input) { cart { id checkoutUrl totalQuantity cost { totalAmount { amount currencyCode } } } userErrors { field message } } }",
"variables": {
"input": {
"lines": [
{ "merchandiseId": "gid://shopify/ProductVariant/1234567890", "quantity": 1 }
],
"buyerIdentity": {
"countryCode": "US",
"email": "agent-test@example.com"
}
}
}
}'
get_order_status — Admin API (proxied)
Never call Admin directly from agent code. Wrap it in your own authenticated backend. The query itself is straightforward.
query OrderStatus($id: ID!) {
order(id: $id) {
id
name
displayFinancialStatus
displayFulfillmentStatus
fulfillments(first: 5) {
trackingInfo { number url company }
status
createdAt
}
}
}
Tip · API Version Pinning
Shopify ships quarterly releases (2026-04, 2026-07, 2026-10, 2027-01 — re-verify before launch). Pin your agent integration to a specific version and audit each release for breaking changes in the changelog. Unstable and unsupported versions are subject to deprecation — never pin to unstable in production.
For the full eight-endpoint pattern and the framework-agnostic OpenAPI spec, see the API Endpoint spoke.
The 30-Day AgentMall Newsletter
One operator note per week. The weekend build in your inbox.
Field-tested patterns, real failure modes, and the next platform spoke as it ships. No fluff. Cancel any time.
Layer 3 · MCP
Two MCP endpoints out of the box. One custom Python wrapper on top.
Shopify is the first major commerce platform to ship a native MCP server. Two endpoints, both live (rollout staged across the Summer '25 and Winter '26 Editions — re-verify per plan):
Native · /api/mcp
Storefront MCP
The general-purpose Storefront MCP. Read-only tools for catalog search, product detail, cart, policy lookup. Tool set is fixed by Shopify — you cannot register custom tools here.
UCP · /api/ucp/mcp
UCP-Conformant MCP
The UCP-shaped endpoint that powers ChatGPT, Microsoft Copilot, and the Jan 11, 2026 NRF Google co-launch (re-verify). Requires an agent profile URL in each request. Conforms to the UCP catalog tool schema.
Your Server · Custom
Custom Python MCP Wrapper
Your own MCP server, deployed beside the native ones. Add store-specific tools (size charts, loyalty lookups, bundle calculators) the fixed native tool list cannot express. Registered in Claude Desktop next to the native endpoint.
The native /api/mcp tool surface
Tool names are stable across stores. Inputs vary slightly per plan — re-verify the exact schema by calling tools/list against your own store before launch.
| Tool | Purpose | Returns |
search_shop_catalog | Full-text + filter search across active products | Array of products with handle, title, price, image, availability |
get_product_details | Variant-level detail for a product by handle or ID | Variants, options, price, inventory, media |
get_cart | Read current cart state by cart ID | Cart lines, totals, checkoutUrl |
update_cart | Add, update, remove lines; update buyer identity | Updated cart with checkoutUrl |
search_shop_policies_and_faqs | Search merchant policies (returns, shipping, terms) and FAQ content | Relevant policy or FAQ excerpts |
curl test against the native endpoint
curl -X POST "https://{your-store}.myshopify.com/api/mcp" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": "1",
"method": "tools/list"
}'
You should receive a JSON-RPC response listing the five tools above with their input schemas. If you get a 404 or a Shopify storefront HTML page, the MCP endpoint is not yet active on your store — log it, re-check after 30 days, and proceed with the custom wrapper as your interim path. To exercise a tool:
curl -X POST "https://{your-store}.myshopify.com/api/mcp" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": "2",
"method": "tools/call",
"params": {
"name": "search_shop_catalog",
"arguments": { "query": "wireless headphones", "first": 5 }
}
}'
Validating with @shopify/dev-mcp
Shopify ships an official dev-time MCP server that wraps the same surface for local testing and provides utilities to introspect schemas. Install with npm install -g @shopify/dev-mcp and run shopify-dev-mcp to spin up a local introspection server pointing at your shop. Use this during development; do not expose it to production buyers.
The custom Python MCP wrapper — full source
Native tools cover catalog and cart. For everything else — a size chart lookup, a loyalty tier check, a bundle calculator, a promo-code validator — you ship your own MCP server next to the native one. The wrapper below uses the official MCP Python SDK and the Storefront API. Save as shopify_mcp_server.py, set SHOPIFY_DOMAIN and SHOPIFY_STOREFRONT_TOKEN in your environment, and run pip install mcp httpx.
#!/usr/bin/env python3
"""
shopify_mcp_server.py
A custom MCP server that sits alongside the native /api/mcp endpoint.
Wraps the Shopify Storefront API for tools the native server does not expose.
"""
import os
import asyncio
import httpx
from typing import Any, Optional
from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationOptions
from mcp.types import Tool, TextContent
import mcp.server.stdio
SHOPIFY_DOMAIN = os.environ["SHOPIFY_DOMAIN"] # e.g. mystore.myshopify.com
SHOPIFY_TOKEN = os.environ["SHOPIFY_STOREFRONT_TOKEN"] # Storefront access token
API_VERSION = "2026-04"
GRAPHQL_URL = f"https://{SHOPIFY_DOMAIN}/api/{API_VERSION}/graphql.json"
server = Server("shopify-custom-mcp")
# --- Shared GraphQL caller ---------------------------------------------------
async def gql(query: str, variables: Optional[dict[str, Any]] = None) -> dict:
async with httpx.AsyncClient(timeout=15) as client:
r = await client.post(
GRAPHQL_URL,
headers={
"Content-Type": "application/json",
"X-Shopify-Storefront-Access-Token": SHOPIFY_TOKEN,
},
json={"query": query, "variables": variables or {}},
)
r.raise_for_status()
body = r.json()
if "errors" in body:
raise RuntimeError(f"Storefront GraphQL error: {body['errors']}")
return body["data"]
# --- Tool definitions --------------------------------------------------------
@server.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name="get_size_chart",
description="Return the size chart for a product by handle. Reads custom.size_chart metafield.",
inputSchema={
"type": "object",
"properties": {"handle": {"type": "string"}},
"required": ["handle"],
},
),
Tool(
name="check_buyable",
description="Strict buyability check: requires availableForSale AND quantityAvailable > 0 AND not currentlyNotInStock.",
inputSchema={
"type": "object",
"properties": {"variant_id": {"type": "string"}},
"required": ["variant_id"],
},
),
Tool(
name="get_bundle_price",
description="Compute total price for a list of variant IDs in a single agent-suggested bundle.",
inputSchema={
"type": "object",
"properties": {
"variant_ids": {"type": "array", "items": {"type": "string"}}
},
"required": ["variant_ids"],
},
),
Tool(
name="search_with_filters",
description="Search products with structured filters (price range, vendor, available only).",
inputSchema={
"type": "object",
"properties": {
"q": {"type": "string"},
"price_min": {"type": "number"},
"price_max": {"type": "number"},
"available_only": {"type": "boolean", "default": True},
},
"required": ["q"],
},
),
]
# --- Tool implementations ----------------------------------------------------
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
if name == "get_size_chart":
data = await gql(
"""query($h:String!){ productByHandle(handle:$h){
title
metafield(namespace:"custom", key:"size_chart"){ value type }
}}""",
{"h": arguments["handle"]},
)
prod = data.get("productByHandle") or {}
mf = prod.get("metafield") or {}
return [TextContent(type="text", text=mf.get("value") or "No size chart on file.")]
if name == "check_buyable":
data = await gql(
"""query($id:ID!){ node(id:$id){ ... on ProductVariant {
availableForSale quantityAvailable currentlyNotInStock
product { title } title sku
}}}""",
{"id": arguments["variant_id"]},
)
v = data.get("node") or {}
ok = bool(v.get("availableForSale")) and (v.get("quantityAvailable") or 0) > 0 and not v.get("currentlyNotInStock")
return [TextContent(
type="text",
text=f"buyable={ok} title={v.get('product',{}).get('title','')}/{v.get('title','')} qty={v.get('quantityAvailable')}"
)]
if name == "get_bundle_price":
data = await gql(
"""query($ids:[ID!]!){ nodes(ids:$ids){ ... on ProductVariant {
id title price { amount currencyCode }
}}}""",
{"ids": arguments["variant_ids"]},
)
nodes = data.get("nodes") or []
total = 0.0
currency = "USD"
lines = []
for n in nodes:
if not n: continue
amt = float(n["price"]["amount"])
currency = n["price"]["currencyCode"]
total += amt
lines.append(f" {n['id']} {n['title']} {amt:.2f} {currency}")
return [TextContent(type="text", text="\n".join(lines + [f"TOTAL {total:.2f} {currency}"]))]
if name == "search_with_filters":
q = arguments["q"]
if arguments.get("available_only", True):
q = f"{q} available_for_sale:true"
if "price_min" in arguments:
q = f"{q} variants.price:>={arguments['price_min']}"
if "price_max" in arguments:
q = f"{q} variants.price:<={arguments['price_max']}"
data = await gql(
"""query($q:String!){ products(first:10, query:$q){ edges { node {
handle title vendor
priceRange { minVariantPrice { amount currencyCode } }
}}}}""",
{"q": q},
)
edges = data.get("products", {}).get("edges", [])
lines = [f"{e['node']['handle']} {e['node']['title']} {e['node']['priceRange']['minVariantPrice']['amount']} {e['node']['priceRange']['minVariantPrice']['currencyCode']}" for e in edges]
return [TextContent(type="text", text="\n".join(lines) or "No results.")]
return [TextContent(type="text", text=f"Unknown tool: {name}")]
# --- Entrypoint --------------------------------------------------------------
async def main():
async with mcp.server.stdio.stdio_server() as (read, write):
await server.run(
read,
write,
InitializationOptions(
server_name="shopify-custom-mcp",
server_version="0.1.0",
capabilities=server.get_capabilities(
notification_options=NotificationOptions(),
experimental_capabilities={},
),
),
)
if __name__ == "__main__":
asyncio.run(main())
Wire it into Claude Desktop
Edit ~/Library/Application Support/Claude/claude_desktop_config.json on macOS (or %APPDATA%\Claude\claude_desktop_config.json on Windows) and add both the native Shopify MCP endpoint and your custom wrapper.
{
"mcpServers": {
"shopify-native": {
"type": "http",
"url": "https://{your-store}.myshopify.com/api/mcp"
},
"shopify-custom": {
"command": "python3",
"args": ["/absolute/path/to/shopify_mcp_server.py"],
"env": {
"SHOPIFY_DOMAIN": "{your-store}.myshopify.com",
"SHOPIFY_STOREFRONT_TOKEN": "shpat_XXXXXXXXXXXXXXXXX"
}
}
}
}
Why Two Servers, Not One
The native server gets you the long-tail catalog, cart, and policy surface for free, with Shopify shipping updates as the platform evolves. Your custom server stays small and store-specific — the four tools above plus whatever your business uniquely needs. You get the platform velocity of the native endpoint and the flexibility of a custom server without re-implementing search, cart, or policy lookup yourself.
For the MCP protocol primer (JSON-RPC envelope, tool schemas, transport options) see the MCP spoke.
Layer 4 · UCP Compatibility
Shopify Catalog enrollment is automatic. The eight-step checkout is the contract.
The Universal Commerce Protocol (UCP) launched as a Google + Shopify co-developed standard at NRF on January 11, 2026 (re-verify before launch). Every eligible Shopify merchant whose products are in Shopify Catalog is automatically enrolled — there is no separate signup, no developer queue, no integration to maintain. The Shopify Catalog feed becomes the UCP product surface, and the /api/ucp/mcp endpoint is the UCP tool surface.
Where your products show up
| Channel | Mechanism | Status (re-verify before launch) |
| ChatGPT shopping results | Shopify Catalog → ChatGPT | Live for eligible US merchants on Standard plan and up |
| Microsoft Copilot | Shopify Catalog → Copilot Merchant Program | Live for eligible US merchants |
| Google AI Mode & Gemini | Shopify Catalog → Google Agentic Storefronts | Staged rollout to select US shops, expanding |
| Other UCP-conformant agents | UCP /api/ucp/mcp endpoint | Open spec; any UCP-conformant agent can connect |
Standard vs Plus differences
| Capability | Standard / Advanced | Plus |
| UCP Catalog enrollment | Automatic for eligible products | Automatic + B2B catalog feeds |
| Checkout MCP partner preview | Not currently included (re-verify) | Eligible to apply (re-verify) |
| Shop Pay agent checkout | Standard checkoutUrl handoff | Standard checkoutUrl handoff; partner-preview direct checkout available |
| API rate limit headroom | Standard limits (re-verify) | Higher per-app quotas (re-verify) |
The eight-step UCP checkout state machine
UCP normalizes the agent purchase flow into eight discrete states. Shopify's /api/ucp/mcp endpoint conforms; you do not implement the state machine yourself, but you must understand it so your custom tools and your error messages slot in cleanly.
| # | State | Shopify Backing | Agent Action |
| 1 | discover | Storefront products query / UCP search_catalog | Find candidate products |
| 2 | inspect | Storefront product query / UCP get_product | Read details, variants, policies |
| 3 | check_availability | availableForSale + quantityAvailable | Confirm buyable variant |
| 4 | build_intent | cartCreate | Create cart with selected lines |
| 5 | quote | Cart cost.totalAmount | Read final total incl. tax + shipping |
| 6 | confirm | Buyer authorization (Shop Pay or Checkout MCP) | Lock buyer identity + payment intent |
| 7 | execute | Hand off cart.checkoutUrl or invoke Checkout MCP | Complete payment |
| 8 | track | Admin order query (proxied) + webhooks | Surface fulfillment status |
Critical · Direct Checkout Is Preview-Only
Checkout MCP — the path that lets an agent complete payment without bouncing the buyer to a Shopify-hosted page — is in select-partner preview only (re-verify before launch). The default path on every Shopify plan is the checkoutUrl handoff: the agent surfaces the URL, the buyer pays on Shopify, and your agent listens via the order webhook. UCP exposes this cleanly as the requires_escalation state with a continue_url.
For the full UCP state machine, agent profile schema, and the cross-platform protocol detail see the UCP spoke.
§7 · Honest Trade-offs
What Shopify makes easy. What Shopify makes hard.
Easy
| Capability | Why It Is Easy |
| Auto-emitted Product schema | Dawn ships {{ product | structured_data }} — you only extend the gaps |
| Storefront API at every plan | GraphQL surface is identical across Basic, Shopify, Advanced, Plus |
| Native MCP server | No install, no app — read-only tools live at /api/mcp |
| UCP catalog enrollment | Automatic for eligible products via Shopify Catalog (re-verify before launch) |
| checkoutUrl handoff | Cart returns a hosted checkout URL on every plan — no auth complexity |
| Shop Pay buyer accounts | Cross-merchant trusted identity; surfaces in agent flows automatically |
| Quarterly API releases | Predictable version cadence (2026-04, -07, -10, etc.) — re-verify before launch |
Hard
| Capability | Why It Is Hard |
| Custom tools in native MCP | Native tool list is fixed — you run a separate custom MCP server |
| Direct agent checkout | Checkout MCP is select-partner preview only — default is checkoutUrl handoff |
| Admin API from the agent | High-privilege scopes; must proxy through your own authenticated backend |
| "Continue selling when out of stock" | Returns availableForSale: true even when quantityAvailable is 0 — agents must check both |
| Quote/quantityAvailable accuracy | Eventually-consistent inventory; you may need a 409 retry on cartLinesAdd |
| GTIN coverage | Lives in variant.barcode — must be populated per-variant; absent on many migrated stores |
| Multi-currency schema | Single Product block; per-region pricing schema requires per-market re-emission |
§8 · End-to-End Verification
Test it with Claude Desktop. Real cart, real checkout URL.
You have a Storefront access token, a native MCP endpoint, and a custom Python MCP server. The last weekend hour is the end-to-end test: a real agent, your real store, a real checkout URL that opens in a real browser. Use Claude Desktop with both MCP servers registered. The prompts below walk the agent through the canonical eight-step UCP flow and exercise every layer you built.
Setup
- Confirm
claude_desktop_config.json contains both shopify-native and shopify-custom entries (see Layer 3).
- Restart Claude Desktop fully — quit from the menu bar, do not just close the window.
- Verify the tool icons appear in a new chat. You should see tools from both servers.
- Have a real product in your store with at least one variant in stock and a non-zero quantity.
The eight-step prompt walkthrough
| # | Prompt to Claude | What Should Happen |
| 1 | "Search my Shopify store for products under $50 related to {your category}." | Claude calls search_shop_catalog (native) and returns a list with handles, titles, prices. |
| 2 | "Show me variant details for the first result." | Claude calls get_product_details and returns variants, options, inventory. |
| 3 | "Confirm the first variant is actually buyable using check_buyable." | Claude calls your custom check_buyable tool and returns buyable=true plus quantity. |
| 4 | "Add it to a new cart with quantity 1 and return the checkout URL." | Claude calls update_cart and returns a real Shopify checkoutUrl. |
| 5 | "Open this URL in my browser." (manual) | Shopify hosted checkout loads with the line item and accurate total. |
| 6 | "Look up my store's return policy." | Claude calls search_shop_policies_and_faqs and returns relevant text. |
| 7 | "Look up the size chart for {a product with one}." | Claude calls your custom get_size_chart and returns the metafield value. |
| 8 | "Compute the total price if I bundle these three variant IDs." | Claude calls your custom get_bundle_price and returns line-by-line + total. |
Tip · Tag the Order
When you complete a real test checkout, tag the resulting order with agent-test via a draft order or a tag rule. Then in Shopify Admin reporting, filter by tag to measure agent-originated revenue separately from organic and paid traffic. This becomes your baseline metric for the first 90 days post-launch.
What to log
- Which tools the agent called, in what order, and how many times.
- Any
userErrors returned from cartCreate or cartLinesAdd — especially out-of-stock or invalid-variant errors.
- Time from first prompt to a working
checkoutUrl in your browser — your target is under 60 seconds end-to-end.
- Any tool the agent attempted to call that does not exist — clue to add it to your custom server.
§9 · App Roundup
Shopify apps that shortcut the build.
Every price below is flagged "re-verify before launch" — App Store pricing tiers change, and a free tier may be capped to product count, store traffic, or feature scope. Confirm on each listing before installing.
| App | What It Does | Layer | Price (re-verify before launch) |
| Schema Plus for SEO | Extends Product, Breadcrumb, FAQ schema; covers most missing fields automatically | Layer 1 | Tiered — free starter through paid plans; re-verify on the App Store listing |
| JSON-LD for SEO | Adds Article, Recipe, Event, FAQ, and Review schema beyond Product | Layer 1 | Tiered subscription — re-verify before launch |
| Smart SEO | Schema + meta tags + sitemap in one suite | Layer 1 | Tiered subscription — re-verify before launch |
| Judge.me Product Reviews | Free review collection + ratings + AggregateRating metafield sync | Layer 1 | Free + paid tiers — re-verify before launch |
| Yotpo Reviews & UGC | Reviews + ratings + photo/video; syncs to AggregateRating | Layer 1 | Tiered subscription — re-verify before launch |
| Loox Product Reviews | Photo reviews with AggregateRating sync | Layer 1 | Tiered subscription — re-verify before launch |
| Matrixify (Excelify) | Bulk import/export including metafield columns for MPN and barcode | Layer 1 | Tiered subscription — re-verify before launch |
| Shopify CLI | Local theme + app development; deploy themes and run dev MCP server | Layer 1 + 3 | Free |
The picks-and-shovels equivalents that work across platforms (not Shopify-specific) are catalogued on the Picks & Shovels spoke.
§10 · Common Mistakes
Eight ways agent integrations break in production.
1. Still calling checkoutCreate
The legacy Storefront checkoutCreate mutation was permanently shut down on April 1, 2025 and returns 410. Migrate to the Cart API: cartCreate → cartLinesAdd → hand off cart.checkoutUrl. Audit your custom MCP server, your wrappers, and your saved agent prompts for the dead name.
2. Trusting availableForSale alone
Merchants who enable "Continue selling when out of stock" cause availableForSale to return true while quantityAvailable is 0. Your tool descriptions and your check_buyable custom tool must require all three: availableForSale: true AND quantityAvailable > 0 AND currentlyNotInStock: false. Re-check on every cartCreate — inventory shifts between search and checkout.
3. Missing GTIN coverage
Shopify stores the GTIN in variant.barcode. Most migrated stores leave this empty. Without it, Google AI Overviews demotes your products and AI shopping agents may exclude them from results entirely. Bulk-import barcodes from your supplier feed before launch using Matrixify or a CSV import.
4. No hasMerchantReturnPolicy or shippingDetails
The Dawn auto-emitted block omits both. Google and most agents treat the absence as a disqualifying signal for shopping results. Add them via the Liquid snippet in Layer 1 — and make the values in schema match your published policy pages exactly to avoid a structured-data-vs-content mismatch flag.
5. Hardcoded prices in the schema block
If you hand-edit the schema block and hardcode a price string, you will desync from Shopify's variant pricing the moment a variant changes. Always pull from variant.price via Liquid. Same for availability — always pull from variant.available.
6. Exposing the Admin access token to agent code
The Admin API token has broad write privilege — orders, inventory, customer data. Never put it in a Storefront-accessible config, never put it in a public MCP server response, never log it. Agent code uses the Storefront token only; Admin calls go through your own authenticated backend.
7. Pinning to "unstable" or a version older than two releases
Shopify ships quarterly API versions (2026-04, 2026-07, etc.) with a defined support window (re-verify before launch). Pinning to unstable means breaking changes can land any week. Pinning to a version older than the supported window means deprecated fields can disappear without notice. Pin to a stable version and audit each release.
8. Skipping the eventually-consistent inventory retry
Storefront inventory reads are not strongly consistent. An agent can read quantityAvailable: 1, call cartLinesAdd two seconds later, and get a userErrors response because the last unit just sold. Encode a single retry-with-fresh-read in your custom MCP tool, then surface a clean "out of stock" message — do not loop indefinitely.
§11 · FAQ
Frequently asked questions.
Does every Shopify store have a built-in MCP server, or do I need to install something?
Every Shopify store now ships with a built-in Storefront MCP server at https://{your-store}.myshopify.com/api/mcp — no app install, no configuration, no API key required for the read-only tools. A second endpoint at https://{your-store}.myshopify.com/api/ucp/mcp carries the UCP-conformant catalog tools and requires an agent profile URL in each request. Rollout was staged across the Summer '25 and Winter '26 Editions, so re-verify availability for your specific store and plan tier before launch.
What Shopify plan do I need to sell through AI agents like ChatGPT or Google AI Mode?
Any merchant with products eligible for Shopify Catalog and selling to US buyers is enrolled — no specific plan tier is required for ChatGPT or Microsoft Copilot. Google AI Mode and Gemini rolled out to select US-based shops first via Agentic Storefronts and continue to expand. Shop Pay and the standard checkoutUrl handoff work on every plan. Direct agent checkout completion via Checkout MCP is currently in select-partner preview and is not gated by plan alone — re-verify before launch.
What is the difference between the Storefront API and the Admin API for agent use?
The Storefront API is the public, GraphQL-only surface designed for buyer-facing and agent-facing traffic. It uses a low-privilege Storefront access token, runs at /api/2026-04/graphql.json, and covers products, search, cart, and checkout via cartCreate.checkoutUrl. The Admin API is high-privilege, server-side only, and required for order status, returns, and inventory writes. For agent shopping, use Storefront exclusively; proxy Admin calls through your own authenticated backend for post-purchase actions.
Can I add custom tools to the built-in Shopify MCP server?
No. The tool set on the native /api/mcp endpoint is fixed by Shopify — you cannot register custom tools or change the behavior of existing ones. For custom logic (a get_size_chart tool, a loyalty lookup, a bundle calculator) you run a separate MCP server alongside the native one. Your custom server can call both the Storefront API and the Admin API and gets registered in Claude Desktop or Cursor next to the native Shopify endpoint.
Does the auto-emitted Schema.org block from Dawn give me everything I need?
No. The {{ product | structured_data }} Liquid filter emits Product or ProductGroup with name, description, image, brand, price, and availability — and stops there. AggregateRating, GTIN, MPN, hasMerchantReturnPolicy, and shippingDetails are all missing. Google AI Overviews and most AI shopping agents treat the absence of hasMerchantReturnPolicy and shippingDetails as a disqualifying signal, so extend the block via the Liquid snippet in the Layer 1 section or install a schema app.
Will an agent be able to complete a checkout end-to-end with no human in the loop?
Only for buyers with a Shop Pay account and a trusted-agent credential operating under Checkout MCP — which is in select-partner preview today. The default Shopify flow is the checkoutUrl handoff: the agent calls cartCreate, receives a pre-authenticated checkout URL, and surfaces it to the buyer, who completes payment on Shopify's hosted checkout. UCP exposes this as the requires_escalation state with a continue_url for seamless human handoff. Re-verify Checkout MCP availability before launch.
How do I prevent agents from selling items that are technically out of stock?
Shopify lets merchants enable Continue selling when out of stock, which sets availableForSale to true while quantityAvailable is 0 — a recipe for an agent creating a cart for a backorder. The fix is to require both availableForSale: true AND quantityAvailable > 0 AND currentlyNotInStock: false before an agent presents an item as buyable. Encode this in your tool descriptions and re-check on every cartCreate, returning a 409-style userError if stock has shifted between search and checkout.
Why did my checkoutCreate mutation start returning 410?
Shopify permanently shut down the legacy Storefront checkoutCreate mutation on April 1, 2025. Any integration still calling it will 410. The current path is the Storefront Cart API: cartCreate, cartLinesAdd, cartLinesUpdate, cartBuyerIdentityUpdate, then hand off cart.checkoutUrl. Audit any older MCP wrappers or agent integrations for checkoutCreate and migrate before they break for real buyers.
§12 · Step-by-Step
The weekend build, in five steps.
Each step mirrors the HowTo JSON-LD at the top of this page word for word. Execute in order. The whole sequence should fit in two evenings or one full Saturday.
Step 1 — Audit and extend your Schema.org JSON-LD
Open your theme's sections/main-product.liquid or snippets/product-schema.liquid in the theme code editor and locate the application/ld+json block. Replace or extend it with the complete Liquid snippet from Layer 1 — adding AggregateRating, GTIN, hasMerchantReturnPolicy, and shippingDetails. Create matching metafield definitions under Settings → Custom data → Products, wire up your review app's rating metafield sync, and validate every change at Google's Rich Results Test before deploying.
Step 2 — Create your Storefront access token and verify API access
In Shopify Admin → Apps → Develop apps, create a new app, enable the Storefront API scopes (unauthenticated_read_product_listings, unauthenticated_read_inventory, unauthenticated_write_checkouts), install, and copy the Storefront access token. Run the list_products curl from Layer 2 against /api/2026-04/graphql.json and confirm the JSON response includes the 20-field MVP surface. Run cartCreate and confirm the returned checkoutUrl opens a real checkout in your browser.
Step 3 — Verify your native MCP endpoint
POST the tools/list JSON-RPC request from Layer 3 to https://{your-store}.myshopify.com/api/mcp and confirm you receive an enumerated list of tools. Then call search_catalog on the /api/ucp/mcp endpoint with a query relevant to your catalog. If either endpoint is not yet active for your store, log the gap in your launch doc, re-check after 30 days, and proceed with the custom MCP wrapper as an interim path.
Step 4 — Deploy a custom Python MCP wrapper for tools the native endpoint does not cover
Copy the shopify_mcp_server.py from Layer 3, set SHOPIFY_DOMAIN and SHOPIFY_STOREFRONT_TOKEN as environment variables, run pip install mcp httpx, and test locally with python shopify_mcp_server.py. Wire it into ~/Library/Application Support/Claude/claude_desktop_config.json next to the native MCP entry. Use this wrapper for custom tool logic — size charts, loyalty lookups, bundle calculators — that the fixed native tool list cannot express.
Step 5 — Run a real end-to-end agent test with Claude Desktop
Restart Claude Desktop after wiring the config. Ask Claude to search the store for a product under $50, fetch the variant details, add the first available variant to a cart, and return the checkout URL. Open the URL and confirm a valid Shopify hosted checkout. Then ask Claude to call search_shop_policies_and_faqs and confirm the store's return policy is returned. Tag any orders that originate from this flow so you can measure agent commerce revenue separately in admin reporting.
§13 · Continue the Guide
Next stops in the AgentMall guide.
Layer 4 · Protocol
UCP — Universal Commerce Protocol
The eight-step state machine, agent profile schema, and the cross-platform contract that powers ChatGPT, Copilot, and Google AI Mode.
Layer 3 · Protocol
MCP — Model Context Protocol
Tool schemas, JSON-RPC envelope, transports, and the framework you implement once and reuse across Claude, ChatGPT, Cursor, and any future agent runtime.
Layer 1 · Reference
Product Data — The 20-Field MVP
Field-by-field reference for the Schema.org block that gets you discoverable across every agent runtime, with mapping tables for Shopify, WooCommerce, and BigCommerce.
Layer 2 · Build
API Endpoint — The Eight-Endpoint Surface
The framework-agnostic, OpenAPI-driven endpoint pattern. FastAPI + Vercel reference implementation. Drop-in for any non-Shopify stack.
Market
The Agent Commerce Market
TAM, channel mix, and the buyer-side data that lets you size the agent-commerce opportunity before you ship a single tool.
Stack
Free vs Paid Stack Choices
Where the free tiers run out and which paid services are worth the upgrade for a real production agent integration.
Stack
The Agent-Commerce Stack
End-to-end reference stack — storefront, schema, API, MCP, UCP, observability — for operators building greenfield.
Tools
Picks & Shovels
The cross-platform tools (review collectors, schema validators, MCP scaffolds, monitoring) that work the same whether you are on Shopify, Woo, BigCommerce, or custom.
Pillar
The Full AgentMall Roadmap
The pillar page that ties the four layers and every platform spoke together into one 30-day operator plan.
The Window
The window for agent-ready Shopify stores is now.
Google + Shopify co-launched UCP at NRF on January 11, 2026 (re-verify per plan). The native /api/mcp endpoint is live. Checkout MCP is in select-partner preview. Every quarter that passes, the floor moves up — schema fields once treated as "nice to have" become disqualifying, the agent tool surface gets richer, and the buyers who are now experimenting in ChatGPT and Google AI Mode become habitual agent shoppers. The merchants who close all four layers this weekend get the compounding catalog placement, the trusted-store status, and the operator-class agent telemetry. The merchants who wait will spend the back half of 2026 catching up.
Open the AgentMall Roadmap →