Skip to main content
Glama

Shopify Storefront MCP Server

index.ts12.9 kB
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import { handleStorefrontApiResult, requestStorefrontApi, listAvailableStores, type TextContentBlock, } from "../utils/shopify-client.js"; import { CartLineInput, CartBuyerIdentityInput, AttributeInput, CartLineUpdateInput, FindProductsInputShape, GetProductByIdInputShape, CartCreateInputShape, CartLinesAddInputShape, CartLinesUpdateInputShape, CartLinesRemoveInputShape, GetCartInputShape, } from "../utils/input-schemas.js"; export function registerTools(server: McpServer) { server.tool( "findProducts", "Searches or filters products with pagination and sorting.", FindProductsInputShape, async ({ storeId, query: searchQuery, after, sortKey, reverse }) => { const query = ` query FindProducts($after: String, $query: String, $sortKey: ProductSortKeys, $reverse: Boolean) { products(first: 100, after: $after, query: $query, sortKey: $sortKey, reverse: $reverse) { pageInfo { hasNextPage hasPreviousPage startCursor endCursor } edges { cursor node { id title handle vendor priceRange { minVariantPrice { amount currencyCode } maxVariantPrice { amount currencyCode } } } } } } `; const graphQLPayload = { query, variables: { after, query: searchQuery, sortKey, reverse }, }; console.info( `[${new Date().toISOString()}] INFO: [tool:findProducts] Executing with search query: ${searchQuery} on store: ${storeId}`, ); const proxyResult = await requestStorefrontApi({ storeId, query: graphQLPayload.query, variables: graphQLPayload.variables, }); return handleStorefrontApiResult(proxyResult); }, ); server.tool( "getProductById", "Fetches product by ID. If includeImages=true, also returns the URL of the first image (resized to 75px).", GetProductByIdInputShape, async ({ storeId, productId, includeVariants, variantCount, includeImages }) => { const imageCountForQuery = includeImages ? 1 : undefined; // Only need URL if requested const query = ` query GetProduct($productId: ID!${includeVariants ? ", $variantCount: Int!" : ""}${includeImages ? ", $imageCount: Int!" : ""}) { product(id: $productId) { id title descriptionHtml vendor ${ includeVariants ? ` variants(first: $variantCount) { edges { node { id title price { amount currencyCode } selectedOptions { name value } } } }` : "" } ${ includeImages ? ` images(first: $imageCount) { edges { node { id url altText width height } } }` : "" } } }`; const graphQLPayload = { query, variables: { productId, variantCount, imageCount: imageCountForQuery }, }; console.info( `[${new Date().toISOString()}] INFO: [tool:getProductById] Executing for ID: ${productId} on store: ${storeId}`, ); const proxyResult = await requestStorefrontApi({ storeId, query: graphQLPayload.query, variables: graphQLPayload.variables, }); // If successful AND image URL requested if (!proxyResult.errors && includeImages) { interface ProductData { // Define expected structure id?: string; title?: string; descriptionHtml?: string; vendor?: string; variants?: { edges: unknown[] }; images?: { edges: { node: { url: string; altText?: string; width?: number; height?: number; }; }[]; }; } const responseData = proxyResult.data as { data?: { product?: ProductData }; }; const product = responseData?.data?.product; const firstImageEdge = product?.images?.edges?.[0]; if (product && firstImageEdge) { const img = firstImageEdge.node; const contentBlocks: TextContentBlock[] = []; // Create productInfo copy for JSON, excluding images array const productInfoForJson = { ...product }; productInfoForJson.images = undefined; // Add JSON details block contentBlocks.push({ type: "text", text: `Product Details:\n\`\`\`json\n${JSON.stringify( productInfoForJson, null, 2, )}\n\`\`\``, }); // Construct resized image URL const separator = img.url.includes("?") ? "&" : "?"; const resizedImageUrl = `${img.url}${separator}width=75`; // Use width=75 // Add the resized image URL as a separate text block contentBlocks.push({ type: "text", text: `Image URL (75px): ${resizedImageUrl}`, }); console.info( `[${new Date().toISOString()}] INFO: [getProductById] Returning product JSON and resized image URL.`, ); return { content: contentBlocks }; } } // Fallback if no images requested/found or if proxy failed return handleStorefrontApiResult(proxyResult); // Returns original data wrapped by jsonResult }, ); // --- Cart Tools --- server.tool( "cartCreate", "Creates a new shopping cart. Uses the Storefront API.", CartCreateInputShape, async ({ storeId, lines, buyerIdentity, attributes }) => { const mutation = ` mutation CartCreate($input: CartInput!) { cartCreate(input: $input) { cart { id checkoutUrl cost { totalAmount { amount currencyCode } } lines(first: 50) { edges { node { id quantity merchandise { ... on ProductVariant { id title product { title } } } } } } } userErrors { field message } } } `; const input: { lines?: unknown; buyerIdentity?: unknown; attributes?: unknown; } = {}; if (lines) input.lines = lines; if (buyerIdentity) input.buyerIdentity = buyerIdentity; if (attributes) input.attributes = attributes; const graphQLPayload = { query: mutation, variables: { input } }; console.info( `[${new Date().toISOString()}] INFO: [tool:cartCreate] Creating cart on store: ${storeId}`, ); const proxyResult = await requestStorefrontApi({ storeId, query: graphQLPayload.query, variables: graphQLPayload.variables, }); return handleStorefrontApiResult(proxyResult); }, ); server.tool( "cartLinesAdd", "Adds line items to an existing shopping cart. Uses the Storefront API.", CartLinesAddInputShape, async ({ storeId, cartId, lines }) => { const mutation = ` mutation CartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) { cartLinesAdd(cartId: $cartId, lines: $lines) { cart { id checkoutUrl cost { totalAmount { amount currencyCode } } lines(first: 50) { edges { node { id quantity merchandise { ... on ProductVariant { id title product { title } } } } } } } userErrors { field message } } } `; const graphQLPayload = { query: mutation, variables: { cartId, lines } }; console.info( `[${new Date().toISOString()}] INFO: [tool:cartLinesAdd] Adding lines to cart on store: ${storeId}`, ); const proxyResult = await requestStorefrontApi({ storeId, query: graphQLPayload.query, variables: graphQLPayload.variables, }); return handleStorefrontApiResult(proxyResult); }, ); server.tool( "cartLinesUpdate", "Updates line items (e.g., quantity) in an existing shopping cart. Uses the Storefront API.", CartLinesUpdateInputShape, async ({ storeId, cartId, lines }) => { const mutation = ` mutation CartLinesUpdate($cartId: ID!, $lines: [CartLineUpdateInput!]!) { cartLinesUpdate(cartId: $cartId, lines: $lines) { cart { id checkoutUrl cost { totalAmount { amount currencyCode } } lines(first: 50) { edges { node { id quantity merchandise { ... on ProductVariant { id title product { title } } } } } } } userErrors { field message } } } `; const graphQLPayload = { query: mutation, variables: { cartId, lines } }; console.info( `[${new Date().toISOString()}] INFO: [tool:cartLinesUpdate] Updating cart lines on store: ${storeId}`, ); const proxyResult = await requestStorefrontApi({ storeId, query: graphQLPayload.query, variables: graphQLPayload.variables, }); return handleStorefrontApiResult(proxyResult); }, ); server.tool( "cartLinesRemove", "Removes line items from an existing shopping cart. Uses the Storefront API.", CartLinesRemoveInputShape, async ({ storeId, cartId, lineIds }) => { const mutation = ` mutation CartLinesRemove($cartId: ID!, $lineIds: [ID!]!) { cartLinesRemove(cartId: $cartId, lineIds: $lineIds) { cart { id checkoutUrl cost { totalAmount { amount currencyCode } } lines(first: 50) { edges { node { id quantity merchandise { ... on ProductVariant { id title product { title } } } } } } } userErrors { field message } } } `; const graphQLPayload = { query: mutation, variables: { cartId, lineIds }, }; console.info( `[${new Date().toISOString()}] INFO: [tool:cartLinesRemove] Removing lines from cart on store: ${storeId}`, ); const proxyResult = await requestStorefrontApi({ storeId, query: graphQLPayload.query, variables: graphQLPayload.variables, }); return handleStorefrontApiResult(proxyResult); }, ); server.tool( "getCart", "Fetches the details of an existing shopping cart by its ID. Uses the Storefront API.", GetCartInputShape, async ({ storeId, cartId }) => { const query = ` query GetCart($cartId: ID!) { cart(id: $cartId) { id createdAt updatedAt checkoutUrl cost { totalAmount { amount currencyCode } subtotalAmount { amount currencyCode } totalTaxAmount { amount currencyCode } totalDutyAmount { amount currencyCode } } lines(first: 50) { edges { node { id quantity cost { totalAmount { amount currencyCode } } merchandise { ... on ProductVariant { id title price { amount currencyCode } product { id title handle } } } } } } buyerIdentity { email phone countryCode customer { id } } attributes { key value } } } `; const graphQLPayload = { query, variables: { cartId } }; console.info( `[${new Date().toISOString()}] INFO: [tool:getCart] Getting cart details on store: ${storeId}`, ); const proxyResult = await requestStorefrontApi({ storeId, query: graphQLPayload.query, variables: graphQLPayload.variables, }); return handleStorefrontApiResult(proxyResult); }, ); // --- Store Management Tools --- server.tool( "listStores", "Lists all available Shopify stores configured in the system.", {}, async () => { const stores = listAvailableStores(); console.info( `[${new Date().toISOString()}] INFO: [tool:listStores] Returning ${stores.length} configured stores`, ); return { content: [ { type: "text", text: `Available Stores:\n\`\`\`json\n${JSON.stringify(stores, null, 2)}\n\`\`\``, }, ], }; }, ); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/volticscontent/axios_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server