Skip to main content

Finish checkout with Checkout MCP

This guide is the fourth part of a four-part tutorial series that describes how to build an agentic commerce application with the Universal Commerce Protocol (UCP) using Shopify's MCP servers. It demonstrates how to create a checkout session, add buyer information, and refer the buyer to a merchant storefront to finish their purchase.

By the end of this tutorial, you'll have extended the demo scripts from the Search the catalog tutorial to create and manage checkout sessions using Checkout MCP Tools.

Coming soon

Checkout MCP tools are coming soon.


In this tutorial, you'll learn how to:

  • Discover the merchant's Checkout MCP endpoint from /.well-known/ucp
  • Create a checkout session with a selected product variant
  • Add buyer email with update_checkout and collect it interactively
  • Append UTM attribution to continue_url before referring buyers
  • Optionally cancel a checkout session when the buyer abandons the flow


Anchor to Step 1: Discover merchant capabilitiesStep 1: Discover merchant capabilities

Before calling Checkout MCP, you need the merchant's MCP endpoint.

Merchants that support UCP expose a business profile at /.well-known/ucp on their storefront origin. That document describes their UCP version, services (including the Checkout MCP endpoint), and capabilities. The following example shows the shape of a well-known document:

{shop}.example.com/.well-known/ucp

{
"ucp": {
"version": "2026-01-23",
"supported_versions": {
"2026-01-23": "https://{shop}.example.com/.well-known/ucp"
},
"services": {
"dev.ucp.shopping": [
{
"version": "2026-01-23",
"spec": "https://ucp.dev/specification/overview/",
"transport": "mcp",
"endpoint": "https://{shop}.example.com/api/ucp/mcp",
"schema": "https://ucp.dev/services/shopping/openrpc.json"
}
]
},
"capabilities": {
"dev.ucp.shopping.checkout": [
{
"version": "2026-01-23",
"spec": "https://ucp.dev/specification/checkout",
"schema": "https://ucp.dev/schemas/shopping/checkout.json"
}
]
}
}
}

You can use this document to confirm what the merchant supports before starting checkout.

Create a checkout.js file that includes a helper function that fetches this document and returns the MCP endpoint for a given merchant origin. You'll use this helper in the next step when you create the checkout.

checkout.js

const AGENT_PROFILE = 'https://shopify.dev/ucp/agent-profiles/2026-01-23/checkout-only.json';

export async function getMcpEndpoint(merchantOrigin) {
try {
const res = await fetch(`${merchantOrigin}/.well-known/ucp`);
if (!res.ok) throw new Error(res.status);
const ucp = await res.json();
const shopping = ucp?.ucp?.services?.['dev.ucp.shopping'];
const mcp = Array.isArray(shopping) && shopping.find(s => s.transport === 'mcp');
if (mcp?.endpoint) return mcp.endpoint;
} catch (_) {}
return `${merchantOrigin}/api/ucp/mcp`;
}

Be sure to replace AGENT_PROFILE with your hosted profile URL from the Define a profile step.


Anchor to Step 2: Create checkoutStep 2: Create checkout

Now that you have a way to discover the MCP endpoint, create the checkout session. Add createCheckout to checkout.js:

const AGENT_PROFILE = 'https://agent.example/profiles/ucp-demo-agent.json';

async function getMcpEndpoint(merchantOrigin) {
try {
const res = await fetch(`${merchantOrigin}/.well-known/ucp`);
if (!res.ok) throw new Error(res.status);
const ucp = await res.json();
const shopping = ucp?.ucp?.services?.['dev.ucp.shopping'];
const mcp = Array.isArray(shopping) && shopping.find(s => s.transport === 'mcp');
if (mcp?.endpoint) return mcp.endpoint;
} catch (_) {}
return `${merchantOrigin}/api/ucp/mcp`;
}

// Returns the checkout ID for use in subsequent calls.
export async function createCheckout(token, variantId, checkoutUrl) {
const origin = new URL(checkoutUrl).origin;
const mcpEndpoint = await getMcpEndpoint(origin);
const res = await fetch(mcpEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
jsonrpc: '2.0',
method: 'tools/call',
id: 3,
params: {
name: 'create_checkout',
arguments: {
checkout: {
currency: 'USD',
line_items: [{ quantity: 1, item: { id: variantId } }]
},
meta: { 'ucp-agent': { profile: AGENT_PROFILE } }
}
}
})
});
const data = await res.json();
if (data?.result?.content?.[0]?.text) {
data.result.content[0].text = JSON.parse(data.result.content[0].text);
}
if (!data.result) throw new Error(`create_checkout failed: ${JSON.stringify(data)}`);
const { id, totals } = data.result.content[0].text;
const total = totals?.find(t => t.type === 'total')?.amount ?? 0;
console.log('\n── 4. Create Checkout ─────────────────────────────\n');
console.log(` ID: ${id}`);
console.log(` Total: $${(total / 100).toFixed(2)}`);
return id;
}
{
"jsonrpc": "2.0",
"method": "tools/call",
"id": 3,
"params": {
"name": "create_checkout",
"arguments": {
"checkout": {
"currency": "USD",
"line_items": [
{
"quantity": 1,
"item": {
"id": "<VARIANT_ID>"
}
}
]
},
"meta": {
"ucp-agent": {
"profile": "https://agent.example/profiles/ucp-demo-agent.json"
}
}
}
}
}
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{
"type": "text",
"text": {
"id": "gid://shopify/Checkout/abc123?key=xyz789",
"status": "requires_escalation",
"messages": [
{ "type": "error", "severity": "requires_buyer_input" }
],
"currency": "USD",
"line_items": [
{
"id": "gid://shopify/CartLine/li_1?cart=abc123",
"item": {
"id": "gid://shopify/ProductVariant/11111111111",
"title": "Organic Cotton Crewneck Sweater",
"price": 8900
},
"quantity": 1,
"totals": [
{ "type": "subtotal", "amount": 8900, "display_text": "Subtotal" },
{ "type": "total", "amount": 8900, "display_text": "Total" }
]
}
],
"totals": [
{ "type": "subtotal", "amount": 8900, "display_text": "Subtotal" },
{ "type": "total", "amount": 8900, "display_text": "Total" }
],
"expires_at": "2026-02-20T15:17:07Z",
"continue_url": "https://ecowear-example.myshopify.com/cart/c/abc123?key=xyz789"
}
}
]
}
}

Anchor to Step 3: Update checkoutStep 3: Update checkout

After checkout is created, you may optionally want to attach buyer information to that checkout. The update_checkout tool can add the buyer's email to the checkout session.

Add the updateCheckout function to checkout.js:

const AGENT_PROFILE = 'https://agent.example/profiles/ucp-demo-agent.json';

async function getMcpEndpoint(merchantOrigin) {
try {
const res = await fetch(`${merchantOrigin}/.well-known/ucp`);
if (!res.ok) throw new Error(res.status);
const ucp = await res.json();
const shopping = ucp?.ucp?.services?.['dev.ucp.shopping'];
const mcp = Array.isArray(shopping) && shopping.find(s => s.transport === 'mcp');
if (mcp?.endpoint) return mcp.endpoint;
} catch (_) {}
return `${merchantOrigin}/api/ucp/mcp`;
}

export async function createCheckout(token, variantId, checkoutUrl) {
const origin = new URL(checkoutUrl).origin;
const mcpEndpoint = await getMcpEndpoint(origin);
const res = await fetch(mcpEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
jsonrpc: '2.0',
method: 'tools/call',
id: 3,
params: {
name: 'create_checkout',
arguments: {
checkout: {
currency: 'USD',
line_items: [{ quantity: 1, item: { id: variantId } }]
},
meta: { 'ucp-agent': { profile: AGENT_PROFILE } }
}
}
})
});
const data = await res.json();
if (data?.result?.content?.[0]?.text) {
data.result.content[0].text = JSON.parse(data.result.content[0].text);
}
if (!data.result) throw new Error(`create_checkout failed: ${JSON.stringify(data)}`);
const { id, totals } = data.result.content[0].text;
const total = totals?.find(t => t.type === 'total')?.amount ?? 0;
console.log('\n── 4. Create Checkout ─────────────────────────────\n');
console.log(` ID: ${id}`);
console.log(` Total: $${(total / 100).toFixed(2)}`);
return id;
}

export async function updateCheckout(token, checkoutId, variantId, email, checkoutUrl) {
const origin = new URL(checkoutUrl).origin;
const mcpEndpoint = await getMcpEndpoint(origin);
const res = await fetch(mcpEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
jsonrpc: '2.0',
method: 'tools/call',
id: 4,
params: {
name: 'update_checkout',
arguments: {
id: checkoutId,
checkout: {
currency: 'USD',
line_items: [{ quantity: 1, item: { id: variantId } }],
buyer: { email }
},
meta: { 'ucp-agent': { profile: AGENT_PROFILE } }
}
}
})
});
const data = await res.json();
if (data?.result?.content?.[0]?.text) {
data.result.content[0].text = JSON.parse(data.result.content[0].text);
}
if (!data.result) throw new Error(`update_checkout failed: ${JSON.stringify(data)}`);
console.log('\n── 5. Update Checkout ─────────────────────────────\n');
return data.result.content[0].text.continue_url;
}

export async function cancelCheckout(token, checkoutId, checkoutUrl) {
const origin = new URL(checkoutUrl).origin;
const mcpEndpoint = await getMcpEndpoint(origin);
const res = await fetch(mcpEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
jsonrpc: '2.0',
method: 'tools/call',
id: 5,
params: {
name: 'cancel_checkout',
arguments: {
id: checkoutId,
meta: {
'ucp-agent': { profile: AGENT_PROFILE },
'idempotency-key': crypto.randomUUID()
}
}
}
})
});
const data = await res.json();
if (data?.result?.content?.[0]?.text) {
data.result.content[0].text = JSON.parse(data.result.content[0].text);
}
if (!data.result) throw new Error(`cancel_checkout failed: ${JSON.stringify(data)}`);
const status = data.result.structuredContent?.status;
if (status !== 'canceled') throw new Error(`Unexpected status: ${status}`);
console.log('\n── 6. Cancel Checkout ─────────────────────────────\n');
console.log(` Status: ${status}`);
console.log(' Checkout has been successfully cancelled.');
console.log(' Demo complete.\n');
}
{
"jsonrpc": "2.0",
"method": "tools/call",
"id": 4,
"params": {
"name": "update_checkout",
"arguments": {
"id": "<CHECKOUT_ID>",
"checkout": {
"currency": "USD",
"line_items": [
{
"quantity": 1,
"item": {
"id": "<VARIANT_ID>"
}
}
],
"buyer": {
"email": "buyer@example.com"
}
},
"meta": {
"ucp-agent": {
"profile": "https://agent.example/profiles/ucp-demo-agent.json"
}
}
}
}
}
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"content": [
{
"type": "text",
"text": {
"id": "gid://shopify/Checkout/abc123?key=xyz789",
"status": "requires_escalation",
"messages": [
{ "type": "error", "severity": "requires_buyer_input" }
],
"currency": "USD",
"buyer": {
"email": "buyer@example.com"
},
"line_items": [
{
"id": "gid://shopify/CartLine/li_1?cart=abc123",
"item": {
"id": "gid://shopify/ProductVariant/11111111111",
"title": "Organic Cotton Crewneck Sweater",
"price": 8900
},
"quantity": 1
}
],
"totals": [
{ "type": "subtotal", "amount": 8900, "display_text": "Subtotal" },
{ "type": "total", "amount": 8900, "display_text": "Total" }
],
"expires_at": "2026-02-20T15:17:07Z",
"continue_url": "https://ecowear-example.myshopify.com/cart/c/abc123?key=xyz789"
}
}
]
}
}

Anchor to Step 4: Finish checkoutStep 4: Finish checkout

Update ucp_demo.js to import checkout.js. The final script will create a checkout using the selected product variant, prompt the buyer to add their email to that checkout, and then append attribution to the final checkout URL (utm_source and ucp_demo_app) so the merchant can recognize your agent's influence on the completed sale.

ucp_demo.js

import { prompt } from './utils.js';
import { getAccessToken } from './auth.js';
import { searchProducts, displayOffers } from './search.js';
import { selectProduct } from './product.js';
import { createCheckout, updateCheckout, cancelCheckout } from './checkout.js';

async function main() {
// 1. Authentication
const token = await getAccessToken();
// 2 & 3. Search and select a variant
let variant = null;
while (!variant) {
const searchResults = await searchProducts(token, {
include_secondhand: true,
min_price: 50,
max_price: 200,
ships_to: 'US',
});
if (!searchResults?.offers?.length) return;
displayOffers(searchResults.offers);
variant = await selectProduct(token, searchResults.offers);
}
const { variantId, checkoutUrl } = variant;

// 4. Create checkout
const checkoutId = await createCheckout(token, variantId, checkoutUrl);

// 5. Update checkout — add buyer email
const email = await prompt('\n\x1b[1m Enter your email address:\x1b[0m ');
const continueUrl = await updateCheckout(token, checkoutId, variantId, email, checkoutUrl);
// Append UTM attribution so the referral source is tracked on the merchant's analytics.
const attributedUrl = new URL(continueUrl);
attributedUrl.searchParams.set('utm_source', 'ucp_demo_app');
console.log(` Refer your buyer to finish checkout at:\n\n ${attributedUrl}\n`);

// 6. Cancel checkout
await prompt('\x1b[1m Are you finished with the demo? Press Enter to cancel the checkout and exit.\x1b[0m ');
await cancelCheckout(token, checkoutId, checkoutUrl);
}

main().catch(err => console.error('Request failed:', err));

Run the full demo with node ucp_demo.js. Click the final link to view the checkout URL on the merchant storefront prepared for referral. When finished, press Enter to cancel the checkout and exit the demo.

Output

── 1. Authentication ─────────────────────────

Scopes: read_global_api_catalog_search
Expires: 6:02:46 PM

── 2. Search the Catalog ─────────────────────────

Catalog ID: {your_catalog_id}

Hello! What are you looking for today?

> I need a crewneck sweater

── Results ────────────────────────────────────────

[1] Organic Cotton Crewneck Sweater | $89.00 | Size: S, M, L | Color: Oatmeal, Forest Green
[2] Recycled Wool Blend Crewneck | $115.00 | Size: S, M, L, XL | Color: Charcoal, Navy
[3] Hemp Cotton Crew Pullover | $72.00 | Size: XS, S, M, L

Lookup details on a result [1-3]: 1

── 3. Product Details ─────────────────────────────

Organic Cotton Crewneck Sweater - M / Oatmeal
$89.00 · EcoWear

A soft crewneck sweater crafted from 100% organic cotton with a relaxed fit for everyday comfort.

· 100% organic cotton for breathable comfort
· Relaxed fit with ribbed cuffs and hem
· GOTS certified sustainable production
· Pre-washed for softness
· Classic crewneck design

Options:

Size:
[1] ○ S
[2] ● M
[3] ○ L

Color:
[4] ● Oatmeal
[5] ○ Forest Green

Selected: M / Oatmeal

[s] Select this variant [number] Pick an option [b] Back to results

> s

Checkout: https://ecowear-example.myshopify.com/cart/11111111111:1?_gsid=example123

── 4. Create Checkout ─────────────────────────────

ID: gid://shopify/Checkout/abc123?key=xyz789
Total: $89.00

── 5. Update Checkout ─────────────────────────────

Enter your email address: buyer@example.com

Refer your buyer to finish checkout at:

https://ecowear-example.myshopify.com/cart/c/abc123?key=xyz789&utm_source=ucp_demo_app

Are you finished with the demo? Press Enter to cancel the checkout and exit.

── 6. Cancel Checkout ─────────────────────────────

Status: canceled
Checkout has been successfully cancelled.
Demo complete.

Notice that this script leverages another function called cancelCheckout. Canceling checkout is optional, but it's included here to clean up the tutorial. See the section below for more details.


Anchor to Step 5: (Optional) Cancel checkoutStep 5: (Optional) Cancel checkout

When a buyer abandons your agentic experience before completing checkout, you can call the cancel_checkout tool to cancel the active session.

Add a cancelCheckout function that calls that tool to the same checkout.js script:

const AGENT_PROFILE = 'https://agent.example/profiles/ucp-demo-agent.json';

async function getMcpEndpoint(merchantOrigin) {
try {
const res = await fetch(`${merchantOrigin}/.well-known/ucp`);
if (!res.ok) throw new Error(res.status);
const ucp = await res.json();
const shopping = ucp?.ucp?.services?.['dev.ucp.shopping'];
const mcp = Array.isArray(shopping) && shopping.find(s => s.transport === 'mcp');
if (mcp?.endpoint) return mcp.endpoint;
} catch (_) {}
return `${merchantOrigin}/api/ucp/mcp`;
}

export async function createCheckout(token, variantId, checkoutUrl) {
const origin = new URL(checkoutUrl).origin;
const mcpEndpoint = await getMcpEndpoint(origin);
const res = await fetch(mcpEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
jsonrpc: '2.0',
method: 'tools/call',
id: 3,
params: {
name: 'create_checkout',
arguments: {
checkout: {
currency: 'USD',
line_items: [{ quantity: 1, item: { id: variantId } }]
},
meta: { 'ucp-agent': { profile: AGENT_PROFILE } }
}
}
})
});
const data = await res.json();
if (data?.result?.content?.[0]?.text) {
data.result.content[0].text = JSON.parse(data.result.content[0].text);
}
if (!data.result) throw new Error(`create_checkout failed: ${JSON.stringify(data)}`);
const { id, totals } = data.result.content[0].text;
const total = totals?.find(t => t.type === 'total')?.amount ?? 0;
console.log('\n── 4. Create Checkout ─────────────────────────────\n');
console.log(` ID: ${id}`);
console.log(` Total: $${(total / 100).toFixed(2)}`);
return id;
}

export async function updateCheckout(token, checkoutId, variantId, email, checkoutUrl) {
const origin = new URL(checkoutUrl).origin;
const mcpEndpoint = await getMcpEndpoint(origin);
const res = await fetch(mcpEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
jsonrpc: '2.0',
method: 'tools/call',
id: 4,
params: {
name: 'update_checkout',
arguments: {
id: checkoutId,
checkout: {
currency: 'USD',
line_items: [{ quantity: 1, item: { id: variantId } }],
buyer: { email }
},
meta: { 'ucp-agent': { profile: AGENT_PROFILE } }
}
}
})
});
const data = await res.json();
if (data?.result?.content?.[0]?.text) {
data.result.content[0].text = JSON.parse(data.result.content[0].text);
}
if (!data.result) throw new Error(`update_checkout failed: ${JSON.stringify(data)}`);
console.log('\n── 5. Update Checkout ─────────────────────────────\n');
return data.result.content[0].text.continue_url;
}

export async function cancelCheckout(token, checkoutId, checkoutUrl) {
const origin = new URL(checkoutUrl).origin;
const mcpEndpoint = await getMcpEndpoint(origin);
const res = await fetch(mcpEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
jsonrpc: '2.0',
method: 'tools/call',
id: 5,
params: {
name: 'cancel_checkout',
arguments: {
id: checkoutId,
meta: {
'ucp-agent': { profile: AGENT_PROFILE },
'idempotency-key': crypto.randomUUID()
}
}
}
})
});
const data = await res.json();
if (data?.result?.content?.[0]?.text) {
data.result.content[0].text = JSON.parse(data.result.content[0].text);
}
if (!data.result) throw new Error(`cancel_checkout failed: ${JSON.stringify(data)}`);
const status = data.result.structuredContent?.status;
if (status !== 'canceled') throw new Error(`Unexpected status: ${status}`);
console.log('\n── 6. Cancel Checkout ─────────────────────────────\n');
console.log(` Status: ${status}`);
console.log(' Checkout has been successfully cancelled.');
console.log(' Demo complete.\n');
}
{
"jsonrpc": "2.0",
"method": "tools/call",
"id": 5,
"params": {
"name": "cancel_checkout",
"arguments": {
"id": "<CHECKOUT_ID>",
"meta": {
"ucp-agent": {
"profile": "https://agent.example/profiles/ucp-demo-agent.json"
},
"idempotency-key": "<UUID>"
}
}
}
}
{
"jsonrpc": "2.0",
"id": 5,
"result": {
"structuredContent": {
"id": "gid://shopify/Checkout/abc123?key=xyz789",
"status": "canceled"
}
}
}

  • About Shopify Catalog: Learn how Catalog search and lookup work across the Shopify ecosystem.
  • Checkout for agents: Learn how checkout works with Checkout MCP and cart permalinks.
  • UCP Checkout specification: Explore the full UCP Checkout capability specification.
  • UCP Order specification: Learn about confirmed transactions and post-purchase events, including line items, fulfillment expectations and events, and adjustments like refunds and returns.

Was this page helpful?