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.
Checkout MCP tools are coming soon.
Checkout MCP tools are coming soon.
Anchor to What you'll learnWhat you'll learn
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_checkoutand collect it interactively - Append UTM attribution to
continue_urlbefore referring buyers - Optionally cancel a checkout session when the buyer abandons the flow
Anchor to RequirementsRequirements
- Complete the Search the catalog tutorial
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
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
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:
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;
}{} MCP input reference
{
"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"
}
}
}
}
}{} Response
{
"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:
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');
}{} MCP input reference
{
"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"
}
}
}
}
}{} Response
{
"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
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
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:
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');
}{} MCP input reference
{
"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>"
}
}
}
}{} Response
{
"jsonrpc": "2.0",
"id": 5,
"result": {
"structuredContent": {
"id": "gid://shopify/Checkout/abc123?key=xyz789",
"status": "canceled"
}
}
}Anchor to Next stepsNext steps
- 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.