Platform Spoke · Shopify
Spoke · Make Your Platform Agent-Ready

Make Your Shopify Store Agent-Ready in a Weekend — Schema, MCP, and the Checkout Hook.

Most of the agent-readiness layers are already on your Shopify store. You don't need to rebuild the stack. You need to extend the schema, populate a handful of metafields, verify the native MCP endpoint at /api/mcp, and confirm your Shopify Catalog enrollment for UCP-powered checkout. Two days, four layers, one operational store ready for ChatGPT, Microsoft Copilot, and Google AI Mode traffic.

4
Layers to Close
2 days
Weekend Build
/api/mcp
Live on Every Store
Jan 2026
UCP Co-Launch w/ Google
§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

FieldSourceShopify LocationStatus
@typeSchema.orgLiquid literalAuto
nameproduct.titleProduct detailAuto
descriptionproduct.descriptionProduct detailAuto
imageproduct.featured_imageProduct mediaAuto
brandproduct.vendorProduct detailAuto
skuvariant.skuVariantAuto
pricevariant.priceVariantAuto
priceCurrencyshop.currencyShop settingsAuto
availabilityvariant.availableVariant inventoryAuto
urlshop.url + product.urlComputedAuto
gtin13 / gtin14variant.barcodeVariant barcodePopulate
mpnMetafieldcustom.mpnPopulate
aggregateRating.ratingValueReview app metafieldreviews.ratingAdd
aggregateRating.reviewCountReview app metafieldreviews.rating_countAdd
hasMerchantReturnPolicy.returnPolicyCategoryLiquid literalSnippetAdd
hasMerchantReturnPolicy.merchantReturnDaysLiquid literalSnippetAdd
shippingDetails.shippingRateLiquid literalSnippetAdd
shippingDetails.shippingDestinationLiquid literalSnippetAdd
shippingDetails.deliveryTimeLiquid literalSnippetAdd
itemConditionLiquid literalSnippetAdd

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.KeyTypeSourceNotes
custom.mpnSingle line textManual or CSV importManufacturer part number; not always required if GTIN present
reviews.ratingRating (Shopify type)Review app sync0.0–5.0 scale; Judge.me, Yotpo, Loox, Stamped all support this
reviews.rating_countIntegerReview app syncTotal count of reviews aggregated for ratingValue

Schema apps if you do not want to edit Liquid

AppApproachPrice (re-verify before launch)
Schema Plus for SEOAuto-extends product, breadcrumb, FAQ schema; full ecosystem of typesTiered — free starter through paid plans; re-verify on the App Store listing before launch
JSON-LD for SEOAdds Article, Recipe, Event, FAQ, and review schema on top of productTiered subscription — re-verify before launch
Smart SEOBundles meta tags + schema + sitemaps in one suiteTiered subscription — re-verify before launch
SEO ManagerSchema plus meta tag and broken link auditingTiered 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

PropertyStorefront APIAdmin API
Surface/api/2026-04/graphql.json/admin/api/2026-04/graphql.json
ProtocolGraphQL onlyGraphQL preferred; REST legacy as of Oct 1, 2024
AuthStorefront access token (low privilege, public)Admin access token (high privilege, server-only)
Who uses itBuyer-facing apps, themes, AI agents directlyBackend services, scripts, never the agent directly
Rate limitCalls-per-IP throttle; varies by app/store class (re-verify before launch)Calculated cost model — points per query (re-verify before launch)
Typical agent uselist_products, search, cart, checkoutUrl handofforder status, returns, inventory adjustments (proxied)
Cart vs CheckoutCart API: cartCreate → cartLinesAdd → cart.checkoutUrlOrder 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: cartCreatecartLinesAdd → 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

  1. Shopify Admin → Apps → Develop apps → Create an app.
  2. Configuration → Storefront API access → enable scopes: unauthenticated_read_product_listings, unauthenticated_read_product_inventory, unauthenticated_read_product_pickup_locations, unauthenticated_write_checkouts.
  3. Install the app to your store and copy the Storefront access token from the API credentials tab.
  4. 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 EndpointShopify APIOperation
list_productsStorefrontproducts(first: N) query
get_product_detailsStorefrontproduct(handle: $h) query
search_productsStorefrontproducts(query: $q) with search syntax
check_availabilityStorefrontproductVariant.availableForSale + quantityAvailable
initiate_checkoutStorefrontcartCreate mutation → return cart.checkoutUrl
get_order_statusAdmin (proxy)order(id: $id) query with fulfillmentStatus
webhook_order_updatesAdmin WebhooksORDERS_UPDATED, FULFILLMENTS_CREATE topics
get_manifestNative MCPtools/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.

ToolPurposeReturns
search_shop_catalogFull-text + filter search across active productsArray of products with handle, title, price, image, availability
get_product_detailsVariant-level detail for a product by handle or IDVariants, options, price, inventory, media
get_cartRead current cart state by cart IDCart lines, totals, checkoutUrl
update_cartAdd, update, remove lines; update buyer identityUpdated cart with checkoutUrl
search_shop_policies_and_faqsSearch merchant policies (returns, shipping, terms) and FAQ contentRelevant 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

ChannelMechanismStatus (re-verify before launch)
ChatGPT shopping resultsShopify Catalog → ChatGPTLive for eligible US merchants on Standard plan and up
Microsoft CopilotShopify Catalog → Copilot Merchant ProgramLive for eligible US merchants
Google AI Mode & GeminiShopify Catalog → Google Agentic StorefrontsStaged rollout to select US shops, expanding
Other UCP-conformant agentsUCP /api/ucp/mcp endpointOpen spec; any UCP-conformant agent can connect

Standard vs Plus differences

CapabilityStandard / AdvancedPlus
UCP Catalog enrollmentAutomatic for eligible productsAutomatic + B2B catalog feeds
Checkout MCP partner previewNot currently included (re-verify)Eligible to apply (re-verify)
Shop Pay agent checkoutStandard checkoutUrl handoffStandard checkoutUrl handoff; partner-preview direct checkout available
API rate limit headroomStandard 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.

#StateShopify BackingAgent Action
1discoverStorefront products query / UCP search_catalogFind candidate products
2inspectStorefront product query / UCP get_productRead details, variants, policies
3check_availabilityavailableForSale + quantityAvailableConfirm buyable variant
4build_intentcartCreateCreate cart with selected lines
5quoteCart cost.totalAmountRead final total incl. tax + shipping
6confirmBuyer authorization (Shop Pay or Checkout MCP)Lock buyer identity + payment intent
7executeHand off cart.checkoutUrl or invoke Checkout MCPComplete payment
8trackAdmin order query (proxied) + webhooksSurface 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

CapabilityWhy It Is Easy
Auto-emitted Product schemaDawn ships {{ product | structured_data }} — you only extend the gaps
Storefront API at every planGraphQL surface is identical across Basic, Shopify, Advanced, Plus
Native MCP serverNo install, no app — read-only tools live at /api/mcp
UCP catalog enrollmentAutomatic for eligible products via Shopify Catalog (re-verify before launch)
checkoutUrl handoffCart returns a hosted checkout URL on every plan — no auth complexity
Shop Pay buyer accountsCross-merchant trusted identity; surfaces in agent flows automatically
Quarterly API releasesPredictable version cadence (2026-04, -07, -10, etc.) — re-verify before launch

Hard

CapabilityWhy It Is Hard
Custom tools in native MCPNative tool list is fixed — you run a separate custom MCP server
Direct agent checkoutCheckout MCP is select-partner preview only — default is checkoutUrl handoff
Admin API from the agentHigh-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 accuracyEventually-consistent inventory; you may need a 409 retry on cartLinesAdd
GTIN coverageLives in variant.barcode — must be populated per-variant; absent on many migrated stores
Multi-currency schemaSingle 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

  1. Confirm claude_desktop_config.json contains both shopify-native and shopify-custom entries (see Layer 3).
  2. Restart Claude Desktop fully — quit from the menu bar, do not just close the window.
  3. Verify the tool icons appear in a new chat. You should see tools from both servers.
  4. 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 ClaudeWhat 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

§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.

AppWhat It DoesLayerPrice (re-verify before launch)
Schema Plus for SEOExtends Product, Breadcrumb, FAQ schema; covers most missing fields automaticallyLayer 1Tiered — free starter through paid plans; re-verify on the App Store listing
JSON-LD for SEOAdds Article, Recipe, Event, FAQ, and Review schema beyond ProductLayer 1Tiered subscription — re-verify before launch
Smart SEOSchema + meta tags + sitemap in one suiteLayer 1Tiered subscription — re-verify before launch
Judge.me Product ReviewsFree review collection + ratings + AggregateRating metafield syncLayer 1Free + paid tiers — re-verify before launch
Yotpo Reviews & UGCReviews + ratings + photo/video; syncs to AggregateRatingLayer 1Tiered subscription — re-verify before launch
Loox Product ReviewsPhoto reviews with AggregateRating syncLayer 1Tiered subscription — re-verify before launch
Matrixify (Excelify)Bulk import/export including metafield columns for MPN and barcodeLayer 1Tiered subscription — re-verify before launch
Shopify CLILocal theme + app development; deploy themes and run dev MCP serverLayer 1 + 3Free

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: cartCreatecartLinesAdd → 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 →

One AgentMall note per week.

Platform-specific weekend builds, real failure modes from operator logs, and the next spoke the morning it ships. No fluff. Cancel any time.