A condensed, do-this-then-do-that guide for an agent that needs to talk to a UCP-enabled Shopify storefront using the MCP transport. Worked examples use https://retro-rewind-shop.myshopify.com/ throughout.
The full spec lives at https://github.com/Universal-Commerce-Protocol/ucp and https://ucp.dev. This document is intentionally lossy: it covers only what's needed to discover a business, search its catalog, link identity via OAuth, and complete a checkout.
UCP requires that every request you make declares who you are by URL. You must therefore host a platform profile JSON document at a stable HTTPS URL before doing anything else.
Host this somewhere reachable (S3, GitHub Pages, your own server). Call its URL $AGENT_PROFILE_URL.
{
"ucp": {
"version": "2026-04-08",
"services": {
"dev.ucp.shopping": [
{
"version": "2026-04-08",
"spec": "https://ucp.dev/2026-04-08/specification/overview",
"transport": "mcp",
"schema": "https://ucp.dev/2026-04-08/services/shopping/mcp.openrpc.json",
"endpoint": "https://your-agent.example.com/ucp/mcp"
}
]
},
"capabilities": {
"dev.ucp.shopping.catalog.search": [{"version": "2026-04-08", "spec": "https://ucp.dev/2026-04-08/specification/catalog", "schema": "https://ucp.dev/2026-04-08/schemas/shopping/catalog_search.json"}],
"dev.ucp.shopping.catalog.lookup": [{"version": "2026-04-08", "spec": "https://ucp.dev/2026-04-08/specification/catalog", "schema": "https://ucp.dev/2026-04-08/schemas/shopping/catalog_lookup.json"}],
"dev.ucp.shopping.checkout": [{"version": "2026-04-08", "spec": "https://ucp.dev/2026-04-08/specification/checkout", "schema": "https://ucp.dev/2026-04-08/schemas/shopping/checkout.json"}],
"dev.ucp.shopping.fulfillment": [{"version": "2026-04-08", "spec": "https://ucp.dev/2026-04-08/specification/fulfillment", "schema": "https://ucp.dev/2026-04-08/schemas/shopping/fulfillment.json", "extends": "dev.ucp.shopping.checkout"}],
"dev.ucp.common.identity_linking": [{"version": "2026-04-08", "spec": "https://ucp.dev/specification/identity-linking", "schema": "https://ucp.dev/schemas/common/identity_linking.json"}]
},
"payment_handlers": {}
}
}The URL serving this JSON MUST:
- Be HTTPS.
- Return
Cache-Control: public, max-age=86400(or anymax-age≥ 60, neverprivate,no-store, orno-cache). Businesses will reject the request otherwise. - Not redirect (no 3xx).
- Be a valid JSON object matching the schema above.
export BUSINESS_HOST="https://retro-rewind-shop.myshopify.com"
export AGENT_PROFILE_URL="https://your-agent.example.com/.well-known/ucp" # the JSON aboveEvery MCP tool call has this shape. Pay attention to meta.ucp-agent.profile — it is mandatory on every call.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "<tool_name>",
"arguments": {
"meta": {
"ucp-agent": { "profile": "$AGENT_PROFILE_URL" }
},
"<domain_payload_key>": { ... }
}
}
}<domain_payload_key> is catalog for catalog tools and checkout for checkout tools. For complete_checkout / cancel_checkout, meta must also contain an idempotency-key (UUIDv4).
- Transport failure → JSON-RPC
errorfield. Most common:-32001 UCP discovery failed(your profile URL is wrong or uncacheable). Fix and retry. - Business outcome → JSON-RPC
result.structuredContent. Inside that:ucp.version/ucp.capabilities— the negotiated set for this response.messages[]— zero or more diagnostics. Each hastype(error/warning/info),code,content, and (for errors)severity.- The domain payload (e.g.
products,id/status/line_itemsfor checkouts).
- You know the business's base domain (
$BUSINESS_HOST).
GET $BUSINESS_HOST/.well-known/ucp. Follow one redirect if returned (Shopify shops redirectretro-rewind-shop.myshopify.comto a canonical hash subdomain).- Parse JSON. Cache by
Cache-Controldirectives. - Locate the MCP endpoint:
ucp.services["dev.ucp.shopping"][?transport=="mcp"].endpoint. Call this$MCP_ENDPOINT. - Confirm the capabilities you need are present in
ucp.capabilities(e.g.dev.ucp.shopping.catalog.search,dev.ucp.shopping.checkout,dev.ucp.common.identity_linking). - Note
ucp.payment_handlers— you'll need them at checkout completion time. - If
ucp.capabilities["dev.ucp.common.identity_linking"][0].config.scopesis absent or empty, identity linking is purely optional (e.g. retro-rewind-shop). If populated, the listed scopes gate the operations they name behind user auth (see Task D).
curl -sLi "$BUSINESS_HOST/.well-known/ucp"$MCP_ENDPOINTis set. Forretro-rewind-shop.myshopify.comit resolves to:https://78eae1-95.myshopify.com/api/ucp/mcp- You have a map of capabilities and payment handlers.
{
"ucp": {
"version": "2026-04-08",
"services": {
"dev.ucp.shopping": [
{ "transport": "mcp", "endpoint": "https://78eae1-95.myshopify.com/api/ucp/mcp" },
{ "transport": "embedded", "schema": "https://ucp.dev/2026-04-08/services/shopping/embedded.openrpc.json" }
]
},
"capabilities": {
"dev.ucp.shopping.checkout": [{ "version": "2026-04-08" }],
"dev.ucp.shopping.fulfillment": [{ "extends": ["dev.ucp.shopping.checkout","dev.ucp.shopping.cart"] }],
"dev.ucp.shopping.discount": [{ "extends": ["dev.ucp.shopping.checkout","dev.ucp.shopping.cart"] }],
"dev.ucp.shopping.cart": [{ "version": "2026-04-08" }],
"dev.ucp.shopping.order": [{ "version": "2026-04-08" }],
"dev.ucp.shopping.catalog.search": [{ "version": "2026-04-08" }],
"dev.ucp.shopping.catalog.lookup": [{ "version": "2026-04-08" }],
"dev.ucp.common.identity_linking": [{ "version": "2026-04-08" }]
},
"payment_handlers": {
"com.google.pay": [ { "id": "gpay", ... } ],
"dev.shopify.card": [ { "id": "shopify.card", ... } ],
"dev.shopify.shop_pay":[ { "id": "shop_pay", ... } ]
}
}
}- Task A completed.
$MCP_ENDPOINTis known. dev.ucp.shopping.catalog.searchis present inucp.capabilities.$AGENT_PROFILE_URLis live and properly cached.
- Issue an MCP
tools/calltosearch_catalogwith the search query, optional filters, and pagination. - A valid request MUST include at least one of:
query,filters, or an extension-defined input. - Read
result.structuredContent.products[]from the response. - Pagination: pass the returned
pagination.cursorback in the next request to page through.
| Field | Type | Notes |
|---|---|---|
params.name |
string | "search_catalog" |
params.arguments.meta.ucp-agent.profile |
URL | $AGENT_PROFILE_URL |
params.arguments.catalog.query |
string | Free-text. Omit for browse. |
params.arguments.catalog.filters |
object | Optional. categories, price.min, price.max (in minor currency units). |
params.arguments.catalog.context.address_country |
ISO 3166-1 alpha-2 | Recommended to get region-correct prices. |
params.arguments.catalog.pagination.limit |
int | Requested page size. May be clamped silently. Default 10. |
params.arguments.catalog.pagination.cursor |
string | Opaque cursor from prior response. |
curl -sL -X POST "$MCP_ENDPOINT" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "search_catalog",
"arguments": {
"meta": { "ucp-agent": { "profile": "'"$AGENT_PROFILE_URL"'" } },
"catalog": {
"query": "retro t-shirt",
"context": { "address_country": "US" },
"filters": { "price": { "max": 5000 } },
"pagination": { "limit": 10 }
}
}
}
}'-
result.structuredContent.products[]is an array of products. Each product has:id— opaque product identifier.title,description.plain,url,media[].price_range.min.amount/price_range.max.amount— in minor units (cents).variants[]— each variant has its ownid,sku,price,availability.available, andoptions[].
-
result.structuredContent.pagination.has_next_page/pagination.cursorfor paging. -
If you need full product detail with selectable options, follow up with
get_product:curl -sL -X POST "$MCP_ENDPOINT" -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -d '{ "jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_product","arguments":{ "meta":{"ucp-agent":{"profile":"'"$AGENT_PROFILE_URL"'"}}, "catalog":{"id":"<PRODUCT_ID>","selected":[{"name":"Size","label":"M"}],"context":{"address_country":"US"}} }} }'
-
For bulk identifier resolution, use
lookup_catalogwithcatalog.ids: ["prod_a","prod_b"].
result.structuredContent.ucp.status == "error"withmessages[].code == "not_found"— product/identifier missing. Not a transport error.- JSON-RPC
error.code == -32602— input validation failure (e.g., empty request body). - JSON-RPC
error.code == -32001— discovery error. Re-check your platform profile URL and its cache headers.
The checkout has a small state machine. The platform drives it forward by calling tools; the business is the authoritative state holder.
incomplete ──update──▶ ready_for_complete ──complete──▶ complete_in_progress ──▶ completed
│ ▲
│ │
▼ │
requires_escalation ──(buyer finishes via continue_url)─────────────────────────────────────
Plus canceled (terminal, may be reached from anywhere).
- Task A completed.
$MCP_ENDPOINTanducp.payment_handlersare known. dev.ucp.shopping.checkoutis in the business's capabilities.- You have one or more variant
ids andquantitys to purchase (from Task B or pre-known). - You have buyer email and (for shipping) a destination address.
- You have a way to generate UUIDv4 idempotency keys.
- If the business's
dev.ucp.common.identity_linking.config.scopesincludesdev.ucp.shopping.checkout:manage, you need a Bearer access token from Task D. retro-rewind-shop does NOT gate checkout, so a token is not required for that store.
Identifier rule: on create_checkout, omit id on the top-level checkout and on each line_items[] entry. The business mints CartLine GIDs and returns them on the response — reuse those GIDs on subsequent update_checkout / complete_checkout calls when you need to reference a line.
curl -sL -X POST "$MCP_ENDPOINT" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "create_checkout",
"arguments": {
"meta": { "ucp-agent": { "profile": "'"$AGENT_PROFILE_URL"'" } },
"checkout": {
"buyer": {
"email": "jane.doe@example.com",
"first_name": "Jane",
"last_name": "Doe"
},
"line_items": [
{ "item": { "id": "<VARIANT_ID_FROM_SEARCH>" }, "quantity": 1 }
],
"currency": "USD",
"fulfillment": {
"methods": [
{
"type": "shipping",
"destinations": [
{
"street_address": "123 Main St",
"address_locality": "Springfield",
"address_region": "IL",
"postal_code": "62701",
"address_country": "US"
}
]
}
]
}
}
}
}
}'Capture from the response:
result.structuredContent.id→$CHECKOUT_IDresult.structuredContent.statusresult.structuredContent.continue_url(use for buyer handoff if escalation occurs)result.structuredContent.expires_at(don't act after this timestamp)result.structuredContent.ucp.payment_handlers— authoritative list of handlers/instruments available to this checkout. Use this, not the business profile's list.
Inspect messages[] and partition by severity:
| Severity | Action |
|---|---|
unrecoverable |
Stop. Inform user. Maybe restart with different inputs. |
recoverable |
Fix the offending input (path is given in message.path) and call update_checkout. Repeat until clean. |
requires_buyer_input |
Hand off to continue_url — you cannot complete from the API. |
requires_buyer_review |
Hand off to continue_url — buyer must approve. |
update_checkout is a full replacement of the resource. Send the entire desired state of checkout, omit only the top-level id. Read line_items[i].id from the create response — those are server-minted CartLine GIDs — and use them verbatim wherever you reference the line (e.g., fulfillment.methods[].line_item_ids):
curl -sL -X POST "$MCP_ENDPOINT" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "update_checkout",
"arguments": {
"meta": { "ucp-agent": { "profile": "'"$AGENT_PROFILE_URL"'" } },
"id": "'"$CHECKOUT_ID"'",
"checkout": {
"buyer": { "email": "jane.doe@example.com", "first_name": "Jane", "last_name": "Doe" },
"line_items": [
{ "id": "<CART_LINE_ID_FROM_CREATE>", "item": { "id": "<VARIANT_ID>" }, "quantity": 2 }
],
"currency": "USD",
"fulfillment": {
"methods": [
{ "id": "shipping_1", "line_item_ids": ["<CART_LINE_ID_FROM_CREATE>"],
"groups": [{ "id": "package_1", "selected_option_id": "express" }] }
]
}
}
}
}
}'Loop until status == "ready_for_complete".
Look at the ucp.payment_handlers you got back. Each handler is one of:
com.google.pay— collect via Google Pay JS SDK usingmerchant_info+allowed_payment_methodsfromconfig.dev.shopify.card— direct card entry; you'd use Shopify's tokenization endpoint.dev.shopify.shop_pay— Shop Pay redirect/handoff.
Each handler defines how to produce a payment.instruments[i].credential blob. This protocol-101 doc does not reproduce each handler's spec — follow the handler's spec URL.
The output of collection is a payment.instruments[] array of the shape:
{
"instruments": [
{
"id": "pi_xyz_1",
"handler_id": "<id FROM payment_handlers, e.g. \"gpay\" or \"shopify.card\">",
"type": "card",
"selected": true,
"display": { "brand": "visa", "last_digits": "4242", "description": "Visa •••• 4242" },
"billing_address": {
"street_address": "123 Main St",
"address_locality": "Springfield",
"address_region": "IL",
"postal_code": "62701",
"address_country": "US"
},
"credential": { "type": "PAYMENT_GATEWAY", "token": "<handler-issued-token>" }
}
]
}This is the moment the order is placed. Idempotency key is required.
IDEMPOTENCY_KEY=$(uuidgen)
curl -sL -X POST "$MCP_ENDPOINT" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "complete_checkout",
"arguments": {
"meta": {
"ucp-agent": { "profile": "'"$AGENT_PROFILE_URL"'" },
"idempotency-key": "'"$IDEMPOTENCY_KEY"'"
},
"id": "'"$CHECKOUT_ID"'",
"checkout": {
"payment": {
"instruments": [
{
"id": "pi_xyz_1",
"handler_id": "shopify.card",
"type": "card",
"selected": true,
"display": { "brand": "visa", "last_digits": "4242" },
"billing_address": {
"street_address": "123 Main St",
"address_locality": "Springfield",
"address_region": "IL",
"postal_code": "62701",
"address_country": "US"
},
"credential": { "type": "PAYMENT_GATEWAY", "token": "<handler-token>" }
}
]
},
"signals": {
"dev.ucp.buyer_ip": "203.0.113.42",
"dev.ucp.user_agent": "Mozilla/5.0"
}
}
}
}
}'If complete_checkout times out or returns a network error, retry with the same idempotency key. The business will deduplicate and return the same final outcome.
curl -sL -X POST "$MCP_ENDPOINT" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc":"2.0","id":9,"method":"tools/call",
"params": {
"name":"cancel_checkout",
"arguments":{
"meta":{"ucp-agent":{"profile":"'"$AGENT_PROFILE_URL"'"},"idempotency-key":"'"$(uuidgen)"'"},
"id":"'"$CHECKOUT_ID"'"
}
}
}'result.structuredContent.status == "completed".result.structuredContent.order.id— the placed order's identifier. Persist this.result.structuredContent.order.permalink_url— a URL the buyer can visit for a hosted order receipt.- The business sends a confirmation email to the buyer.
| Symptom | Meaning | What to do |
|---|---|---|
messages[].code == "out_of_stock" (recoverable) |
Item went out of stock between search and checkout. | Update with different variant/quantity. |
messages[].code == "address_undeliverable" |
Cannot ship there. | Update with a different destinations[]. |
messages[].code == "payment_failed" |
Payment did not authorize. | Collect a new instrument and call complete_checkout again with a new idempotency key (this is a new attempt). |
messages[].code == "eligibility_invalid" |
A claimed loyalty / member benefit could not be verified. | Either remove the claim from context.eligibility (and re-update), or switch to the qualifying instrument. |
HTTP 401 with WWW-Authenticate: Bearer and code == "identity_required" |
This business requires user auth for checkout. Go do Task D first, then retry with Authorization: Bearer <token>. |
|
HTTP 403 with code == "insufficient_scope" |
Token is missing a scope. Do not restart linking; use the OAuth incremental-authorization flow to request only the missing scope listed in the scope parameter of the WWW-Authenticate header. |
This is only required if the business's /.well-known/ucp lists scopes under dev.ucp.common.identity_linking.config.scopes, or if you receive an identity_required / insufficient_scope error mid-flow. For retro-rewind-shop it is optional but unlocks personalization.
UCP v1 identity linking is OAuth 2.0 Authorization Code with PKCE (S256), per RFC 6749 / 7636 / 9207.
dev.ucp.common.identity_linkingcapability advertised in business profile.- You have a registered OAuth client (
$CLIENT_ID, optional$CLIENT_SECRETfor confidential clients) with the business. - You have a registered
$REDIRECT_URIthat exactly matches what the business expects (byte-for-byte; loopback127.0.0.1/[::1]is the only exception, where the port is ignored). - You have a user agent (browser) you can launch.
Two-tier strict hierarchy. Do not silently fall through on any error other than 404 in step 1.
GET $BUSINESS_HOST/.well-known/oauth-authorization-server2xx→ use it.404→ try step 2.- Any other error → abort.
GET $BUSINESS_HOST/.well-known/openid-configuration2xx→ use it.- Anything else → abort.
curl -sL "$BUSINESS_HOST/.well-known/oauth-authorization-server"For retro-rewind-shop you get back (abridged):
{
"issuer": "https://shopify.com/authentication/84411744278",
"authorization_endpoint": "https://shopify.com/authentication/84411744278/oauth/authorize",
"token_endpoint": "https://shopify.com/authentication/84411744278/oauth/token",
"jwks_uri": "https://shopify.com/authentication/84411744278/.well-known/jwks.json",
"scopes_supported": ["openid","email","customer-account-api:full","customer-account-mcp-api:full","ucp:scopes:checkout_session"],
"response_types_supported": ["code"],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported":["client_secret_basic"],
"grant_types_supported": ["authorization_code","refresh_token","urn:ietf:params:oauth:grant-type:jwt-bearer"]
}Set:
export AUTH_ENDPOINT="https://shopify.com/authentication/84411744278/oauth/authorize"
export TOKEN_ENDPOINT="https://shopify.com/authentication/84411744278/oauth/token"
export ISSUER="https://shopify.com/authentication/84411744278"- Read
dev.ucp.common.identity_linking.config.scopesfrom the business profile. - Keep only scopes whose capability prefix you implement.
- Pick what your user is consenting to (e.g.
dev.ucp.shopping.order:read dev.ucp.shopping.order:manage). - Verify each selected scope appears in
scopes_supported. If not, abort; the merchant will reject anyway. - Request only the derived set — never a superset.
# code_verifier: 43..128 chars URL-safe
CODE_VERIFIER=$(openssl rand -base64 96 | tr -d '+/=\n' | head -c 96)
CODE_CHALLENGE=$(printf '%s' "$CODE_VERIFIER" | openssl dgst -sha256 -binary | openssl base64 | tr -d '=\n' | tr '/+' '_-')
STATE=$(uuidgen)Open this URL in a browser (or print it for the user). Reserved characters in scope and redirect_uri must be percent-encoded.
$AUTH_ENDPOINT
?response_type=code
&client_id=$CLIENT_ID
&redirect_uri=$REDIRECT_URI
&scope=<derived space-separated scopes>
&code_challenge=$CODE_CHALLENGE
&code_challenge_method=S256
&state=$STATE
The business redirects to $REDIRECT_URI?code=...&state=...&iss=....
Mandatory checks before continuing:
statereturned ==$STATEyou sent. Otherwise discard.issreturned == theissuerfrom Step D1 metadata, byte-for-byte. Do not trim trailing slashes. Otherwise discard.- If either check fails, abort. Do not proceed to the token endpoint.
Confidential client (Shopify retro-rewind-shop's advertised method is client_secret_basic):
curl -sL -X POST "$TOKEN_ENDPOINT" \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=authorization_code" \
--data-urlencode "code=$AUTH_CODE" \
--data-urlencode "redirect_uri=$REDIRECT_URI" \
--data-urlencode "code_verifier=$CODE_VERIFIER"Public client (no secret, PKCE-only):
curl -sL -X POST "$TOKEN_ENDPOINT" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=authorization_code" \
--data-urlencode "client_id=$CLIENT_ID" \
--data-urlencode "code=$AUTH_CODE" \
--data-urlencode "redirect_uri=$REDIRECT_URI" \
--data-urlencode "code_verifier=$CODE_VERIFIER"Expected response:
{
"access_token": "<opaque or JWT>",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "<opaque>",
"scope": "<granted scopes, space-separated>"
}On subsequent UCP MCP requests, present the token in the standard HTTP header in addition to the meta.ucp-agent.profile field:
curl -sL -X POST "$MCP_ENDPOINT" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{ "jsonrpc":"2.0","id":1,"method":"tools/call","params":{ ... }}'$ACCESS_TOKEN(and$REFRESH_TOKEN) are stored securely.- Subsequent gated capability requests authenticate the user.
- On user unlink action, you MUST call the business's
revocation_endpoint(RFC 7009) with the refresh token to invalidate the grant.
- HTTP
401 identity_requiredfrom a UCP endpoint: token absent/invalid/expired. Refresh or re-initiate Step D4. - HTTP
403 insufficient_scope: token is good but missing scopes. Read thescope=parameter in theWWW-Authenticateheader (the full required set for that operation), diff against scopes you already hold, and request just the missing ones via incremental authorization (re-run D3–D6 with the reduced set). Do not re-link from scratch. - PKCE mismatch at token endpoint →
invalid_grant. Restart from D3. - Client auth failure at token endpoint →
invalid_client. Check the auth method matchestoken_endpoint_auth_methods_supported.
| Tool | Capability | Idempotency key required? |
|---|---|---|
search_catalog |
dev.ucp.shopping.catalog.search |
no |
lookup_catalog, get_product |
dev.ucp.shopping.catalog.lookup |
no |
create_checkout |
dev.ucp.shopping.checkout |
no |
get_checkout |
dev.ucp.shopping.checkout |
no |
update_checkout |
dev.ucp.shopping.checkout |
no |
complete_checkout |
dev.ucp.shopping.checkout |
yes |
cancel_checkout |
dev.ucp.shopping.checkout |
yes |
| Code | Meaning | Likely cause |
|---|---|---|
-32600 |
Invalid request | Malformed JSON-RPC envelope. |
-32601 |
Method not found | Wrong tool name. |
-32602 |
Invalid params | Schema validation failed. Re-check required fields. |
-32000 |
Server / capability error | Generic UCP error. Read error.data. |
-32001 |
UCP discovery / negotiation failed | Profile URL unreachable, malformed, or non-cacheable. Fix the platform profile in §0. |
All amount fields are integers in minor units of currency (e.g. cents for USD). Negative = subtractive. A totals[] array contains exactly one subtotal entry and exactly one total entry; do not aggregate or reorder when displaying — render in the order the business sent.
| Resource | Required cache headers | Notes |
|---|---|---|
Your platform profile ($AGENT_PROFILE_URL) |
Cache-Control: public, max-age=N with N ≥ 60 |
Must not redirect. HTTPS only. |
Business profile (/.well-known/ucp) |
Same | You as platform respect what they return. |
If you see -32001 invalid_cache_control, the offender is almost always your platform profile.
# 0. one-time
export BUSINESS_HOST="https://retro-rewind-shop.myshopify.com"
export AGENT_PROFILE_URL="https://your-agent.example.com/.well-known/ucp"
# A. discover
curl -sL "$BUSINESS_HOST/.well-known/ucp" > /tmp/biz.json
MCP_ENDPOINT=$(jq -r '.ucp.services["dev.ucp.shopping"][] | select(.transport=="mcp") | .endpoint' /tmp/biz.json)
# B. search
curl -sL -X POST "$MCP_ENDPOINT" -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search_catalog","arguments":{
"meta":{"ucp-agent":{"profile":"'"$AGENT_PROFILE_URL"'"}},
"catalog":{"query":"retro shirt","context":{"address_country":"US"},"pagination":{"limit":5}}
}}}' > /tmp/search.json
VARIANT_ID=$(jq -r '.result.structuredContent.products[0].variants[0].id' /tmp/search.json)
# C1. create checkout
curl -sL -X POST "$MCP_ENDPOINT" -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"create_checkout","arguments":{
"meta":{"ucp-agent":{"profile":"'"$AGENT_PROFILE_URL"'"}},
"checkout":{
"buyer":{"email":"jane@example.com","first_name":"Jane","last_name":"Doe"},
"line_items":[{"item":{"id":"'"$VARIANT_ID"'"},"quantity":1}],
"currency":"USD",
"fulfillment":{"methods":[{"type":"shipping","destinations":[{
"street_address":"123 Main St","address_locality":"Springfield","address_region":"IL",
"postal_code":"62701","address_country":"US"
}]}]}
}
}}}' > /tmp/checkout.json
CHECKOUT_ID=$(jq -r '.result.structuredContent.id' /tmp/checkout.json)
STATUS=$(jq -r '.result.structuredContent.status' /tmp/checkout.json)
# C2/C3. (collect payment via the handler defined in /tmp/checkout.json -> .ucp.payment_handlers)
# C4. complete
curl -sL -X POST "$MCP_ENDPOINT" -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"complete_checkout","arguments":{
"meta":{"ucp-agent":{"profile":"'"$AGENT_PROFILE_URL"'"},"idempotency-key":"'"$(uuidgen)"'"},
"id":"'"$CHECKOUT_ID"'",
"checkout":{"payment":{"instruments":[ /* handler-issued instrument */ ]}}
}}}'