Skip to main content

Shopify and MCP

MCP Example Shopify Store

Ecommerce

Integration

MCP API

MCP API

LLM Params

setup the llm params

With MCP review the schema of the tool you are using, check what is required

Create Test Data

After setting up the MCP API, and LLM Params, you need test data for the next steps, so you can create the JSONata

Using JSONata Response for the transformation

setup JSONata response

Shopifiy returns products vs items, use the JSONata Response to transform the output.

{
"items": products
}

Create Test Data (Again after JSONata setup)

You need to create test data, after you setup the JSONata, because it is this new test data that the Carousel AI will use

Example Shopify Store using a MCP endpoint

ai to set up the carousel handlebars and templates

The carousel-card class is the main container for each product card in the carousel. data-index is used to track the index of each card in the carousel. The card-button class must be used for all the buttons in the card to listen the click events.

{{#each items}}
<div class="carousel-card" data-index="{{@index}}" data-item="{{{json this}}}">
<!-- Top Row: Image left, Title/Price/Color right -->
<div class="card-top-row">
<div class="card-image-col">
<img
id="main-image-{{@index}}"
src="{{variants.[0].image_url}}"
alt="{{title}}"
class="card-image"
/>
</div>
<div class="card-info-col">
<h2 class="card-title">{{title}}</h2>
<p class="card-price">USD ${{price_range.min}}</p>
<!-- Color dots -->
<div class="color-dot-group">
{{#each (uniqueColors variants) as |color colorIndex|}}
<button
type="button"
class="color-btn"
style="background:#ddd;"
aria-label="{{color}}"
onclick="selectColorByName(this, '{{color}}')"
data-color="{{color}}"
title="{{color}}"
></button>
{{/each}}
</div>
</div>
</div>
<!-- Full Width: Description -->
<div class="card-description">{{description}}</div>
<!-- Full Width: Size Selector -->
<div class="card-size-section">
<label class="size-label">Select Size:</label>
<div class="size-btn-group">
{{#each (uniqueSizes variants) as |size|}}
<button
class="size-btn"
data-size="{{size}}"
onclick="selectSize(this, '{{size}}')"
type="button"
>
{{size}}
</button>
{{/each}}
</div>
</div>
<!-- Full Width: Add to Cart -->
<div class="card-action-row">
<button class="card-button add-to-cart-btn" onclick="addToCart(this)">
ADD TO CART
</button>
</div>
</div>
{{/each}}

JavaScript

// **********
// Ecomm
// === ai12z-carousel-variants.js ===

// --- Robust Color/Size Extraction ---
function extractColorAndSize(title, productType) {
const parts = title.split("/").map((p) => p.trim())
let color, size
if (parts.length === 2) {
color = parts[0]
size = parts[1]
} else if (parts.length >= 3) {
color = parts[1]
size = parts[2]
} else if (parts.length === 1) {
color = parts[0]
size = ""
} else {
color = ""
size = ""
}
return { color, size }
}
window.extractColorAndSize = extractColorAndSize

// --- Handlebars Helpers ---
window.Handlebars.registerHelper("uniqueColors", function (variants) {
const colors = []
const contextProductType = (this && this.product_type) || ""
variants?.forEach((v) => {
const productType = v.product_type || contextProductType
const { color } = extractColorAndSize(v.title, productType)
if (color && !colors.includes(color)) colors.push(color)
})
return colors
})
window.Handlebars.registerHelper("uniqueSizes", function (variants) {
const sizes = []
const contextProductType = (this && this.product_type) || ""
variants?.forEach((v) => {
const productType = v.product_type || contextProductType
const { size } = extractColorAndSize(v.title, productType)
if (size && !sizes.includes(size)) sizes.push(size)
})
return sizes
})

// --- Deep Shadow DOM Query Utility ---
function findAllInDeepShadow(selector, root = document) {
let results = Array.from(root.querySelectorAll(selector))
Array.from(root.querySelectorAll("*")).forEach((el) => {
if (el.shadowRoot) {
results = results.concat(findAllInDeepShadow(selector, el.shadowRoot))
}
})
return results
}

// --- Apply Color Swatches ---
function applyColorSwatches() {
const colorMap = {
Black: "#222",
Slate: "#a9b3ba",
Sunrise: "#faa61a",
Ironwood: "#ae5249",
Chalk: "#eaeaea",
Red: "#b30000",
Purple: "#b185db",
Gold: "#ecc94b",
Turquoise: "#40e0d0",
Brown: "#8b4513",
Green: "#228b22",
Blackout: "#111",
White: "#fff",
Charcoal: "#36454f",
}
findAllInDeepShadow(".carousel-card").forEach((card) => {
card.querySelectorAll(".color-btn").forEach((btn) => {
const colorText = btn.title || btn.getAttribute("aria-label") || ""
let colorHex = "#ddd"
Object.keys(colorMap).forEach((key) => {
if (colorText.toLowerCase().includes(key.toLowerCase()))
colorHex = colorMap[key]
})
btn.style.background = colorHex
})
})
}

// --- Color Selection Logic ---
function selectColorByName(btn, color) {
const card = btn.closest(".carousel-card")
if (!card) return
const data = JSON.parse(card.getAttribute("data-item"))
card.setAttribute("data-selected-color", color)

const variant = data.variants.find((v) => {
const { color: vColor } = extractColorAndSize(v.title, data.product_type)
return vColor === color
})
const img = card.querySelector('img[id^="main-image"]')
if (img && variant?.image_url) img.src = variant.image_url

updateSizeButtons(card, color, data.variants, data.product_type)

const colors = [
...new Set(
data.variants.map(
(v) => extractColorAndSize(v.title, data.product_type).color
)
),
]
card.querySelectorAll(".color-btn").forEach((dot) => {
dot.classList.toggle(
"selected-color",
dot.getAttribute("data-color") === color
)
dot.classList.toggle(
"extra-circle",
dot.getAttribute("data-color") === color && colors.length === 2
)
})
}

// --- Size Selection Logic ---
function selectSize(btn, size) {
const card = btn.closest(".carousel-card")
card.setAttribute("data-selected-size", size)
card
.querySelectorAll(".size-btn")
.forEach((b) => b.classList.remove("ring", "ring-blue-500"))
btn.classList.add("ring", "ring-blue-500")
}

// --- Enable/Disable Size Buttons for Selected Color ---
function updateSizeButtons(card, selectedColor, variants, productType) {
card.querySelectorAll(".size-btn").forEach((btn) => {
const size = btn.getAttribute("data-size")
const available = variants?.some((v) => {
const { color: vColor, size: vSize } = extractColorAndSize(
v.title,
productType
)
return v.available && vColor === selectedColor && vSize === size
})
btn.disabled = !available
btn.classList.toggle("bg-gray-100", !available)
btn.classList.toggle("text-gray-400", !available)
btn.classList.toggle("cursor-not-allowed", !available)
btn.classList.toggle("unavailable", !available)

let slash = btn.querySelector(".slash")
if (!available && !slash) {
slash = document.createElement("span")
slash.className = "slash"
btn.appendChild(slash)
}
if (available && slash) {
slash.remove()
}
if (!available) btn.classList.remove("ring", "ring-blue-500")
})
}

// --- Add to Cart Logic ---
function addToCart(button) {
const card = button.closest(".carousel-card")
if (!card) return
const data = JSON.parse(card.getAttribute("data-item"))
const selectedColor = card.getAttribute("data-selected-color") || ""
const selectedSize = card.getAttribute("data-selected-size") || ""
const selectedVariant = data.variants.find((v) => {
const { color: vColor, size: vSize } = extractColorAndSize(
v.title,
data.product_type
)
return v.available && vColor === selectedColor && vSize === selectedSize
})
if (selectedVariant) {
alert(
`Added to cart: ${data.title}\nColor: ${selectedColor}\nSize: ${selectedSize}\nVariant: ${selectedVariant.title}`
)
} else {
alert("Please select an available color and size.")
}
}

// --- Initialize Card (Select First Color) ---
function initCardSelection(card, tries = 0) {
const data = JSON.parse(card.getAttribute("data-item"))
const colors = [
...new Set(
data.variants.map(
(v) => extractColorAndSize(v.title, data.product_type).color
)
),
]
const firstColor = colors[0]
card.setAttribute("data-selected-color", firstColor)

const dot = card.querySelector(`.color-btn[data-color="${firstColor}"]`)
if (!dot && tries < 10) {
setTimeout(() => initCardSelection(card, tries + 1), 60)
return
}
if (dot) selectColorByName(dot, firstColor)
}

// --- Observer/Startup ---
function tryInitializeCardsNow() {
const cards = findAllInDeepShadow(".carousel-card")
if (cards.length) {
cards.forEach((card) => initCardSelection(card))
applyColorSwatches()
}
}
tryInitializeCardsNow()

function waitForCards() {
const cards = findAllInDeepShadow(".carousel-card")
if (cards.length > 0) {
tryInitializeCardsNow()
} else {
requestAnimationFrame(waitForCards)
}
}
requestAnimationFrame(waitForCards)

window.selectColorByName = selectColorByName
window.selectSize = selectSize
window.addToCart = addToCart

Note:: root = carousel?.shadowRoot; This is how we reference the shadowRoot, if carousel

Ecom CSS

.carousel-card {
display: flex;
flex-direction: column;
background: #ffffff;
border-radius: 1.25rem;
box-shadow:
0 4px 16px 0 rgba(51, 92, 103, 0.1),
0 1px 4px 0 rgba(34, 34, 34, 0.04);
margin-bottom: 1.5rem;
padding: 1rem;
border: 1.5px solid #ededed;
width: 100%;
overflow: visible;
transition:
box-shadow 0.18s,
transform 0.12s;
}

.carousel-card:hover {
box-shadow:
0 8px 24px 0 rgba(51, 92, 103, 0.16),
0 2px 8px 0 rgba(34, 34, 34, 0.08);
transform: translateY(-2px) scale(1.01);
}

/* Top Row: 2 columns */
.card-top-row {
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 1rem;
width: 100%;
margin-bottom: 0.75rem;
}

.card-image-col {
flex: 0 0 auto;
display: flex;
align-items: center;
justify-content: center;
}

.card-image {
width: 120px;
height: 120px;
object-fit: cover;
border-radius: 0.75rem;
background: #ededed;
}

.card-info-col {
flex: 1 1 0%;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
min-width: 0;
}

.card-title {
font-size: 1.1rem;
font-weight: 700;
color: #222222;
margin: 0 0 0.25rem 0;
font-family: sans-serif;
line-height: 1.3;
}

.card-price {
color: #335c67;
font-size: 1rem;
font-weight: 600;
margin: 0 0 0.5rem 0;
}

.color-dot-group {
display: flex;
flex-direction: row;
gap: 0.5rem;
margin-top: 0.25rem;
}

.color-btn {
width: 1.75rem;
height: 1.75rem;
border-radius: 50%;
border: 2px solid #ededed;
cursor: pointer;
transition:
border-color 0.15s,
box-shadow 0.15s;
}

.color-btn:hover,
.color-btn:focus {
border-color: #2ec4b6;
}

.color-btn.selected-color {
border: 2.5px solid #2ec4b6;
}

.color-btn.extra-circle {
box-shadow:
0 0 0 4px #f7f7f7,
0 0 0 7px #2ec4b6;
}

/* Full Width: Description */
.card-description {
width: 100%;
color: #222222;
font-size: 0.95rem;
margin-bottom: 0.75rem;
line-height: 1.5;
font-family: sans-serif;
}

/* Full Width: Size Selector */
.card-size-section {
width: 100%;
margin-bottom: 0.75rem;
}

.size-label {
font-weight: 600;
color: #335c67;
font-size: 0.85rem;
margin-bottom: 0.25rem;
text-transform: uppercase;
letter-spacing: 0.04em;
display: block;
}

.size-btn-group {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-top: 0.5rem;
}

.size-btn {
padding: 0.5rem 1.25rem;
border: 1.5px solid #ededed;
border-radius: 0.5rem;
font-size: 1rem;
font-weight: 500;
color: #222222;
background: #ffffff;
cursor: pointer;
transition:
border-color 0.15s,
background 0.15s,
color 0.15s;
position: relative;
}

.size-btn:hover,
.size-btn:focus {
border-color: #2ec4b6;
background: #f7f7f7;
}

.size-btn.ring,
.size-btn.ring-blue-500 {
border-color: #2ec4b6;
box-shadow: 0 0 0 2px rgba(46, 196, 182, 0.25);
}

.size-btn.unavailable {
position: relative;
pointer-events: none;
background: #f7f7f7;
color: #999;
cursor: not-allowed;
}

.size-btn.unavailable .slash {
position: absolute;
left: 12px;
top: 50%;
width: 36px;
height: 2px;
background: #999;
transform: rotate(-24deg) translateY(-50%);
content: "";
display: inline-block;
z-index: 2;
border-radius: 2px;
}

/* Full Width: Add to Cart */
.card-action-row {
width: 100%;
margin-top: 0.5rem;
}

.add-to-cart-btn {
width: 100%;
background: #335c67;
color: #ffffff;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
font-size: 0.95rem;
padding: 0.75em 1.5em;
border-radius: 0.5rem;
border: none;
box-shadow: 0 2px 8px rgba(51, 92, 103, 0.12);
cursor: pointer;
transition:
background 0.18s,
box-shadow 0.14s,
transform 0.1s;
min-height: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
font-family: sans-serif;
}

.add-to-cart-btn:hover,
.add-to-cart-btn:focus {
background: #2ec4b6;
box-shadow: 0 4px 16px rgba(46, 196, 182, 0.18);
transform: scale(1.03);
}

.list-btn[disabled] {
opacity: 0.5;
cursor: not-allowed;
}

.pagination-controls {
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
margin: 1.25rem 0 0 0;
}

.prev-btn,
.next-btn {
background: #f7f7f7;
color: #335c67;
border: 1.5px solid #ededed;
border-radius: 0.5rem;
padding: 0.5em 1.25em;
font-weight: 600;
font-size: 0.95rem;
cursor: pointer;
transition:
background 0.15s,
color 0.15s,
border-color 0.15s;
}

.prev-btn:hover,
.prev-btn:focus,
.next-btn:hover,
.next-btn:focus {
background: #2ec4b6;
color: #ffffff;
border-color: #2ec4b6;
}

@media (max-width: 700px) {
.carousel-card {
border-radius: 1rem;
max-width: 99vw;
padding: 0.75rem;
}

.card-top-row {
flex-direction: row;
gap: 0.75rem;
}

.card-image {
width: 90px;
height: 90px;
border-radius: 0.5rem;
}

.card-title {
font-size: 1rem;
}

.card-price {
font-size: 0.95rem;
}

.card-description {
font-size: 0.9rem;
}

.size-btn-group {
justify-content: flex-start;
}

.add-to-cart-btn {
min-height: 50px;
font-size: 1rem;
padding: 1em 0;
border-radius: 0.5rem;
}
}

JSON from endpoint, simplified...

{
"items": [
{
"description": "Don't leave home without the Smith Level Mips snow helmet. It's built with a hybrid shell construction and Mips technology, which helps reduce rotational forces if the helmet gets hit at an angle.",
"image_url": "https://cdn.shopify.com/s/files/1/0920/9112/1967/files/1.png?v=1749094711",
"price_range": {
"currency": "USD",
"max": "225.0",
"min": "225.0"
},
"product_id": "gid://shopify/Product/10607997387055",
"product_type": "Helmet",
"tags": ["Men"],
"title": "Men's Level Mips Snow Helmet",
"variants": [
{
"available": true,
"currency": "USD",
"image_url": "https://cdn.shopify.com/s/files/1/0920/9112/1967/files/1.png?v=1749094711",
"price": "225.0",
"title": "Black / S",
"variant_id": "gid://shopify/ProductVariant/51390165909807"
},
{
"available": true,
"currency": "USD",
"image_url": "https://cdn.shopify.com/s/files/1/0920/9112/1967/files/1.png?v=1749094711",
"price": "225.0",
"title": "Black / M",
"variant_id": "gid://shopify/ProductVariant/51390165975343"
},
{
"available": true,
"currency": "USD",
"image_url": "https://cdn.shopify.com/s/files/1/0920/9112/1967/files/1.png?v=1749094711",
"price": "225.0",
"title": "Black / L",
"variant_id": "gid://shopify/ProductVariant/51390166040879"
},
{
"available": true,
"currency": "USD",
"image_url": "https://cdn.shopify.com/s/files/1/0920/9112/1967/files/1.png?v=1749094711",
"price": "225.0",
"title": "Black / XL",
"variant_id": "gid://shopify/ProductVariant/51390166106415"
},
{
"available": true,
"currency": "USD",
"image_url": "https://cdn.shopify.com/s/files/1/0920/9112/1967/files/E0062800TB00_P00_ec62e1a4-e582-443f-80d2-8abe8f76add4.webp?v=1750297479",
"price": "225.0",
"title": "Matte Slate / S",
"variant_id": "gid://shopify/ProductVariant/51390165942575"
},
{
"available": true,
"currency": "USD",
"image_url": "https://cdn.shopify.com/s/files/1/0920/9112/1967/files/E0062800TB00_P00_ec62e1a4-e582-443f-80d2-8abe8f76add4.webp?v=1750297479",
"price": "225.0",
"title": "Matte Slate / M",
"variant_id": "gid://shopify/ProductVariant/51390166008111"
},
{
"available": true,
"currency": "USD",
"image_url": "https://cdn.shopify.com/s/files/1/0920/9112/1967/files/E0062800TB00_P00_ec62e1a4-e582-443f-80d2-8abe8f76add4.webp?v=1750297479",
"price": "225.0",
"title": "Matte Slate / L",
"variant_id": "gid://shopify/ProductVariant/51390166073647"
},
{
"available": true,
"currency": "USD",
"image_url": "https://cdn.shopify.com/s/files/1/0920/9112/1967/files/E0062800TB00_P00_ec62e1a4-e582-443f-80d2-8abe8f76add4.webp?v=1750297479",
"price": "225.0",
"title": "Matte Slate / XL",
"variant_id": "gid://shopify/ProductVariant/51390166139183"
}
]
},
{
"description": "Like a good day on the mountain, the Smith Level ski and snowboard helmet is at your service for ranging far and wide. It brings the added energy absorption of Zonal KOROYD® and the advanced angled impact protection of Mips® to let you focus on ripping high-speed arcs in the alpine and chasing untracked lines in the trees. The hybrid shell design adds durability without weighing you down for a helmet that you will actually take with you when the backcountry beckons. And because you love to ski all day, the fit and vents are adjustable on the fly, so you never need to slow down.",
"image_url": "https://cdn.shopify.com/s/files/1/0920/9112/1967/files/level-mips-helmet_matteIronwood_3Q.png?v=1749177564",
"price_range": {
"currency": "USD",
"max": "225.0",
"min": "195.0"
},
"product_id": "gid://shopify/Product/10615507124527",
"product_type": "Helmet",
"tags": ["Men"],
"title": "Men's Smith Level Snow Helmet",
"variants": [
{
"available": true,
"currency": "USD",
"image_url": "https://cdn.shopify.com/s/files/1/0920/9112/1967/files/level-mips-helmet_matteIronwood_3Q.png?v=1749177564",
"price": "225.0",
"title": "Matte Ironwood / MEDIUM-MIPS",
"variant_id": "gid://shopify/ProductVariant/51399728595247"
},
{
"available": true,
"currency": "USD",
"image_url": "https://cdn.shopify.com/s/files/1/0920/9112/1967/files/level-mips-helmet_matteIronwood_3Q.png?v=1749177564",
"price": "195.0",
"title": "Matte Ironwood / S",
"variant_id": "gid://shopify/ProductVariant/51399728628015"
},
{
"available": true,
"currency": "USD",
"image_url": "https://cdn.shopify.com/s/files/1/0920/9112/1967/files/level-mips-helmet_matteIronwood_3Q.png?v=1749177564",
"price": "195.0",
"title": "Matte Ironwood / M",
"variant_id": "gid://shopify/ProductVariant/51399728660783"
},
{
"available": true,
"currency": "USD",
"image_url": "https://cdn.shopify.com/s/files/1/0920/9112/1967/files/level-mips-helmet_matteIronwood_3Q.png?v=1749177564",
"price": "195.0",
"title": "Matte Ironwood / L",
"variant_id": "gid://shopify/ProductVariant/51399728693551"
},
{
"available": true,
"currency": "USD",
"image_url": "https://cdn.shopify.com/s/files/1/0920/9112/1967/files/level-mips-helmet_matteSunrise_3Q.png?v=1750298981",
"price": "225.0",
"title": "Matte Sunrise / MEDIUM-MIPS",
"variant_id": "gid://shopify/ProductVariant/51399728824623"
},
{
"available": true,
"currency": "USD",
"image_url": "https://cdn.shopify.com/s/files/1/0920/9112/1967/files/level-mips-helmet_matteSunrise_3Q.png?v=1750298981",
"price": "195.0",
"title": "Matte Sunrise / S",
"variant_id": "gid://shopify/ProductVariant/51399728857391"
},
{
"available": true,
"currency": "USD",
"image_url": "https://cdn.shopify.com/s/files/1/0920/9112/1967/files/level-mips-helmet_matteSunrise_3Q.png?v=1750298981",
"price": "195.0",
"title": "Matte Sunrise / M",
"variant_id": "gid://shopify/ProductVariant/51399728890159"
},
{
"available": true,
"currency": "USD",
"image_url": "https://cdn.shopify.com/s/files/1/0920/9112/1967/files/level-mips-helmet_matteSunrise_3Q.png?v=1750298981",
"price": "195.0",
"title": "Matte Sunrise / L",
"variant_id": "gid://shopify/ProductVariant/51399728922927"
}
]
}
]
}