---
title: Finish checkout with Checkout MCP
description: >-
  Learn how to create and manage checkout sessions with Checkout MCP and direct
  buyers to the merchant's storefront to finish their purchase.
source_url:
  html: 'https://shopify.dev/docs/agents/get-started/checkout'
  md: 'https://shopify.dev/docs/agents/get-started/checkout.md'
---

# 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](https://shopify.dev/docs/agents/get-started/search-catalog) tutorial to create and manage checkout sessions using [Checkout MCP Tools](https://shopify.dev/docs/agents/checkout/mcp).

**Coming soon:**

Checkout MCP tools are coming soon.

***

## What 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_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

***

## Requirements

* Complete the [Search the catalog](https://shopify.dev/docs/agents/get-started/search-catalog) tutorial

***

## Step 1: Discover merchant capabilities

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

Merchants that support UCP expose a [business profile](https://ucp.dev/specification/overview/) 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

```json
{
  "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

```javascript
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](https://shopify.dev/docs/agents/get-started/profile#step-2-host-the-profile) step.

***

## Step 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

```javascript
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

```json
{
  "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

```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"
        }
      }
    ]
  }
}
```

***

## Step 3: Update checkout

After checkout is created, you may optionally want to attach buyer information to that checkout. The [`update_checkout`](https://shopify.dev/docs/agents/checkout/mcp#update_checkout) tool can add the buyer's email to the checkout session.

Add the `updateCheckout` function to `checkout.js`:

##### checkout.js

```javascript
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

```json
{
  "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

```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"
        }
      }
    ]
  }
}
```

***

## Step 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

```javascript
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](#step-5-optional-cancel-checkout) for more details.

***

## Step 5: (Optional) Cancel checkout

When a buyer abandons your agentic experience before completing checkout, you can call the [`cancel_checkout`](https://shopify.dev/docs/agents/checkout/mcp#cancel_checkout) tool to cancel the active session.

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

##### checkout.js

```javascript
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

```json
{
  "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

```json
{
  "jsonrpc": "2.0",
  "id": 5,
  "result": {
    "structuredContent": {
      "id": "gid://shopify/Checkout/abc123?key=xyz789",
      "status": "canceled"
    }
  }
}
```

***

## Next steps

* [About Shopify Catalog](https://shopify.dev/docs/agents/catalog): Learn how Catalog search and lookup work across the Shopify ecosystem.
* [Checkout for agents](https://shopify.dev/docs/agents/checkout): Learn how checkout works with Checkout MCP and cart permalinks.
* [UCP Checkout specification](https://ucp.dev/specification/checkout/): Explore the full UCP Checkout capability specification.
* [UCP Order specification](https://ucp.dev/specification/order/): Learn about confirmed transactions and post-purchase events, including line items, fulfillment expectations and events, and adjustments like refunds and returns.

***
