@anton.andrusenko/shopify-mcp-admin 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -4
- package/dist/index.js +152 -31
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @anton.andrusenko/shopify-mcp-admin
|
|
2
2
|
|
|
3
|
-
> 🛍️ **MCP Server for Shopify Admin API** — Enable AI agents to manage Shopify stores with
|
|
3
|
+
> 🛍️ **MCP Server for Shopify Admin API** — Enable AI agents to manage Shopify stores with 71 powerful tools
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@anton.andrusenko/shopify-mcp-admin)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
## ✨ Features
|
|
13
13
|
|
|
14
|
-
- 🛠️ **
|
|
14
|
+
- 🛠️ **71 MCP Tools** — Complete store management: products, inventory, collections, pages, blogs, redirects, metafields, markets, locales & translations
|
|
15
15
|
- 🤖 **AI-Optimized** — Tool descriptions and error messages designed for LLM comprehension
|
|
16
16
|
- 🔌 **Dual Transport** — STDIO for Claude Desktop, HTTP for ChatGPT/OpenAI
|
|
17
17
|
- ⚡ **Rate Limiting** — Automatic retry with exponential backoff for Shopify API limits
|
|
@@ -250,7 +250,16 @@ Each tool can be converted to OpenAI function format:
|
|
|
250
250
|
|
|
251
251
|
## 🛠️ Available Tools
|
|
252
252
|
|
|
253
|
-
@anton.andrusenko/shopify-mcp-admin provides **
|
|
253
|
+
@anton.andrusenko/shopify-mcp-admin provides **71 tools** organized into 16 categories:
|
|
254
|
+
|
|
255
|
+
<details>
|
|
256
|
+
<summary><strong>🏪 Store Info (1 tool)</strong></summary>
|
|
257
|
+
|
|
258
|
+
| Tool | Description |
|
|
259
|
+
|------|-------------|
|
|
260
|
+
| `get-store-info` | Get basic store information (name, domain, plan, currency, timezone, email) |
|
|
261
|
+
|
|
262
|
+
</details>
|
|
254
263
|
|
|
255
264
|
<details>
|
|
256
265
|
<summary><strong>📦 Product Management (7 tools)</strong></summary>
|
|
@@ -488,7 +497,7 @@ npm run inspect:config
|
|
|
488
497
|
|
|
489
498
|
MCP Inspector opens a web UI at `http://localhost:6274` where you can:
|
|
490
499
|
|
|
491
|
-
- 📋 Browse all
|
|
500
|
+
- 📋 Browse all 71 registered tools with schemas
|
|
492
501
|
- ▶️ Execute tools interactively and view results
|
|
493
502
|
- 🔍 Inspect JSON-RPC protocol messages
|
|
494
503
|
- 📊 Monitor server events in real-time
|
package/dist/index.js
CHANGED
|
@@ -2451,7 +2451,7 @@ function wrapToolHandler(toolName, schema, handler) {
|
|
|
2451
2451
|
};
|
|
2452
2452
|
}
|
|
2453
2453
|
function registerTool(definition, handler, options = {}) {
|
|
2454
|
-
const { name, title, description, inputSchema:
|
|
2454
|
+
const { name, title, description, inputSchema: inputSchema72, annotations } = definition;
|
|
2455
2455
|
try {
|
|
2456
2456
|
if (!options.skipNameValidation) {
|
|
2457
2457
|
validateToolName(name);
|
|
@@ -2459,8 +2459,8 @@ function registerTool(definition, handler, options = {}) {
|
|
|
2459
2459
|
if (registeredTools.has(name)) {
|
|
2460
2460
|
throw new Error(`Tool "${name}" is already registered. Tool names must be unique.`);
|
|
2461
2461
|
}
|
|
2462
|
-
const jsonSchema = convertZodToJsonSchema(
|
|
2463
|
-
const wrappedHandler = wrapToolHandler(name,
|
|
2462
|
+
const jsonSchema = convertZodToJsonSchema(inputSchema72);
|
|
2463
|
+
const wrappedHandler = wrapToolHandler(name, inputSchema72, handler);
|
|
2464
2464
|
const finalAnnotations = {
|
|
2465
2465
|
...deriveDefaultAnnotations(name),
|
|
2466
2466
|
...annotations,
|
|
@@ -2472,7 +2472,7 @@ function registerTool(definition, handler, options = {}) {
|
|
|
2472
2472
|
title,
|
|
2473
2473
|
description,
|
|
2474
2474
|
inputSchema: jsonSchema,
|
|
2475
|
-
zodSchema:
|
|
2475
|
+
zodSchema: inputSchema72,
|
|
2476
2476
|
handler: wrappedHandler,
|
|
2477
2477
|
annotations: finalAnnotations
|
|
2478
2478
|
};
|
|
@@ -2648,6 +2648,28 @@ var PRODUCT_VARIANTS_BULK_UPDATE_MUTATION = `
|
|
|
2648
2648
|
}
|
|
2649
2649
|
}
|
|
2650
2650
|
`;
|
|
2651
|
+
var PRODUCT_VARIANTS_BULK_CREATE_MUTATION = `
|
|
2652
|
+
mutation ProductVariantsBulkCreate($productId: ID!, $variants: [ProductVariantsBulkInput!]!) {
|
|
2653
|
+
productVariantsBulkCreate(productId: $productId, variants: $variants) {
|
|
2654
|
+
product {
|
|
2655
|
+
id
|
|
2656
|
+
}
|
|
2657
|
+
productVariants {
|
|
2658
|
+
id
|
|
2659
|
+
title
|
|
2660
|
+
price
|
|
2661
|
+
compareAtPrice
|
|
2662
|
+
sku
|
|
2663
|
+
barcode
|
|
2664
|
+
inventoryQuantity
|
|
2665
|
+
}
|
|
2666
|
+
userErrors {
|
|
2667
|
+
field
|
|
2668
|
+
message
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
`;
|
|
2651
2673
|
var PRODUCT_SET_MEDIA_MUTATION = `
|
|
2652
2674
|
mutation ProductSetMedia($input: ProductSetInput!) {
|
|
2653
2675
|
productSet(input: $input) {
|
|
@@ -2781,26 +2803,6 @@ function buildProductCreateVariables(input) {
|
|
|
2781
2803
|
if (input.handle !== void 0) {
|
|
2782
2804
|
productData.handle = input.handle;
|
|
2783
2805
|
}
|
|
2784
|
-
if (input.variants && input.variants.length > 0) {
|
|
2785
|
-
productData.variants = input.variants.map((variant) => {
|
|
2786
|
-
const variantInput = {
|
|
2787
|
-
price: variant.price
|
|
2788
|
-
};
|
|
2789
|
-
if (variant.compareAtPrice !== void 0) {
|
|
2790
|
-
variantInput.compareAtPrice = variant.compareAtPrice;
|
|
2791
|
-
}
|
|
2792
|
-
if (variant.sku !== void 0) {
|
|
2793
|
-
variantInput.sku = variant.sku;
|
|
2794
|
-
}
|
|
2795
|
-
if (variant.barcode !== void 0) {
|
|
2796
|
-
variantInput.barcode = variant.barcode;
|
|
2797
|
-
}
|
|
2798
|
-
if (variant.options !== void 0 && variant.options.length > 0) {
|
|
2799
|
-
variantInput.options = variant.options;
|
|
2800
|
-
}
|
|
2801
|
-
return variantInput;
|
|
2802
|
-
});
|
|
2803
|
-
}
|
|
2804
2806
|
let media;
|
|
2805
2807
|
if (input.images && input.images.length > 0) {
|
|
2806
2808
|
media = input.images.map((image) => ({
|
|
@@ -2811,6 +2813,65 @@ function buildProductCreateVariables(input) {
|
|
|
2811
2813
|
}
|
|
2812
2814
|
return { product: productData, media };
|
|
2813
2815
|
}
|
|
2816
|
+
async function createProductVariants(productId, variants) {
|
|
2817
|
+
const client = await getShopifyClient();
|
|
2818
|
+
const variantInputs = variants.map((variant) => {
|
|
2819
|
+
const variantInput = {
|
|
2820
|
+
price: variant.price
|
|
2821
|
+
};
|
|
2822
|
+
if (variant.compareAtPrice !== void 0) {
|
|
2823
|
+
variantInput.compareAtPrice = variant.compareAtPrice;
|
|
2824
|
+
}
|
|
2825
|
+
if (variant.sku !== void 0) {
|
|
2826
|
+
variantInput.sku = variant.sku;
|
|
2827
|
+
}
|
|
2828
|
+
if (variant.barcode !== void 0) {
|
|
2829
|
+
variantInput.barcode = variant.barcode;
|
|
2830
|
+
}
|
|
2831
|
+
if (variant.options !== void 0 && variant.options.length > 0) {
|
|
2832
|
+
variantInput.optionValues = variant.options.map((optionValue) => ({
|
|
2833
|
+
name: optionValue,
|
|
2834
|
+
optionName: optionValue
|
|
2835
|
+
// Use the option value as the option name for simple cases
|
|
2836
|
+
}));
|
|
2837
|
+
}
|
|
2838
|
+
return variantInput;
|
|
2839
|
+
});
|
|
2840
|
+
log.debug(`Creating ${variants.length} variants for product: ${productId}`);
|
|
2841
|
+
const response = await withRateLimit(
|
|
2842
|
+
() => client.graphql.request(
|
|
2843
|
+
PRODUCT_VARIANTS_BULK_CREATE_MUTATION,
|
|
2844
|
+
{
|
|
2845
|
+
variables: { productId, variants: variantInputs }
|
|
2846
|
+
}
|
|
2847
|
+
),
|
|
2848
|
+
"create-product-variants"
|
|
2849
|
+
);
|
|
2850
|
+
if (response.errors && response.errors.length > 0) {
|
|
2851
|
+
const errorMessage = response.errors.map((e) => e.message).join(", ");
|
|
2852
|
+
throw new Error(`GraphQL error: ${errorMessage}`);
|
|
2853
|
+
}
|
|
2854
|
+
const result = response.data?.productVariantsBulkCreate;
|
|
2855
|
+
if (!result) {
|
|
2856
|
+
throw new Error("No response data from productVariantsBulkCreate mutation");
|
|
2857
|
+
}
|
|
2858
|
+
if (result.userErrors && result.userErrors.length > 0) {
|
|
2859
|
+
throw new ShopifyUserErrorException(result.userErrors);
|
|
2860
|
+
}
|
|
2861
|
+
if (!result.productVariants || result.productVariants.length === 0) {
|
|
2862
|
+
throw new Error("No variants were created");
|
|
2863
|
+
}
|
|
2864
|
+
log.debug(`Created ${result.productVariants.length} variants`);
|
|
2865
|
+
return result.productVariants.map((variant) => ({
|
|
2866
|
+
id: variant.id,
|
|
2867
|
+
title: variant.title,
|
|
2868
|
+
price: variant.price,
|
|
2869
|
+
compareAtPrice: variant.compareAtPrice,
|
|
2870
|
+
sku: variant.sku,
|
|
2871
|
+
barcode: variant.barcode,
|
|
2872
|
+
inventoryQuantity: variant.inventoryQuantity
|
|
2873
|
+
}));
|
|
2874
|
+
}
|
|
2814
2875
|
async function createProduct(input) {
|
|
2815
2876
|
const client = await getShopifyClient();
|
|
2816
2877
|
const variables = buildProductCreateVariables(input);
|
|
@@ -2836,7 +2897,23 @@ async function createProduct(input) {
|
|
|
2836
2897
|
throw new Error("Product was not created (no product in response)");
|
|
2837
2898
|
}
|
|
2838
2899
|
log.debug(`Created product: ${result.product.id}`);
|
|
2839
|
-
|
|
2900
|
+
let productOutput = transformProductToOutput(result.product);
|
|
2901
|
+
if (input.variants && input.variants.length > 0) {
|
|
2902
|
+
try {
|
|
2903
|
+
const createdVariants = await createProductVariants(result.product.id, input.variants);
|
|
2904
|
+
productOutput = {
|
|
2905
|
+
...productOutput,
|
|
2906
|
+
variants: createdVariants
|
|
2907
|
+
};
|
|
2908
|
+
log.debug(`Created ${createdVariants.length} variants for product: ${result.product.id}`);
|
|
2909
|
+
} catch (variantError) {
|
|
2910
|
+
log.warn(
|
|
2911
|
+
`Product created but variant creation failed: ${variantError instanceof Error ? variantError.message : "Unknown error"}`
|
|
2912
|
+
);
|
|
2913
|
+
log.warn("Variants can be added using update-product-variant tool");
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
return productOutput;
|
|
2840
2917
|
}
|
|
2841
2918
|
async function getProduct(id) {
|
|
2842
2919
|
const client = await getShopifyClient();
|
|
@@ -5540,10 +5617,10 @@ var inputSchema8 = z10.object({
|
|
|
5540
5617
|
// Variants
|
|
5541
5618
|
variants: z10.array(
|
|
5542
5619
|
z10.object({
|
|
5543
|
-
price: z10.string().describe('Variant price as decimal string. Example: "29.99"'),
|
|
5544
|
-
compareAtPrice: z10.string().optional().describe('Original price for sale display. Example: "39.99"'),
|
|
5620
|
+
price: z10.union([z10.string(), z10.number()]).transform((val) => typeof val === "number" ? String(val) : val).describe('Variant price as decimal string. Example: "29.99"'),
|
|
5621
|
+
compareAtPrice: z10.union([z10.string(), z10.number()]).optional().transform((val) => typeof val === "number" ? String(val) : val).describe('Original price for sale display. Example: "39.99"'),
|
|
5545
5622
|
sku: z10.string().optional().describe('Stock keeping unit. Example: "SHIRT-001-RED-M"'),
|
|
5546
|
-
barcode: z10.string().optional().describe("Barcode (UPC, EAN, etc.)"),
|
|
5623
|
+
barcode: z10.union([z10.string(), z10.number()]).optional().transform((val) => typeof val === "number" ? String(val) : val).describe("Barcode (UPC, EAN, etc.)"),
|
|
5547
5624
|
inventoryQuantity: z10.number().int().min(0).optional().describe("Initial inventory quantity (note: may require separate inventory API)"),
|
|
5548
5625
|
options: z10.array(z10.string()).optional().describe('Variant options. Example: ["Red", "Medium"]')
|
|
5549
5626
|
})
|
|
@@ -10300,11 +10377,11 @@ var inputSchema44 = z46.object({
|
|
|
10300
10377
|
id: variantIdSchema.describe(
|
|
10301
10378
|
'Shopify variant GID (e.g., "gid://shopify/ProductVariant/456"). Required. Use get-product to find variant IDs for a product.'
|
|
10302
10379
|
),
|
|
10303
|
-
price: z46.string().optional().describe('New variant price as decimal string (e.g., "29.99")'),
|
|
10304
|
-
compareAtPrice: z46.string().nullable().optional().describe(
|
|
10380
|
+
price: z46.union([z46.string(), z46.number()]).optional().transform((val) => typeof val === "number" ? String(val) : val).describe('New variant price as decimal string (e.g., "29.99")'),
|
|
10381
|
+
compareAtPrice: z46.union([z46.string(), z46.number()]).nullable().optional().transform((val) => typeof val === "number" ? String(val) : val).describe(
|
|
10305
10382
|
'Original price for sale display (e.g., "39.99"). When set higher than price, Shopify shows strikethrough pricing. Set to null to remove the compare at price.'
|
|
10306
10383
|
),
|
|
10307
|
-
barcode: z46.string().nullable().optional().describe("Barcode (UPC, EAN, ISBN, etc.). Set to null to remove.")
|
|
10384
|
+
barcode: z46.union([z46.string(), z46.number()]).nullable().optional().transform((val) => typeof val === "number" ? String(val) : val).describe("Barcode (UPC, EAN, ISBN, etc.). Set to null to remove.")
|
|
10308
10385
|
}).refine(
|
|
10309
10386
|
(data) => {
|
|
10310
10387
|
const { productId: _productId, id: _id, ...updateFields } = data;
|
|
@@ -12483,6 +12560,49 @@ function registerSubmitRedirectImportTool() {
|
|
|
12483
12560
|
);
|
|
12484
12561
|
}
|
|
12485
12562
|
|
|
12563
|
+
// src/tools/get-store-info.ts
|
|
12564
|
+
import { z as z73 } from "zod";
|
|
12565
|
+
var inputSchema71 = z73.object({});
|
|
12566
|
+
var outputSchema71 = z73.object({
|
|
12567
|
+
name: z73.string().describe("Store name"),
|
|
12568
|
+
domain: z73.string().describe("Store domain (without .myshopify.com)"),
|
|
12569
|
+
myshopifyDomain: z73.string().describe("Full myshopify.com domain"),
|
|
12570
|
+
plan: z73.object({
|
|
12571
|
+
displayName: z73.string().describe("Current plan name"),
|
|
12572
|
+
partnerDevelopment: z73.boolean().describe("Whether partner development store")
|
|
12573
|
+
}).describe("Store plan details"),
|
|
12574
|
+
currencyCode: z73.string().describe("Store currency code (e.g., USD, EUR)"),
|
|
12575
|
+
timezone: z73.string().describe("Store IANA timezone"),
|
|
12576
|
+
contactEmail: z73.string().describe("Store contact email")
|
|
12577
|
+
});
|
|
12578
|
+
async function handleGetStoreInfo(context, _params) {
|
|
12579
|
+
log.debug(`Getting store info for shop: ${context.shopDomain}`);
|
|
12580
|
+
return await getStoreInfo();
|
|
12581
|
+
}
|
|
12582
|
+
function registerGetStoreInfoTool() {
|
|
12583
|
+
registerContextAwareTool(
|
|
12584
|
+
{
|
|
12585
|
+
name: "get-store-info",
|
|
12586
|
+
title: "Get Store Info",
|
|
12587
|
+
description: "Get basic information about the connected Shopify store including name, domain, plan, currency, timezone, and contact email. **Prerequisites:** None. **Use this** to understand store context before performing operations.",
|
|
12588
|
+
inputSchema: inputSchema71,
|
|
12589
|
+
outputSchema: outputSchema71,
|
|
12590
|
+
category: "store",
|
|
12591
|
+
relationships: {
|
|
12592
|
+
relatedTools: [],
|
|
12593
|
+
followUps: ["list-products", "list-collections"]
|
|
12594
|
+
},
|
|
12595
|
+
annotations: {
|
|
12596
|
+
readOnlyHint: true,
|
|
12597
|
+
destructiveHint: false,
|
|
12598
|
+
idempotentHint: true,
|
|
12599
|
+
openWorldHint: true
|
|
12600
|
+
}
|
|
12601
|
+
},
|
|
12602
|
+
handleGetStoreInfo
|
|
12603
|
+
);
|
|
12604
|
+
}
|
|
12605
|
+
|
|
12486
12606
|
// src/tools/index.ts
|
|
12487
12607
|
function setupToolHandlers(server) {
|
|
12488
12608
|
server.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
@@ -12609,6 +12729,7 @@ function registerAllTools(server) {
|
|
|
12609
12729
|
registerBulkDeleteRedirectsBySearchTool();
|
|
12610
12730
|
registerImportRedirectsTool();
|
|
12611
12731
|
registerSubmitRedirectImportTool();
|
|
12732
|
+
registerGetStoreInfoTool();
|
|
12612
12733
|
const toolCount = getRegisteredTools().length;
|
|
12613
12734
|
log.debug(`Tool registration complete: ${toolCount} tools registered`);
|
|
12614
12735
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anton.andrusenko/shopify-mcp-admin",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MCP server for Shopify Admin API - enables AI agents to manage Shopify stores with
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "MCP server for Shopify Admin API - enables AI agents to manage Shopify stores with 71 tools for products, inventory, collections, content, SEO, metafields, markets & translations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|