Build a cart with Cart 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 fourth part of a six-part tutorial series on building agentic commerce with the Universal Commerce Protocol (UCP) and Shopify's MCP servers. It shows how to use Cart MCP to build carts that estimate totals and let buyers iterate on line items before committing to purchase.
By the end of this tutorial, you'll have a cart_id you can convert into a checkout in the Finish checkout tutorial.
Anchor to What you'll learnWhat you'll learn
In this tutorial, you'll learn how to:
- Discover the merchant's Cart MCP endpoint from
/.well-known/ucp. - Build a cart with
create_cartand retrieve an estimated total. - Refresh a cart's state with
get_cart. - Replace a cart's contents with
update_cart, using PUT semantics. - Optionally cancel a cart with
cancel_cart. - Return a
cart_idyou can use later to convert the cart into a checkout.
Anchor to RequirementsRequirements
- Complete the Authenticate your agent, Define a profile, and Search the catalog tutorials.
Anchor to Step 1: Discover merchant capabilitiesStep 1: Discover merchant capabilities
Before calling Cart 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 MCP endpoint), and negotiated capabilities. The following example shows the shape of a well-known document with the cart capability:
{shop}.example.com/.well-known/ucp
You can use this document to confirm the merchant supports carts before calling create_cart.
Create an mcp.js file with a helper function that fetches this document and returns the MCP endpoint for a given merchant origin. You'll reuse this helper from cart.js in the next step, and from checkout.js and orders.js in later tutorials, so it lives in its own file.
mcp.js
Anchor to Step 2: Create a cartStep 2: Create a cart
Call create_cart with the selected variant. The response includes a cart object with the merchant-assigned id, validated line items, estimated totals, and a continue_url that the buyer can use to pick up the cart on the merchant's storefront.
Create a cart.js file that imports getMcpEndpoint and defines AGENT_PROFILE, then add a createCart function to it:
cart.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 createCart(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' },
body: JSON.stringify({
jsonrpc: '2.0',
method: 'tools/call',
id: 3,
params: {
name: 'create_cart',
arguments: {
cart: {
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_cart failed: ${JSON.stringify(data)}`);
const cart = data.result.structuredContent?.cart ?? data.result.content[0].text.cart;
const total = cart.totals?.find(t => t.type === 'total')?.amount ?? 0;
console.log('\n── Create Cart ────────────────────────────────────\n');
console.log(` Cart ID: ${cart.id}`);
console.log(` Total: $${(total / 100).toFixed(2)}`);
return cart.id;
}AI Toolkit / UCP CLI
ucp cart create --business https://{shop}.example.com \
--set /line_items/0/item/id='<VARIANT_ID>' \
--set /line_items/0/quantity=1 \
--set /context/address_country=US{} MCP input reference
{
"jsonrpc": "2.0",
"method": "tools/call",
"id": 3,
"params": {
"name": "create_cart",
"arguments": {
"cart": {
"line_items": [
{
"quantity": 1,
"item": {
"id": "<VARIANT_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": {
"cart": {
"ucp": { "version": "2026-04-08" },
"id": "gid://shopify/Cart/cart_abc123",
"currency": "USD",
"line_items": [
{
"id": "gid://shopify/CartLine/li_1?cart=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" }
],
"continue_url": "https://ecowear-example.myshopify.com/cart/c/cart_abc123",
"expires_at": "2026-05-08T15:17:07Z"
}
}
}
}Replace AGENT_PROFILE with your hosted profile URL from the Define a profile step.
createCart returns the cart's id. Store it so you can call the other cart tools on the same cart, and pass it as cart_id to create_checkout when the buyer is ready to purchase.
Anchor to Step 3: Get the cartStep 3: Get the cart
Call get_cart to refresh a cart's state. Use this when the buyer returns to the conversation and you want to show the current cart contents, or after a context change (like shipping country) where you want the latest estimated totals.
If the cart doesn't exist or has expired, the response is a successful JSON-RPC result whose messages array contains an unrecoverable error with code not_found.
Add getCart to cart.js:
cart.js
export async function getCart(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' },
body: JSON.stringify({
jsonrpc: '2.0',
method: 'tools/call',
id: 4,
params: {
name: 'get_cart',
arguments: {
id: cartId,
meta: { 'ucp-agent': { profile: AGENT_PROFILE } }
}
}
})
});
const data = await res.json();
if (!data.result) throw new Error(`get_cart failed: ${JSON.stringify(data)}`);
const cart = data.result.structuredContent?.cart;
const notFound = cart?.messages?.find(m => m.code === 'not_found');
if (notFound) throw new Error('Cart not found or expired');
const total = cart.totals?.find(t => t.type === 'total')?.amount ?? 0;
console.log('\n── Get Cart ───────────────────────────────────────\n');
console.log(` Cart ID: ${cart.id}`);
console.log(` Items: ${cart.line_items.length}`);
console.log(` Total: $${(total / 100).toFixed(2)}`);
return cart;
}AI Toolkit / UCP CLI
ucp cart get <CART_ID> --business https://{shop}.example.com{} MCP input reference
{
"jsonrpc": "2.0",
"method": "tools/call",
"id": 4,
"params": {
"name": "get_cart",
"arguments": {
"id": "<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": 4,
"result": {
"structuredContent": {
"cart": {
"id": "gid://shopify/Cart/cart_abc123",
"line_items": [
{
"id": "gid://shopify/CartLine/li_1?cart=cart_abc123",
"quantity": 1,
"totals": [
{ "type": "total", "amount": 8900, "display_text": "Total" }
]
}
],
"totals": [
{ "type": "total", "amount": 8900, "display_text": "Total" }
],
"continue_url": "https://ecowear-example.myshopify.com/cart/c/cart_abc123"
}
}
}
}The cart ID is passed at the top level of arguments (as id), not inside a cart object. UCP separates resource identification from payload so the server can validate requests correctly.
Anchor to Step 4: Update the cartStep 4: Update the cart
Call update_cart to change the contents of a cart, for example, when the buyer adjusts a quantity, adds another product, or provides a shipping country.
Because updates replace state, build the full desired cart payload before sending it. A common pattern is to fetch the current cart with getCart, apply your change locally, then pass the result to updateCart.
Add updateCart to cart.js:
cart.js
export async function updateCart(cartId, cart, checkoutUrl) {
const origin = new URL(checkoutUrl).origin;
const mcpEndpoint = await getMcpEndpoint(origin);
const res = await fetch(mcpEndpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
method: 'tools/call',
id: 5,
params: {
name: 'update_cart',
arguments: {
id: cartId,
cart,
meta: { 'ucp-agent': { profile: AGENT_PROFILE } }
}
}
})
});
const data = await res.json();
if (!data.result) throw new Error(`update_cart failed: ${JSON.stringify(data)}`);
const updated = data.result.structuredContent?.cart;
const total = updated.totals?.find(t => t.type === 'total')?.amount ?? 0;
console.log('\n── Update Cart ────────────────────────────────────\n');
console.log(` Items: ${updated.line_items.length}`);
console.log(` Total: $${(total / 100).toFixed(2)}`);
return updated;
}AI Toolkit / UCP CLI
ucp cart update <CART_ID> --business https://{shop}.example.com \
--set /line_items/0/item/id='<VARIANT_ID>' \
--set /line_items/0/quantity=2 \
--set /context/address_country=US \
--set /context/address_region=CA \
--set /context/postal_code=94105{} MCP input reference
{
"jsonrpc": "2.0",
"method": "tools/call",
"id": 5,
"params": {
"name": "update_cart",
"arguments": {
"id": "<CART_ID>",
"cart": {
"line_items": [
{ "quantity": 2, "item": { "id": "<VARIANT_ID>" } }
],
"context": {
"address_country": "US",
"address_region": "CA",
"postal_code": "94105"
}
},
"meta": {
"ucp-agent": {
"profile": "https://shopify.dev/ucp/agent-profiles/examples/2026-04-08/cart-and-checkout.json"
}
}
}
}
}{} Response
{
"jsonrpc": "2.0",
"id": 5,
"result": {
"structuredContent": {
"cart": {
"id": "gid://shopify/Cart/cart_abc123",
"line_items": [
{
"id": "gid://shopify/CartLine/li_1?cart=cart_abc123",
"quantity": 2,
"totals": [
{ "type": "total", "amount": 17800, "display_text": "Total" }
]
}
],
"totals": [
{ "type": "total", "amount": 17800, "display_text": "Total" }
],
"continue_url": "https://ecowear-example.myshopify.com/cart/c/cart_abc123"
}
}
}
}For example, to double the quantity of the item in the cart:
Anchor to Step 5: (Optional) Cancel the cartStep 5: (Optional) Cancel the cart
When a buyer abandons the conversation or you want to clean up a stale cart before starting a new one, call cancel_cart. cancel_cart requires an idempotency-key in meta so retries are safe.
Canceling a cart removes it from storage. Subsequent calls to get_cart with the same ID return a not_found business outcome.
Add cancelCart to cart.js:
cart.js
import { randomUUID } from 'node:crypto';
export async function cancelCart(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' },
body: JSON.stringify({
jsonrpc: '2.0',
method: 'tools/call',
id: 6,
params: {
name: 'cancel_cart',
arguments: {
id: cartId,
meta: {
'ucp-agent': { profile: AGENT_PROFILE },
'idempotency-key': randomUUID()
}
}
}
})
});
const data = await res.json();
if (!data.result) throw new Error(`cancel_cart failed: ${JSON.stringify(data)}`);
console.log('\n── Cancel Cart ────────────────────────────────────\n');
console.log(` Cart ${cartId} canceled.`);
}AI Toolkit / UCP CLI
ucp cart cancel <CART_ID> --business https://{shop}.example.com{} MCP input reference
{
"jsonrpc": "2.0",
"method": "tools/call",
"id": 6,
"params": {
"name": "cancel_cart",
"arguments": {
"id": "<CART_ID>",
"meta": {
"ucp-agent": {
"profile": "https://shopify.dev/ucp/agent-profiles/examples/2026-04-08/cart-and-checkout.json"
},
"idempotency-key": "<UUID>"
}
}
}
}Reuse the same idempotency-key when retrying a cancel after a network failure. The server returns the same outcome without canceling twice.