為 Shopify 建置自訂 MCP 伺服器
現有的 MCP 伺服器涵蓋了常見的使用情境,但每個 Shopify 商家都有獨特的需求。也許您需要一個結合庫存資料與專有需求預測模型的 MCP 伺服器。或者是一個在建立產品時強制執行特定商業規則的伺服器。又或者是一個將 Shopify 與內部 ERP 系統整合的伺服器。
本指南將引導您使用官方 TypeScript SDK 從頭建置自訂 MCP 伺服器、實作 Shopify Admin API 工具,以及為團隊發佈該伺服器。
何時建置自訂伺服器與使用現有伺服器
在以下情況下建置自訂 MCP 伺服器:
- 現有伺服器不涵蓋您的使用案例:您需要的工具結合了 Shopify 資料與內部系統
- 您需要商業邏輯強制執行:產品建立必須遵循特定的命名規範、定價規則或審批工作流程
- 安全需求要求如此:您需要精確控制暴露哪些 API 操作並稽核其使用情況
- 您想要領域特定的抽象層:不使用原始的 CRUD,而是要像「create_seasonal_collection」這樣編碼您商業流程的工具
- 效能最佳化:您需要以通用伺服器不支援的方式批次處理或快取 Shopify API 呼叫
在以下情況下使用現有伺服器:
- 標準 CRUD 操作已足夠
- 您正在製作原型或進行探索
- 社群伺服器持續維護且涵蓋您的需求
使用社群 shopify-mcp 伺服器來滿足 80% 的需求(標準操作),並為您業務中獨特的 20% 建置自訂伺服器。它們可以同時運行 -- Claude Code 支援多個 MCP 伺服器。
MCP SDK 設定(TypeScript)
專案初始化
mkdir shopify-custom-mcp
cd shopify-custom-mcp
npm init -y
npm install @modelcontextprotocol/sdk @shopify/shopify-api zod
npm install -D typescript @types/node tsx
npx tsc --init
更新您的 tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"declaration": true
},
"include": ["src/**/*"]
}
更新 package.json:
{
"name": "shopify-custom-mcp",
"version": "1.0.0",
"type": "module",
"bin": {
"shopify-custom-mcp": "./dist/index.js"
},
"scripts": {
"build": "tsc",
"dev": "tsx src/index.ts",
"start": "node dist/index.js"
}
}
基本伺服器結構
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// 解析命令列引數
const args = process.argv.slice(2);
const accessToken = args.find(a => a.startsWith("--token="))?.split("=")[1]
|| process.env.SHOPIFY_ACCESS_TOKEN;
const storeDomain = args.find(a => a.startsWith("--domain="))?.split("=")[1]
|| process.env.SHOPIFY_STORE_DOMAIN;
if (!accessToken || !storeDomain) {
console.error("Usage: shopify-custom-mcp --token=shpat_xxx --domain=store.myshopify.com");
process.exit(1);
}
// 建立 MCP 伺服器
const server = new McpServer({
name: "shopify-custom-mcp",
version: "1.0.0",
});
// Shopify API 輔助函式
async function shopifyGraphQL(query: string, variables?: Record<string, unknown>) {
const response = await fetch(
`https://${storeDomain}/admin/api/2025-01/graphql.json`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Shopify-Access-Token": accessToken!,
},
body: JSON.stringify({ query, variables }),
}
);
if (!response.ok) {
throw new Error(`Shopify API error: ${response.status} ${response.statusText}`);
}
return response.json();
}
// --- 在此註冊工具、資源和提示 ---
// (請參閱以下章節)
// 使用 stdio 傳輸啟動伺服器
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Shopify Custom MCP server running on stdio");
}
main().catch(console.error);
實作 Shopify Admin API 工具
工具 1:取得低庫存產品
此工具結合 Shopify 查詢與商業邏輯(自訂門檻):
// 註冊一個尋找低庫存產品的工具
server.tool(
"get_low_inventory_products",
"Find products where total inventory is below a threshold",
{
threshold: z.number().default(10).describe("Inventory threshold (default: 10)"),
location_id: z.string().optional().describe("Filter by location ID (optional)"),
},
async ({ threshold, location_id }) => {
const query = `
query GetProducts($first: Int!, $after: String) {
products(first: 50, after: $after) {
edges {
node {
id
title
totalInventory
variants(first: 100) {
edges {
node {
id
title
inventoryQuantity
sku
}
}
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
`;
// 分頁遍歷所有產品
let allProducts: any[] = [];
let hasNextPage = true;
let cursor: string | null = null;
while (hasNextPage) {
const result = await shopifyGraphQL(query, {
first: 50,
after: cursor,
});
const products = result.data.products.edges.map((e: any) => e.node);
allProducts.push(...products);
hasNextPage = result.data.products.pageInfo.hasNextPage;
cursor = result.data.products.pageInfo.endCursor;
}
// 依門檻篩選
const lowInventory = allProducts
.filter((p: any) => p.totalInventory < threshold)
.map((p: any) => ({
id: p.id,
title: p.title,
totalInventory: p.totalInventory,
variants: p.variants.edges.map((v: any) => ({
title: v.node.title,
sku: v.node.sku,
quantity: v.node.inventoryQuantity,
})),
}));
return {
content: [
{
type: "text" as const,
text: JSON.stringify({
threshold,
count: lowInventory.length,
products: lowInventory,
}, null, 2),
},
],
};
}
);
工具 2:依商業規則建立產品
此工具強制執行您公司的產品建立標準:
server.tool(
"create_standard_product",
"Create a product following company standards (auto-generates SKU, applies naming conventions, sets required metafields)",
{
title: z.string().describe("Product title"),
category: z.enum(["apparel", "accessories", "home", "electronics"]).describe("Product category"),
base_price: z.number().positive().describe("Base price in dollars"),
description: z.string().describe("Product description"),
vendor: z.string().optional().describe("Vendor name (defaults to company name)"),
},
async ({ title, category, base_price, description, vendor }) => {
// 商業規則:標題必須為標題大小寫格式
const formattedTitle = title
.split(" ")
.map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
.join(" ");
// 商業規則:依類別自動產生 SKU 前綴
const skuPrefixes: Record<string, string> = {
apparel: "APR",
accessories: "ACC",
home: "HOM",
electronics: "ELC",
};
const skuPrefix = skuPrefixes[category];
const skuNumber = Date.now().toString(36).toUpperCase();
const sku = `${skuPrefix}-${skuNumber}`;
// 商業規則:最小描述長度
if (description.length < 50) {
return {
content: [{
type: "text" as const,
text: JSON.stringify({
error: "Description must be at least 50 characters for SEO compliance",
currentLength: description.length,
}),
}],
isError: true,
};
}
// 商業規則:價格必須在標準級距內
const validPrices = [19.99, 29.99, 39.99, 49.99, 59.99, 79.99, 99.99, 149.99, 199.99];
const nearestPrice = validPrices.reduce((prev, curr) =>
Math.abs(curr - base_price) < Math.abs(prev - base_price) ? curr : prev
);
const mutation = `
mutation CreateProduct($input: ProductInput!) {
productCreate(input: $input) {
product {
id
title
handle
variants(first: 1) {
edges {
node {
id
sku
price
}
}
}
}
userErrors {
field
message
}
}
}
`;
const result = await shopifyGraphQL(mutation, {
input: {
title: formattedTitle,
descriptionHtml: `<p>${description}</p>`,
vendor: vendor || "Your Company Name",
productType: category,
tags: [category, "new-arrival"],
variants: [{
price: nearestPrice.toString(),
sku,
inventoryManagement: "SHOPIFY",
}],
metafields: [
{
namespace: "custom",
key: "category",
value: category,
type: "single_line_text_field",
},
{
namespace: "custom",
key: "created_via",
value: "mcp-server",
type: "single_line_text_field",
},
],
},
});
const { productCreate } = result.data;
if (productCreate.userErrors.length > 0) {
return {
content: [{
type: "text" as const,
text: JSON.stringify({ errors: productCreate.userErrors }),
}],
isError: true,
};
}
return {
content: [{
type: "text" as const,
text: JSON.stringify({
message: "Product created successfully",
product: productCreate.product,
appliedRules: {
titleFormatted: formattedTitle !== title,
skuGenerated: sku,
priceAdjusted: nearestPrice !== base_price
? `Adjusted from $${base_price} to nearest tier $${nearestPrice}`
: "No adjustment needed",
},
}, null, 2),
}],
};
}
);
新增資源
資源提供唯讀的上下文,幫助 AI 了解您 Shopify 商店的設定:
// 靜態資源:商店設定
server.resource(
"store-config",
"shopify://store/config",
async (uri) => ({
contents: [{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify({
domain: storeDomain,
apiVersion: "2025-01",
categories: ["apparel", "accessories", "home", "electronics"],
priceTiers: [19.99, 29.99, 39.99, 49.99, 59.99, 79.99, 99.99, 149.99, 199.99],
skuPrefixes: {
apparel: "APR",
accessories: "ACC",
home: "HOM",
electronics: "ELC",
},
businessRules: {
minDescriptionLength: 50,
requiredMetafields: ["custom.category"],
defaultVendor: "Your Company Name",
},
}, null, 2),
}],
})
);
// 動態資源:商店摘要(即時取得)
server.resource(
"store-summary",
"shopify://store/summary",
async (uri) => {
const result = await shopifyGraphQL(`{
shop {
name
email
currencyCode
primaryDomain { url }
}
products(first: 1) { edges { cursor } }
orders(first: 1) { edges { cursor } }
}`);
return {
contents: [{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(result.data, null, 2),
}],
};
}
);
新增提示
提示為常見工作流程提供可重複使用的對話開場白:
server.prompt(
"weekly_inventory_report",
"Generate a weekly inventory status report",
{
threshold: z.string().default("10").describe("Low stock threshold"),
},
async ({ threshold }) => ({
messages: [
{
role: "user" as const,
content: {
type: "text" as const,
text: `Generate a weekly inventory report for our Shopify store.
1. Use the get_low_inventory_products tool with threshold ${threshold}
2. Organize results by category
3. For each low-stock product, include:
- Product title and SKU
- Current inventory level
- How far below threshold it is
4. Prioritize products with 0 inventory (out of stock) at the top
5. Include a summary with total products checked, products below threshold, and out-of-stock count
6. Format as a clean Markdown table`,
},
},
],
})
);
server.prompt(
"new_product_launch",
"Guided workflow for launching a new product",
{
product_name: z.string().describe("Name of the product to launch"),
},
async ({ product_name }) => ({
messages: [
{
role: "user" as const,
content: {
type: "text" as const,
text: `Help me launch a new product: "${product_name}"
Walk me through this checklist:
1. Create the product using create_standard_product with proper category and pricing
2. Verify the product was created correctly
3. Check that SKU was generated and metafields were set
4. Suggest collections to add it to based on the category
5. Draft a product description if one wasn't provided (minimum 50 chars)
6. Recommend initial inventory levels based on similar products
Ask me for any required information before proceeding.`,
},
},
],
})
);
使用 MCP Inspector 進行測試
MCP Inspector 是一個視覺化測試工具,讓您可以在不連接 AI 主機的情況下與伺服器互動。
安裝與執行
npx @modelcontextprotocol/inspector tsx src/index.ts -- --token=shpat_xxx --domain=store.myshopify.com
這會開啟一個網頁介面,您可以:
- 檢視可用工具:查看所有已註冊工具及其結構描述
- 測試工具呼叫:填入參數並執行工具
- 檢查回應:檢視每個工具的 JSON 回應
- 檢查資源:瀏覽和讀取已註冊的資源
- 測試提示:執行提示並查看產生的訊息
在將 MCP 伺服器連接到 Claude Code 之前,請務必先使用 Inspector 進行測試。這可以在不需要 AI 對話開銷的情況下捕捉結構描述錯誤、認證問題和執行時期錯誤。
單元測試
為您的工具邏輯撰寫正式的測試:
import { describe, it, expect, vi } from 'vitest';
// 將商業邏輯與 MCP 層分開測試
describe('Product creation rules', () => {
it('formats titles to title case', () => {
const input = "organic COTTON t-shirt";
const result = input
.split(" ")
.map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
.join(" ");
expect(result).toBe("Organic Cotton T-shirt");
});
it('generates correct SKU prefix for category', () => {
const prefixes: Record<string, string> = {
apparel: "APR",
accessories: "ACC",
home: "HOM",
electronics: "ELC",
};
expect(prefixes["apparel"]).toBe("APR");
expect(prefixes["electronics"]).toBe("ELC");
});
it('snaps price to nearest tier', () => {
const tiers = [19.99, 29.99, 39.99, 49.99];
const input = 35;
const nearest = tiers.reduce((prev, curr) =>
Math.abs(curr - input) < Math.abs(prev - input) ? curr : prev
);
expect(nearest).toBe(39.99);
});
it('rejects short descriptions', () => {
const description = "A nice shirt";
expect(description.length).toBeLessThan(50);
});
});
發佈
為您的團隊發佈(npm)
npm run build
npm publish
團隊成員安裝:
claude mcp add your-custom-mcp -- npx -- -y shopify-custom-mcp --token=shpat_xxx --domain=store.myshopify.com
為您的團隊發佈(Git)
如果您不想發佈到 npm,團隊成員可以直接從您的儲存庫使用:
claude mcp add custom-shopify -- npx -- tsx /path/to/shopify-custom-mcp/src/index.ts -- --token=shpat_xxx --domain=store.myshopify.com
Docker 部署(SSE 傳輸)
對於共享團隊伺服器,您可以將傳輸修改為 SSE 並使用 Docker 部署:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express from "express";
const app = express();
const server = new McpServer({ name: "shopify-custom-mcp", version: "1.0.0" });
// ... 註冊工具、資源、提示(與上面相同)...
app.get("/sse", async (req, res) => {
const transport = new SSEServerTransport("/messages", res);
await server.connect(transport);
});
app.post("/messages", async (req, res) => {
// 處理傳入訊息
});
app.listen(3100, () => {
console.log("MCP SSE server running on port 3100");
});
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY dist/ ./dist/
EXPOSE 3100
CMD ["node", "dist/index-sse.js"]
如果您將 MCP 伺服器部署為網路服務(SSE 或 HTTP),它可以直接存取您 Shopify 商店的 Admin API。請務必:
- 在認證後端運行(API 金鑰、OAuth 或 VPN)
- 在正式環境中使用 HTTPS
- 記錄所有工具呼叫以供稽核
- 限制傳入連線速率
- 不要在沒有認證的情況下暴露到公共網際網路
完整專案結構
shopify-custom-mcp/
src/
index.ts # 主要進入點(伺服器 + 工具 + 資源 + 提示)
__tests__/
tools.test.ts # 商業邏輯的單元測試
dist/ # 編譯輸出
package.json
tsconfig.json
README.md
對於較大的伺服器,將工具拆分為單獨的檔案:
shopify-custom-mcp/
src/
index.ts # 伺服器設定和傳輸
shopify-client.ts # 共用 Shopify API 輔助工具
tools/
inventory.ts # 庫存相關工具
products.ts # 產品相關工具
orders.ts # 訂單相關工具
resources/
store-config.ts # 靜態商店設定
store-summary.ts # 動態商店資料
prompts/
reports.ts # 報告產生提示
workflows.ts # 工作流程提示
__tests__/
inventory.test.ts
products.test.ts
總結
使用 TypeScript SDK 為 Shopify 建置自訂 MCP 伺服器非常簡單直接。關鍵步驟如下:
- 初始化專案,包含 MCP SDK 和 Shopify API 用戶端
- 實作工具,封裝您的商業邏輯和 Shopify API 呼叫
- 新增資源,用於靜態設定和動態商店資料
- 新增提示,用於常見的工作流程範本
- 測試,使用 MCP Inspector 和單元測試
- 部署,依您團隊的需求透過 npm、Git 或 Docker
這項投資很快就會回本 -- 一個具有 5-10 個精心設計工具的自訂 MCP 伺服器,可以透過給予 AI 助手對您 Shopify 商店的直接、受控存取(同時內建您的商業規則),每週節省數小時的重複性工作。