Finish checkout with Checkout MCP
The quickstart runs this same flow with the UCP CLI and Shopify AI Toolkit in about five minutes, and is the recommended way to get started. Follow this six-part series if you want to walk the protocol end-to-end against Shopify's MCP servers, integrate into an existing HTTP client, or build without the toolkit.
The quickstart runs this same flow with the UCP CLI and Shopify AI Toolkit in about five minutes, and is the recommended way to get started. Follow this six-part series if you want to walk the protocol end-to-end against Shopify's MCP servers, integrate into an existing HTTP client, or build without the toolkit.
This guide is the fifth part of a six-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 convert a cart built with Cart MCP into 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 Build a cart tutorial to create a checkout from the cart and manage it using Checkout MCP tools.
Anchor to What you'll learnWhat you'll learn
In this tutorial, you'll learn how to:
- Convert a cart built with Cart MCP into a checkout session by passing
cart_idtocreate_checkout - 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 Build a cart tutorial
Anchor to Step 1: Reuse the MCP endpoint helperStep 1: Reuse the MCP endpoint helper
In the Build a cart tutorial, you added a getMcpEndpoint helper to mcp.js that fetches the merchant's /.well-known/ucp document and returns its MCP endpoint. You'll reuse it from checkout.js, so there's no new discovery code to add in this tutorial.
Create a checkout.js file that imports getMcpEndpoint and defines AGENT_PROFILE:
checkout.js
Replace AGENT_PROFILE with your hosted profile URL from the Define a profile step. The profile must declare both the dev.ucp.shopping.cart and dev.ucp.shopping.checkout capabilities so create_checkout accepts a cart_id. See Convert a cart into a checkout.
Anchor to Step 2: Create a checkout from the cartStep 2: Create a checkout from the cart
Call create_checkout with the cart_id from the Build a cart tutorial. The server loads the referenced cart and inherits its line_items, context, and buyer. Pass cart_id as a top-level argument rather than inside the checkout payload; the checkout payload is optional when cart_id is provided.
Add createCheckout to checkout.js:
checkout.js
import { getMcpEndpoint } from './mcp.js';
const AGENT_PROFILE = 'https://shopify.dev/ucp/agent-profiles/examples/2026-04-08/cart-and-checkout.json';
export async function createCheckout(token, cartId, 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: {
cart_id: cartId,
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 checkout = data.result.structuredContent ?? data.result.content[0].text;
const { id, totals } = checkout;
const total = totals?.find(t => t.type === 'total')?.amount ?? 0;
console.log('\n── Create Checkout ────────────────────────────────\n');
console.log(` ID: ${id}`);
console.log(` Total: $${(total / 100).toFixed(2)}`);
return id;
}AI Toolkit / UCP CLI
ucp checkout create --business https://{shop}.example.com \
--set /cart_id='gid://shopify/Cart/<CART_ID>'{} MCP input reference
{
"jsonrpc": "2.0",
"method": "tools/call",
"id": 3,
"params": {
"name": "create_checkout",
"arguments": {
"cart_id": "gid://shopify/Cart/<CART_ID>",
"meta": {
"ucp-agent": {
"profile": "https://shopify.dev/ucp/agent-profiles/examples/2026-04-08/cart-and-checkout.json"
}
}
}
}
}{} Response
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"structuredContent": {
"id": "gid://shopify/Checkout/abc123?key=xyz789",
"status": "requires_escalation",
"messages": [
{ "type": "error", "severity": "requires_buyer_input" },
{ "type": "info", "severity": "requires_buyer_review" }
],
"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"
}
}
}A checkout's status tells you what to do next:
incomplete: Information is still missing. Resolve it withupdate_checkout.ready_for_complete: The checkout has everything it needs and can be submitted.requires_escalation: The buyer needs to complete the checkout on the merchant's storefront. Direct them tocontinue_url. Inspect themessagesarray for the reason; each message includes aseverity(for example,requires_buyer_inputorrequires_buyer_review) that describes what the buyer needs to do.
See the Checkout MCP lifecycle for the full status table.
See Convert a cart into a checkout for merge rules (cart contents win), idempotency behavior, and the invalid_cart_id and cart_not_found business outcomes.
Anchor to Alternative: Create a checkout without a cartAlternative: Create a checkout without a cart
If your agent doesn't build a cart first (for example, a buy-it-now flow from a single variant), create_checkout also accepts line_items directly. Omit cart_id and pass a checkout payload with currency and line_items:
{} MCP input reference
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.
update_checkout uses PUT semantics. Each request replaces the full checkout state with the payload you send, so include every field you want to keep. See the Checkout MCP reference for details.
update_checkout uses PUT semantics. Each request replaces the full checkout state with the payload you send, so include every field you want to keep. See the Checkout MCP reference for details.
Add the updateCheckout function to checkout.js:
When you convert a cart into a checkout, the checkout inherits the cart's line_items, context, and any buyer fields. Because update_checkout uses PUT semantics, omitting any of those fields would clear them. The helper below fetches the current checkout with getCheckout, then forwards currency, context, and line_items unchanged while merging the new email into the existing buyer object.
checkout.js
async function getCheckout(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: 4,
params: {
name: 'get_checkout',
arguments: {
id: checkoutId,
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(`get_checkout failed: ${JSON.stringify(data)}`);
return data.result.structuredContent ?? data.result.content[0].text;
}
export async function updateCheckout(token, checkoutId, email, checkoutUrl) {
const origin = new URL(checkoutUrl).origin;
const mcpEndpoint = await getMcpEndpoint(origin);
const current = await getCheckout(token, checkoutId, checkoutUrl);
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: 'update_checkout',
arguments: {
id: checkoutId,
checkout: {
currency: current.currency,
context: current.context,
line_items: current.line_items.map(li => ({
quantity: li.quantity,
item: { id: li.item.id }
})),
buyer: { ...(current.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)}`);
const checkout = data.result.structuredContent ?? data.result.content[0].text;
console.log('\n── Update Checkout ────────────────────────────────\n');
return checkout.continue_url;
}AI Toolkit / UCP CLI
ucp checkout get <CHECKOUT_ID> --business https://{shop}.example.com
ucp checkout update <CHECKOUT_ID> --business https://{shop}.example.com \
--set /currency=USD \
--set /context/country=US \
--set /context/language=en \
--set /line_items/0/quantity=1 \
--set /line_items/0/item/id='<VARIANT_ID>' \
--set /buyer/email=buyer@example.com{} MCP input reference
{
"jsonrpc": "2.0",
"method": "tools/call",
"id": 5,
"params": {
"name": "update_checkout",
"arguments": {
"id": "<CHECKOUT_ID>",
"checkout": {
"currency": "USD",
"context": { "country": "US", "language": "en" },
"line_items": [
{ "quantity": 1, "item": { "id": "<VARIANT_ID>" } }
],
"buyer": { "email": "buyer@example.com" }
},
"meta": {
"ucp-agent": {
"profile": "https://shopify.dev/ucp/agent-profiles/examples/2026-04-08/cart-and-checkout.json"
}
}
}
}
}Anchor to Step 4: Cancel checkoutStep 4: 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 checkout.js:
checkout.js
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: 6,
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── Cancel Checkout ────────────────────────────────\n');
console.log(` Status: ${status}`);
console.log(' Checkout has been successfully cancelled.');
console.log(' Demo complete.\n');
}AI Toolkit / UCP CLI
ucp checkout cancel <CHECKOUT_ID> --business https://{shop}.example.com{} MCP input reference
{
"jsonrpc": "2.0",
"method": "tools/call",
"id": 6,
"params": {
"name": "cancel_checkout",
"arguments": {
"id": "<CHECKOUT_ID>",
"meta": {
"ucp-agent": {
"profile": "https://shopify.dev/ucp/agent-profiles/examples/2026-04-08/cart-and-checkout.json"
},
"idempotency-key": "<UUID>"
}
}
}
}{} Response
{
"jsonrpc": "2.0",
"id": 6,
"result": {
"structuredContent": {
"id": "gid://shopify/Checkout/abc123?key=xyz789",
"status": "canceled"
}
}
}Anchor to Step 5: Finish checkoutStep 5: Finish checkout
Update ucp_demo.js to import checkout.js and wire up the cart-to-checkout handoff. The final script will build a cart from the selected variant, convert that cart into a checkout by passing cart_id to create_checkout, prompt the buyer to add their email, and 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
The demo calls cancelCheckout at the end to keep the tutorial state clean. In production, only call cancel_checkout when a buyer abandons the session.
Anchor to Step 6: (Alternative) Complete the checkout in your applicationStep 6: (Alternative) Complete the checkout in your application
The flow above hands the buyer off to the merchant's checkout via continue_url, which is the path most agents take. When your agent is eligible to call complete_checkout and the checkout reaches status: ready_for_complete, you can place the order in your application instead.
Eligible checkouts can still return requires_escalation when the merchant requires additional buyer input, such as 3DS challenges, channel opt-in, or other interactive review steps. Plan for this on every path: when a checkout escalates, hand the buyer off to the merchant's checkout via continue_url.
Add a completeCheckout function to checkout.js. It fetches the current checkout, submits payment with complete_checkout when status is ready_for_complete, and otherwise hands the buyer off to continue_url so they can resolve missing details on the merchant's storefront. In a production flow, you'd typically resolve recoverable messages with additional update_checkout calls before reaching this point:
checkout.js
export async function completeCheckout(token, checkoutId, checkoutUrl, payment) {
const origin = new URL(checkoutUrl).origin;
const mcpEndpoint = await getMcpEndpoint(origin);
const current = await getCheckout(token, checkoutId, checkoutUrl);
if (current.status !== 'ready_for_complete') {
console.log(` Checkout is ${current.status}. Hand off to the buyer at:\n ${current.continue_url}\n`);
return null;
}
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: 7,
params: {
name: 'complete_checkout',
arguments: {
id: checkoutId,
checkout: { payment },
meta: {
'ucp-agent': { profile: AGENT_PROFILE },
'idempotency-key': crypto.randomUUID()
}
}
}
})
});
const data = await res.json();
if (!data.result) throw new Error(`complete_checkout failed: ${JSON.stringify(data)}`);
const checkout = data.result.structuredContent;
console.log('\n── Complete Checkout ──────────────────────────────\n');
console.log(` Status: ${checkout.status}`);
if (checkout.order) console.log(` Order: ${checkout.order.id}`);
return checkout;
}AI Toolkit / UCP CLI
ucp checkout complete <CHECKOUT_ID> --business https://{shop}.example.com \
--set /payment/selected_instrument_id='<INSTRUMENT_ID>'{} MCP input reference
{
"jsonrpc": "2.0",
"method": "tools/call",
"id": 7,
"params": {
"name": "complete_checkout",
"arguments": {
"id": "<CHECKOUT_ID>",
"checkout": {
"payment": {
"instruments": [...],
"selected_instrument_id": "..."
}
},
"meta": {
"ucp-agent": {
"profile": "https://shopify.dev/ucp/agent-profiles/examples/2026-04-08/cart-and-checkout.json"
},
"idempotency-key": "<UUID>"
}
}
}
}{} Response
{
"jsonrpc": "2.0",
"id": 7,
"result": {
"structuredContent": {
"id": "gid://shopify/Checkout/abc123?key=xyz789",
"status": "completed",
"order": {
"id": "gid://shopify/Order/order_abc123",
"permalink_url": "https://ecowear-example.myshopify.com/orders/order_abc123"
}
}
}
}For payment integration details, see the Shop Pay payment handler and the UCP Payment Handler Guide. To embed the checkout UI in your application instead of redirecting to continue_url, see Embedded Checkout Protocol.
Anchor to Next stepsNext steps
- Monitor orders: Continue the tutorial series by fetching the order placed at checkout and receiving order webhook deliveries.
- About carts and checkout: Learn how carts, checkout, and cart permalinks fit together across the referral path.
- Checkout MCP reference: Full reference for
create_checkout,update_checkout,get_checkout,complete_checkout, andcancel_checkout. - Cart MCP reference: Full reference for
create_cart,update_cart,get_cart, andcancel_cart. - 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.