@anton.andrusenko/shopify-mcp-admin 0.2.0 → 0.3.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 +31 -0
  2. package/dist/index.js +1081 -681
  3. package/package.json +12 -2
package/dist/index.js CHANGED
@@ -22,7 +22,10 @@ var configSchema = z.object({
22
22
  PORT: z.string().default("3000").transform(Number),
23
23
  // Transport selection (AC-2.2.6, AC-2.2.7)
24
24
  // Default: stdio for Claude Desktop compatibility
25
- TRANSPORT: z.enum(["stdio", "http"]).default("stdio")
25
+ TRANSPORT: z.enum(["stdio", "http"]).default("stdio"),
26
+ // Store info cache TTL (milliseconds)
27
+ // Default: 5 minutes (300000ms) - configurable for performance tuning
28
+ STORE_INFO_CACHE_TTL_MS: z.string().optional().default("300000").transform(Number).describe("Cache TTL for store info in milliseconds (default: 5 minutes)")
26
29
  }).refine(
27
30
  (data) => {
28
31
  const hasLegacyAuth = !!data.SHOPIFY_ACCESS_TOKEN;
@@ -936,7 +939,7 @@ function transformShopResponse(shop) {
936
939
  async function getStoreInfo() {
937
940
  const now = Date.now();
938
941
  const config = getConfig();
939
- const ttl = "STORE_INFO_CACHE_TTL_MS" in config ? config.STORE_INFO_CACHE_TTL_MS ?? DEFAULT_CACHE_TTL_MS : DEFAULT_CACHE_TTL_MS;
942
+ const ttl = config.STORE_INFO_CACHE_TTL_MS ?? DEFAULT_CACHE_TTL_MS;
940
943
  if (_cachedStoreInfo && now - _cacheTimestamp < ttl) {
941
944
  log.debug("Returning cached store info");
942
945
  return _cachedStoreInfo;
@@ -963,6 +966,42 @@ async function getStoreInfo() {
963
966
  var require2 = createRequire(import.meta.url);
964
967
  var packageJson = require2("../package.json");
965
968
  var SERVER_NAME = "shopify-mcp-admin";
969
+ var SERVER_INSTRUCTIONS = `This MCP server provides access to a Shopify store's Admin API.
970
+
971
+ KEY CONCEPTS:
972
+ - Products contain variants; each variant has its own price, SKU, and inventory
973
+ - Inventory is tracked per variant AND per location (multi-location stores)
974
+ - Metafields store custom data using namespace/key pairs
975
+ - Collections organize products for navigation and SEO
976
+ - All IDs use Shopify GID format: "gid://shopify/Product/123"
977
+
978
+ RESOURCE TYPES & GID FORMATS:
979
+ - Products: gid://shopify/Product/{id}
980
+ - Variants: gid://shopify/ProductVariant/{id}
981
+ - Collections: gid://shopify/Collection/{id}
982
+ - Images: gid://shopify/MediaImage/{id}
983
+ - Pages: gid://shopify/Page/{id}
984
+ - Blogs: gid://shopify/Blog/{id}
985
+ - Articles: gid://shopify/Article/{id}
986
+ - Redirects: gid://shopify/UrlRedirect/{id}
987
+ - InventoryItems: gid://shopify/InventoryItem/{id}
988
+ - Locations: gid://shopify/Location/{id}
989
+
990
+ BEST PRACTICES:
991
+ - Always verify product/variant IDs using get-product before updates
992
+ - Use list-low-inventory to proactively identify stock issues
993
+ - When updating SEO, consider updating URL redirects
994
+ - For bulk operations, prefer bulk tools when available
995
+ - Use metafields to store custom SEO data (JSON-LD, Open Graph)
996
+
997
+ RATE LIMITS:
998
+ - Shopify GraphQL has cost-based rate limiting (~50 points/sec)
999
+ - Large queries may retry automatically with exponential backoff
1000
+ - Rate limit errors include helpful retry suggestions
1001
+
1002
+ WORKFLOW HINTS:
1003
+ - Tool descriptions include **Prerequisites:** and **Follow-ups:** for multi-step operations
1004
+ - Check tool category and relationships for semantic grouping`;
966
1005
  function getServerVersion() {
967
1006
  return packageJson.version;
968
1007
  }
@@ -978,9 +1017,13 @@ function createServer() {
978
1017
  capabilities: {
979
1018
  tools: {},
980
1019
  // Enable tools capability (AC-2.1.4)
981
- resources: {}
1020
+ resources: {},
982
1021
  // Enable resources capability (AC-2.1.4)
983
- }
1022
+ logging: {}
1023
+ // Enable logging capability (AC-9.5.1.2)
1024
+ },
1025
+ instructions: SERVER_INSTRUCTIONS
1026
+ // Provide Shopify context to AI agents (AC-9.5.1.1)
984
1027
  }
985
1028
  );
986
1029
  return server;
@@ -2334,6 +2377,21 @@ function createSingleTenantContext(client, shopDomain) {
2334
2377
 
2335
2378
  // src/tools/registration.ts
2336
2379
  var KEBAB_CASE_REGEX = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
2380
+ function deriveDefaultAnnotations(name) {
2381
+ const isReadOnly = name.startsWith("get-") || name.startsWith("list-");
2382
+ const isDestructive = name.startsWith("delete-");
2383
+ const isCreate = name.startsWith("create-");
2384
+ return {
2385
+ readOnlyHint: isReadOnly,
2386
+ destructiveHint: isDestructive,
2387
+ // create-* is NOT idempotent (each call creates new resource)
2388
+ // delete-* is NOT idempotent (second call fails - resource gone)
2389
+ // update-*, set-*, add-*, remove-*, reorder-* ARE idempotent
2390
+ idempotentHint: !isCreate && !isDestructive,
2391
+ // All our tools interact with Shopify API
2392
+ openWorldHint: true
2393
+ };
2394
+ }
2337
2395
  var registeredTools = /* @__PURE__ */ new Map();
2338
2396
  function validateToolName(name) {
2339
2397
  if (!name || name.trim() === "") {
@@ -2393,7 +2451,7 @@ function wrapToolHandler(toolName, schema, handler) {
2393
2451
  };
2394
2452
  }
2395
2453
  function registerTool(definition, handler, options = {}) {
2396
- const { name, title, description, inputSchema: inputSchema41 } = definition;
2454
+ const { name, title, description, inputSchema: inputSchema41, annotations } = definition;
2397
2455
  try {
2398
2456
  if (!options.skipNameValidation) {
2399
2457
  validateToolName(name);
@@ -2403,13 +2461,20 @@ function registerTool(definition, handler, options = {}) {
2403
2461
  }
2404
2462
  const jsonSchema = convertZodToJsonSchema(inputSchema41);
2405
2463
  const wrappedHandler = wrapToolHandler(name, inputSchema41, handler);
2464
+ const finalAnnotations = {
2465
+ ...deriveDefaultAnnotations(name),
2466
+ ...annotations,
2467
+ // Use definition title as fallback for annotation title
2468
+ title: annotations?.title ?? title
2469
+ };
2406
2470
  const registeredTool = {
2407
2471
  name,
2408
2472
  title,
2409
2473
  description,
2410
2474
  inputSchema: jsonSchema,
2411
2475
  zodSchema: inputSchema41,
2412
- handler: wrappedHandler
2476
+ handler: wrappedHandler,
2477
+ annotations: finalAnnotations
2413
2478
  };
2414
2479
  registeredTools.set(name, registeredTool);
2415
2480
  log.debug(`Registered tool: ${name}`);
@@ -2428,7 +2493,8 @@ function getRegisteredTools() {
2428
2493
  return Array.from(registeredTools.values()).map((tool) => ({
2429
2494
  name: tool.name,
2430
2495
  description: `${tool.title}: ${tool.description}`,
2431
- inputSchema: tool.inputSchema
2496
+ inputSchema: tool.inputSchema,
2497
+ annotations: tool.annotations
2432
2498
  }));
2433
2499
  }
2434
2500
  function getToolByName(name) {
@@ -2448,7 +2514,7 @@ function registerContextAwareTool(definition, handler, options = {}) {
2448
2514
  }
2449
2515
 
2450
2516
  // src/tools/add-product-image.ts
2451
- import { z as z2 } from "zod";
2517
+ import { z as z3 } from "zod";
2452
2518
 
2453
2519
  // src/shopify/mutations.ts
2454
2520
  var PRODUCT_CREATE_MUTATION = `
@@ -3088,22 +3154,41 @@ async function addProductImage(productId, input) {
3088
3154
  };
3089
3155
  }
3090
3156
 
3157
+ // src/tools/validators.ts
3158
+ import { z as z2 } from "zod";
3159
+ var GID_PATTERN = /^gid:\/\/shopify\/[A-Za-z]+\/\d+$/;
3160
+ function gidSchema(resourceType) {
3161
+ const pattern = resourceType ? new RegExp(`^gid://shopify/${resourceType}/\\d+$`) : GID_PATTERN;
3162
+ const example = resourceType ? `gid://shopify/${resourceType}/123456789` : "gid://shopify/Product/123456789";
3163
+ return z2.string().regex(pattern, `Must be a valid Shopify GID (e.g., ${example})`).describe(`Shopify Global ID (format: ${example})`);
3164
+ }
3165
+ var productIdSchema = gidSchema("Product");
3166
+ var variantIdSchema = gidSchema("ProductVariant");
3167
+ var collectionIdSchema = gidSchema("Collection");
3168
+ var imageIdSchema = gidSchema("MediaImage");
3169
+ var pageIdSchema = gidSchema("Page");
3170
+ var blogIdSchema = gidSchema("Blog");
3171
+ var articleIdSchema = gidSchema("Article");
3172
+ var redirectIdSchema = gidSchema("UrlRedirect");
3173
+ var inventoryItemIdSchema = gidSchema("InventoryItem");
3174
+ var locationIdSchema = gidSchema("Location");
3175
+
3091
3176
  // src/tools/add-product-image.ts
3092
- var inputSchema = z2.object({
3093
- productId: z2.string().min(1).describe(
3177
+ var inputSchema = z3.object({
3178
+ productId: productIdSchema.describe(
3094
3179
  'Shopify product GID (e.g., "gid://shopify/Product/123"). Required. Use list-products to find product IDs.'
3095
3180
  ),
3096
- url: z2.string().url().describe(
3181
+ url: z3.string().url().describe(
3097
3182
  "Publicly accessible image URL. Shopify will fetch and host the image. Required. Supported formats: JPEG, PNG, GIF, WEBP."
3098
3183
  ),
3099
- altText: z2.string().optional().describe(
3184
+ altText: z3.string().optional().describe(
3100
3185
  "Alt text for accessibility and SEO. Describes the image for screen readers. Recommended for better accessibility and search rankings."
3101
3186
  )
3102
3187
  });
3103
- var outputSchema = z2.object({
3104
- id: z2.string().describe('Image GID (e.g., "gid://shopify/MediaImage/123")'),
3105
- url: z2.string().describe("Shopify CDN URL for the image"),
3106
- altText: z2.string().nullable().describe("Alt text for the image")
3188
+ var outputSchema = z3.object({
3189
+ id: z3.string().describe('Image GID (e.g., "gid://shopify/MediaImage/123")'),
3190
+ url: z3.string().describe("Shopify CDN URL for the image"),
3191
+ altText: z3.string().nullable().describe("Alt text for the image")
3107
3192
  });
3108
3193
  var handleAddProductImage = async (context, params) => {
3109
3194
  log.debug(`Adding image to product on shop: ${context.shopDomain}`);
@@ -3161,6 +3246,14 @@ function registerAddProductImageTool() {
3161
3246
  relatedTools: ["get-product", "update-product-image"],
3162
3247
  prerequisites: ["create-product"],
3163
3248
  followUps: ["update-product-image", "reorder-product-images"]
3249
+ },
3250
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
3251
+ annotations: {
3252
+ readOnlyHint: false,
3253
+ destructiveHint: false,
3254
+ idempotentHint: false,
3255
+ // Adding same URL creates duplicate images!
3256
+ openWorldHint: true
3164
3257
  }
3165
3258
  },
3166
3259
  handleAddProductImage
@@ -3168,7 +3261,7 @@ function registerAddProductImageTool() {
3168
3261
  }
3169
3262
 
3170
3263
  // src/tools/add-products-to-collection.ts
3171
- import { z as z3 } from "zod";
3264
+ import { z as z4 } from "zod";
3172
3265
 
3173
3266
  // src/shopify/collections.ts
3174
3267
  var GET_COLLECTION_QUERY = `
@@ -3569,18 +3662,18 @@ async function removeProductsFromCollection(collectionId, productIds) {
3569
3662
  }
3570
3663
 
3571
3664
  // src/tools/add-products-to-collection.ts
3572
- var inputSchema2 = z3.object({
3573
- collectionId: z3.string().min(1).describe(
3665
+ var inputSchema2 = z4.object({
3666
+ collectionId: collectionIdSchema.describe(
3574
3667
  'Collection GID (e.g., "gid://shopify/Collection/123"). Use list-collections to find valid collection IDs.'
3575
3668
  ),
3576
- productIds: z3.array(z3.string().min(1)).min(1).max(250).describe(
3669
+ productIds: z4.array(productIdSchema).min(1).max(250).describe(
3577
3670
  'Array of product GIDs to add (e.g., ["gid://shopify/Product/123", "gid://shopify/Product/456"]). Maximum 250 products per call. Use list-products to find valid product IDs.'
3578
3671
  )
3579
3672
  });
3580
- var outputSchema2 = z3.object({
3581
- collectionId: z3.string().describe("Collection GID"),
3582
- productsCount: z3.number().describe("Total number of products now in the collection"),
3583
- addedCount: z3.number().describe("Number of products added in this operation")
3673
+ var outputSchema2 = z4.object({
3674
+ collectionId: z4.string().describe("Collection GID"),
3675
+ productsCount: z4.number().describe("Total number of products now in the collection"),
3676
+ addedCount: z4.number().describe("Number of products added in this operation")
3584
3677
  });
3585
3678
  var handleAddProductsToCollection = async (context, params) => {
3586
3679
  log.debug(
@@ -3624,6 +3717,14 @@ function registerAddProductsToCollectionTool() {
3624
3717
  relatedTools: ["remove-products-from-collection", "list-products"],
3625
3718
  prerequisites: ["create-collection", "list-products"],
3626
3719
  followUps: ["get-collection"]
3720
+ },
3721
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
3722
+ annotations: {
3723
+ readOnlyHint: false,
3724
+ destructiveHint: false,
3725
+ idempotentHint: true,
3726
+ // Adding same products is idempotent (no-op)
3727
+ openWorldHint: true
3627
3728
  }
3628
3729
  },
3629
3730
  handleAddProductsToCollection
@@ -3631,7 +3732,7 @@ function registerAddProductsToCollectionTool() {
3631
3732
  }
3632
3733
 
3633
3734
  // src/tools/create-article.ts
3634
- import { z as z4 } from "zod";
3735
+ import { z as z5 } from "zod";
3635
3736
 
3636
3737
  // src/shopify/blogs.ts
3637
3738
  var LIST_BLOGS_QUERY = `
@@ -4137,51 +4238,51 @@ async function deleteArticle(articleId) {
4137
4238
  }
4138
4239
 
4139
4240
  // src/tools/create-article.ts
4140
- var inputSchema3 = z4.object({
4141
- blogId: z4.string().describe(
4241
+ var inputSchema3 = z5.object({
4242
+ blogId: blogIdSchema.describe(
4142
4243
  'The blog ID to create the article in (required). Must be a valid Shopify GID format. Example: "gid://shopify/Blog/12345". Use list-blogs to find blog IDs.'
4143
4244
  ),
4144
- title: z4.string().min(1).describe(
4245
+ title: z5.string().min(1).describe(
4145
4246
  "The title of the article (required). This will be displayed as the article heading. Example: 'How to Use Our Products', '10 Tips for Beginners'."
4146
4247
  ),
4147
- authorName: z4.string().min(1).describe(
4248
+ authorName: z5.string().min(1).describe(
4148
4249
  "The name of the article author (required). This is displayed on the article. Example: 'John Doe', 'Marketing Team'."
4149
4250
  ),
4150
- body: z4.string().optional().describe(
4251
+ body: z5.string().optional().describe(
4151
4252
  "The HTML body content of the article. Supports HTML markup for formatting. Example: '<h2>Introduction</h2><p>Welcome to our guide...</p>'"
4152
4253
  ),
4153
- summary: z4.string().optional().describe(
4254
+ summary: z5.string().optional().describe(
4154
4255
  "A summary or excerpt of the article. Used for previews on blog listing pages. Can include HTML markup."
4155
4256
  ),
4156
- tags: z4.array(z4.string()).optional().describe(
4257
+ tags: z5.array(z5.string()).optional().describe(
4157
4258
  "Tags for categorization. Helps organize articles and improve discoverability. Example: ['guide', 'tutorial', 'beginner']."
4158
4259
  ),
4159
- image: z4.object({
4160
- url: z4.string().url().describe("The URL of the featured image"),
4161
- altText: z4.string().optional().describe("Alt text for accessibility")
4260
+ image: z5.object({
4261
+ url: z5.string().url().describe("The URL of the featured image"),
4262
+ altText: z5.string().optional().describe("Alt text for accessibility")
4162
4263
  }).optional().describe("Featured image for the article."),
4163
- isPublished: z4.boolean().optional().describe(
4264
+ isPublished: z5.boolean().optional().describe(
4164
4265
  "Whether the article should be visible on the storefront. Defaults to false (unpublished). Set to true to make the article immediately visible."
4165
4266
  ),
4166
- publishDate: z4.string().optional().describe(
4267
+ publishDate: z5.string().optional().describe(
4167
4268
  "The date and time when the article should become visible (ISO 8601 format). Example: '2024-01-15T10:00:00Z'. If not set and isPublished is true, publishes immediately."
4168
4269
  ),
4169
- handle: z4.string().optional().describe(
4270
+ handle: z5.string().optional().describe(
4170
4271
  "The URL handle/slug for the article. If not provided, it will be auto-generated from the title. Example: 'how-to-use-our-products' would create URL /blogs/blog-handle/how-to-use-our-products."
4171
4272
  ),
4172
- templateSuffix: z4.string().optional().describe(
4273
+ templateSuffix: z5.string().optional().describe(
4173
4274
  "The suffix of the Liquid template used to render the article. For example, 'featured' would use the template 'article.featured.liquid'."
4174
4275
  )
4175
4276
  });
4176
- var outputSchema3 = z4.object({
4177
- id: z4.string().describe('Created article GID (e.g., "gid://shopify/Article/123")'),
4178
- title: z4.string().describe("Article title"),
4179
- handle: z4.string().describe("URL handle/slug"),
4180
- blog: z4.object({
4181
- id: z4.string().describe("Parent blog GID"),
4182
- title: z4.string().describe("Parent blog title")
4277
+ var outputSchema3 = z5.object({
4278
+ id: z5.string().describe('Created article GID (e.g., "gid://shopify/Article/123")'),
4279
+ title: z5.string().describe("Article title"),
4280
+ handle: z5.string().describe("URL handle/slug"),
4281
+ blog: z5.object({
4282
+ id: z5.string().describe("Parent blog GID"),
4283
+ title: z5.string().describe("Parent blog title")
4183
4284
  }),
4184
- isPublished: z4.boolean().describe("Whether article is visible on storefront")
4285
+ isPublished: z5.boolean().describe("Whether article is visible on storefront")
4185
4286
  });
4186
4287
  var handleCreateArticle = async (context, params) => {
4187
4288
  log.debug(`Creating article on shop: ${context.shopDomain}`);
@@ -4237,6 +4338,14 @@ function registerCreateArticleTool() {
4237
4338
  relatedTools: ["list-blogs", "list-articles", "update-article"],
4238
4339
  prerequisites: ["list-blogs"],
4239
4340
  followUps: ["list-articles", "update-article"]
4341
+ },
4342
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
4343
+ annotations: {
4344
+ readOnlyHint: false,
4345
+ destructiveHint: false,
4346
+ idempotentHint: false,
4347
+ // create-* tools are NOT idempotent
4348
+ openWorldHint: true
4240
4349
  }
4241
4350
  },
4242
4351
  handleCreateArticle
@@ -4244,26 +4353,26 @@ function registerCreateArticleTool() {
4244
4353
  }
4245
4354
 
4246
4355
  // src/tools/create-blog.ts
4247
- import { z as z5 } from "zod";
4248
- var inputSchema4 = z5.object({
4249
- title: z5.string().min(1).describe(
4356
+ import { z as z6 } from "zod";
4357
+ var inputSchema4 = z6.object({
4358
+ title: z6.string().min(1).describe(
4250
4359
  "The title of the blog (required). This will be displayed as the blog heading. Example: 'Company News', 'Product Guides', 'Industry Insights'."
4251
4360
  ),
4252
- handle: z5.string().optional().describe(
4361
+ handle: z6.string().optional().describe(
4253
4362
  "The URL handle/slug for the blog. If not provided, it will be auto-generated from the title. Example: 'news' would create URL /blogs/news."
4254
4363
  ),
4255
- commentPolicy: z5.enum(["CLOSED", "MODERATE", "OPEN"]).optional().describe(
4364
+ commentPolicy: z6.enum(["CLOSED", "MODERATE", "OPEN"]).optional().describe(
4256
4365
  "Comment moderation policy for the blog. CLOSED: Comments disabled. MODERATE: Comments require approval. OPEN: Comments appear immediately. Defaults to MODERATE if not specified."
4257
4366
  ),
4258
- templateSuffix: z5.string().optional().describe(
4367
+ templateSuffix: z6.string().optional().describe(
4259
4368
  "The suffix of the Liquid template used to render the blog. For example, 'news' would use the template 'blog.news.liquid'."
4260
4369
  )
4261
4370
  });
4262
- var outputSchema4 = z5.object({
4263
- id: z5.string().describe('Created blog GID (e.g., "gid://shopify/Blog/123")'),
4264
- title: z5.string().describe("Blog title"),
4265
- handle: z5.string().describe("URL handle/slug"),
4266
- commentPolicy: z5.enum(["CLOSED", "MODERATE", "OPEN"]).describe("Comment moderation policy")
4371
+ var outputSchema4 = z6.object({
4372
+ id: z6.string().describe('Created blog GID (e.g., "gid://shopify/Blog/123")'),
4373
+ title: z6.string().describe("Blog title"),
4374
+ handle: z6.string().describe("URL handle/slug"),
4375
+ commentPolicy: z6.enum(["CLOSED", "MODERATE", "OPEN"]).describe("Comment moderation policy")
4267
4376
  });
4268
4377
  var handleCreateBlog = async (context, params) => {
4269
4378
  log.debug(`Creating blog on shop: ${context.shopDomain}`);
@@ -4305,6 +4414,14 @@ function registerCreateBlogTool() {
4305
4414
  relationships: {
4306
4415
  relatedTools: ["list-blogs", "update-blog", "create-article"],
4307
4416
  followUps: ["create-article", "list-blogs"]
4417
+ },
4418
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
4419
+ annotations: {
4420
+ readOnlyHint: false,
4421
+ destructiveHint: false,
4422
+ idempotentHint: false,
4423
+ // create-* tools are NOT idempotent
4424
+ openWorldHint: true
4308
4425
  }
4309
4426
  },
4310
4427
  handleCreateBlog
@@ -4312,20 +4429,20 @@ function registerCreateBlogTool() {
4312
4429
  }
4313
4430
 
4314
4431
  // src/tools/create-collection.ts
4315
- import { z as z6 } from "zod";
4316
- var inputSchema5 = z6.object({
4317
- title: z6.string().min(1).describe("Collection title (required). This will be displayed to customers."),
4318
- handle: z6.string().optional().describe(
4432
+ import { z as z7 } from "zod";
4433
+ var inputSchema5 = z7.object({
4434
+ title: z7.string().min(1).describe("Collection title (required). This will be displayed to customers."),
4435
+ handle: z7.string().optional().describe(
4319
4436
  'URL handle/slug for the collection (e.g., "summer-sale"). Auto-generated from title if not provided.'
4320
4437
  ),
4321
- descriptionHtml: z6.string().optional().describe(
4438
+ descriptionHtml: z7.string().optional().describe(
4322
4439
  "Collection description with HTML formatting. Displayed on collection pages in the store."
4323
4440
  ),
4324
- seo: z6.object({
4325
- title: z6.string().optional().describe("SEO title for search engines"),
4326
- description: z6.string().optional().describe("SEO meta description for search engines")
4441
+ seo: z7.object({
4442
+ title: z7.string().optional().describe("SEO title for search engines"),
4443
+ description: z7.string().optional().describe("SEO meta description for search engines")
4327
4444
  }).optional().describe("SEO metadata to optimize collection visibility in search results"),
4328
- sortOrder: z6.enum([
4445
+ sortOrder: z7.enum([
4329
4446
  "ALPHA_ASC",
4330
4447
  "ALPHA_DESC",
4331
4448
  "BEST_SELLING",
@@ -4337,18 +4454,18 @@ var inputSchema5 = z6.object({
4337
4454
  ]).optional().describe(
4338
4455
  "How products are sorted within the collection. Options: ALPHA_ASC (A-Z), ALPHA_DESC (Z-A), BEST_SELLING, CREATED (oldest first), CREATED_DESC (newest first), MANUAL, PRICE_ASC (low to high), PRICE_DESC (high to low)"
4339
4456
  ),
4340
- image: z6.object({
4341
- src: z6.string().url().describe("Publicly accessible image URL"),
4342
- altText: z6.string().optional().describe("Alt text for accessibility and SEO")
4457
+ image: z7.object({
4458
+ src: z7.string().url().describe("Publicly accessible image URL"),
4459
+ altText: z7.string().optional().describe("Alt text for accessibility and SEO")
4343
4460
  }).optional().describe("Collection image displayed on collection pages"),
4344
- templateSuffix: z6.string().optional().describe(
4461
+ templateSuffix: z7.string().optional().describe(
4345
4462
  'Liquid template suffix for custom collection templates. For example, "featured" uses collection.featured.liquid'
4346
4463
  )
4347
4464
  });
4348
- var outputSchema5 = z6.object({
4349
- id: z6.string().describe('Created collection GID (e.g., "gid://shopify/Collection/123")'),
4350
- title: z6.string().describe("Collection title"),
4351
- handle: z6.string().describe("Collection URL handle")
4465
+ var outputSchema5 = z7.object({
4466
+ id: z7.string().describe('Created collection GID (e.g., "gid://shopify/Collection/123")'),
4467
+ title: z7.string().describe("Collection title"),
4468
+ handle: z7.string().describe("Collection URL handle")
4352
4469
  });
4353
4470
  var handleCreateCollection = async (context, params) => {
4354
4471
  log.debug(`Creating collection on shop: ${context.shopDomain}`);
@@ -4387,6 +4504,14 @@ function registerCreateCollectionTool() {
4387
4504
  relationships: {
4388
4505
  relatedTools: ["list-collections", "get-collection"],
4389
4506
  followUps: ["add-products-to-collection", "update-collection"]
4507
+ },
4508
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
4509
+ annotations: {
4510
+ readOnlyHint: false,
4511
+ destructiveHint: false,
4512
+ idempotentHint: false,
4513
+ // Each call creates a new collection
4514
+ openWorldHint: true
4390
4515
  }
4391
4516
  },
4392
4517
  handleCreateCollection
@@ -4394,7 +4519,7 @@ function registerCreateCollectionTool() {
4394
4519
  }
4395
4520
 
4396
4521
  // src/tools/create-page.ts
4397
- import { z as z7 } from "zod";
4522
+ import { z as z8 } from "zod";
4398
4523
 
4399
4524
  // src/shopify/pages.ts
4400
4525
  var PAGE_QUERY = `
@@ -4676,28 +4801,28 @@ async function deletePage(pageId) {
4676
4801
  }
4677
4802
 
4678
4803
  // src/tools/create-page.ts
4679
- var inputSchema6 = z7.object({
4680
- title: z7.string().min(1).describe(
4804
+ var inputSchema6 = z8.object({
4805
+ title: z8.string().min(1).describe(
4681
4806
  "The title of the page (required). This will be displayed as the page heading. Example: 'About Us', 'Contact', 'Privacy Policy'."
4682
4807
  ),
4683
- body: z7.string().optional().describe(
4808
+ body: z8.string().optional().describe(
4684
4809
  "The HTML body content of the page. Supports HTML markup for formatting. Example: '<h1>About Our Company</h1><p>We are a great company.</p>'"
4685
4810
  ),
4686
- handle: z7.string().optional().describe(
4811
+ handle: z8.string().optional().describe(
4687
4812
  "The URL handle/slug for the page. If not provided, it will be auto-generated from the title. Example: 'about-us' would create URL /pages/about-us."
4688
4813
  ),
4689
- isPublished: z7.boolean().optional().describe(
4814
+ isPublished: z8.boolean().optional().describe(
4690
4815
  "Whether the page should be visible on the storefront. Defaults to false (unpublished). Set to true to make the page immediately visible."
4691
4816
  ),
4692
- templateSuffix: z7.string().optional().describe(
4817
+ templateSuffix: z8.string().optional().describe(
4693
4818
  "The suffix of the Liquid template used to render the page. For example, 'contact' would use the template 'page.contact.liquid'."
4694
4819
  )
4695
4820
  });
4696
- var outputSchema6 = z7.object({
4697
- id: z7.string().describe('Created page GID (e.g., "gid://shopify/Page/123")'),
4698
- title: z7.string().describe("Page title"),
4699
- handle: z7.string().describe("URL handle/slug"),
4700
- isPublished: z7.boolean().describe("Whether page is visible on storefront")
4821
+ var outputSchema6 = z8.object({
4822
+ id: z8.string().describe('Created page GID (e.g., "gid://shopify/Page/123")'),
4823
+ title: z8.string().describe("Page title"),
4824
+ handle: z8.string().describe("URL handle/slug"),
4825
+ isPublished: z8.boolean().describe("Whether page is visible on storefront")
4701
4826
  });
4702
4827
  var handleCreatePage = async (context, params) => {
4703
4828
  log.debug(`Creating page on shop: ${context.shopDomain}`);
@@ -4740,6 +4865,14 @@ function registerCreatePageTool() {
4740
4865
  relationships: {
4741
4866
  relatedTools: ["list-pages", "get-page", "update-page"],
4742
4867
  followUps: ["get-page", "update-page"]
4868
+ },
4869
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
4870
+ annotations: {
4871
+ readOnlyHint: false,
4872
+ destructiveHint: false,
4873
+ idempotentHint: false,
4874
+ // create-* tools are NOT idempotent
4875
+ openWorldHint: true
4743
4876
  }
4744
4877
  },
4745
4878
  handleCreatePage
@@ -4747,78 +4880,78 @@ function registerCreatePageTool() {
4747
4880
  }
4748
4881
 
4749
4882
  // src/tools/create-product.ts
4750
- import { z as z8 } from "zod";
4751
- var inputSchema7 = z8.object({
4883
+ import { z as z9 } from "zod";
4884
+ var inputSchema7 = z9.object({
4752
4885
  // Required field
4753
- title: z8.string().min(1, "title is required").describe('Product title (required). Example: "Premium Cotton T-Shirt"'),
4886
+ title: z9.string().min(1, "title is required").describe('Product title (required). Example: "Premium Cotton T-Shirt"'),
4754
4887
  // Optional core fields
4755
- description: z8.string().optional().describe('Product description (HTML supported). Example: "<p>Soft cotton t-shirt.</p>"'),
4756
- vendor: z8.string().optional().describe('Product vendor/brand name. Example: "Acme Corp"'),
4757
- productType: z8.string().optional().describe('Product type for categorization. Example: "T-Shirts"'),
4758
- tags: z8.array(z8.string()).optional().describe('Tags for search and filtering. Example: ["summer", "cotton", "casual"]'),
4759
- status: z8.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).optional().default("DRAFT").describe(
4888
+ description: z9.string().optional().describe('Product description (HTML supported). Example: "<p>Soft cotton t-shirt.</p>"'),
4889
+ vendor: z9.string().optional().describe('Product vendor/brand name. Example: "Acme Corp"'),
4890
+ productType: z9.string().optional().describe('Product type for categorization. Example: "T-Shirts"'),
4891
+ tags: z9.array(z9.string()).optional().describe('Tags for search and filtering. Example: ["summer", "cotton", "casual"]'),
4892
+ status: z9.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).optional().default("DRAFT").describe(
4760
4893
  "Product status: ACTIVE (visible), DRAFT (hidden), ARCHIVED (hidden). Default: DRAFT"
4761
4894
  ),
4762
4895
  // SEO metadata
4763
- seo: z8.object({
4764
- title: z8.string().optional().describe("SEO title for search engine results"),
4765
- description: z8.string().optional().describe("SEO meta description for search engines")
4896
+ seo: z9.object({
4897
+ title: z9.string().optional().describe("SEO title for search engine results"),
4898
+ description: z9.string().optional().describe("SEO meta description for search engines")
4766
4899
  }).optional().describe(
4767
4900
  "SEO metadata for search engine optimization. If not provided, Shopify uses product title/description."
4768
4901
  ),
4769
4902
  // Variants
4770
- variants: z8.array(
4771
- z8.object({
4772
- price: z8.string().describe('Variant price as decimal string. Example: "29.99"'),
4773
- compareAtPrice: z8.string().optional().describe('Original price for sale display. Example: "39.99"'),
4774
- sku: z8.string().optional().describe('Stock keeping unit. Example: "SHIRT-001-RED-M"'),
4775
- barcode: z8.string().optional().describe("Barcode (UPC, EAN, etc.)"),
4776
- inventoryQuantity: z8.number().int().min(0).optional().describe("Initial inventory quantity (note: may require separate inventory API)"),
4777
- options: z8.array(z8.string()).optional().describe('Variant options. Example: ["Red", "Medium"]')
4903
+ variants: z9.array(
4904
+ z9.object({
4905
+ price: z9.string().describe('Variant price as decimal string. Example: "29.99"'),
4906
+ compareAtPrice: z9.string().optional().describe('Original price for sale display. Example: "39.99"'),
4907
+ sku: z9.string().optional().describe('Stock keeping unit. Example: "SHIRT-001-RED-M"'),
4908
+ barcode: z9.string().optional().describe("Barcode (UPC, EAN, etc.)"),
4909
+ inventoryQuantity: z9.number().int().min(0).optional().describe("Initial inventory quantity (note: may require separate inventory API)"),
4910
+ options: z9.array(z9.string()).optional().describe('Variant options. Example: ["Red", "Medium"]')
4778
4911
  })
4779
4912
  ).optional().describe("Product variants. If not provided, a default variant is created."),
4780
4913
  // Images
4781
- images: z8.array(
4782
- z8.object({
4783
- url: z8.string().url("Invalid image URL format").describe("Publicly accessible image URL"),
4784
- altText: z8.string().optional().describe("Alt text for accessibility")
4914
+ images: z9.array(
4915
+ z9.object({
4916
+ url: z9.string().url("Invalid image URL format").describe("Publicly accessible image URL"),
4917
+ altText: z9.string().optional().describe("Alt text for accessibility")
4785
4918
  })
4786
4919
  ).optional().describe("Product images. Shopify fetches images from URLs.")
4787
4920
  });
4788
- var outputSchema7 = z8.object({
4789
- id: z8.string().describe('Product GID (e.g., "gid://shopify/Product/123")'),
4790
- title: z8.string().describe("Product title"),
4791
- handle: z8.string().describe("URL handle/slug"),
4792
- description: z8.string().nullable().describe("Product description (HTML)"),
4793
- vendor: z8.string().describe("Vendor/brand name"),
4794
- productType: z8.string().describe("Product type"),
4795
- status: z8.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).describe("Product status"),
4796
- tags: z8.array(z8.string()).describe("Product tags"),
4797
- seo: z8.object({
4798
- title: z8.string().nullable().describe("SEO title for search engines"),
4799
- description: z8.string().nullable().describe("SEO description/meta description")
4921
+ var outputSchema7 = z9.object({
4922
+ id: z9.string().describe('Product GID (e.g., "gid://shopify/Product/123")'),
4923
+ title: z9.string().describe("Product title"),
4924
+ handle: z9.string().describe("URL handle/slug"),
4925
+ description: z9.string().nullable().describe("Product description (HTML)"),
4926
+ vendor: z9.string().describe("Vendor/brand name"),
4927
+ productType: z9.string().describe("Product type"),
4928
+ status: z9.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).describe("Product status"),
4929
+ tags: z9.array(z9.string()).describe("Product tags"),
4930
+ seo: z9.object({
4931
+ title: z9.string().nullable().describe("SEO title for search engines"),
4932
+ description: z9.string().nullable().describe("SEO description/meta description")
4800
4933
  }).describe("SEO metadata for search engine optimization"),
4801
- createdAt: z8.string().describe("Creation timestamp (ISO 8601)"),
4802
- updatedAt: z8.string().describe("Last update timestamp (ISO 8601)"),
4803
- variants: z8.array(
4804
- z8.object({
4805
- id: z8.string().describe("Variant GID"),
4806
- title: z8.string().describe("Variant title"),
4807
- price: z8.string().describe("Price"),
4808
- compareAtPrice: z8.string().nullable().describe("Compare at price"),
4809
- sku: z8.string().nullable().describe("SKU"),
4810
- barcode: z8.string().nullable().describe("Barcode"),
4811
- inventoryQuantity: z8.number().nullable().describe("Inventory quantity")
4934
+ createdAt: z9.string().describe("Creation timestamp (ISO 8601)"),
4935
+ updatedAt: z9.string().describe("Last update timestamp (ISO 8601)"),
4936
+ variants: z9.array(
4937
+ z9.object({
4938
+ id: z9.string().describe("Variant GID"),
4939
+ title: z9.string().describe("Variant title"),
4940
+ price: z9.string().describe("Price"),
4941
+ compareAtPrice: z9.string().nullable().describe("Compare at price"),
4942
+ sku: z9.string().nullable().describe("SKU"),
4943
+ barcode: z9.string().nullable().describe("Barcode"),
4944
+ inventoryQuantity: z9.number().nullable().describe("Inventory quantity")
4812
4945
  })
4813
4946
  ).describe("Product variants"),
4814
- images: z8.array(
4815
- z8.object({
4816
- id: z8.string().describe("Image GID"),
4817
- url: z8.string().describe("Image URL"),
4818
- altText: z8.string().nullable().describe("Alt text")
4947
+ images: z9.array(
4948
+ z9.object({
4949
+ id: z9.string().describe("Image GID"),
4950
+ url: z9.string().describe("Image URL"),
4951
+ altText: z9.string().nullable().describe("Alt text")
4819
4952
  })
4820
4953
  ).describe("Product images"),
4821
- totalInventory: z8.number().describe("Total inventory across variants")
4954
+ totalInventory: z9.number().describe("Total inventory across variants")
4822
4955
  });
4823
4956
  var handleCreateProduct = async (context, params) => {
4824
4957
  log.debug(`Creating product on shop: ${context.shopDomain}`);
@@ -4861,6 +4994,14 @@ function registerCreateProductTool() {
4861
4994
  relatedTools: ["get-product", "update-product", "list-products"],
4862
4995
  followUps: ["add-product-image", "set-product-metafields", "add-products-to-collection"],
4863
4996
  alternatives: ["update-product"]
4997
+ },
4998
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
4999
+ annotations: {
5000
+ readOnlyHint: false,
5001
+ destructiveHint: false,
5002
+ idempotentHint: false,
5003
+ // Each call creates a new product
5004
+ openWorldHint: true
4864
5005
  }
4865
5006
  },
4866
5007
  handleCreateProduct
@@ -4868,7 +5009,7 @@ function registerCreateProductTool() {
4868
5009
  }
4869
5010
 
4870
5011
  // src/tools/create-redirect.ts
4871
- import { z as z9 } from "zod";
5012
+ import { z as z10 } from "zod";
4872
5013
 
4873
5014
  // src/shopify/redirects.ts
4874
5015
  var URL_REDIRECT_CREATE_MUTATION = `
@@ -5016,20 +5157,20 @@ async function deleteRedirect(redirectId) {
5016
5157
  }
5017
5158
 
5018
5159
  // src/tools/create-redirect.ts
5019
- var inputSchema8 = z9.object({
5020
- path: z9.string().min(1).refine((val) => val.startsWith("/"), {
5160
+ var inputSchema8 = z10.object({
5161
+ path: z10.string().min(1).refine((val) => val.startsWith("/"), {
5021
5162
  message: "Path must start with '/' (e.g., '/old-product-url')"
5022
5163
  }).describe(
5023
5164
  "The source path to redirect FROM. Must start with '/'. Example: '/old-product-url' or '/collections/old-collection'."
5024
5165
  ),
5025
- target: z9.string().min(1).describe(
5166
+ target: z10.string().min(1).describe(
5026
5167
  "The destination URL to redirect TO. Can be relative ('/new-product-url') or absolute ('https://example.com/page')."
5027
5168
  )
5028
5169
  });
5029
- var outputSchema8 = z9.object({
5030
- id: z9.string().describe('Created redirect GID (e.g., "gid://shopify/UrlRedirect/123")'),
5031
- path: z9.string().describe("Source path that will redirect"),
5032
- target: z9.string().describe("Target URL visitors will be redirected to")
5170
+ var outputSchema8 = z10.object({
5171
+ id: z10.string().describe('Created redirect GID (e.g., "gid://shopify/UrlRedirect/123")'),
5172
+ path: z10.string().describe("Source path that will redirect"),
5173
+ target: z10.string().describe("Target URL visitors will be redirected to")
5033
5174
  });
5034
5175
  var handleCreateRedirect = async (context, params) => {
5035
5176
  log.debug(`Creating redirect on shop: ${context.shopDomain}`);
@@ -5070,6 +5211,14 @@ function registerCreateRedirectTool() {
5070
5211
  relationships: {
5071
5212
  relatedTools: ["list-redirects", "delete-redirect"],
5072
5213
  followUps: ["list-redirects"]
5214
+ },
5215
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
5216
+ annotations: {
5217
+ readOnlyHint: false,
5218
+ destructiveHint: false,
5219
+ idempotentHint: false,
5220
+ // Each call creates a new redirect (duplicate path fails)
5221
+ openWorldHint: true
5073
5222
  }
5074
5223
  },
5075
5224
  handleCreateRedirect
@@ -5077,15 +5226,15 @@ function registerCreateRedirectTool() {
5077
5226
  }
5078
5227
 
5079
5228
  // src/tools/delete-article.ts
5080
- import { z as z10 } from "zod";
5081
- var inputSchema9 = z10.object({
5082
- id: z10.string().describe(
5229
+ import { z as z11 } from "zod";
5230
+ var inputSchema9 = z11.object({
5231
+ id: articleIdSchema.describe(
5083
5232
  'The article ID to delete (required). Must be a valid Shopify GID format. Example: "gid://shopify/Article/12345". Use list-articles to find article IDs. This action cannot be undone - consider using update-article to unpublish instead.'
5084
5233
  )
5085
5234
  });
5086
- var outputSchema9 = z10.object({
5087
- success: z10.boolean().describe("Whether the deletion was successful"),
5088
- deletedArticleId: z10.string().describe("The ID of the deleted article")
5235
+ var outputSchema9 = z11.object({
5236
+ success: z11.boolean().describe("Whether the deletion was successful"),
5237
+ deletedArticleId: z11.string().describe("The ID of the deleted article")
5089
5238
  });
5090
5239
  var handleDeleteArticle = async (context, params) => {
5091
5240
  log.debug(`Deleting article on shop: ${context.shopDomain}`);
@@ -5126,6 +5275,14 @@ function registerDeleteArticleTool() {
5126
5275
  relatedTools: ["list-articles", "update-article"],
5127
5276
  prerequisites: ["list-articles"],
5128
5277
  alternatives: ["update-article"]
5278
+ },
5279
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
5280
+ annotations: {
5281
+ readOnlyHint: false,
5282
+ destructiveHint: true,
5283
+ idempotentHint: false,
5284
+ // delete-* tools are NOT idempotent
5285
+ openWorldHint: true
5129
5286
  }
5130
5287
  },
5131
5288
  handleDeleteArticle
@@ -5133,15 +5290,15 @@ function registerDeleteArticleTool() {
5133
5290
  }
5134
5291
 
5135
5292
  // src/tools/delete-blog.ts
5136
- import { z as z11 } from "zod";
5137
- var inputSchema10 = z11.object({
5138
- id: z11.string().describe(
5293
+ import { z as z12 } from "zod";
5294
+ var inputSchema10 = z12.object({
5295
+ id: blogIdSchema.describe(
5139
5296
  'The blog ID to delete (required). Must be a valid Shopify GID format. Example: "gid://shopify/Blog/12345". Use list-blogs to find blog IDs. WARNING: Deleting a blog also deletes ALL articles within it.'
5140
5297
  )
5141
5298
  });
5142
- var outputSchema10 = z11.object({
5143
- success: z11.boolean().describe("Whether the deletion was successful"),
5144
- deletedBlogId: z11.string().describe("The ID of the deleted blog")
5299
+ var outputSchema10 = z12.object({
5300
+ success: z12.boolean().describe("Whether the deletion was successful"),
5301
+ deletedBlogId: z12.string().describe("The ID of the deleted blog")
5145
5302
  });
5146
5303
  var handleDeleteBlog = async (context, params) => {
5147
5304
  log.debug(`Deleting blog on shop: ${context.shopDomain}`);
@@ -5181,6 +5338,14 @@ function registerDeleteBlogTool() {
5181
5338
  relationships: {
5182
5339
  relatedTools: ["list-blogs", "list-articles"],
5183
5340
  prerequisites: ["list-blogs"]
5341
+ },
5342
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
5343
+ annotations: {
5344
+ readOnlyHint: false,
5345
+ destructiveHint: true,
5346
+ idempotentHint: false,
5347
+ // delete-* tools are NOT idempotent
5348
+ openWorldHint: true
5184
5349
  }
5185
5350
  },
5186
5351
  handleDeleteBlog
@@ -5188,14 +5353,14 @@ function registerDeleteBlogTool() {
5188
5353
  }
5189
5354
 
5190
5355
  // src/tools/delete-collection.ts
5191
- import { z as z12 } from "zod";
5192
- var inputSchema11 = z12.object({
5193
- collectionId: z12.string().min(1).describe(
5356
+ import { z as z13 } from "zod";
5357
+ var inputSchema11 = z13.object({
5358
+ collectionId: collectionIdSchema.describe(
5194
5359
  'Collection ID to delete (GID format, e.g., "gid://shopify/Collection/123"). Use list-collections or get-collection to find collection IDs.'
5195
5360
  )
5196
5361
  });
5197
- var outputSchema11 = z12.object({
5198
- deletedCollectionId: z12.string().describe("The GID of the deleted collection")
5362
+ var outputSchema11 = z13.object({
5363
+ deletedCollectionId: z13.string().describe("The GID of the deleted collection")
5199
5364
  });
5200
5365
  var handleDeleteCollection = async (context, params) => {
5201
5366
  log.debug(`Deleting collection on shop: ${context.shopDomain}`);
@@ -5232,6 +5397,15 @@ function registerDeleteCollectionTool() {
5232
5397
  relationships: {
5233
5398
  relatedTools: ["list-collections"],
5234
5399
  prerequisites: ["get-collection"]
5400
+ },
5401
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
5402
+ annotations: {
5403
+ readOnlyHint: false,
5404
+ destructiveHint: true,
5405
+ // Permanently deletes collection
5406
+ idempotentHint: false,
5407
+ // Second call fails (already deleted)
5408
+ openWorldHint: true
5235
5409
  }
5236
5410
  },
5237
5411
  handleDeleteCollection
@@ -5239,15 +5413,15 @@ function registerDeleteCollectionTool() {
5239
5413
  }
5240
5414
 
5241
5415
  // src/tools/delete-page.ts
5242
- import { z as z13 } from "zod";
5243
- var inputSchema12 = z13.object({
5244
- id: z13.string().min(1).describe(
5416
+ import { z as z14 } from "zod";
5417
+ var inputSchema12 = z14.object({
5418
+ id: pageIdSchema.describe(
5245
5419
  'The page ID to delete (required). Must be a valid Shopify GID format. Example: "gid://shopify/Page/123456789". Use list-pages to find page IDs. WARNING: This action is permanent and cannot be undone.'
5246
5420
  )
5247
5421
  });
5248
- var outputSchema12 = z13.object({
5249
- success: z13.boolean().describe("Whether the deletion was successful"),
5250
- deletedPageId: z13.string().describe("The ID of the deleted page")
5422
+ var outputSchema12 = z14.object({
5423
+ success: z14.boolean().describe("Whether the deletion was successful"),
5424
+ deletedPageId: z14.string().describe("The ID of the deleted page")
5251
5425
  });
5252
5426
  var handleDeletePage = async (context, params) => {
5253
5427
  log.debug(`Deleting page on shop: ${context.shopDomain}`);
@@ -5288,6 +5462,14 @@ function registerDeletePageTool() {
5288
5462
  relatedTools: ["get-page", "list-pages"],
5289
5463
  prerequisites: ["get-page"],
5290
5464
  alternatives: ["update-page"]
5465
+ },
5466
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
5467
+ annotations: {
5468
+ readOnlyHint: false,
5469
+ destructiveHint: true,
5470
+ idempotentHint: false,
5471
+ // delete-* tools are NOT idempotent
5472
+ openWorldHint: true
5291
5473
  }
5292
5474
  },
5293
5475
  handleDeletePage
@@ -5295,7 +5477,7 @@ function registerDeletePageTool() {
5295
5477
  }
5296
5478
 
5297
5479
  // src/tools/delete-product-image.ts
5298
- import { z as z14 } from "zod";
5480
+ import { z as z15 } from "zod";
5299
5481
  var FILE_DELETE_MUTATION = `
5300
5482
  mutation FileDelete($fileIds: [ID!]!) {
5301
5483
  fileDelete(fileIds: $fileIds) {
@@ -5307,14 +5489,14 @@ var FILE_DELETE_MUTATION = `
5307
5489
  }
5308
5490
  }
5309
5491
  `;
5310
- var inputSchema13 = z14.object({
5311
- imageId: z14.string().min(1).describe(
5492
+ var inputSchema13 = z15.object({
5493
+ imageId: imageIdSchema.describe(
5312
5494
  'Shopify image GID to delete (e.g., "gid://shopify/MediaImage/123"). Required. Use get-product to find image IDs in the images array.'
5313
5495
  )
5314
5496
  });
5315
- var outputSchema13 = z14.object({
5316
- success: z14.boolean().describe("Whether the deletion was successful"),
5317
- deletedImageId: z14.string().describe("ID of the deleted image")
5497
+ var outputSchema13 = z15.object({
5498
+ success: z15.boolean().describe("Whether the deletion was successful"),
5499
+ deletedImageId: z15.string().describe("ID of the deleted image")
5318
5500
  });
5319
5501
  var handleDeleteProductImage = async (context, params) => {
5320
5502
  log.debug(`Deleting image on shop: ${context.shopDomain}`);
@@ -5372,6 +5554,15 @@ function registerDeleteProductImageTool() {
5372
5554
  relationships: {
5373
5555
  relatedTools: ["get-product", "add-product-image"],
5374
5556
  prerequisites: ["get-product"]
5557
+ },
5558
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
5559
+ annotations: {
5560
+ readOnlyHint: false,
5561
+ destructiveHint: true,
5562
+ // Permanently deletes image from Shopify CDN
5563
+ idempotentHint: false,
5564
+ // Second call fails (already deleted)
5565
+ openWorldHint: true
5375
5566
  }
5376
5567
  },
5377
5568
  handleDeleteProductImage
@@ -5379,7 +5570,7 @@ function registerDeleteProductImageTool() {
5379
5570
  }
5380
5571
 
5381
5572
  // src/tools/delete-product-metafields.ts
5382
- import { z as z15 } from "zod";
5573
+ import { z as z16 } from "zod";
5383
5574
 
5384
5575
  // src/shopify/metafields.ts
5385
5576
  var GET_PRODUCT_METAFIELDS_QUERY = `
@@ -5558,28 +5749,28 @@ async function deleteProductMetafields(productId, identifiers) {
5558
5749
  }
5559
5750
 
5560
5751
  // src/tools/delete-product-metafields.ts
5561
- var metafieldIdentifierSchema = z15.object({
5562
- namespace: z15.string().min(1).describe('Metafield namespace (e.g., "custom", "seo"). Must match existing metafield.'),
5563
- key: z15.string().min(1).describe('Metafield key (e.g., "color_hex"). Must match existing metafield.')
5752
+ var metafieldIdentifierSchema = z16.object({
5753
+ namespace: z16.string().min(1).describe('Metafield namespace (e.g., "custom", "seo"). Must match existing metafield.'),
5754
+ key: z16.string().min(1).describe('Metafield key (e.g., "color_hex"). Must match existing metafield.')
5564
5755
  });
5565
- var inputSchema14 = z15.object({
5566
- productId: z15.string().min(1).describe(
5756
+ var inputSchema14 = z16.object({
5757
+ productId: productIdSchema.describe(
5567
5758
  'Shopify product ID (GID format, e.g., "gid://shopify/Product/123"). Use get-product or list-products to find product IDs.'
5568
5759
  ),
5569
- identifiers: z15.array(metafieldIdentifierSchema).min(1, "At least one metafield identifier is required").describe(
5760
+ identifiers: z16.array(metafieldIdentifierSchema).min(1, "At least one metafield identifier is required").describe(
5570
5761
  "Array of metafield identifiers to delete. Each identifier must have namespace and key. Use get-product-metafields first to verify which metafields exist."
5571
5762
  )
5572
5763
  });
5573
- var outputSchema14 = z15.object({
5574
- success: z15.boolean().describe("Whether the operation succeeded"),
5575
- deletedMetafields: z15.array(
5576
- z15.object({
5577
- ownerId: z15.string().describe("Product GID that owned the metafield"),
5578
- namespace: z15.string().describe("Namespace of deleted metafield"),
5579
- key: z15.string().describe("Key of deleted metafield")
5764
+ var outputSchema14 = z16.object({
5765
+ success: z16.boolean().describe("Whether the operation succeeded"),
5766
+ deletedMetafields: z16.array(
5767
+ z16.object({
5768
+ ownerId: z16.string().describe("Product GID that owned the metafield"),
5769
+ namespace: z16.string().describe("Namespace of deleted metafield"),
5770
+ key: z16.string().describe("Key of deleted metafield")
5580
5771
  })
5581
5772
  ).describe("Identifiers of successfully deleted metafields"),
5582
- deletedCount: z15.number().describe("Number of metafields deleted")
5773
+ deletedCount: z16.number().describe("Number of metafields deleted")
5583
5774
  });
5584
5775
  var handleDeleteProductMetafields = async (context, params) => {
5585
5776
  log.debug(
@@ -5616,6 +5807,15 @@ function registerDeleteProductMetafieldsTool() {
5616
5807
  relationships: {
5617
5808
  relatedTools: ["get-product-metafields"],
5618
5809
  prerequisites: ["get-product-metafields"]
5810
+ },
5811
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
5812
+ annotations: {
5813
+ readOnlyHint: false,
5814
+ destructiveHint: true,
5815
+ // Partial data deletion - cannot be recovered
5816
+ idempotentHint: false,
5817
+ // Second call fails (already deleted)
5818
+ openWorldHint: true
5619
5819
  }
5620
5820
  },
5621
5821
  handleDeleteProductMetafields
@@ -5623,13 +5823,15 @@ function registerDeleteProductMetafieldsTool() {
5623
5823
  }
5624
5824
 
5625
5825
  // src/tools/delete-product.ts
5626
- import { z as z16 } from "zod";
5627
- var inputSchema15 = z16.object({
5628
- id: z16.string().min(1).describe('Shopify product ID (GID format, e.g., "gid://shopify/Product/123"). Required.')
5826
+ import { z as z17 } from "zod";
5827
+ var inputSchema15 = z17.object({
5828
+ id: productIdSchema.describe(
5829
+ 'Shopify product ID (GID format, e.g., "gid://shopify/Product/123"). Required.'
5830
+ )
5629
5831
  });
5630
- var outputSchema15 = z16.object({
5631
- deletedProductId: z16.string().describe("The ID of the deleted product"),
5632
- title: z16.string().describe("The title of the deleted product (for confirmation)")
5832
+ var outputSchema15 = z17.object({
5833
+ deletedProductId: z17.string().describe("The ID of the deleted product"),
5834
+ title: z17.string().describe("The title of the deleted product (for confirmation)")
5633
5835
  });
5634
5836
  var handleDeleteProduct = async (context, params) => {
5635
5837
  log.debug(`Deleting product on shop: ${context.shopDomain}`);
@@ -5678,6 +5880,15 @@ function registerDeleteProductTool() {
5678
5880
  relationships: {
5679
5881
  relatedTools: ["list-products"],
5680
5882
  prerequisites: ["get-product"]
5883
+ },
5884
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
5885
+ annotations: {
5886
+ readOnlyHint: false,
5887
+ destructiveHint: true,
5888
+ // Permanently deletes product
5889
+ idempotentHint: false,
5890
+ // Second call fails (already deleted)
5891
+ openWorldHint: true
5681
5892
  }
5682
5893
  },
5683
5894
  handleDeleteProduct
@@ -5685,15 +5896,15 @@ function registerDeleteProductTool() {
5685
5896
  }
5686
5897
 
5687
5898
  // src/tools/delete-redirect.ts
5688
- import { z as z17 } from "zod";
5689
- var inputSchema16 = z17.object({
5690
- id: z17.string().min(1).describe(
5899
+ import { z as z18 } from "zod";
5900
+ var inputSchema16 = z18.object({
5901
+ id: redirectIdSchema.describe(
5691
5902
  'Shopify redirect GID (e.g., "gid://shopify/UrlRedirect/123"). Use list-redirects to find redirect IDs.'
5692
5903
  )
5693
5904
  });
5694
- var outputSchema16 = z17.object({
5695
- success: z17.boolean().describe("Whether the deletion succeeded"),
5696
- deletedRedirectId: z17.string().describe("ID of the deleted redirect")
5905
+ var outputSchema16 = z18.object({
5906
+ success: z18.boolean().describe("Whether the deletion succeeded"),
5907
+ deletedRedirectId: z18.string().describe("ID of the deleted redirect")
5697
5908
  });
5698
5909
  var handleDeleteRedirect = async (context, params) => {
5699
5910
  log.debug(`Deleting redirect on shop: ${context.shopDomain}`);
@@ -5742,6 +5953,15 @@ function registerDeleteRedirectTool() {
5742
5953
  relationships: {
5743
5954
  relatedTools: ["create-redirect"],
5744
5955
  prerequisites: ["list-redirects"]
5956
+ },
5957
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
5958
+ annotations: {
5959
+ readOnlyHint: false,
5960
+ destructiveHint: true,
5961
+ // Permanently removes redirect - can hurt SEO
5962
+ idempotentHint: false,
5963
+ // Second call fails (already deleted)
5964
+ openWorldHint: true
5745
5965
  }
5746
5966
  },
5747
5967
  handleDeleteRedirect
@@ -5749,7 +5969,7 @@ function registerDeleteRedirectTool() {
5749
5969
  }
5750
5970
 
5751
5971
  // src/tools/get-bulk-inventory.ts
5752
- import { z as z18 } from "zod";
5972
+ import { z as z19 } from "zod";
5753
5973
 
5754
5974
  // src/shopify/inventory.ts
5755
5975
  var GET_INVENTORY_ITEM_QUERY = `
@@ -6320,46 +6540,46 @@ async function getBulkInventory(options) {
6320
6540
  }
6321
6541
 
6322
6542
  // src/tools/get-bulk-inventory.ts
6323
- var inputSchema17 = z18.object({
6324
- productIds: z18.array(z18.string()).min(1, { message: "productIds array cannot be empty" }).max(50, { message: "Maximum 50 product IDs allowed per request" }).describe(
6543
+ var inputSchema17 = z19.object({
6544
+ productIds: z19.array(productIdSchema).min(1, { message: "productIds array cannot be empty" }).max(50, { message: "Maximum 50 product IDs allowed per request" }).describe(
6325
6545
  'Array of Shopify product IDs (e.g., ["gid://shopify/Product/123", "gid://shopify/Product/456"]). Maximum 50 products per request. Get product IDs from list-products or search.'
6326
6546
  ),
6327
- includeVariants: z18.boolean().default(true).describe(
6547
+ includeVariants: z19.boolean().default(true).describe(
6328
6548
  "Include per-variant inventory breakdown. Default: true. Set to false for faster response with only product totals."
6329
6549
  ),
6330
- locationId: z18.string().optional().describe(
6550
+ locationId: locationIdSchema.optional().describe(
6331
6551
  'Optional location ID to filter inventory (e.g., "gid://shopify/Location/789"). If not provided, returns total inventory across all locations.'
6332
6552
  )
6333
6553
  });
6334
- var outputSchema17 = z18.object({
6335
- products: z18.array(
6336
- z18.object({
6337
- productId: z18.string().describe("Product GID"),
6338
- productTitle: z18.string().describe("Product title"),
6339
- totalInventory: z18.number().describe("Total inventory across all variants"),
6340
- variants: z18.array(
6341
- z18.object({
6342
- variantId: z18.string().describe("Variant GID"),
6343
- variantTitle: z18.string().describe('Variant title (e.g., "Red / Large")'),
6344
- sku: z18.string().nullable().describe("SKU (Stock Keeping Unit)"),
6345
- inventoryItemId: z18.string().describe("Inventory item GID"),
6346
- available: z18.number().describe("Available quantity"),
6347
- locations: z18.array(
6348
- z18.object({
6349
- locationId: z18.string().describe("Location GID"),
6350
- locationName: z18.string().describe("Human-readable location name"),
6351
- available: z18.number().describe("Available quantity at this location")
6554
+ var outputSchema17 = z19.object({
6555
+ products: z19.array(
6556
+ z19.object({
6557
+ productId: z19.string().describe("Product GID"),
6558
+ productTitle: z19.string().describe("Product title"),
6559
+ totalInventory: z19.number().describe("Total inventory across all variants"),
6560
+ variants: z19.array(
6561
+ z19.object({
6562
+ variantId: z19.string().describe("Variant GID"),
6563
+ variantTitle: z19.string().describe('Variant title (e.g., "Red / Large")'),
6564
+ sku: z19.string().nullable().describe("SKU (Stock Keeping Unit)"),
6565
+ inventoryItemId: z19.string().describe("Inventory item GID"),
6566
+ available: z19.number().describe("Available quantity"),
6567
+ locations: z19.array(
6568
+ z19.object({
6569
+ locationId: z19.string().describe("Location GID"),
6570
+ locationName: z19.string().describe("Human-readable location name"),
6571
+ available: z19.number().describe("Available quantity at this location")
6352
6572
  })
6353
6573
  ).optional().describe("Per-location inventory breakdown")
6354
6574
  })
6355
6575
  ).optional().describe("Variant details (if includeVariants=true)")
6356
6576
  })
6357
6577
  ).describe("Products with inventory data"),
6358
- notFound: z18.array(z18.string()).describe("Product IDs that were not found in the store"),
6359
- summary: z18.object({
6360
- productsQueried: z18.number().describe("Total number of product IDs provided"),
6361
- productsFound: z18.number().describe("Number of products found in the store"),
6362
- totalInventory: z18.number().describe("Sum of inventory across all found products")
6578
+ notFound: z19.array(z19.string()).describe("Product IDs that were not found in the store"),
6579
+ summary: z19.object({
6580
+ productsQueried: z19.number().describe("Total number of product IDs provided"),
6581
+ productsFound: z19.number().describe("Number of products found in the store"),
6582
+ totalInventory: z19.number().describe("Sum of inventory across all found products")
6363
6583
  }).describe("Summary statistics")
6364
6584
  });
6365
6585
  var handleGetBulkInventory = async (context, params) => {
@@ -6399,6 +6619,13 @@ function registerGetBulkInventoryTool() {
6399
6619
  prerequisites: ["list-products"],
6400
6620
  followUps: ["update-inventory", "get-inventory"],
6401
6621
  alternatives: ["get-inventory"]
6622
+ },
6623
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
6624
+ annotations: {
6625
+ readOnlyHint: true,
6626
+ destructiveHint: false,
6627
+ idempotentHint: true,
6628
+ openWorldHint: true
6402
6629
  }
6403
6630
  },
6404
6631
  handleGetBulkInventory
@@ -6406,30 +6633,30 @@ function registerGetBulkInventoryTool() {
6406
6633
  }
6407
6634
 
6408
6635
  // src/tools/get-collection.ts
6409
- import { z as z19 } from "zod";
6410
- var inputSchema18 = z19.object({
6411
- collectionId: z19.string().min(1).describe(
6636
+ import { z as z20 } from "zod";
6637
+ var inputSchema18 = z20.object({
6638
+ collectionId: collectionIdSchema.describe(
6412
6639
  'Collection ID (GID format, e.g., "gid://shopify/Collection/123"). Use list-collections to find collection IDs.'
6413
6640
  )
6414
6641
  });
6415
- var outputSchema18 = z19.object({
6416
- id: z19.string().describe("Collection GID"),
6417
- title: z19.string().describe("Collection title"),
6418
- handle: z19.string().describe("URL handle/slug"),
6419
- description: z19.string().describe("Plain text description"),
6420
- descriptionHtml: z19.string().describe("HTML description"),
6421
- seo: z19.object({
6422
- title: z19.string().nullable().describe("SEO title"),
6423
- description: z19.string().nullable().describe("SEO description")
6642
+ var outputSchema18 = z20.object({
6643
+ id: z20.string().describe("Collection GID"),
6644
+ title: z20.string().describe("Collection title"),
6645
+ handle: z20.string().describe("URL handle/slug"),
6646
+ description: z20.string().describe("Plain text description"),
6647
+ descriptionHtml: z20.string().describe("HTML description"),
6648
+ seo: z20.object({
6649
+ title: z20.string().nullable().describe("SEO title"),
6650
+ description: z20.string().nullable().describe("SEO description")
6424
6651
  }),
6425
- image: z19.object({
6426
- id: z19.string().describe("Image GID"),
6427
- url: z19.string().describe("Image URL"),
6428
- altText: z19.string().nullable().describe("Alt text")
6652
+ image: z20.object({
6653
+ id: z20.string().describe("Image GID"),
6654
+ url: z20.string().describe("Image URL"),
6655
+ altText: z20.string().nullable().describe("Alt text")
6429
6656
  }).nullable(),
6430
- sortOrder: z19.string().describe("Product sort order within collection"),
6431
- productsCount: z19.number().describe("Number of products in collection"),
6432
- updatedAt: z19.string().describe("Last update timestamp (ISO 8601)")
6657
+ sortOrder: z20.string().describe("Product sort order within collection"),
6658
+ productsCount: z20.number().describe("Number of products in collection"),
6659
+ updatedAt: z20.string().describe("Last update timestamp (ISO 8601)")
6433
6660
  });
6434
6661
  var handleGetCollection = async (context, params) => {
6435
6662
  log.debug(`Getting collection on shop: ${context.shopDomain}`);
@@ -6456,6 +6683,13 @@ function registerGetCollectionTool() {
6456
6683
  relationships: {
6457
6684
  relatedTools: ["list-collections"],
6458
6685
  followUps: ["update-collection", "delete-collection", "add-products-to-collection"]
6686
+ },
6687
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
6688
+ annotations: {
6689
+ readOnlyHint: true,
6690
+ destructiveHint: false,
6691
+ idempotentHint: true,
6692
+ openWorldHint: true
6459
6693
  }
6460
6694
  },
6461
6695
  handleGetCollection
@@ -6463,37 +6697,37 @@ function registerGetCollectionTool() {
6463
6697
  }
6464
6698
 
6465
6699
  // src/tools/get-inventory.ts
6466
- import { z as z20 } from "zod";
6467
- var inputSchema19 = z20.object({
6468
- variantId: z20.string().optional().describe(
6700
+ import { z as z21 } from "zod";
6701
+ var inputSchema19 = z21.object({
6702
+ variantId: variantIdSchema.optional().describe(
6469
6703
  'The Shopify product variant ID (e.g., "gid://shopify/ProductVariant/123"). Use this when you have a variant ID from a product query (get-product or list-products).'
6470
6704
  ),
6471
- inventoryItemId: z20.string().optional().describe(
6705
+ inventoryItemId: inventoryItemIdSchema.optional().describe(
6472
6706
  'The Shopify inventory item ID (e.g., "gid://shopify/InventoryItem/456"). Use this if you already have the inventory item ID from a previous query.'
6473
6707
  ),
6474
- locationId: z20.string().optional().describe(
6708
+ locationId: locationIdSchema.optional().describe(
6475
6709
  'Optional location ID to filter results (e.g., "gid://shopify/Location/789"). If not provided, returns inventory at all locations.'
6476
6710
  )
6477
6711
  }).refine((data) => data.variantId || data.inventoryItemId, {
6478
6712
  message: "Either variantId or inventoryItemId must be provided"
6479
6713
  });
6480
- var outputSchema19 = z20.object({
6481
- inventoryItemId: z20.string().describe("Inventory item GID"),
6482
- variantId: z20.string().describe("Product variant GID"),
6483
- variantTitle: z20.string().describe('Variant title (e.g., "Red / Large")'),
6484
- productId: z20.string().describe("Product GID"),
6485
- productTitle: z20.string().describe("Product title"),
6486
- sku: z20.string().nullable().describe("SKU (Stock Keeping Unit)"),
6487
- tracked: z20.boolean().describe("Whether inventory tracking is enabled for this item"),
6488
- totalAvailable: z20.number().describe("Total available quantity across all locations"),
6489
- locations: z20.array(
6490
- z20.object({
6491
- locationId: z20.string().describe("Location GID"),
6492
- locationName: z20.string().describe("Human-readable location name"),
6493
- isActive: z20.boolean().describe("Whether the location is active"),
6494
- available: z20.number().describe("Quantity available for sale"),
6495
- onHand: z20.number().describe("Physical quantity on hand"),
6496
- committed: z20.number().describe("Quantity committed to orders")
6714
+ var outputSchema19 = z21.object({
6715
+ inventoryItemId: z21.string().describe("Inventory item GID"),
6716
+ variantId: z21.string().describe("Product variant GID"),
6717
+ variantTitle: z21.string().describe('Variant title (e.g., "Red / Large")'),
6718
+ productId: z21.string().describe("Product GID"),
6719
+ productTitle: z21.string().describe("Product title"),
6720
+ sku: z21.string().nullable().describe("SKU (Stock Keeping Unit)"),
6721
+ tracked: z21.boolean().describe("Whether inventory tracking is enabled for this item"),
6722
+ totalAvailable: z21.number().describe("Total available quantity across all locations"),
6723
+ locations: z21.array(
6724
+ z21.object({
6725
+ locationId: z21.string().describe("Location GID"),
6726
+ locationName: z21.string().describe("Human-readable location name"),
6727
+ isActive: z21.boolean().describe("Whether the location is active"),
6728
+ available: z21.number().describe("Quantity available for sale"),
6729
+ onHand: z21.number().describe("Physical quantity on hand"),
6730
+ committed: z21.number().describe("Quantity committed to orders")
6497
6731
  })
6498
6732
  ).describe("Inventory levels by location")
6499
6733
  });
@@ -6567,6 +6801,13 @@ function registerGetInventoryTool() {
6567
6801
  ],
6568
6802
  prerequisites: ["get-product", "list-products"],
6569
6803
  followUps: ["update-inventory"]
6804
+ },
6805
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
6806
+ annotations: {
6807
+ readOnlyHint: true,
6808
+ destructiveHint: false,
6809
+ idempotentHint: true,
6810
+ openWorldHint: true
6570
6811
  }
6571
6812
  },
6572
6813
  handleGetInventory
@@ -6574,23 +6815,23 @@ function registerGetInventoryTool() {
6574
6815
  }
6575
6816
 
6576
6817
  // src/tools/get-page.ts
6577
- import { z as z21 } from "zod";
6578
- var inputSchema20 = z21.object({
6579
- id: z21.string().min(1).describe(
6818
+ import { z as z22 } from "zod";
6819
+ var inputSchema20 = z22.object({
6820
+ id: pageIdSchema.describe(
6580
6821
  'The page ID to retrieve. Must be a valid Shopify GID format. Example: "gid://shopify/Page/123456789". Use list-pages to find page IDs.'
6581
6822
  )
6582
6823
  });
6583
- var outputSchema20 = z21.object({
6584
- id: z21.string().describe("Page GID"),
6585
- title: z21.string().describe("Page title"),
6586
- handle: z21.string().describe("URL handle/slug"),
6587
- body: z21.string().describe("HTML body content"),
6588
- bodySummary: z21.string().describe("Plain text summary (first 150 chars)"),
6589
- isPublished: z21.boolean().describe("Whether page is visible on storefront"),
6590
- publishedAt: z21.string().nullable().describe("ISO timestamp when published"),
6591
- templateSuffix: z21.string().nullable().describe("Custom template suffix"),
6592
- createdAt: z21.string().describe("ISO timestamp of creation"),
6593
- updatedAt: z21.string().describe("ISO timestamp of last update")
6824
+ var outputSchema20 = z22.object({
6825
+ id: z22.string().describe("Page GID"),
6826
+ title: z22.string().describe("Page title"),
6827
+ handle: z22.string().describe("URL handle/slug"),
6828
+ body: z22.string().describe("HTML body content"),
6829
+ bodySummary: z22.string().describe("Plain text summary (first 150 chars)"),
6830
+ isPublished: z22.boolean().describe("Whether page is visible on storefront"),
6831
+ publishedAt: z22.string().nullable().describe("ISO timestamp when published"),
6832
+ templateSuffix: z22.string().nullable().describe("Custom template suffix"),
6833
+ createdAt: z22.string().describe("ISO timestamp of creation"),
6834
+ updatedAt: z22.string().describe("ISO timestamp of last update")
6594
6835
  });
6595
6836
  var handleGetPage = async (context, params) => {
6596
6837
  log.debug(`Getting page on shop: ${context.shopDomain}`);
@@ -6617,6 +6858,13 @@ function registerGetPageTool() {
6617
6858
  relatedTools: ["list-pages", "update-page", "delete-page"],
6618
6859
  prerequisites: ["list-pages"],
6619
6860
  followUps: ["update-page", "delete-page"]
6861
+ },
6862
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
6863
+ annotations: {
6864
+ readOnlyHint: true,
6865
+ destructiveHint: false,
6866
+ idempotentHint: true,
6867
+ openWorldHint: true
6620
6868
  }
6621
6869
  },
6622
6870
  handleGetPage
@@ -6624,27 +6872,27 @@ function registerGetPageTool() {
6624
6872
  }
6625
6873
 
6626
6874
  // src/tools/get-product-metafields.ts
6627
- import { z as z22 } from "zod";
6628
- var inputSchema21 = z22.object({
6629
- productId: z22.string().min(1).describe(
6875
+ import { z as z23 } from "zod";
6876
+ var inputSchema21 = z23.object({
6877
+ productId: productIdSchema.describe(
6630
6878
  'Shopify product ID (GID format, e.g., "gid://shopify/Product/123"). Use get-product or list-products to find product IDs.'
6631
6879
  ),
6632
- namespace: z22.string().optional().describe(
6880
+ namespace: z23.string().optional().describe(
6633
6881
  'Filter metafields by namespace (e.g., "custom", "seo", "my_app"). If not provided, returns metafields from all namespaces.'
6634
6882
  ),
6635
- keys: z22.array(z22.string()).optional().describe(
6883
+ keys: z23.array(z23.string()).optional().describe(
6636
6884
  'Filter by specific metafield keys within the namespace (e.g., ["color", "size"]). Requires namespace to be specified for meaningful filtering.'
6637
6885
  )
6638
6886
  });
6639
- var outputSchema21 = z22.array(
6640
- z22.object({
6641
- id: z22.string().describe('Metafield GID (e.g., "gid://shopify/Metafield/123")'),
6642
- namespace: z22.string().describe("Metafield namespace (grouping identifier)"),
6643
- key: z22.string().describe("Metafield key (field name)"),
6644
- value: z22.string().describe("Metafield value (the stored data)"),
6645
- type: z22.string().describe('Metafield type (e.g., "single_line_text_field", "json", "number_integer")'),
6646
- createdAt: z22.string().describe("Creation timestamp (ISO 8601)"),
6647
- updatedAt: z22.string().describe("Last update timestamp (ISO 8601)")
6887
+ var outputSchema21 = z23.array(
6888
+ z23.object({
6889
+ id: z23.string().describe('Metafield GID (e.g., "gid://shopify/Metafield/123")'),
6890
+ namespace: z23.string().describe("Metafield namespace (grouping identifier)"),
6891
+ key: z23.string().describe("Metafield key (field name)"),
6892
+ value: z23.string().describe("Metafield value (the stored data)"),
6893
+ type: z23.string().describe('Metafield type (e.g., "single_line_text_field", "json", "number_integer")'),
6894
+ createdAt: z23.string().describe("Creation timestamp (ISO 8601)"),
6895
+ updatedAt: z23.string().describe("Last update timestamp (ISO 8601)")
6648
6896
  })
6649
6897
  );
6650
6898
  var handleGetProductMetafields = async (context, params) => {
@@ -6677,6 +6925,13 @@ function registerGetProductMetafieldsTool() {
6677
6925
  relatedTools: ["get-product", "set-product-metafields"],
6678
6926
  prerequisites: ["get-product"],
6679
6927
  followUps: ["set-product-metafields", "delete-product-metafields"]
6928
+ },
6929
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
6930
+ annotations: {
6931
+ readOnlyHint: true,
6932
+ destructiveHint: false,
6933
+ idempotentHint: true,
6934
+ openWorldHint: true
6680
6935
  }
6681
6936
  },
6682
6937
  handleGetProductMetafields
@@ -6684,51 +6939,51 @@ function registerGetProductMetafieldsTool() {
6684
6939
  }
6685
6940
 
6686
6941
  // src/tools/get-product.ts
6687
- import { z as z23 } from "zod";
6688
- var inputSchema22 = z23.object({
6689
- id: z23.string().optional().describe(
6942
+ import { z as z24 } from "zod";
6943
+ var inputSchema22 = z24.object({
6944
+ id: productIdSchema.optional().describe(
6690
6945
  'Shopify product ID (GID format, e.g., "gid://shopify/Product/123"). Use either id or handle.'
6691
6946
  ),
6692
- handle: z23.string().optional().describe(
6947
+ handle: z24.string().optional().describe(
6693
6948
  'Product URL handle/slug (e.g., "premium-cotton-t-shirt"). Use either id or handle.'
6694
6949
  )
6695
6950
  }).refine((data) => data.id || data.handle, {
6696
6951
  message: "Either id or handle must be provided"
6697
6952
  });
6698
- var outputSchema22 = z23.object({
6699
- id: z23.string().describe('Product GID (e.g., "gid://shopify/Product/123")'),
6700
- title: z23.string().describe("Product title"),
6701
- handle: z23.string().describe("URL handle/slug"),
6702
- description: z23.string().nullable().describe("Product description (HTML)"),
6703
- vendor: z23.string().describe("Vendor/brand name"),
6704
- productType: z23.string().describe("Product type"),
6705
- status: z23.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).describe("Product status"),
6706
- tags: z23.array(z23.string()).describe("Product tags"),
6707
- seo: z23.object({
6708
- title: z23.string().nullable().describe("SEO title for search engines"),
6709
- description: z23.string().nullable().describe("SEO description/meta description")
6953
+ var outputSchema22 = z24.object({
6954
+ id: z24.string().describe('Product GID (e.g., "gid://shopify/Product/123")'),
6955
+ title: z24.string().describe("Product title"),
6956
+ handle: z24.string().describe("URL handle/slug"),
6957
+ description: z24.string().nullable().describe("Product description (HTML)"),
6958
+ vendor: z24.string().describe("Vendor/brand name"),
6959
+ productType: z24.string().describe("Product type"),
6960
+ status: z24.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).describe("Product status"),
6961
+ tags: z24.array(z24.string()).describe("Product tags"),
6962
+ seo: z24.object({
6963
+ title: z24.string().nullable().describe("SEO title for search engines"),
6964
+ description: z24.string().nullable().describe("SEO description/meta description")
6710
6965
  }).describe("SEO metadata for search engine optimization"),
6711
- createdAt: z23.string().describe("Creation timestamp (ISO 8601)"),
6712
- updatedAt: z23.string().describe("Last update timestamp (ISO 8601)"),
6713
- variants: z23.array(
6714
- z23.object({
6715
- id: z23.string().describe("Variant GID"),
6716
- title: z23.string().describe("Variant title"),
6717
- price: z23.string().describe("Price"),
6718
- compareAtPrice: z23.string().nullable().describe("Compare at price"),
6719
- sku: z23.string().nullable().describe("SKU"),
6720
- barcode: z23.string().nullable().describe("Barcode"),
6721
- inventoryQuantity: z23.number().nullable().describe("Inventory quantity")
6966
+ createdAt: z24.string().describe("Creation timestamp (ISO 8601)"),
6967
+ updatedAt: z24.string().describe("Last update timestamp (ISO 8601)"),
6968
+ variants: z24.array(
6969
+ z24.object({
6970
+ id: z24.string().describe("Variant GID"),
6971
+ title: z24.string().describe("Variant title"),
6972
+ price: z24.string().describe("Price"),
6973
+ compareAtPrice: z24.string().nullable().describe("Compare at price"),
6974
+ sku: z24.string().nullable().describe("SKU"),
6975
+ barcode: z24.string().nullable().describe("Barcode"),
6976
+ inventoryQuantity: z24.number().nullable().describe("Inventory quantity")
6722
6977
  })
6723
6978
  ).describe("Product variants"),
6724
- images: z23.array(
6725
- z23.object({
6726
- id: z23.string().describe("Image GID"),
6727
- url: z23.string().describe("Image URL"),
6728
- altText: z23.string().nullable().describe("Alt text")
6979
+ images: z24.array(
6980
+ z24.object({
6981
+ id: z24.string().describe("Image GID"),
6982
+ url: z24.string().describe("Image URL"),
6983
+ altText: z24.string().nullable().describe("Alt text")
6729
6984
  })
6730
6985
  ).describe("Product images"),
6731
- totalInventory: z23.number().describe("Total inventory across variants")
6986
+ totalInventory: z24.number().describe("Total inventory across variants")
6732
6987
  });
6733
6988
  var handleGetProduct = async (context, params) => {
6734
6989
  log.debug(`Getting product on shop: ${context.shopDomain}`);
@@ -6779,6 +7034,13 @@ function registerGetProductTool() {
6779
7034
  "add-product-image",
6780
7035
  "get-product-metafields"
6781
7036
  ]
7037
+ },
7038
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
7039
+ annotations: {
7040
+ readOnlyHint: true,
7041
+ destructiveHint: false,
7042
+ idempotentHint: true,
7043
+ openWorldHint: true
6782
7044
  }
6783
7045
  },
6784
7046
  handleGetProduct
@@ -6786,36 +7048,36 @@ function registerGetProductTool() {
6786
7048
  }
6787
7049
 
6788
7050
  // src/tools/list-articles.ts
6789
- import { z as z24 } from "zod";
6790
- var inputSchema23 = z24.object({
6791
- first: z24.number().int().min(1).max(50).optional().default(10).describe("Number of articles to return (1-50). Defaults to 10."),
6792
- cursor: z24.string().optional().describe(
7051
+ import { z as z25 } from "zod";
7052
+ var inputSchema23 = z25.object({
7053
+ first: z25.number().int().min(1).max(50).optional().default(10).describe("Number of articles to return (1-50). Defaults to 10."),
7054
+ cursor: z25.string().optional().describe(
6793
7055
  "Pagination cursor from a previous response. Use the endCursor from the previous response to get the next page of results."
6794
7056
  ),
6795
- query: z24.string().optional().describe(
7057
+ query: z25.string().optional().describe(
6796
7058
  "Search query to filter articles. Supports Shopify search syntax. Examples: 'title:Guide', 'tag:tutorial', 'blog_id:123456789'."
6797
7059
  ),
6798
- blogId: z24.string().optional().describe(
7060
+ blogId: z25.string().optional().describe(
6799
7061
  'Filter articles by blog ID. Provide the numeric ID (not the full GID) to filter. Example: To filter blog "gid://shopify/Blog/123456", pass blogId as "123456".'
6800
7062
  ),
6801
- sortKey: z24.enum(["ID", "TITLE", "UPDATED_AT", "PUBLISHED_AT", "AUTHOR", "BLOG_TITLE"]).optional().describe("Sort order for results. Defaults to ID.")
7063
+ sortKey: z25.enum(["ID", "TITLE", "UPDATED_AT", "PUBLISHED_AT", "AUTHOR", "BLOG_TITLE"]).optional().describe("Sort order for results. Defaults to ID.")
6802
7064
  });
6803
- var outputSchema23 = z24.object({
6804
- articles: z24.array(
6805
- z24.object({
6806
- id: z24.string().describe("Article GID"),
6807
- title: z24.string().describe("Article title"),
6808
- handle: z24.string().describe("URL handle/slug"),
6809
- blog: z24.object({
6810
- id: z24.string().describe("Parent blog GID"),
6811
- title: z24.string().describe("Parent blog title")
7065
+ var outputSchema23 = z25.object({
7066
+ articles: z25.array(
7067
+ z25.object({
7068
+ id: z25.string().describe("Article GID"),
7069
+ title: z25.string().describe("Article title"),
7070
+ handle: z25.string().describe("URL handle/slug"),
7071
+ blog: z25.object({
7072
+ id: z25.string().describe("Parent blog GID"),
7073
+ title: z25.string().describe("Parent blog title")
6812
7074
  }),
6813
- isPublished: z24.boolean().describe("Whether article is visible on storefront"),
6814
- publishedAt: z24.string().nullable().describe("ISO timestamp when published")
7075
+ isPublished: z25.boolean().describe("Whether article is visible on storefront"),
7076
+ publishedAt: z25.string().nullable().describe("ISO timestamp when published")
6815
7077
  })
6816
7078
  ),
6817
- hasNextPage: z24.boolean().describe("Whether more articles are available"),
6818
- endCursor: z24.string().nullable().describe("Cursor for next page pagination")
7079
+ hasNextPage: z25.boolean().describe("Whether more articles are available"),
7080
+ endCursor: z25.string().nullable().describe("Cursor for next page pagination")
6819
7081
  });
6820
7082
  var handleListArticles = async (context, params) => {
6821
7083
  log.debug(`Listing articles on shop: ${context.shopDomain}`);
@@ -6865,6 +7127,13 @@ function registerListArticlesTool() {
6865
7127
  relationships: {
6866
7128
  relatedTools: ["list-blogs", "create-article", "update-article"],
6867
7129
  followUps: ["update-article", "delete-article", "create-article"]
7130
+ },
7131
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
7132
+ annotations: {
7133
+ readOnlyHint: true,
7134
+ destructiveHint: false,
7135
+ idempotentHint: true,
7136
+ openWorldHint: true
6868
7137
  }
6869
7138
  },
6870
7139
  handleListArticles
@@ -6872,29 +7141,29 @@ function registerListArticlesTool() {
6872
7141
  }
6873
7142
 
6874
7143
  // src/tools/list-blogs.ts
6875
- import { z as z25 } from "zod";
6876
- var inputSchema24 = z25.object({
6877
- first: z25.number().int().min(1).max(50).optional().default(10).describe("Number of blogs to return (1-50). Defaults to 10."),
6878
- cursor: z25.string().optional().describe(
7144
+ import { z as z26 } from "zod";
7145
+ var inputSchema24 = z26.object({
7146
+ first: z26.number().int().min(1).max(50).optional().default(10).describe("Number of blogs to return (1-50). Defaults to 10."),
7147
+ cursor: z26.string().optional().describe(
6879
7148
  "Pagination cursor from a previous response. Use the endCursor from the previous response to get the next page of results."
6880
7149
  ),
6881
- query: z25.string().optional().describe(
7150
+ query: z26.string().optional().describe(
6882
7151
  "Search query to filter blogs. Supports Shopify search syntax. Examples: 'title:News', 'handle:company-blog'."
6883
7152
  ),
6884
- sortKey: z25.enum(["ID", "TITLE", "UPDATED_AT"]).optional().describe("Sort order for results. Defaults to ID.")
7153
+ sortKey: z26.enum(["ID", "TITLE", "UPDATED_AT"]).optional().describe("Sort order for results. Defaults to ID.")
6885
7154
  });
6886
- var outputSchema24 = z25.object({
6887
- blogs: z25.array(
6888
- z25.object({
6889
- id: z25.string().describe("Blog GID"),
6890
- title: z25.string().describe("Blog title"),
6891
- handle: z25.string().describe("URL handle/slug"),
6892
- commentPolicy: z25.enum(["CLOSED", "MODERATE", "OPEN"]).describe("Comment moderation policy"),
6893
- articlesCount: z25.number().describe("Number of articles in the blog")
7155
+ var outputSchema24 = z26.object({
7156
+ blogs: z26.array(
7157
+ z26.object({
7158
+ id: z26.string().describe("Blog GID"),
7159
+ title: z26.string().describe("Blog title"),
7160
+ handle: z26.string().describe("URL handle/slug"),
7161
+ commentPolicy: z26.enum(["CLOSED", "MODERATE", "OPEN"]).describe("Comment moderation policy"),
7162
+ articlesCount: z26.number().describe("Number of articles in the blog")
6894
7163
  })
6895
7164
  ),
6896
- hasNextPage: z25.boolean().describe("Whether more blogs are available"),
6897
- endCursor: z25.string().nullable().describe("Cursor for next page pagination")
7165
+ hasNextPage: z26.boolean().describe("Whether more blogs are available"),
7166
+ endCursor: z26.string().nullable().describe("Cursor for next page pagination")
6898
7167
  });
6899
7168
  var handleListBlogs = async (context, params) => {
6900
7169
  log.debug(`Listing blogs on shop: ${context.shopDomain}`);
@@ -6938,6 +7207,13 @@ function registerListBlogsTool() {
6938
7207
  relationships: {
6939
7208
  relatedTools: ["create-blog", "update-blog", "list-articles"],
6940
7209
  followUps: ["create-article", "update-blog", "create-blog"]
7210
+ },
7211
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
7212
+ annotations: {
7213
+ readOnlyHint: true,
7214
+ destructiveHint: false,
7215
+ idempotentHint: true,
7216
+ openWorldHint: true
6941
7217
  }
6942
7218
  },
6943
7219
  handleListBlogs
@@ -6945,40 +7221,40 @@ function registerListBlogsTool() {
6945
7221
  }
6946
7222
 
6947
7223
  // src/tools/list-collections.ts
6948
- import { z as z26 } from "zod";
6949
- var inputSchema25 = z26.object({
6950
- first: z26.number().int().min(1).max(50).optional().default(10).describe("Number of collections to return (1-50). Default: 10"),
6951
- after: z26.string().optional().describe(
7224
+ import { z as z27 } from "zod";
7225
+ var inputSchema25 = z27.object({
7226
+ first: z27.number().int().min(1).max(50).optional().default(10).describe("Number of collections to return (1-50). Default: 10"),
7227
+ after: z27.string().optional().describe(
6952
7228
  "Pagination cursor from previous response. Use the endCursor value from the previous page to get the next page of results."
6953
7229
  ),
6954
- query: z26.string().optional().describe(
7230
+ query: z27.string().optional().describe(
6955
7231
  'Search query to filter collections. Searches across collection titles and handles. Example: "summer" finds collections with "summer" in the title or handle.'
6956
7232
  ),
6957
- sortKey: z26.enum(["TITLE", "UPDATED_AT", "ID", "RELEVANCE"]).optional().describe(
7233
+ sortKey: z27.enum(["TITLE", "UPDATED_AT", "ID", "RELEVANCE"]).optional().describe(
6958
7234
  "Sort collections by: TITLE (alphabetical), UPDATED_AT (most recently updated first), ID (by collection ID), or RELEVANCE (best match when using query filter)"
6959
7235
  )
6960
7236
  });
6961
- var outputSchema25 = z26.object({
6962
- collections: z26.array(
6963
- z26.object({
6964
- id: z26.string().describe("Collection GID"),
6965
- title: z26.string().describe("Collection title"),
6966
- handle: z26.string().describe("URL handle"),
6967
- seo: z26.object({
6968
- title: z26.string().nullable().describe("SEO title"),
6969
- description: z26.string().nullable().describe("SEO description")
7237
+ var outputSchema25 = z27.object({
7238
+ collections: z27.array(
7239
+ z27.object({
7240
+ id: z27.string().describe("Collection GID"),
7241
+ title: z27.string().describe("Collection title"),
7242
+ handle: z27.string().describe("URL handle"),
7243
+ seo: z27.object({
7244
+ title: z27.string().nullable().describe("SEO title"),
7245
+ description: z27.string().nullable().describe("SEO description")
6970
7246
  }),
6971
- productsCount: z26.number().describe("Number of products in collection")
7247
+ productsCount: z27.number().describe("Number of products in collection")
6972
7248
  })
6973
7249
  ),
6974
- pageInfo: z26.object({
6975
- hasNextPage: z26.boolean().describe("Whether more pages exist"),
6976
- endCursor: z26.string().nullable().describe("Cursor for next page")
7250
+ pageInfo: z27.object({
7251
+ hasNextPage: z27.boolean().describe("Whether more pages exist"),
7252
+ endCursor: z27.string().nullable().describe("Cursor for next page")
6977
7253
  }),
6978
- summary: z26.object({
6979
- totalReturned: z26.number().describe("Number of collections in this response"),
6980
- hasMore: z26.boolean().describe("Whether more collections are available"),
6981
- hint: z26.string().describe("Suggestion for next action")
7254
+ summary: z27.object({
7255
+ totalReturned: z27.number().describe("Number of collections in this response"),
7256
+ hasMore: z27.boolean().describe("Whether more collections are available"),
7257
+ hint: z27.string().describe("Suggestion for next action")
6982
7258
  }).describe("AI-friendly summary for context management")
6983
7259
  });
6984
7260
  var handleListCollections = async (context, params) => {
@@ -7012,6 +7288,13 @@ function registerListCollectionsTool() {
7012
7288
  relationships: {
7013
7289
  relatedTools: ["get-collection"],
7014
7290
  followUps: ["get-collection", "update-collection", "add-products-to-collection"]
7291
+ },
7292
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
7293
+ annotations: {
7294
+ readOnlyHint: true,
7295
+ destructiveHint: false,
7296
+ idempotentHint: true,
7297
+ openWorldHint: true
7015
7298
  }
7016
7299
  },
7017
7300
  handleListCollections
@@ -7019,47 +7302,47 @@ function registerListCollectionsTool() {
7019
7302
  }
7020
7303
 
7021
7304
  // src/tools/list-low-inventory.ts
7022
- import { z as z27 } from "zod";
7023
- var inputSchema26 = z27.object({
7024
- threshold: z27.number().int().min(0).default(10).describe(
7305
+ import { z as z28 } from "zod";
7306
+ var inputSchema26 = z28.object({
7307
+ threshold: z28.number().int().min(0).default(10).describe(
7025
7308
  "Products with total inventory at or below this number will be included. Default: 10. Use 0 to find only out-of-stock products."
7026
7309
  ),
7027
- includeZero: z27.boolean().default(true).describe(
7310
+ includeZero: z28.boolean().default(true).describe(
7028
7311
  "Include products with zero inventory. Default: true. Set to false to see only low-stock (not out-of-stock) products."
7029
7312
  ),
7030
- status: z27.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).optional().describe(
7313
+ status: z28.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).optional().describe(
7031
7314
  "Filter by product status. Default: ACTIVE. Use DRAFT to check pre-launch products, ARCHIVED for historical."
7032
7315
  ),
7033
- first: z27.number().int().min(1).max(100).default(50).describe("Number of products to return per page. Default: 50, max: 100."),
7034
- after: z27.string().optional().describe("Pagination cursor from previous response. Use pageInfo.endCursor to get next page.")
7316
+ first: z28.number().int().min(1).max(100).default(50).describe("Number of products to return per page. Default: 50, max: 100."),
7317
+ after: z28.string().optional().describe("Pagination cursor from previous response. Use pageInfo.endCursor to get next page.")
7035
7318
  });
7036
- var outputSchema26 = z27.object({
7037
- products: z27.array(
7038
- z27.object({
7039
- productId: z27.string().describe('Product GID (e.g., "gid://shopify/Product/123")'),
7040
- productTitle: z27.string().describe("Product title"),
7041
- status: z27.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).describe("Product status"),
7042
- totalInventory: z27.number().describe("Total inventory across all variants"),
7043
- isOutOfStock: z27.boolean().describe("True if total inventory is zero"),
7044
- variants: z27.array(
7045
- z27.object({
7046
- variantId: z27.string().describe("Variant GID"),
7047
- variantTitle: z27.string().describe('Variant title (e.g., "Red / Large")'),
7048
- sku: z27.string().nullable().describe("SKU (Stock Keeping Unit)"),
7049
- inventoryItemId: z27.string().describe("Inventory item GID"),
7050
- available: z27.number().describe("Available quantity for sale")
7319
+ var outputSchema26 = z28.object({
7320
+ products: z28.array(
7321
+ z28.object({
7322
+ productId: z28.string().describe('Product GID (e.g., "gid://shopify/Product/123")'),
7323
+ productTitle: z28.string().describe("Product title"),
7324
+ status: z28.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).describe("Product status"),
7325
+ totalInventory: z28.number().describe("Total inventory across all variants"),
7326
+ isOutOfStock: z28.boolean().describe("True if total inventory is zero"),
7327
+ variants: z28.array(
7328
+ z28.object({
7329
+ variantId: z28.string().describe("Variant GID"),
7330
+ variantTitle: z28.string().describe('Variant title (e.g., "Red / Large")'),
7331
+ sku: z28.string().nullable().describe("SKU (Stock Keeping Unit)"),
7332
+ inventoryItemId: z28.string().describe("Inventory item GID"),
7333
+ available: z28.number().describe("Available quantity for sale")
7051
7334
  })
7052
7335
  ).describe("Variant breakdown with inventory")
7053
7336
  })
7054
7337
  ).describe("Products with low inventory, sorted by total inventory ascending"),
7055
- pageInfo: z27.object({
7056
- hasNextPage: z27.boolean().describe("Whether more products exist after this page"),
7057
- endCursor: z27.string().nullable().describe('Cursor for the last item - use as "after" to get next page')
7338
+ pageInfo: z28.object({
7339
+ hasNextPage: z28.boolean().describe("Whether more products exist after this page"),
7340
+ endCursor: z28.string().nullable().describe('Cursor for the last item - use as "after" to get next page')
7058
7341
  }).describe("Pagination information"),
7059
- summary: z27.object({
7060
- totalProducts: z27.number().describe("Number of products in this response"),
7061
- outOfStockCount: z27.number().describe("Number of products with zero inventory"),
7062
- lowStockCount: z27.number().describe("Number of products with inventory > 0 but below threshold")
7342
+ summary: z28.object({
7343
+ totalProducts: z28.number().describe("Number of products in this response"),
7344
+ outOfStockCount: z28.number().describe("Number of products with zero inventory"),
7345
+ lowStockCount: z28.number().describe("Number of products with inventory > 0 but below threshold")
7063
7346
  }).describe("Summary statistics")
7064
7347
  });
7065
7348
  var handleListLowInventory = async (context, params) => {
@@ -7089,6 +7372,13 @@ function registerListLowInventoryTool() {
7089
7372
  relationships: {
7090
7373
  relatedTools: ["get-inventory", "update-inventory", "get-bulk-inventory"],
7091
7374
  followUps: ["get-inventory", "update-inventory"]
7375
+ },
7376
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
7377
+ annotations: {
7378
+ readOnlyHint: true,
7379
+ destructiveHint: false,
7380
+ idempotentHint: true,
7381
+ openWorldHint: true
7092
7382
  }
7093
7383
  },
7094
7384
  handleListLowInventory
@@ -7096,31 +7386,31 @@ function registerListLowInventoryTool() {
7096
7386
  }
7097
7387
 
7098
7388
  // src/tools/list-pages.ts
7099
- import { z as z28 } from "zod";
7100
- var inputSchema27 = z28.object({
7101
- first: z28.number().int().min(1).max(50).optional().describe(
7389
+ import { z as z29 } from "zod";
7390
+ var inputSchema27 = z29.object({
7391
+ first: z29.number().int().min(1).max(50).optional().describe(
7102
7392
  "Number of pages to return per request. Default: 10, Maximum: 50. Use pagination (cursor) to retrieve more results."
7103
7393
  ),
7104
- cursor: z28.string().optional().describe(
7394
+ cursor: z29.string().optional().describe(
7105
7395
  "Pagination cursor from a previous list-pages response (endCursor). Use this to get the next page of results."
7106
7396
  ),
7107
- query: z28.string().optional().describe(
7397
+ query: z29.string().optional().describe(
7108
7398
  "Search query to filter pages. Supports Shopify search syntax. Examples: 'title:About', 'handle:contact', 'created_at:>2024-01-01'."
7109
7399
  ),
7110
- sortKey: z28.enum(["ID", "TITLE", "UPDATED_AT", "PUBLISHED_AT"]).optional().describe("Field to sort pages by. Options: ID (default), TITLE, UPDATED_AT, PUBLISHED_AT.")
7400
+ sortKey: z29.enum(["ID", "TITLE", "UPDATED_AT", "PUBLISHED_AT"]).optional().describe("Field to sort pages by. Options: ID (default), TITLE, UPDATED_AT, PUBLISHED_AT.")
7111
7401
  });
7112
- var outputSchema27 = z28.object({
7113
- pages: z28.array(
7114
- z28.object({
7115
- id: z28.string().describe("Page GID"),
7116
- title: z28.string().describe("Page title"),
7117
- handle: z28.string().describe("URL handle/slug"),
7118
- isPublished: z28.boolean().describe("Whether page is visible"),
7119
- updatedAt: z28.string().describe("ISO timestamp of last update")
7402
+ var outputSchema27 = z29.object({
7403
+ pages: z29.array(
7404
+ z29.object({
7405
+ id: z29.string().describe("Page GID"),
7406
+ title: z29.string().describe("Page title"),
7407
+ handle: z29.string().describe("URL handle/slug"),
7408
+ isPublished: z29.boolean().describe("Whether page is visible"),
7409
+ updatedAt: z29.string().describe("ISO timestamp of last update")
7120
7410
  })
7121
7411
  ),
7122
- hasNextPage: z28.boolean().describe("Whether more pages exist"),
7123
- endCursor: z28.string().nullable().describe("Cursor for next page of results")
7412
+ hasNextPage: z29.boolean().describe("Whether more pages exist"),
7413
+ endCursor: z29.string().nullable().describe("Cursor for next page of results")
7124
7414
  });
7125
7415
  var handleListPages = async (context, params) => {
7126
7416
  log.debug(`Listing pages on shop: ${context.shopDomain}`);
@@ -7152,6 +7442,13 @@ function registerListPagesTool() {
7152
7442
  relationships: {
7153
7443
  relatedTools: ["get-page", "create-page"],
7154
7444
  followUps: ["get-page", "update-page", "create-page"]
7445
+ },
7446
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
7447
+ annotations: {
7448
+ readOnlyHint: true,
7449
+ destructiveHint: false,
7450
+ idempotentHint: true,
7451
+ openWorldHint: true
7155
7452
  }
7156
7453
  },
7157
7454
  handleListPages
@@ -7159,46 +7456,46 @@ function registerListPagesTool() {
7159
7456
  }
7160
7457
 
7161
7458
  // src/tools/list-products.ts
7162
- import { z as z29 } from "zod";
7163
- var inputSchema28 = z29.object({
7164
- first: z29.number().int().min(1).max(50).optional().describe("Number of products to return (1-50). Default: 10"),
7165
- after: z29.string().optional().describe("Pagination cursor from previous response's endCursor. Omit for first page."),
7166
- query: z29.string().optional().describe(
7459
+ import { z as z30 } from "zod";
7460
+ var inputSchema28 = z30.object({
7461
+ first: z30.number().int().min(1).max(50).optional().describe("Number of products to return (1-50). Default: 10"),
7462
+ after: z30.string().optional().describe("Pagination cursor from previous response's endCursor. Omit for first page."),
7463
+ query: z30.string().optional().describe(
7167
7464
  'Search query for title, description, tags. Example: "summer dress", "organic cotton"'
7168
7465
  ),
7169
- status: z29.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).optional().describe(
7466
+ status: z30.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).optional().describe(
7170
7467
  "Filter by product status. ACTIVE = published, DRAFT = not published, ARCHIVED = hidden"
7171
7468
  ),
7172
- vendor: z29.string().optional().describe('Filter by exact vendor name. Example: "Acme Corp", "Nike"'),
7173
- productType: z29.string().optional().describe('Filter by exact product type. Example: "T-Shirts", "Shoes"'),
7174
- sortBy: z29.enum(["TITLE", "CREATED_AT", "UPDATED_AT", "INVENTORY_TOTAL"]).optional().describe("Sort field. TITLE, CREATED_AT (default), UPDATED_AT, or INVENTORY_TOTAL"),
7175
- sortOrder: z29.enum(["ASC", "DESC"]).optional().describe("Sort direction. ASC (default) for ascending, DESC for descending")
7469
+ vendor: z30.string().optional().describe('Filter by exact vendor name. Example: "Acme Corp", "Nike"'),
7470
+ productType: z30.string().optional().describe('Filter by exact product type. Example: "T-Shirts", "Shoes"'),
7471
+ sortBy: z30.enum(["TITLE", "CREATED_AT", "UPDATED_AT", "INVENTORY_TOTAL"]).optional().describe("Sort field. TITLE, CREATED_AT (default), UPDATED_AT, or INVENTORY_TOTAL"),
7472
+ sortOrder: z30.enum(["ASC", "DESC"]).optional().describe("Sort direction. ASC (default) for ascending, DESC for descending")
7176
7473
  });
7177
- var outputSchema28 = z29.object({
7178
- products: z29.array(
7179
- z29.object({
7180
- id: z29.string().describe('Product GID (e.g., "gid://shopify/Product/123")'),
7181
- title: z29.string().describe("Product title"),
7182
- handle: z29.string().describe("URL handle/slug"),
7183
- status: z29.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).describe("Product status"),
7184
- vendor: z29.string().describe("Vendor/brand name"),
7185
- productType: z29.string().describe("Product type"),
7186
- totalInventory: z29.number().describe("Total inventory across all variants"),
7187
- primaryVariantPrice: z29.string().describe('Price of the first variant (e.g., "29.99")'),
7188
- imageUrl: z29.string().nullable().describe("First image URL or null if no images")
7474
+ var outputSchema28 = z30.object({
7475
+ products: z30.array(
7476
+ z30.object({
7477
+ id: z30.string().describe('Product GID (e.g., "gid://shopify/Product/123")'),
7478
+ title: z30.string().describe("Product title"),
7479
+ handle: z30.string().describe("URL handle/slug"),
7480
+ status: z30.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).describe("Product status"),
7481
+ vendor: z30.string().describe("Vendor/brand name"),
7482
+ productType: z30.string().describe("Product type"),
7483
+ totalInventory: z30.number().describe("Total inventory across all variants"),
7484
+ primaryVariantPrice: z30.string().describe('Price of the first variant (e.g., "29.99")'),
7485
+ imageUrl: z30.string().nullable().describe("First image URL or null if no images")
7189
7486
  })
7190
7487
  ).describe("Array of product summaries"),
7191
- pageInfo: z29.object({
7192
- hasNextPage: z29.boolean().describe("Whether more products exist after this page"),
7193
- hasPreviousPage: z29.boolean().describe("Whether products exist before this page"),
7194
- startCursor: z29.string().nullable().describe("Cursor for the first item in this page"),
7195
- endCursor: z29.string().nullable().describe('Cursor for the last item - use as "after" to get next page')
7488
+ pageInfo: z30.object({
7489
+ hasNextPage: z30.boolean().describe("Whether more products exist after this page"),
7490
+ hasPreviousPage: z30.boolean().describe("Whether products exist before this page"),
7491
+ startCursor: z30.string().nullable().describe("Cursor for the first item in this page"),
7492
+ endCursor: z30.string().nullable().describe('Cursor for the last item - use as "after" to get next page')
7196
7493
  }).describe("Pagination information"),
7197
- totalCount: z29.number().describe("Number of products returned in this page"),
7198
- summary: z29.object({
7199
- totalReturned: z29.number().describe("Number of products in this response"),
7200
- hasMore: z29.boolean().describe("Whether more products are available"),
7201
- hint: z29.string().describe("Suggestion for next action")
7494
+ totalCount: z30.number().describe("Number of products returned in this page"),
7495
+ summary: z30.object({
7496
+ totalReturned: z30.number().describe("Number of products in this response"),
7497
+ hasMore: z30.boolean().describe("Whether more products are available"),
7498
+ hint: z30.string().describe("Suggestion for next action")
7202
7499
  }).describe("AI-friendly summary for context management")
7203
7500
  });
7204
7501
  var handleListProducts = async (context, params) => {
@@ -7239,6 +7536,13 @@ function registerListProductsTool() {
7239
7536
  relationships: {
7240
7537
  relatedTools: ["get-product"],
7241
7538
  followUps: ["get-product", "update-product", "delete-product"]
7539
+ },
7540
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
7541
+ annotations: {
7542
+ readOnlyHint: true,
7543
+ destructiveHint: false,
7544
+ idempotentHint: true,
7545
+ openWorldHint: true
7242
7546
  }
7243
7547
  },
7244
7548
  handleListProducts
@@ -7246,30 +7550,30 @@ function registerListProductsTool() {
7246
7550
  }
7247
7551
 
7248
7552
  // src/tools/list-redirects.ts
7249
- import { z as z30 } from "zod";
7250
- var inputSchema29 = z30.object({
7251
- first: z30.number().int().min(1).max(50).optional().default(10).describe("Number of redirects to return (1-50, default: 10)."),
7252
- cursor: z30.string().optional().describe(
7553
+ import { z as z31 } from "zod";
7554
+ var inputSchema29 = z31.object({
7555
+ first: z31.number().int().min(1).max(50).optional().default(10).describe("Number of redirects to return (1-50, default: 10)."),
7556
+ cursor: z31.string().optional().describe(
7253
7557
  "Pagination cursor from previous response (endCursor). Use this to fetch the next page of results."
7254
7558
  ),
7255
- query: z30.string().optional().describe(
7559
+ query: z31.string().optional().describe(
7256
7560
  "Search filter. Supports 'path:' and 'target:' prefixes. Examples: 'path:/old-' to find redirects starting with /old-, 'target:/products/' to find redirects pointing to /products/."
7257
7561
  )
7258
7562
  });
7259
- var outputSchema29 = z30.object({
7260
- redirects: z30.array(
7261
- z30.object({
7262
- id: z30.string().describe("Redirect GID"),
7263
- path: z30.string().describe("Source path"),
7264
- target: z30.string().describe("Target URL")
7563
+ var outputSchema29 = z31.object({
7564
+ redirects: z31.array(
7565
+ z31.object({
7566
+ id: z31.string().describe("Redirect GID"),
7567
+ path: z31.string().describe("Source path"),
7568
+ target: z31.string().describe("Target URL")
7265
7569
  })
7266
7570
  ),
7267
- hasNextPage: z30.boolean().describe("Whether more results exist"),
7268
- endCursor: z30.string().nullable().describe("Cursor for fetching next page"),
7269
- summary: z30.object({
7270
- totalReturned: z30.number().describe("Number of redirects in this response"),
7271
- hasMore: z30.boolean().describe("Whether more redirects are available"),
7272
- hint: z30.string().describe("Suggestion for next action")
7571
+ hasNextPage: z31.boolean().describe("Whether more results exist"),
7572
+ endCursor: z31.string().nullable().describe("Cursor for fetching next page"),
7573
+ summary: z31.object({
7574
+ totalReturned: z31.number().describe("Number of redirects in this response"),
7575
+ hasMore: z31.boolean().describe("Whether more redirects are available"),
7576
+ hint: z31.string().describe("Suggestion for next action")
7273
7577
  }).describe("AI-friendly summary for context management")
7274
7578
  });
7275
7579
  var handleListRedirects = async (context, params) => {
@@ -7314,6 +7618,13 @@ function registerListRedirectsTool() {
7314
7618
  relationships: {
7315
7619
  relatedTools: ["create-redirect"],
7316
7620
  followUps: ["delete-redirect"]
7621
+ },
7622
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
7623
+ annotations: {
7624
+ readOnlyHint: true,
7625
+ destructiveHint: false,
7626
+ idempotentHint: true,
7627
+ openWorldHint: true
7317
7628
  }
7318
7629
  },
7319
7630
  handleListRedirects
@@ -7321,18 +7632,18 @@ function registerListRedirectsTool() {
7321
7632
  }
7322
7633
 
7323
7634
  // src/tools/remove-products-from-collection.ts
7324
- import { z as z31 } from "zod";
7325
- var inputSchema30 = z31.object({
7326
- collectionId: z31.string().min(1).describe(
7635
+ import { z as z32 } from "zod";
7636
+ var inputSchema30 = z32.object({
7637
+ collectionId: collectionIdSchema.describe(
7327
7638
  'Collection GID (e.g., "gid://shopify/Collection/123"). Use list-collections to find valid collection IDs.'
7328
7639
  ),
7329
- productIds: z31.array(z31.string().min(1)).min(1).max(250).describe(
7640
+ productIds: z32.array(productIdSchema).min(1).max(250).describe(
7330
7641
  'Array of product GIDs to remove (e.g., ["gid://shopify/Product/123", "gid://shopify/Product/456"]). Maximum 250 products per call. Use list-products to find valid product IDs.'
7331
7642
  )
7332
7643
  });
7333
- var outputSchema30 = z31.object({
7334
- success: z31.boolean().describe("Whether the operation succeeded"),
7335
- removedCount: z31.number().describe("Number of products removed from the collection")
7644
+ var outputSchema30 = z32.object({
7645
+ success: z32.boolean().describe("Whether the operation succeeded"),
7646
+ removedCount: z32.number().describe("Number of products removed from the collection")
7336
7647
  });
7337
7648
  var handleRemoveProductsFromCollection = async (context, params) => {
7338
7649
  log.debug(
@@ -7369,6 +7680,15 @@ function registerRemoveProductsFromCollectionTool() {
7369
7680
  relationships: {
7370
7681
  relatedTools: ["add-products-to-collection", "get-collection"],
7371
7682
  prerequisites: ["get-collection"]
7683
+ },
7684
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
7685
+ annotations: {
7686
+ readOnlyHint: false,
7687
+ destructiveHint: false,
7688
+ // Does not delete products, just removes from collection
7689
+ idempotentHint: true,
7690
+ // Removing already-removed products is idempotent
7691
+ openWorldHint: true
7372
7692
  }
7373
7693
  },
7374
7694
  handleRemoveProductsFromCollection
@@ -7376,7 +7696,7 @@ function registerRemoveProductsFromCollectionTool() {
7376
7696
  }
7377
7697
 
7378
7698
  // src/tools/reorder-product-images.ts
7379
- import { z as z32 } from "zod";
7699
+ import { z as z33 } from "zod";
7380
7700
  var PRODUCT_REORDER_MEDIA_MUTATION = `
7381
7701
  mutation ProductReorderMedia($id: ID!, $moves: [MoveInput!]!) {
7382
7702
  productReorderMedia(id: $id, moves: $moves) {
@@ -7391,23 +7711,23 @@ var PRODUCT_REORDER_MEDIA_MUTATION = `
7391
7711
  }
7392
7712
  }
7393
7713
  `;
7394
- var moveSchema = z32.object({
7395
- id: z32.string().min(1).describe('Image GID to move (e.g., "gid://shopify/MediaImage/123")'),
7396
- newPosition: z32.number().int().min(0).describe("Zero-based target position. Position 0 is the featured image.")
7714
+ var moveSchema = z33.object({
7715
+ id: imageIdSchema.describe('Image GID to move (e.g., "gid://shopify/MediaImage/123")'),
7716
+ newPosition: z33.number().int().min(0).describe("Zero-based target position. Position 0 is the featured image.")
7397
7717
  });
7398
- var inputSchema31 = z32.object({
7399
- productId: z32.string().min(1).describe(
7718
+ var inputSchema31 = z33.object({
7719
+ productId: productIdSchema.describe(
7400
7720
  'Shopify product GID (e.g., "gid://shopify/Product/123"). Required. Use list-products to find product IDs.'
7401
7721
  ),
7402
- moves: z32.array(moveSchema).min(1).describe(
7722
+ moves: z33.array(moveSchema).min(1).describe(
7403
7723
  "Array of move operations. Each move specifies an image ID and its new position. Only include images whose position is actually changing. Positions are 0-indexed (position 0 is the featured/hero image)."
7404
7724
  )
7405
7725
  });
7406
- var outputSchema31 = z32.object({
7407
- success: z32.boolean().describe("Whether the reorder operation was initiated successfully"),
7408
- jobId: z32.string().nullable().describe("Background job ID for tracking (may be null if processed immediately)"),
7409
- jobDone: z32.boolean().describe("Whether the job completed immediately"),
7410
- message: z32.string().describe("Human-readable message describing the result")
7726
+ var outputSchema31 = z33.object({
7727
+ success: z33.boolean().describe("Whether the reorder operation was initiated successfully"),
7728
+ jobId: z33.string().nullable().describe("Background job ID for tracking (may be null if processed immediately)"),
7729
+ jobDone: z33.boolean().describe("Whether the job completed immediately"),
7730
+ message: z33.string().describe("Human-readable message describing the result")
7411
7731
  });
7412
7732
  var handleReorderProductImages = async (context, params) => {
7413
7733
  log.debug(`Reordering images on shop: ${context.shopDomain}`);
@@ -7482,6 +7802,14 @@ function registerReorderProductImagesTool() {
7482
7802
  relationships: {
7483
7803
  relatedTools: ["add-product-image", "update-product-image"],
7484
7804
  prerequisites: ["get-product"]
7805
+ },
7806
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
7807
+ annotations: {
7808
+ readOnlyHint: false,
7809
+ destructiveHint: false,
7810
+ idempotentHint: true,
7811
+ // Same order produces same result
7812
+ openWorldHint: true
7485
7813
  }
7486
7814
  },
7487
7815
  handleReorderProductImages
@@ -7489,45 +7817,45 @@ function registerReorderProductImagesTool() {
7489
7817
  }
7490
7818
 
7491
7819
  // src/tools/set-product-metafields.ts
7492
- import { z as z33 } from "zod";
7820
+ import { z as z34 } from "zod";
7493
7821
  var MAX_METAFIELDS_PER_CALL = 25;
7494
- var metafieldInputSchema = z33.object({
7495
- namespace: z33.string().min(1).describe(
7822
+ var metafieldInputSchema = z34.object({
7823
+ namespace: z34.string().min(1).describe(
7496
7824
  'Metafield namespace (grouping identifier, e.g., "custom", "seo", "my_app"). Use consistent namespaces to organize related metafields.'
7497
7825
  ),
7498
- key: z33.string().min(1).describe(
7826
+ key: z34.string().min(1).describe(
7499
7827
  'Metafield key (field name, e.g., "color_hex", "schema_markup"). Keys should be lowercase with underscores, unique within a namespace.'
7500
7828
  ),
7501
- value: z33.string().describe("The value to store. All values are stored as strings; JSON should be stringified."),
7502
- type: z33.string().min(1).describe(
7829
+ value: z34.string().describe("The value to store. All values are stored as strings; JSON should be stringified."),
7830
+ type: z34.string().min(1).describe(
7503
7831
  'Metafield type. Required for creating new metafields. Common types: "single_line_text_field" (short text), "multi_line_text_field" (long text), "number_integer", "number_decimal", "boolean", "json", "url", "date", "date_time".'
7504
7832
  )
7505
7833
  });
7506
- var inputSchema32 = z33.object({
7507
- productId: z33.string().min(1).describe(
7834
+ var inputSchema32 = z34.object({
7835
+ productId: productIdSchema.describe(
7508
7836
  'Shopify product ID (GID format, e.g., "gid://shopify/Product/123"). Use get-product or list-products to find product IDs.'
7509
7837
  ),
7510
- metafields: z33.array(metafieldInputSchema).min(1, "At least one metafield is required").max(
7838
+ metafields: z34.array(metafieldInputSchema).min(1, "At least one metafield is required").max(
7511
7839
  MAX_METAFIELDS_PER_CALL,
7512
7840
  `Maximum ${MAX_METAFIELDS_PER_CALL} metafields per call. Split into multiple calls for larger batches.`
7513
7841
  ).describe(
7514
7842
  `Array of metafields to create or update (max ${MAX_METAFIELDS_PER_CALL} per call). Existing metafields with matching namespace+key are updated; new ones are created.`
7515
7843
  )
7516
7844
  });
7517
- var outputSchema32 = z33.object({
7518
- success: z33.boolean().describe("Whether the operation succeeded"),
7519
- metafields: z33.array(
7520
- z33.object({
7521
- id: z33.string().describe("Metafield GID"),
7522
- namespace: z33.string().describe("Metafield namespace"),
7523
- key: z33.string().describe("Metafield key"),
7524
- value: z33.string().describe("Metafield value"),
7525
- type: z33.string().describe("Metafield type"),
7526
- createdAt: z33.string().describe("Creation timestamp (ISO 8601)"),
7527
- updatedAt: z33.string().describe("Last update timestamp (ISO 8601)")
7845
+ var outputSchema32 = z34.object({
7846
+ success: z34.boolean().describe("Whether the operation succeeded"),
7847
+ metafields: z34.array(
7848
+ z34.object({
7849
+ id: z34.string().describe("Metafield GID"),
7850
+ namespace: z34.string().describe("Metafield namespace"),
7851
+ key: z34.string().describe("Metafield key"),
7852
+ value: z34.string().describe("Metafield value"),
7853
+ type: z34.string().describe("Metafield type"),
7854
+ createdAt: z34.string().describe("Creation timestamp (ISO 8601)"),
7855
+ updatedAt: z34.string().describe("Last update timestamp (ISO 8601)")
7528
7856
  })
7529
7857
  ).describe("Created or updated metafields"),
7530
- count: z33.number().describe("Number of metafields processed")
7858
+ count: z34.number().describe("Number of metafields processed")
7531
7859
  });
7532
7860
  var handleSetProductMetafields = async (context, params) => {
7533
7861
  log.debug(
@@ -7571,6 +7899,14 @@ function registerSetProductMetafieldsTool() {
7571
7899
  relatedTools: ["get-product", "get-product-metafields"],
7572
7900
  prerequisites: ["get-product"],
7573
7901
  followUps: ["get-product-metafields"]
7902
+ },
7903
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
7904
+ annotations: {
7905
+ readOnlyHint: false,
7906
+ destructiveHint: false,
7907
+ idempotentHint: true,
7908
+ // Same values produce same result
7909
+ openWorldHint: true
7574
7910
  }
7575
7911
  },
7576
7912
  handleSetProductMetafields
@@ -7578,47 +7914,47 @@ function registerSetProductMetafieldsTool() {
7578
7914
  }
7579
7915
 
7580
7916
  // src/tools/update-article.ts
7581
- import { z as z34 } from "zod";
7582
- var inputSchema33 = z34.object({
7583
- id: z34.string().describe(
7917
+ import { z as z35 } from "zod";
7918
+ var inputSchema33 = z35.object({
7919
+ id: articleIdSchema.describe(
7584
7920
  'The article ID to update (required). Must be a valid Shopify GID format. Example: "gid://shopify/Article/12345". Use list-articles to find article IDs.'
7585
7921
  ),
7586
- title: z34.string().optional().describe("The new title for the article. Only provided if changing the title."),
7587
- authorName: z34.string().optional().describe("The new author name for the article."),
7588
- body: z34.string().optional().describe("The new HTML body content of the article. Supports HTML markup for formatting."),
7589
- summary: z34.string().optional().describe("A summary or excerpt of the article."),
7590
- tags: z34.array(z34.string()).optional().describe(
7922
+ title: z35.string().optional().describe("The new title for the article. Only provided if changing the title."),
7923
+ authorName: z35.string().optional().describe("The new author name for the article."),
7924
+ body: z35.string().optional().describe("The new HTML body content of the article. Supports HTML markup for formatting."),
7925
+ summary: z35.string().optional().describe("A summary or excerpt of the article."),
7926
+ tags: z35.array(z35.string()).optional().describe(
7591
7927
  "New tags for categorization. This replaces all existing tags. Example: ['guide', 'tutorial', 'beginner']."
7592
7928
  ),
7593
- image: z34.object({
7594
- url: z34.string().url().describe("The URL of the featured image"),
7595
- altText: z34.string().optional().describe("Alt text for accessibility")
7929
+ image: z35.object({
7930
+ url: z35.string().url().describe("The URL of the featured image"),
7931
+ altText: z35.string().optional().describe("Alt text for accessibility")
7596
7932
  }).optional().describe("Featured image for the article."),
7597
- isPublished: z34.boolean().optional().describe(
7933
+ isPublished: z35.boolean().optional().describe(
7598
7934
  "Whether the article should be visible on the storefront. Set to true to publish, false to unpublish."
7599
7935
  ),
7600
- publishDate: z34.string().optional().describe(
7936
+ publishDate: z35.string().optional().describe(
7601
7937
  "The date and time when the article should become visible (ISO 8601 format). Example: '2024-01-15T10:00:00Z'."
7602
7938
  ),
7603
- handle: z34.string().optional().describe(
7939
+ handle: z35.string().optional().describe(
7604
7940
  "The new URL handle/slug for the article. Set redirectNewHandle to true to automatically redirect the old URL to the new one."
7605
7941
  ),
7606
- templateSuffix: z34.string().optional().describe(
7942
+ templateSuffix: z35.string().optional().describe(
7607
7943
  "The suffix of the Liquid template used to render the article. For example, 'featured' would use the template 'article.featured.liquid'."
7608
7944
  ),
7609
- redirectNewHandle: z34.boolean().optional().describe(
7945
+ redirectNewHandle: z35.boolean().optional().describe(
7610
7946
  "Whether to automatically redirect the old URL to the new handle. Set to true when changing the handle to preserve SEO and existing links."
7611
7947
  )
7612
7948
  });
7613
- var outputSchema33 = z34.object({
7614
- id: z34.string().describe("Updated article GID"),
7615
- title: z34.string().describe("Article title"),
7616
- handle: z34.string().describe("URL handle/slug"),
7617
- blog: z34.object({
7618
- id: z34.string().describe("Parent blog GID"),
7619
- title: z34.string().describe("Parent blog title")
7949
+ var outputSchema33 = z35.object({
7950
+ id: z35.string().describe("Updated article GID"),
7951
+ title: z35.string().describe("Article title"),
7952
+ handle: z35.string().describe("URL handle/slug"),
7953
+ blog: z35.object({
7954
+ id: z35.string().describe("Parent blog GID"),
7955
+ title: z35.string().describe("Parent blog title")
7620
7956
  }),
7621
- isPublished: z34.boolean().describe("Whether article is visible on storefront")
7957
+ isPublished: z35.boolean().describe("Whether article is visible on storefront")
7622
7958
  });
7623
7959
  var handleUpdateArticle = async (context, params) => {
7624
7960
  log.debug(`Updating article on shop: ${context.shopDomain}`);
@@ -7674,6 +8010,13 @@ function registerUpdateArticleTool() {
7674
8010
  relatedTools: ["list-articles", "create-article", "delete-article"],
7675
8011
  prerequisites: ["list-articles"],
7676
8012
  followUps: ["list-articles"]
8013
+ },
8014
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
8015
+ annotations: {
8016
+ readOnlyHint: false,
8017
+ destructiveHint: false,
8018
+ idempotentHint: true,
8019
+ openWorldHint: true
7677
8020
  }
7678
8021
  },
7679
8022
  handleUpdateArticle
@@ -7681,30 +8024,30 @@ function registerUpdateArticleTool() {
7681
8024
  }
7682
8025
 
7683
8026
  // src/tools/update-blog.ts
7684
- import { z as z35 } from "zod";
7685
- var inputSchema34 = z35.object({
7686
- id: z35.string().describe(
8027
+ import { z as z36 } from "zod";
8028
+ var inputSchema34 = z36.object({
8029
+ id: blogIdSchema.describe(
7687
8030
  'The blog ID to update (required). Must be a valid Shopify GID format. Example: "gid://shopify/Blog/12345". Use list-blogs to find blog IDs.'
7688
8031
  ),
7689
- title: z35.string().optional().describe("The new title for the blog. Only provided if changing the title."),
7690
- handle: z35.string().optional().describe(
8032
+ title: z36.string().optional().describe("The new title for the blog. Only provided if changing the title."),
8033
+ handle: z36.string().optional().describe(
7691
8034
  "The new URL handle/slug for the blog. Set redirectNewHandle to true to automatically redirect the old URL to the new one."
7692
8035
  ),
7693
- commentPolicy: z35.enum(["CLOSED", "MODERATE", "OPEN"]).optional().describe(
8036
+ commentPolicy: z36.enum(["CLOSED", "MODERATE", "OPEN"]).optional().describe(
7694
8037
  "Comment moderation policy for the blog. CLOSED: Comments disabled. MODERATE: Comments require approval. OPEN: Comments appear immediately."
7695
8038
  ),
7696
- templateSuffix: z35.string().optional().describe(
8039
+ templateSuffix: z36.string().optional().describe(
7697
8040
  "The suffix of the Liquid template used to render the blog. For example, 'news' would use the template 'blog.news.liquid'."
7698
8041
  ),
7699
- redirectNewHandle: z35.boolean().optional().describe(
8042
+ redirectNewHandle: z36.boolean().optional().describe(
7700
8043
  "Whether to automatically redirect the old URL to the new handle. Set to true when changing the handle to preserve SEO and existing links."
7701
8044
  )
7702
8045
  });
7703
- var outputSchema34 = z35.object({
7704
- id: z35.string().describe("Updated blog GID"),
7705
- title: z35.string().describe("Blog title"),
7706
- handle: z35.string().describe("URL handle/slug"),
7707
- commentPolicy: z35.enum(["CLOSED", "MODERATE", "OPEN"]).describe("Comment moderation policy")
8046
+ var outputSchema34 = z36.object({
8047
+ id: z36.string().describe("Updated blog GID"),
8048
+ title: z36.string().describe("Blog title"),
8049
+ handle: z36.string().describe("URL handle/slug"),
8050
+ commentPolicy: z36.enum(["CLOSED", "MODERATE", "OPEN"]).describe("Comment moderation policy")
7708
8051
  });
7709
8052
  var handleUpdateBlog = async (context, params) => {
7710
8053
  log.debug(`Updating blog on shop: ${context.shopDomain}`);
@@ -7754,6 +8097,13 @@ function registerUpdateBlogTool() {
7754
8097
  relatedTools: ["list-blogs", "create-blog", "delete-blog"],
7755
8098
  prerequisites: ["list-blogs"],
7756
8099
  followUps: ["list-blogs"]
8100
+ },
8101
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
8102
+ annotations: {
8103
+ readOnlyHint: false,
8104
+ destructiveHint: false,
8105
+ idempotentHint: true,
8106
+ openWorldHint: true
7757
8107
  }
7758
8108
  },
7759
8109
  handleUpdateBlog
@@ -7761,21 +8111,21 @@ function registerUpdateBlogTool() {
7761
8111
  }
7762
8112
 
7763
8113
  // src/tools/update-collection.ts
7764
- import { z as z36 } from "zod";
7765
- var inputSchema35 = z36.object({
7766
- collectionId: z36.string().min(1).describe(
8114
+ import { z as z37 } from "zod";
8115
+ var inputSchema35 = z37.object({
8116
+ collectionId: collectionIdSchema.describe(
7767
8117
  'Collection ID to update (GID format, e.g., "gid://shopify/Collection/123"). Use list-collections or get-collection to find collection IDs.'
7768
8118
  ),
7769
- title: z36.string().min(1).optional().describe("New collection title"),
7770
- handle: z36.string().optional().describe(
8119
+ title: z37.string().min(1).optional().describe("New collection title"),
8120
+ handle: z37.string().optional().describe(
7771
8121
  "New URL handle/slug. When changing, set redirectNewHandle: true to create automatic redirect from the old URL."
7772
8122
  ),
7773
- descriptionHtml: z36.string().optional().describe("New HTML description"),
7774
- seo: z36.object({
7775
- title: z36.string().optional().describe("New SEO title"),
7776
- description: z36.string().optional().describe("New SEO meta description")
8123
+ descriptionHtml: z37.string().optional().describe("New HTML description"),
8124
+ seo: z37.object({
8125
+ title: z37.string().optional().describe("New SEO title"),
8126
+ description: z37.string().optional().describe("New SEO meta description")
7777
8127
  }).optional().describe("Updated SEO metadata"),
7778
- sortOrder: z36.enum([
8128
+ sortOrder: z37.enum([
7779
8129
  "ALPHA_ASC",
7780
8130
  "ALPHA_DESC",
7781
8131
  "BEST_SELLING",
@@ -7785,19 +8135,19 @@ var inputSchema35 = z36.object({
7785
8135
  "PRICE_ASC",
7786
8136
  "PRICE_DESC"
7787
8137
  ]).optional().describe("New product sort order within collection"),
7788
- image: z36.object({
7789
- src: z36.string().url().describe("New image URL (publicly accessible)"),
7790
- altText: z36.string().optional().describe("New alt text for accessibility")
8138
+ image: z37.object({
8139
+ src: z37.string().url().describe("New image URL (publicly accessible)"),
8140
+ altText: z37.string().optional().describe("New alt text for accessibility")
7791
8141
  }).optional().describe("New collection image"),
7792
- templateSuffix: z36.string().optional().describe("New Liquid template suffix"),
7793
- redirectNewHandle: z36.boolean().optional().describe(
8142
+ templateSuffix: z37.string().optional().describe("New Liquid template suffix"),
8143
+ redirectNewHandle: z37.boolean().optional().describe(
7794
8144
  "When changing the handle, set to true to create an automatic URL redirect from the old handle to the new one. Important for SEO to preserve link equity."
7795
8145
  )
7796
8146
  });
7797
- var outputSchema35 = z36.object({
7798
- id: z36.string().describe("Updated collection GID"),
7799
- title: z36.string().describe("Collection title"),
7800
- handle: z36.string().describe("Collection URL handle")
8147
+ var outputSchema35 = z37.object({
8148
+ id: z37.string().describe("Updated collection GID"),
8149
+ title: z37.string().describe("Collection title"),
8150
+ handle: z37.string().describe("Collection URL handle")
7801
8151
  });
7802
8152
  var handleUpdateCollection = async (context, params) => {
7803
8153
  log.debug(`Updating collection on shop: ${context.shopDomain}`);
@@ -7844,6 +8194,14 @@ function registerUpdateCollectionTool() {
7844
8194
  relatedTools: ["get-collection", "list-collections"],
7845
8195
  prerequisites: ["get-collection"],
7846
8196
  followUps: ["add-products-to-collection", "remove-products-from-collection"]
8197
+ },
8198
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
8199
+ annotations: {
8200
+ readOnlyHint: false,
8201
+ destructiveHint: false,
8202
+ idempotentHint: true,
8203
+ // Same update produces same result
8204
+ openWorldHint: true
7847
8205
  }
7848
8206
  },
7849
8207
  handleUpdateCollection
@@ -7851,8 +8209,8 @@ function registerUpdateCollectionTool() {
7851
8209
  }
7852
8210
 
7853
8211
  // src/tools/update-inventory.ts
7854
- import { z as z37 } from "zod";
7855
- var inventoryReasonEnum = z37.enum([
8212
+ import { z as z38 } from "zod";
8213
+ var inventoryReasonEnum = z38.enum([
7856
8214
  "correction",
7857
8215
  "cycle_count_available",
7858
8216
  "damaged",
@@ -7871,20 +8229,20 @@ var inventoryReasonEnum = z37.enum([
7871
8229
  "safety_stock",
7872
8230
  "shrinkage"
7873
8231
  ]);
7874
- var inputSchema36 = z37.object({
7875
- inventoryItemId: z37.string().min(1).describe(
8232
+ var inputSchema36 = z38.object({
8233
+ inventoryItemId: inventoryItemIdSchema.describe(
7876
8234
  'The Shopify inventory item ID (e.g., "gid://shopify/InventoryItem/456"). Get this from get-inventory tool or from get-product response.'
7877
8235
  ),
7878
- locationId: z37.string().min(1).describe(
8236
+ locationId: locationIdSchema.describe(
7879
8237
  'The location ID where inventory should be updated (e.g., "gid://shopify/Location/789"). Get this from get-inventory tool.'
7880
8238
  ),
7881
- setQuantity: z37.number().int().min(0).optional().describe(
8239
+ setQuantity: z38.number().int().min(0).optional().describe(
7882
8240
  "Set inventory to this exact quantity. Example: 100. Use for absolute stock counts after physical inventory. Cannot be used together with adjustQuantity."
7883
8241
  ),
7884
- adjustQuantity: z37.number().int().optional().describe(
8242
+ adjustQuantity: z38.number().int().optional().describe(
7885
8243
  "Adjust inventory by this amount. Positive to add, negative to subtract. Example: -5 to reduce by 5, +10 to add 10. Use for incremental changes. Cannot be used together with setQuantity."
7886
8244
  ),
7887
- name: z37.enum(["available", "on_hand"]).optional().default("available").describe(
8245
+ name: z38.enum(["available", "on_hand"]).optional().default("available").describe(
7888
8246
  'Which quantity to modify. "available" (default) for sellable stock, "on_hand" for physical count.'
7889
8247
  ),
7890
8248
  reason: inventoryReasonEnum.optional().default("correction").describe(
@@ -7895,16 +8253,16 @@ var inputSchema36 = z37.object({
7895
8253
  }).refine((data) => !(data.setQuantity !== void 0 && data.adjustQuantity !== void 0), {
7896
8254
  message: "Cannot provide both setQuantity and adjustQuantity. Choose one."
7897
8255
  });
7898
- var outputSchema36 = z37.object({
7899
- success: z37.boolean().describe("Whether the operation succeeded"),
7900
- inventoryItemId: z37.string().describe("Inventory item GID"),
7901
- locationId: z37.string().describe("Location GID where inventory was updated"),
7902
- locationName: z37.string().describe("Human-readable location name"),
7903
- previousQuantity: z37.number().describe("Quantity before the change"),
7904
- newQuantity: z37.number().describe("Quantity after the change"),
7905
- delta: z37.number().describe("The actual change applied (positive or negative)"),
7906
- name: z37.string().describe("Which quantity was modified (available or on_hand)"),
7907
- reason: z37.string().describe("Reason for the adjustment")
8256
+ var outputSchema36 = z38.object({
8257
+ success: z38.boolean().describe("Whether the operation succeeded"),
8258
+ inventoryItemId: z38.string().describe("Inventory item GID"),
8259
+ locationId: z38.string().describe("Location GID where inventory was updated"),
8260
+ locationName: z38.string().describe("Human-readable location name"),
8261
+ previousQuantity: z38.number().describe("Quantity before the change"),
8262
+ newQuantity: z38.number().describe("Quantity after the change"),
8263
+ delta: z38.number().describe("The actual change applied (positive or negative)"),
8264
+ name: z38.string().describe("Which quantity was modified (available or on_hand)"),
8265
+ reason: z38.string().describe("Reason for the adjustment")
7908
8266
  });
7909
8267
  var handleUpdateInventory = async (context, params) => {
7910
8268
  log.debug(`Updating inventory on shop: ${context.shopDomain}`);
@@ -7985,6 +8343,13 @@ function registerUpdateInventoryTool() {
7985
8343
  relatedTools: ["get-inventory", "get-bulk-inventory", "list-low-inventory"],
7986
8344
  prerequisites: ["get-inventory"],
7987
8345
  followUps: ["get-inventory"]
8346
+ },
8347
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
8348
+ annotations: {
8349
+ readOnlyHint: false,
8350
+ destructiveHint: false,
8351
+ idempotentHint: true,
8352
+ openWorldHint: true
7988
8353
  }
7989
8354
  },
7990
8355
  handleUpdateInventory
@@ -7992,33 +8357,33 @@ function registerUpdateInventoryTool() {
7992
8357
  }
7993
8358
 
7994
8359
  // src/tools/update-page.ts
7995
- import { z as z38 } from "zod";
7996
- var inputSchema37 = z38.object({
7997
- id: z38.string().min(1).describe(
8360
+ import { z as z39 } from "zod";
8361
+ var inputSchema37 = z39.object({
8362
+ id: pageIdSchema.describe(
7998
8363
  'The page ID to update (required). Must be a valid Shopify GID format. Example: "gid://shopify/Page/123456789". Use list-pages to find page IDs.'
7999
8364
  ),
8000
- title: z38.string().optional().describe("The new title for the page. Only provide if you want to change it."),
8001
- body: z38.string().optional().describe(
8365
+ title: z39.string().optional().describe("The new title for the page. Only provide if you want to change it."),
8366
+ body: z39.string().optional().describe(
8002
8367
  "The new HTML body content for the page. Supports HTML markup. Only provide if you want to change it."
8003
8368
  ),
8004
- handle: z38.string().optional().describe(
8369
+ handle: z39.string().optional().describe(
8005
8370
  "The new URL handle/slug for the page. Only provide if you want to change the URL. Consider setting redirectNewHandle to true when changing handles."
8006
8371
  ),
8007
- isPublished: z38.boolean().optional().describe(
8372
+ isPublished: z39.boolean().optional().describe(
8008
8373
  "Whether the page should be visible on the storefront. Set to true to publish, false to unpublish."
8009
8374
  ),
8010
- templateSuffix: z38.string().optional().describe(
8375
+ templateSuffix: z39.string().optional().describe(
8011
8376
  "The suffix of the Liquid template used to render the page. Set to empty string to use the default template."
8012
8377
  ),
8013
- redirectNewHandle: z38.boolean().optional().describe(
8378
+ redirectNewHandle: z39.boolean().optional().describe(
8014
8379
  "Whether to create a redirect from the old handle to the new handle when changing handles. Recommended to preserve SEO value when changing page URLs."
8015
8380
  )
8016
8381
  });
8017
- var outputSchema37 = z38.object({
8018
- id: z38.string().describe("Updated page GID"),
8019
- title: z38.string().describe("Page title"),
8020
- handle: z38.string().describe("URL handle/slug"),
8021
- isPublished: z38.boolean().describe("Whether page is visible on storefront")
8382
+ var outputSchema37 = z39.object({
8383
+ id: z39.string().describe("Updated page GID"),
8384
+ title: z39.string().describe("Page title"),
8385
+ handle: z39.string().describe("URL handle/slug"),
8386
+ isPublished: z39.boolean().describe("Whether page is visible on storefront")
8022
8387
  });
8023
8388
  var handleUpdatePage = async (context, params) => {
8024
8389
  log.debug(`Updating page on shop: ${context.shopDomain}`);
@@ -8069,6 +8434,13 @@ function registerUpdatePageTool() {
8069
8434
  relatedTools: ["get-page", "list-pages"],
8070
8435
  prerequisites: ["get-page"],
8071
8436
  followUps: ["get-page"]
8437
+ },
8438
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
8439
+ annotations: {
8440
+ readOnlyHint: false,
8441
+ destructiveHint: false,
8442
+ idempotentHint: true,
8443
+ openWorldHint: true
8072
8444
  }
8073
8445
  },
8074
8446
  handleUpdatePage
@@ -8076,7 +8448,7 @@ function registerUpdatePageTool() {
8076
8448
  }
8077
8449
 
8078
8450
  // src/tools/update-product-image.ts
8079
- import { z as z39 } from "zod";
8451
+ import { z as z40 } from "zod";
8080
8452
  var FILE_UPDATE_MUTATION = `
8081
8453
  mutation FileUpdate($files: [FileUpdateInput!]!) {
8082
8454
  fileUpdate(files: $files) {
@@ -8096,18 +8468,18 @@ var FILE_UPDATE_MUTATION = `
8096
8468
  }
8097
8469
  }
8098
8470
  `;
8099
- var inputSchema38 = z39.object({
8100
- imageId: z39.string().min(1).describe(
8471
+ var inputSchema38 = z40.object({
8472
+ imageId: imageIdSchema.describe(
8101
8473
  'Shopify image GID (e.g., "gid://shopify/MediaImage/123"). Required. Use get-product to find image IDs in the media field.'
8102
8474
  ),
8103
- altText: z39.string().describe(
8475
+ altText: z40.string().describe(
8104
8476
  "New alt text for the image. Pass an empty string to clear the alt text. Alt text describes the image for screen readers and helps search engines understand image content."
8105
8477
  )
8106
8478
  });
8107
- var outputSchema38 = z39.object({
8108
- id: z39.string().describe('Image GID (e.g., "gid://shopify/MediaImage/123")'),
8109
- url: z39.string().nullable().describe("Shopify CDN URL for the image"),
8110
- altText: z39.string().nullable().describe("Updated alt text for the image")
8479
+ var outputSchema38 = z40.object({
8480
+ id: z40.string().describe('Image GID (e.g., "gid://shopify/MediaImage/123")'),
8481
+ url: z40.string().nullable().describe("Shopify CDN URL for the image"),
8482
+ altText: z40.string().nullable().describe("Updated alt text for the image")
8111
8483
  });
8112
8484
  var handleUpdateProductImage = async (context, params) => {
8113
8485
  log.debug(`Updating image alt text on shop: ${context.shopDomain}`);
@@ -8174,6 +8546,14 @@ function registerUpdateProductImageTool() {
8174
8546
  relatedTools: ["reorder-product-images", "add-product-image"],
8175
8547
  prerequisites: ["add-product-image"],
8176
8548
  followUps: ["reorder-product-images"]
8549
+ },
8550
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
8551
+ annotations: {
8552
+ readOnlyHint: false,
8553
+ destructiveHint: false,
8554
+ idempotentHint: true,
8555
+ // Same alt text update produces same result
8556
+ openWorldHint: true
8177
8557
  }
8178
8558
  },
8179
8559
  handleUpdateProductImage
@@ -8181,19 +8561,19 @@ function registerUpdateProductImageTool() {
8181
8561
  }
8182
8562
 
8183
8563
  // src/tools/update-product-variant.ts
8184
- import { z as z40 } from "zod";
8185
- var inputSchema39 = z40.object({
8186
- productId: z40.string().min(1).describe(
8564
+ import { z as z41 } from "zod";
8565
+ var inputSchema39 = z41.object({
8566
+ productId: productIdSchema.describe(
8187
8567
  'Shopify product GID (e.g., "gid://shopify/Product/123"). Required. Use get-product to find the product ID containing the variant.'
8188
8568
  ),
8189
- id: z40.string().min(1).describe(
8569
+ id: variantIdSchema.describe(
8190
8570
  'Shopify variant GID (e.g., "gid://shopify/ProductVariant/456"). Required. Use get-product to find variant IDs for a product.'
8191
8571
  ),
8192
- price: z40.string().optional().describe('New variant price as decimal string (e.g., "29.99")'),
8193
- compareAtPrice: z40.string().nullable().optional().describe(
8572
+ price: z41.string().optional().describe('New variant price as decimal string (e.g., "29.99")'),
8573
+ compareAtPrice: z41.string().nullable().optional().describe(
8194
8574
  '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.'
8195
8575
  ),
8196
- barcode: z40.string().nullable().optional().describe("Barcode (UPC, EAN, ISBN, etc.). Set to null to remove.")
8576
+ barcode: z41.string().nullable().optional().describe("Barcode (UPC, EAN, ISBN, etc.). Set to null to remove.")
8197
8577
  }).refine(
8198
8578
  (data) => {
8199
8579
  const { productId: _productId, id: _id, ...updateFields } = data;
@@ -8201,14 +8581,14 @@ var inputSchema39 = z40.object({
8201
8581
  },
8202
8582
  { message: "At least one field to update must be provided (price, compareAtPrice, or barcode)" }
8203
8583
  );
8204
- var outputSchema39 = z40.object({
8205
- id: z40.string().describe('Variant GID (e.g., "gid://shopify/ProductVariant/123")'),
8206
- title: z40.string().describe('Variant title (e.g., "Red / Medium")'),
8207
- price: z40.string().describe("Current price as decimal string"),
8208
- compareAtPrice: z40.string().nullable().describe("Compare at price for sale display"),
8209
- sku: z40.string().nullable().describe("Stock keeping unit"),
8210
- barcode: z40.string().nullable().describe("Barcode (UPC, EAN, etc.)"),
8211
- inventoryQuantity: z40.number().nullable().describe("Available inventory quantity")
8584
+ var outputSchema39 = z41.object({
8585
+ id: z41.string().describe('Variant GID (e.g., "gid://shopify/ProductVariant/123")'),
8586
+ title: z41.string().describe('Variant title (e.g., "Red / Medium")'),
8587
+ price: z41.string().describe("Current price as decimal string"),
8588
+ compareAtPrice: z41.string().nullable().describe("Compare at price for sale display"),
8589
+ sku: z41.string().nullable().describe("Stock keeping unit"),
8590
+ barcode: z41.string().nullable().describe("Barcode (UPC, EAN, etc.)"),
8591
+ inventoryQuantity: z41.number().nullable().describe("Available inventory quantity")
8212
8592
  });
8213
8593
  var handleUpdateProductVariant = async (context, params) => {
8214
8594
  log.debug(`Updating product variant on shop: ${context.shopDomain}`);
@@ -8244,6 +8624,14 @@ function registerUpdateProductVariantTool() {
8244
8624
  relationships: {
8245
8625
  relatedTools: ["get-product", "get-inventory"],
8246
8626
  prerequisites: ["get-product"]
8627
+ },
8628
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
8629
+ annotations: {
8630
+ readOnlyHint: false,
8631
+ destructiveHint: false,
8632
+ idempotentHint: true,
8633
+ // Same update produces same result
8634
+ openWorldHint: true
8247
8635
  }
8248
8636
  },
8249
8637
  handleUpdateProductVariant
@@ -8251,23 +8639,25 @@ function registerUpdateProductVariantTool() {
8251
8639
  }
8252
8640
 
8253
8641
  // src/tools/update-product.ts
8254
- import { z as z41 } from "zod";
8255
- var inputSchema40 = z41.object({
8256
- id: z41.string().min(1).describe('Shopify product ID (GID format, e.g., "gid://shopify/Product/123"). Required.'),
8257
- title: z41.string().min(1).optional().describe("New product title"),
8258
- description: z41.string().optional().describe("New product description (HTML supported)"),
8259
- vendor: z41.string().optional().describe("New product vendor/brand name"),
8260
- productType: z41.string().optional().describe("New product type for categorization"),
8261
- tags: z41.array(z41.string()).optional().describe("New tags (replaces existing tags)"),
8262
- status: z41.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).optional().describe("New product status: ACTIVE (visible), DRAFT (hidden), ARCHIVED (hidden)"),
8263
- seo: z41.object({
8264
- title: z41.string().optional().describe("SEO title for search engine results"),
8265
- description: z41.string().optional().describe("SEO meta description for search engines")
8642
+ import { z as z42 } from "zod";
8643
+ var inputSchema40 = z42.object({
8644
+ id: productIdSchema.describe(
8645
+ 'Shopify product ID (GID format, e.g., "gid://shopify/Product/123"). Required.'
8646
+ ),
8647
+ title: z42.string().min(1).optional().describe("New product title"),
8648
+ description: z42.string().optional().describe("New product description (HTML supported)"),
8649
+ vendor: z42.string().optional().describe("New product vendor/brand name"),
8650
+ productType: z42.string().optional().describe("New product type for categorization"),
8651
+ tags: z42.array(z42.string()).optional().describe("New tags (replaces existing tags)"),
8652
+ status: z42.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).optional().describe("New product status: ACTIVE (visible), DRAFT (hidden), ARCHIVED (hidden)"),
8653
+ seo: z42.object({
8654
+ title: z42.string().optional().describe("SEO title for search engine results"),
8655
+ description: z42.string().optional().describe("SEO meta description for search engines")
8266
8656
  }).optional().describe("SEO metadata for search engine optimization"),
8267
- handle: z41.string().optional().describe(
8657
+ handle: z42.string().optional().describe(
8268
8658
  'New URL handle/slug (e.g., "my-product"). Use with redirectNewHandle for URL changes.'
8269
8659
  ),
8270
- redirectNewHandle: z41.boolean().optional().describe(
8660
+ redirectNewHandle: z42.boolean().optional().describe(
8271
8661
  "If true, creates automatic redirect from old handle to new handle. Use when changing handle."
8272
8662
  )
8273
8663
  }).refine(
@@ -8277,40 +8667,40 @@ var inputSchema40 = z41.object({
8277
8667
  },
8278
8668
  { message: "At least one field to update must be provided" }
8279
8669
  );
8280
- var outputSchema40 = z41.object({
8281
- id: z41.string().describe('Product GID (e.g., "gid://shopify/Product/123")'),
8282
- title: z41.string().describe("Product title"),
8283
- handle: z41.string().describe("URL handle/slug"),
8284
- description: z41.string().nullable().describe("Product description (HTML)"),
8285
- vendor: z41.string().describe("Vendor/brand name"),
8286
- productType: z41.string().describe("Product type"),
8287
- status: z41.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).describe("Product status"),
8288
- tags: z41.array(z41.string()).describe("Product tags"),
8289
- seo: z41.object({
8290
- title: z41.string().nullable().describe("SEO title for search engines"),
8291
- description: z41.string().nullable().describe("SEO description/meta description")
8670
+ var outputSchema40 = z42.object({
8671
+ id: z42.string().describe('Product GID (e.g., "gid://shopify/Product/123")'),
8672
+ title: z42.string().describe("Product title"),
8673
+ handle: z42.string().describe("URL handle/slug"),
8674
+ description: z42.string().nullable().describe("Product description (HTML)"),
8675
+ vendor: z42.string().describe("Vendor/brand name"),
8676
+ productType: z42.string().describe("Product type"),
8677
+ status: z42.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).describe("Product status"),
8678
+ tags: z42.array(z42.string()).describe("Product tags"),
8679
+ seo: z42.object({
8680
+ title: z42.string().nullable().describe("SEO title for search engines"),
8681
+ description: z42.string().nullable().describe("SEO description/meta description")
8292
8682
  }).describe("SEO metadata for search engine optimization"),
8293
- createdAt: z41.string().describe("Creation timestamp (ISO 8601)"),
8294
- updatedAt: z41.string().describe("Last update timestamp (ISO 8601)"),
8295
- variants: z41.array(
8296
- z41.object({
8297
- id: z41.string().describe("Variant GID"),
8298
- title: z41.string().describe("Variant title"),
8299
- price: z41.string().describe("Price"),
8300
- compareAtPrice: z41.string().nullable().describe("Compare at price"),
8301
- sku: z41.string().nullable().describe("SKU"),
8302
- barcode: z41.string().nullable().describe("Barcode"),
8303
- inventoryQuantity: z41.number().nullable().describe("Inventory quantity")
8683
+ createdAt: z42.string().describe("Creation timestamp (ISO 8601)"),
8684
+ updatedAt: z42.string().describe("Last update timestamp (ISO 8601)"),
8685
+ variants: z42.array(
8686
+ z42.object({
8687
+ id: z42.string().describe("Variant GID"),
8688
+ title: z42.string().describe("Variant title"),
8689
+ price: z42.string().describe("Price"),
8690
+ compareAtPrice: z42.string().nullable().describe("Compare at price"),
8691
+ sku: z42.string().nullable().describe("SKU"),
8692
+ barcode: z42.string().nullable().describe("Barcode"),
8693
+ inventoryQuantity: z42.number().nullable().describe("Inventory quantity")
8304
8694
  })
8305
8695
  ).describe("Product variants"),
8306
- images: z41.array(
8307
- z41.object({
8308
- id: z41.string().describe("Image GID"),
8309
- url: z41.string().describe("Image URL"),
8310
- altText: z41.string().nullable().describe("Alt text")
8696
+ images: z42.array(
8697
+ z42.object({
8698
+ id: z42.string().describe("Image GID"),
8699
+ url: z42.string().describe("Image URL"),
8700
+ altText: z42.string().nullable().describe("Alt text")
8311
8701
  })
8312
8702
  ).describe("Product images"),
8313
- totalInventory: z41.number().describe("Total inventory across variants")
8703
+ totalInventory: z42.number().describe("Total inventory across variants")
8314
8704
  });
8315
8705
  var handleUpdateProduct = async (context, params) => {
8316
8706
  log.debug(`Updating product on shop: ${context.shopDomain}`);
@@ -8347,6 +8737,14 @@ function registerUpdateProductTool() {
8347
8737
  relatedTools: ["list-products", "get-product"],
8348
8738
  prerequisites: ["get-product"],
8349
8739
  followUps: ["add-product-image", "set-product-metafields"]
8740
+ },
8741
+ // MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
8742
+ annotations: {
8743
+ readOnlyHint: false,
8744
+ destructiveHint: false,
8745
+ idempotentHint: true,
8746
+ // Same update produces same result
8747
+ openWorldHint: true
8350
8748
  }
8351
8749
  },
8352
8750
  handleUpdateProduct
@@ -8362,7 +8760,9 @@ function setupToolHandlers(server) {
8362
8760
  tools: tools.map((tool) => ({
8363
8761
  name: tool.name,
8364
8762
  description: tool.description,
8365
- inputSchema: tool.inputSchema
8763
+ inputSchema: tool.inputSchema,
8764
+ // Include MCP tool annotations (Epic 9.5 - MCP Best Practices)
8765
+ annotations: tool.annotations
8366
8766
  }))
8367
8767
  };
8368
8768
  });