@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.
Files changed (3) hide show
  1. package/README.md +13 -4
  2. package/dist/index.js +152 -31
  3. 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 70 powerful tools
3
+ > 🛍️ **MCP Server for Shopify Admin API** — Enable AI agents to manage Shopify stores with 71 powerful tools
4
4
 
5
5
  [![npm version](https://badge.fury.io/js/@anton.andrusenko%2Fshopify-mcp-admin.svg)](https://www.npmjs.com/package/@anton.andrusenko/shopify-mcp-admin)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -11,7 +11,7 @@
11
11
 
12
12
  ## ✨ Features
13
13
 
14
- - 🛠️ **70 MCP Tools** — Complete store management: products, inventory, collections, pages, blogs, redirects, metafields, markets, locales & translations
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 **70 tools** organized into 15 categories:
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 70 registered tools with schemas
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: inputSchema71, annotations } = definition;
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(inputSchema71);
2463
- const wrappedHandler = wrapToolHandler(name, inputSchema71, handler);
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: inputSchema71,
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
- return transformProductToOutput(result.product);
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.6.0",
4
- "description": "MCP server for Shopify Admin API - enables AI agents to manage Shopify stores with 70 tools for products, inventory, collections, content, SEO, metafields, markets & translations",
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",