@productmaker/mcp 7.1.0 → 7.2.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/dist/http.js +73 -11
  2. package/dist/stdio.js +73 -11
  3. package/package.json +1 -1
package/dist/http.js CHANGED
@@ -1475,9 +1475,23 @@ var LandingSectionPlanSchema = z11.array(LandingSectionPlanEntrySchema).superRef
1475
1475
 
1476
1476
  // ../../packages/types/src/landing.ts
1477
1477
  var LandingImageReadable = z12.object({
1478
+ /**
1479
+ * Display position in the AGGREGATED gallery (parent images + every `done`
1480
+ * variant's images, concatenated). Recalculated on every read from
1481
+ * `creativeVariants` order (createdAt asc), so it DRIFTS for a given image
1482
+ * when a sibling variant finishes out of creation order — it is NOT a
1483
+ * stable identity. Use `sourceIndex` for that.
1484
+ */
1478
1485
  index: z12.number().int().min(1).max(20),
1479
1486
  /** Child task that owns this image when variants are rendered alongside the root task. */
1480
1487
  variantTaskId: z12.string().uuid().optional(),
1488
+ /**
1489
+ * The image's own `indexNum` within its OWN task (root or variant) — set
1490
+ * once at render time and never recomputed. Unlike `index`, this is stable
1491
+ * across reads and is the correct key for identity/selection tracking.
1492
+ * Optional only for legacy rows persisted before this field existed.
1493
+ */
1494
+ sourceIndex: z12.number().int().min(1).optional(),
1481
1495
  role: LandingRoleLegacy,
1482
1496
  /** Strategy-specific display identity; absent on legacy tasks. */
1483
1497
  sectionKey: LandingSectionKeySchema.optional(),
@@ -5102,6 +5116,7 @@ var SelectedLandingImage = z28.object({
5102
5116
  /** 0-based index of the selected variant (from img.notes JSON array) */
5103
5117
  variantIndex: z28.number().int().nonnegative()
5104
5118
  });
5119
+ var SHOPIFY_MAX_LANDING_IMAGES = 20;
5105
5120
  var ShopifyPublicationMode = z28.enum([
5106
5121
  "auto",
5107
5122
  "existing",
@@ -5116,9 +5131,9 @@ var ShopifyPublishRequest = z28.object({
5116
5131
  * Landing images to include in the Shopify page with their selected variants.
5117
5132
  * Required (≥1) for CLASSIC image landings — the classic publish branch enforces
5118
5133
  * that itself. CODE landings have no images and send an empty array, so the schema
5119
- * allows empty; max 10 (one per landing section).
5134
+ * allows empty; at most SHOPIFY_MAX_LANDING_IMAGES.
5120
5135
  */
5121
- selectedLandingImages: z28.array(SelectedLandingImage).max(10).default([]),
5136
+ selectedLandingImages: z28.array(SelectedLandingImage).max(SHOPIFY_MAX_LANDING_IMAGES).default([]),
5122
5137
  /** Show variant picker pills in the landing when the product has multiple variants. */
5123
5138
  showVariantPicker: z28.boolean().optional(),
5124
5139
  /**
@@ -5304,7 +5319,13 @@ var ShopifyPublishErrorResponse = z28.discriminatedUnion("code", [
5304
5319
  "SHOPIFY_PUBLISH_FAILED",
5305
5320
  "SHOPIFY_NO_PUBLISHED_THEME",
5306
5321
  "SHOPIFY_THEME_STRUCTURE_UNSUPPORTED",
5307
- "SHOPIFY_PUBLISH_CONTEXT_CHANGED"
5322
+ "SHOPIFY_PUBLISH_CONTEXT_CHANGED",
5323
+ /**
5324
+ * A publish failure we understand (Shopify did not keep the offer's
5325
+ * variants, another app rewrote the product, landing too large).
5326
+ * `message` carries the seller's next step; never a 500.
5327
+ */
5328
+ "SHOPIFY_PUBLISH_VERIFICATION_FAILED"
5308
5329
  ]),
5309
5330
  message: z28.string().optional(),
5310
5331
  authUrl: z28.string().url().optional(),
@@ -5314,13 +5335,31 @@ var ShopifyPublishErrorResponse = z28.discriminatedUnion("code", [
5314
5335
  z28.object({
5315
5336
  code: z28.literal("SHOPIFY_PUBLISH_INPUT_REQUIRED"),
5316
5337
  message: z28.string().optional(),
5317
- recovery: PublishPreparationSchema.options[1]
5338
+ recovery: PublishPreparationSchema.options[1],
5339
+ /**
5340
+ * Present when this answers a confirmed correction: the seller already
5341
+ * submitted the form and these issue codes are WHY it was refused. Every
5342
+ * surface must say so instead of re-opening the same form mute (C1).
5343
+ */
5344
+ correctionRejected: z28.literal(true).optional(),
5345
+ rejectedIssueCodes: z28.array(PublishRecoveryCodeSchema).max(6).optional()
5318
5346
  }),
5319
5347
  z28.object({
5320
5348
  code: z28.literal("SHOPIFY_PUBLISH_CORRECTION_REQUIRES_EDITOR"),
5321
5349
  action: z28.literal("open_editor"),
5322
5350
  message: z28.string().optional()
5323
5351
  }),
5352
+ z28.object({
5353
+ /**
5354
+ * The seller confirmed exactly what we suggested and the offer still
5355
+ * cannot publish: another pass of the recovery form cannot converge, so
5356
+ * the only exit is the landing editor. `message` names the reason.
5357
+ */
5358
+ code: z28.literal("SHOPIFY_PUBLISH_CORRECTION_NOT_CONVERGED"),
5359
+ action: z28.literal("open_editor"),
5360
+ message: z28.string().optional(),
5361
+ commerceCorrectionSaved: z28.literal(true).optional()
5362
+ }),
5324
5363
  z28.object({
5325
5364
  /**
5326
5365
  * The chosen existing product cannot express the confirmed offer's
@@ -7229,7 +7268,13 @@ var ImageQaGateEntry = z36.object({
7229
7268
  "passed",
7230
7269
  "failed",
7231
7270
  "unavailable"
7232
- ]).optional()
7271
+ ]).optional(),
7272
+ // Paid edit QA telemetry (plan t2-j9): whether the edit could be checked at all,
7273
+ // whether a previous image was actually available to compare against, and the
7274
+ // measured pixel-delta between the two images.
7275
+ requestedEditVerifiable: z36.boolean().optional(),
7276
+ previousImageUsed: z36.boolean().optional(),
7277
+ editPixelDelta: z36.number().min(0).max(1).optional()
7233
7278
  }).passthrough();
7234
7279
  var ImageQaAssessmentState = z36.enum([
7235
7280
  "approved",
@@ -16715,7 +16760,7 @@ var ApiClient = class {
16715
16760
  };
16716
16761
 
16717
16762
  // src/index.ts
16718
- var MCP_VERSION = "7.1.0";
16763
+ var MCP_VERSION = "7.2.0";
16719
16764
 
16720
16765
  // src/meta-upload-app.ts
16721
16766
  import { readFileSync } from "fs";
@@ -17869,6 +17914,8 @@ var McpErrorCode = {
17869
17914
  SHOPIFY_PUBLISH_INPUT_REQUIRED: "SHOPIFY_PUBLISH_INPUT_REQUIRED",
17870
17915
  SHOPIFY_PUBLISH_CONTEXT_CHANGED: "SHOPIFY_PUBLISH_CONTEXT_CHANGED",
17871
17916
  SHOPIFY_PUBLISH_CORRECTION_REQUIRES_EDITOR: "SHOPIFY_PUBLISH_CORRECTION_REQUIRES_EDITOR",
17917
+ SHOPIFY_PUBLISH_CORRECTION_NOT_CONVERGED: "SHOPIFY_PUBLISH_CORRECTION_NOT_CONVERGED",
17918
+ SHOPIFY_PUBLISH_VERIFICATION_FAILED: "SHOPIFY_PUBLISH_VERIFICATION_FAILED",
17872
17919
  SHOPIFY_PUBLISH_TARGET_INCOMPATIBLE: "SHOPIFY_PUBLISH_TARGET_INCOMPATIBLE",
17873
17920
  SHOPIFY_THEME_SETUP_REQUIRED: "SHOPIFY_THEME_SETUP_REQUIRED",
17874
17921
  // Meta / Facebook Ads
@@ -17941,6 +17988,8 @@ var CODE_MESSAGES = {
17941
17988
  SHOPIFY_PUBLISH_INPUT_REQUIRED: "Review every recovery issue with the seller. Ask for missing prices and confirm the exact currency, unit price and pack totals before resending commerceCorrection with confirmed:true. Never invent amounts, convert currency or remove an offer.",
17942
17989
  SHOPIFY_PUBLISH_CONTEXT_CHANGED: "The publication context changed. Reopen the task in ProductMaker, review the current store, product and landing revision, and obtain fresh confirmation. Do not reuse this correction.",
17943
17990
  SHOPIFY_PUBLISH_CORRECTION_REQUIRES_EDITOR: "Open the landing editor in ProductMaker to reconcile the visible offer with checkout prices. The correction was not saved; do not retry it automatically or regenerate the landing without approval.",
17991
+ SHOPIFY_PUBLISH_CORRECTION_NOT_CONVERGED: "The seller confirmed exactly the suggested values and the offer still cannot publish \u2014 resending the same correction will loop. Read the error's reason to the user and send them to the landing editor in ProductMaker to adjust the offer, then publish again. Do not resend commerceCorrection.",
17992
+ SHOPIFY_PUBLISH_VERIFICATION_FAILED: "Shopify did not keep what the publish wrote (offer variants, template or inventory), or the landing is too large. Read the error's reason to the user; retry the publish once, and if it repeats follow the step in the reason (map packs to existing variants, check apps that edit products, or trim the landing in the editor).",
17944
17993
  SHOPIFY_PUBLISH_TARGET_INCOMPATIBLE: "The selected Shopify product cannot carry the confirmed offer (missing quantity options/prices) or no longer exists. Read the error's message to the user, then retry with publicationMode:'new' to create a fresh product, or ask the user to pick a different existing product.",
17945
17994
  SHOPIFY_THEME_SETUP_REQUIRED: "The store's Shopify theme is missing the ProductMaker 'Code Landing' app block \u2014 publishing now would create a product whose page renders blank. Send the user the themeEditorDeepLink (in error data) to open the Shopify theme editor, have them confirm the Code Landing block is added and press Save, then retry publishing. This is a one-time step per store; nothing was published.",
17946
17995
  SHOPIFY_NO_PUBLISHED_THEME: "The Shopify store has no published theme, so there is nowhere to place the landing. Ask the user to publish a theme (Online Store > Themes > Publish) and retry.",
@@ -18136,6 +18185,8 @@ var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
18136
18185
  McpErrorCode.SHOPIFY_PUBLISH_INPUT_REQUIRED,
18137
18186
  McpErrorCode.SHOPIFY_PUBLISH_CONTEXT_CHANGED,
18138
18187
  McpErrorCode.SHOPIFY_PUBLISH_CORRECTION_REQUIRES_EDITOR,
18188
+ McpErrorCode.SHOPIFY_PUBLISH_CORRECTION_NOT_CONVERGED,
18189
+ McpErrorCode.SHOPIFY_PUBLISH_VERIFICATION_FAILED,
18139
18190
  McpErrorCode.SHOPIFY_PUBLISH_TARGET_INCOMPATIBLE,
18140
18191
  McpErrorCode.SHOPIFY_THEME_SETUP_REQUIRED,
18141
18192
  McpErrorCode.TIKTOK_NOT_CONNECTED,
@@ -18163,6 +18214,8 @@ var PASSTHROUGH_CODES = /* @__PURE__ */ new Set([
18163
18214
  McpErrorCode.SHOPIFY_PUBLISH_INPUT_REQUIRED,
18164
18215
  McpErrorCode.SHOPIFY_PUBLISH_CONTEXT_CHANGED,
18165
18216
  McpErrorCode.SHOPIFY_PUBLISH_CORRECTION_REQUIRES_EDITOR,
18217
+ McpErrorCode.SHOPIFY_PUBLISH_CORRECTION_NOT_CONVERGED,
18218
+ McpErrorCode.SHOPIFY_PUBLISH_VERIFICATION_FAILED,
18166
18219
  McpErrorCode.SHOPIFY_PUBLISH_TARGET_INCOMPATIBLE,
18167
18220
  McpErrorCode.SHOPIFY_NOT_CONNECTED,
18168
18221
  McpErrorCode.SHOPIFY_TOKEN_EXPIRED,
@@ -18286,13 +18339,20 @@ function translateError(err) {
18286
18339
  }
18287
18340
  if (err instanceof ApiHttpError) {
18288
18341
  const body = isRecord2(err.body) ? err.body : {};
18289
- if (body.code === McpErrorCode.SHOPIFY_PUBLISH_INPUT_REQUIRED || body.code === McpErrorCode.SHOPIFY_PUBLISH_CONTEXT_CHANGED || body.code === McpErrorCode.SHOPIFY_PUBLISH_CORRECTION_REQUIRES_EDITOR || body.code === McpErrorCode.SHOPIFY_PUBLISH_TARGET_INCOMPATIBLE || body.code === McpErrorCode.SHOPIFY_THEME_SETUP_REQUIRED || body.code === McpErrorCode.SHOPIFY_PUBLISH_FAILED) {
18342
+ if (body.code === McpErrorCode.SHOPIFY_PUBLISH_INPUT_REQUIRED || body.code === McpErrorCode.SHOPIFY_PUBLISH_CONTEXT_CHANGED || body.code === McpErrorCode.SHOPIFY_PUBLISH_CORRECTION_REQUIRES_EDITOR || body.code === McpErrorCode.SHOPIFY_PUBLISH_CORRECTION_NOT_CONVERGED || body.code === McpErrorCode.SHOPIFY_PUBLISH_VERIFICATION_FAILED || body.code === McpErrorCode.SHOPIFY_PUBLISH_TARGET_INCOMPATIBLE || body.code === McpErrorCode.SHOPIFY_THEME_SETUP_REQUIRED || body.code === McpErrorCode.SHOPIFY_PUBLISH_FAILED) {
18290
18343
  const parsed = ShopifyPublishErrorResponse.safeParse(body);
18291
18344
  if (!parsed.success) return mcp(McpErrorCode.INTERNAL, resolveMessage(McpErrorCode.INTERNAL, void 0));
18292
18345
  const details = parsed.data;
18293
18346
  const extra = {};
18294
- if (details.code === "SHOPIFY_PUBLISH_INPUT_REQUIRED") extra.recovery = details.recovery;
18295
- if (details.code === "SHOPIFY_PUBLISH_CORRECTION_REQUIRES_EDITOR") extra.action = details.action;
18347
+ if (details.code === "SHOPIFY_PUBLISH_INPUT_REQUIRED") {
18348
+ extra.recovery = details.recovery;
18349
+ if (details.correctionRejected) {
18350
+ extra.correctionRejected = true;
18351
+ if (details.rejectedIssueCodes) extra.rejectedIssueCodes = details.rejectedIssueCodes;
18352
+ }
18353
+ }
18354
+ if (details.code === "SHOPIFY_PUBLISH_CORRECTION_REQUIRES_EDITOR" || details.code === "SHOPIFY_PUBLISH_CORRECTION_NOT_CONVERGED") extra.action = details.action;
18355
+ if ((details.code === "SHOPIFY_PUBLISH_CORRECTION_NOT_CONVERGED" || details.code === "SHOPIFY_PUBLISH_VERIFICATION_FAILED" || details.code === "SHOPIFY_PUBLISH_INPUT_REQUIRED" && details.correctionRejected) && details.message) extra.reason = details.message.slice(0, 500);
18296
18356
  if (details.code === "SHOPIFY_THEME_SETUP_REQUIRED") extra.themeEditorDeepLink = details.themeEditorDeepLink;
18297
18357
  const saved = "commerceCorrectionSaved" in details && details.commerceCorrectionSaved === true;
18298
18358
  if (saved) extra.commerceCorrectionSaved = true;
@@ -21597,7 +21657,7 @@ var TikTokInput = z78.object({
21597
21657
  });
21598
21658
  function registerPublishTools(server, api, opts = {}) {
21599
21659
  const transport = opts.transport ?? "http";
21600
- server.tool("publish_to_shopify", publishToShopifyGuidance.summary + " Precondici\xF3n: la tarea debe estar en status='complete' \u2014 usa `get_task_status` antes. Por defecto `publicationMode=auto`: si esta tarea ya fue publicada en esa tienda, actualiza ESE MISMO producto y conserva URL, precio, variantes, inventario y SKU; si no, crea uno. Usa `existing` + `targetProductId` cuando el usuario elija otro producto y `new` s\xF3lo si pide crear uno nuevo. ANTES de llamar preg\xFAntale \"\xBFTu producto tiene variantes (talla, color, etc.)?\" y pasa `showVariantPicker: true` si responde que s\xED. Por defecto publica TODAS las im\xE1genes generadas; pasa `numLandingImages` s\xF3lo si el usuario pide menos. En code landings con una oferta expl\xEDcita de packs, ProductMaker s\xF3lo publica el selector si variantes reales de Shopify cobran los totales exactos; la respuesta explica si las cre\xF3, verific\xF3 o descart\xF3 por no existir ahorro. Si devuelve SHOPIFY_PUBLISH_INPUT_REQUIRED, muestra todos los problemas de recovery y solicita los importes faltantes. Solo env\xEDa commerceCorrection con su revisi\xF3n/huella y confirmed:true despu\xE9s de confirmar los valores exactos; nunca inventes importes, conviertas moneda ni elimines ofertas. Un contexto obsoleto requiere volver a revisar; open_editor requiere el editor de ProductMaker. Si devuelve SHOPIFY_THEME_SETUP_REQUIRED, el tema de la tienda no tiene el bloque Code Landing y nada se public\xF3: env\xEDa al usuario el themeEditorDeepLink del error para abrir el editor del tema de Shopify, p\xEDdele que agregue el bloque y pulse Guardar (una sola vez por tienda), y reintenta s\xF3lo despu\xE9s. Ante resultado incierto, verifica la tarea/tienda antes de volver a publicar, especialmente con new; commerceCorrectionSaved no prueba publicaci\xF3n. La respuesta es markdown con el link a la landing y al producto.", ShopifyInput.shape, OVERWRITES_EXTERNAL, async (input, extra) => {
21660
+ server.tool("publish_to_shopify", publishToShopifyGuidance.summary + " Precondici\xF3n: la tarea debe estar en status='complete' \u2014 usa `get_task_status` antes. Por defecto `publicationMode=auto`: si esta tarea ya fue publicada en esa tienda, actualiza ESE MISMO producto y conserva URL, precio, variantes, inventario y SKU; si no, crea uno. Usa `existing` + `targetProductId` cuando el usuario elija otro producto y `new` s\xF3lo si pide crear uno nuevo. ANTES de llamar preg\xFAntale \"\xBFTu producto tiene variantes (talla, color, etc.)?\" y pasa `showVariantPicker: true` si responde que s\xED. Por defecto publica TODAS las im\xE1genes generadas; pasa `numLandingImages` s\xF3lo si el usuario pide menos. En code landings con una oferta expl\xEDcita de packs, ProductMaker s\xF3lo publica el selector si variantes reales de Shopify cobran los totales exactos; la respuesta explica si las cre\xF3, verific\xF3 o descart\xF3 por no existir ahorro. Si devuelve SHOPIFY_PUBLISH_INPUT_REQUIRED, muestra todos los problemas de recovery y solicita los importes faltantes. Solo env\xEDa commerceCorrection con su revisi\xF3n/huella y confirmed:true despu\xE9s de confirmar los valores exactos; nunca inventes importes, conviertas moneda ni elimines ofertas. Si adem\xE1s trae correctionRejected, lo que el vendedor confirm\xF3 fue rechazado: l\xE9ele `reason` ANTES de volver a pedir datos. Un contexto obsoleto requiere volver a revisar; open_editor (incluido SHOPIFY_PUBLISH_CORRECTION_NOT_CONVERGED) requiere el editor de ProductMaker y NO se reenv\xEDa la misma correcci\xF3n. SHOPIFY_PUBLISH_VERIFICATION_FAILED trae en `reason` el siguiente paso: reintenta una vez y si se repite sigue ese paso. Si devuelve SHOPIFY_THEME_SETUP_REQUIRED, el tema de la tienda no tiene el bloque Code Landing y nada se public\xF3: env\xEDa al usuario el themeEditorDeepLink del error para abrir el editor del tema de Shopify, p\xEDdele que agregue el bloque y pulse Guardar (una sola vez por tienda), y reintenta s\xF3lo despu\xE9s. Ante resultado incierto, verifica la tarea/tienda antes de volver a publicar, especialmente con new; commerceCorrectionSaved no prueba publicaci\xF3n. La respuesta es markdown con el link a la landing y al producto.", ShopifyInput.shape, OVERWRITES_EXTERNAL, async (input, extra) => {
21601
21661
  try {
21602
21662
  const parsed = parseShopifyInput(input);
21603
21663
  if (parsed.publicationMode === "existing" && !parsed.targetProductId) {
@@ -21614,7 +21674,7 @@ function registerPublishTools(server, api, opts = {}) {
21614
21674
  const variantIndex = parsed.variantIndex ?? 0;
21615
21675
  const availableImages = await fetchLandingImageCount(api, parsed.taskId);
21616
21676
  const effectiveAvailable = availableImages > 0 ? availableImages : 3;
21617
- const numImages = Math.min(parsed.numLandingImages ?? effectiveAvailable, effectiveAvailable);
21677
+ const numImages = Math.min(parsed.numLandingImages ?? effectiveAvailable, effectiveAvailable, SHOPIFY_MAX_LANDING_IMAGES);
21618
21678
  const selectedLandingImages = Array.from({
21619
21679
  length: numImages
21620
21680
  }, (_, i) => ({
@@ -21645,6 +21705,8 @@ function registerPublishTools(server, api, opts = {}) {
21645
21705
  "SHOPIFY_PUBLISH_INPUT_REQUIRED",
21646
21706
  "SHOPIFY_PUBLISH_CONTEXT_CHANGED",
21647
21707
  "SHOPIFY_PUBLISH_CORRECTION_REQUIRES_EDITOR",
21708
+ "SHOPIFY_PUBLISH_CORRECTION_NOT_CONVERGED",
21709
+ "SHOPIFY_PUBLISH_VERIFICATION_FAILED",
21648
21710
  "SHOPIFY_PUBLISH_TARGET_INCOMPATIBLE",
21649
21711
  "SHOPIFY_THEME_SETUP_REQUIRED",
21650
21712
  "SHOPIFY_PUBLISH_FAILED"
package/dist/stdio.js CHANGED
@@ -1497,9 +1497,23 @@ var LandingSectionPlanSchema = z11.array(LandingSectionPlanEntrySchema).superRef
1497
1497
 
1498
1498
  // ../../packages/types/src/landing.ts
1499
1499
  var LandingImageReadable = z12.object({
1500
+ /**
1501
+ * Display position in the AGGREGATED gallery (parent images + every `done`
1502
+ * variant's images, concatenated). Recalculated on every read from
1503
+ * `creativeVariants` order (createdAt asc), so it DRIFTS for a given image
1504
+ * when a sibling variant finishes out of creation order — it is NOT a
1505
+ * stable identity. Use `sourceIndex` for that.
1506
+ */
1500
1507
  index: z12.number().int().min(1).max(20),
1501
1508
  /** Child task that owns this image when variants are rendered alongside the root task. */
1502
1509
  variantTaskId: z12.string().uuid().optional(),
1510
+ /**
1511
+ * The image's own `indexNum` within its OWN task (root or variant) — set
1512
+ * once at render time and never recomputed. Unlike `index`, this is stable
1513
+ * across reads and is the correct key for identity/selection tracking.
1514
+ * Optional only for legacy rows persisted before this field existed.
1515
+ */
1516
+ sourceIndex: z12.number().int().min(1).optional(),
1503
1517
  role: LandingRoleLegacy,
1504
1518
  /** Strategy-specific display identity; absent on legacy tasks. */
1505
1519
  sectionKey: LandingSectionKeySchema.optional(),
@@ -5124,6 +5138,7 @@ var SelectedLandingImage = z28.object({
5124
5138
  /** 0-based index of the selected variant (from img.notes JSON array) */
5125
5139
  variantIndex: z28.number().int().nonnegative()
5126
5140
  });
5141
+ var SHOPIFY_MAX_LANDING_IMAGES = 20;
5127
5142
  var ShopifyPublicationMode = z28.enum([
5128
5143
  "auto",
5129
5144
  "existing",
@@ -5138,9 +5153,9 @@ var ShopifyPublishRequest = z28.object({
5138
5153
  * Landing images to include in the Shopify page with their selected variants.
5139
5154
  * Required (≥1) for CLASSIC image landings — the classic publish branch enforces
5140
5155
  * that itself. CODE landings have no images and send an empty array, so the schema
5141
- * allows empty; max 10 (one per landing section).
5156
+ * allows empty; at most SHOPIFY_MAX_LANDING_IMAGES.
5142
5157
  */
5143
- selectedLandingImages: z28.array(SelectedLandingImage).max(10).default([]),
5158
+ selectedLandingImages: z28.array(SelectedLandingImage).max(SHOPIFY_MAX_LANDING_IMAGES).default([]),
5144
5159
  /** Show variant picker pills in the landing when the product has multiple variants. */
5145
5160
  showVariantPicker: z28.boolean().optional(),
5146
5161
  /**
@@ -5326,7 +5341,13 @@ var ShopifyPublishErrorResponse = z28.discriminatedUnion("code", [
5326
5341
  "SHOPIFY_PUBLISH_FAILED",
5327
5342
  "SHOPIFY_NO_PUBLISHED_THEME",
5328
5343
  "SHOPIFY_THEME_STRUCTURE_UNSUPPORTED",
5329
- "SHOPIFY_PUBLISH_CONTEXT_CHANGED"
5344
+ "SHOPIFY_PUBLISH_CONTEXT_CHANGED",
5345
+ /**
5346
+ * A publish failure we understand (Shopify did not keep the offer's
5347
+ * variants, another app rewrote the product, landing too large).
5348
+ * `message` carries the seller's next step; never a 500.
5349
+ */
5350
+ "SHOPIFY_PUBLISH_VERIFICATION_FAILED"
5330
5351
  ]),
5331
5352
  message: z28.string().optional(),
5332
5353
  authUrl: z28.string().url().optional(),
@@ -5336,13 +5357,31 @@ var ShopifyPublishErrorResponse = z28.discriminatedUnion("code", [
5336
5357
  z28.object({
5337
5358
  code: z28.literal("SHOPIFY_PUBLISH_INPUT_REQUIRED"),
5338
5359
  message: z28.string().optional(),
5339
- recovery: PublishPreparationSchema.options[1]
5360
+ recovery: PublishPreparationSchema.options[1],
5361
+ /**
5362
+ * Present when this answers a confirmed correction: the seller already
5363
+ * submitted the form and these issue codes are WHY it was refused. Every
5364
+ * surface must say so instead of re-opening the same form mute (C1).
5365
+ */
5366
+ correctionRejected: z28.literal(true).optional(),
5367
+ rejectedIssueCodes: z28.array(PublishRecoveryCodeSchema).max(6).optional()
5340
5368
  }),
5341
5369
  z28.object({
5342
5370
  code: z28.literal("SHOPIFY_PUBLISH_CORRECTION_REQUIRES_EDITOR"),
5343
5371
  action: z28.literal("open_editor"),
5344
5372
  message: z28.string().optional()
5345
5373
  }),
5374
+ z28.object({
5375
+ /**
5376
+ * The seller confirmed exactly what we suggested and the offer still
5377
+ * cannot publish: another pass of the recovery form cannot converge, so
5378
+ * the only exit is the landing editor. `message` names the reason.
5379
+ */
5380
+ code: z28.literal("SHOPIFY_PUBLISH_CORRECTION_NOT_CONVERGED"),
5381
+ action: z28.literal("open_editor"),
5382
+ message: z28.string().optional(),
5383
+ commerceCorrectionSaved: z28.literal(true).optional()
5384
+ }),
5346
5385
  z28.object({
5347
5386
  /**
5348
5387
  * The chosen existing product cannot express the confirmed offer's
@@ -7251,7 +7290,13 @@ var ImageQaGateEntry = z36.object({
7251
7290
  "passed",
7252
7291
  "failed",
7253
7292
  "unavailable"
7254
- ]).optional()
7293
+ ]).optional(),
7294
+ // Paid edit QA telemetry (plan t2-j9): whether the edit could be checked at all,
7295
+ // whether a previous image was actually available to compare against, and the
7296
+ // measured pixel-delta between the two images.
7297
+ requestedEditVerifiable: z36.boolean().optional(),
7298
+ previousImageUsed: z36.boolean().optional(),
7299
+ editPixelDelta: z36.number().min(0).max(1).optional()
7255
7300
  }).passthrough();
7256
7301
  var ImageQaAssessmentState = z36.enum([
7257
7302
  "approved",
@@ -15094,7 +15139,7 @@ var ApiClient = class {
15094
15139
  };
15095
15140
 
15096
15141
  // src/index.ts
15097
- var MCP_VERSION = "7.1.0";
15142
+ var MCP_VERSION = "7.2.0";
15098
15143
 
15099
15144
  // src/meta-upload-app.ts
15100
15145
  import { readFileSync } from "fs";
@@ -16248,6 +16293,8 @@ var McpErrorCode = {
16248
16293
  SHOPIFY_PUBLISH_INPUT_REQUIRED: "SHOPIFY_PUBLISH_INPUT_REQUIRED",
16249
16294
  SHOPIFY_PUBLISH_CONTEXT_CHANGED: "SHOPIFY_PUBLISH_CONTEXT_CHANGED",
16250
16295
  SHOPIFY_PUBLISH_CORRECTION_REQUIRES_EDITOR: "SHOPIFY_PUBLISH_CORRECTION_REQUIRES_EDITOR",
16296
+ SHOPIFY_PUBLISH_CORRECTION_NOT_CONVERGED: "SHOPIFY_PUBLISH_CORRECTION_NOT_CONVERGED",
16297
+ SHOPIFY_PUBLISH_VERIFICATION_FAILED: "SHOPIFY_PUBLISH_VERIFICATION_FAILED",
16251
16298
  SHOPIFY_PUBLISH_TARGET_INCOMPATIBLE: "SHOPIFY_PUBLISH_TARGET_INCOMPATIBLE",
16252
16299
  SHOPIFY_THEME_SETUP_REQUIRED: "SHOPIFY_THEME_SETUP_REQUIRED",
16253
16300
  // Meta / Facebook Ads
@@ -16320,6 +16367,8 @@ var CODE_MESSAGES = {
16320
16367
  SHOPIFY_PUBLISH_INPUT_REQUIRED: "Review every recovery issue with the seller. Ask for missing prices and confirm the exact currency, unit price and pack totals before resending commerceCorrection with confirmed:true. Never invent amounts, convert currency or remove an offer.",
16321
16368
  SHOPIFY_PUBLISH_CONTEXT_CHANGED: "The publication context changed. Reopen the task in ProductMaker, review the current store, product and landing revision, and obtain fresh confirmation. Do not reuse this correction.",
16322
16369
  SHOPIFY_PUBLISH_CORRECTION_REQUIRES_EDITOR: "Open the landing editor in ProductMaker to reconcile the visible offer with checkout prices. The correction was not saved; do not retry it automatically or regenerate the landing without approval.",
16370
+ SHOPIFY_PUBLISH_CORRECTION_NOT_CONVERGED: "The seller confirmed exactly the suggested values and the offer still cannot publish \u2014 resending the same correction will loop. Read the error's reason to the user and send them to the landing editor in ProductMaker to adjust the offer, then publish again. Do not resend commerceCorrection.",
16371
+ SHOPIFY_PUBLISH_VERIFICATION_FAILED: "Shopify did not keep what the publish wrote (offer variants, template or inventory), or the landing is too large. Read the error's reason to the user; retry the publish once, and if it repeats follow the step in the reason (map packs to existing variants, check apps that edit products, or trim the landing in the editor).",
16323
16372
  SHOPIFY_PUBLISH_TARGET_INCOMPATIBLE: "The selected Shopify product cannot carry the confirmed offer (missing quantity options/prices) or no longer exists. Read the error's message to the user, then retry with publicationMode:'new' to create a fresh product, or ask the user to pick a different existing product.",
16324
16373
  SHOPIFY_THEME_SETUP_REQUIRED: "The store's Shopify theme is missing the ProductMaker 'Code Landing' app block \u2014 publishing now would create a product whose page renders blank. Send the user the themeEditorDeepLink (in error data) to open the Shopify theme editor, have them confirm the Code Landing block is added and press Save, then retry publishing. This is a one-time step per store; nothing was published.",
16325
16374
  SHOPIFY_NO_PUBLISHED_THEME: "The Shopify store has no published theme, so there is nowhere to place the landing. Ask the user to publish a theme (Online Store > Themes > Publish) and retry.",
@@ -16515,6 +16564,8 @@ var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
16515
16564
  McpErrorCode.SHOPIFY_PUBLISH_INPUT_REQUIRED,
16516
16565
  McpErrorCode.SHOPIFY_PUBLISH_CONTEXT_CHANGED,
16517
16566
  McpErrorCode.SHOPIFY_PUBLISH_CORRECTION_REQUIRES_EDITOR,
16567
+ McpErrorCode.SHOPIFY_PUBLISH_CORRECTION_NOT_CONVERGED,
16568
+ McpErrorCode.SHOPIFY_PUBLISH_VERIFICATION_FAILED,
16518
16569
  McpErrorCode.SHOPIFY_PUBLISH_TARGET_INCOMPATIBLE,
16519
16570
  McpErrorCode.SHOPIFY_THEME_SETUP_REQUIRED,
16520
16571
  McpErrorCode.TIKTOK_NOT_CONNECTED,
@@ -16542,6 +16593,8 @@ var PASSTHROUGH_CODES = /* @__PURE__ */ new Set([
16542
16593
  McpErrorCode.SHOPIFY_PUBLISH_INPUT_REQUIRED,
16543
16594
  McpErrorCode.SHOPIFY_PUBLISH_CONTEXT_CHANGED,
16544
16595
  McpErrorCode.SHOPIFY_PUBLISH_CORRECTION_REQUIRES_EDITOR,
16596
+ McpErrorCode.SHOPIFY_PUBLISH_CORRECTION_NOT_CONVERGED,
16597
+ McpErrorCode.SHOPIFY_PUBLISH_VERIFICATION_FAILED,
16545
16598
  McpErrorCode.SHOPIFY_PUBLISH_TARGET_INCOMPATIBLE,
16546
16599
  McpErrorCode.SHOPIFY_NOT_CONNECTED,
16547
16600
  McpErrorCode.SHOPIFY_TOKEN_EXPIRED,
@@ -16665,13 +16718,20 @@ function translateError(err) {
16665
16718
  }
16666
16719
  if (err instanceof ApiHttpError) {
16667
16720
  const body = isRecord2(err.body) ? err.body : {};
16668
- if (body.code === McpErrorCode.SHOPIFY_PUBLISH_INPUT_REQUIRED || body.code === McpErrorCode.SHOPIFY_PUBLISH_CONTEXT_CHANGED || body.code === McpErrorCode.SHOPIFY_PUBLISH_CORRECTION_REQUIRES_EDITOR || body.code === McpErrorCode.SHOPIFY_PUBLISH_TARGET_INCOMPATIBLE || body.code === McpErrorCode.SHOPIFY_THEME_SETUP_REQUIRED || body.code === McpErrorCode.SHOPIFY_PUBLISH_FAILED) {
16721
+ if (body.code === McpErrorCode.SHOPIFY_PUBLISH_INPUT_REQUIRED || body.code === McpErrorCode.SHOPIFY_PUBLISH_CONTEXT_CHANGED || body.code === McpErrorCode.SHOPIFY_PUBLISH_CORRECTION_REQUIRES_EDITOR || body.code === McpErrorCode.SHOPIFY_PUBLISH_CORRECTION_NOT_CONVERGED || body.code === McpErrorCode.SHOPIFY_PUBLISH_VERIFICATION_FAILED || body.code === McpErrorCode.SHOPIFY_PUBLISH_TARGET_INCOMPATIBLE || body.code === McpErrorCode.SHOPIFY_THEME_SETUP_REQUIRED || body.code === McpErrorCode.SHOPIFY_PUBLISH_FAILED) {
16669
16722
  const parsed = ShopifyPublishErrorResponse.safeParse(body);
16670
16723
  if (!parsed.success) return mcp(McpErrorCode.INTERNAL, resolveMessage(McpErrorCode.INTERNAL, void 0));
16671
16724
  const details = parsed.data;
16672
16725
  const extra = {};
16673
- if (details.code === "SHOPIFY_PUBLISH_INPUT_REQUIRED") extra.recovery = details.recovery;
16674
- if (details.code === "SHOPIFY_PUBLISH_CORRECTION_REQUIRES_EDITOR") extra.action = details.action;
16726
+ if (details.code === "SHOPIFY_PUBLISH_INPUT_REQUIRED") {
16727
+ extra.recovery = details.recovery;
16728
+ if (details.correctionRejected) {
16729
+ extra.correctionRejected = true;
16730
+ if (details.rejectedIssueCodes) extra.rejectedIssueCodes = details.rejectedIssueCodes;
16731
+ }
16732
+ }
16733
+ if (details.code === "SHOPIFY_PUBLISH_CORRECTION_REQUIRES_EDITOR" || details.code === "SHOPIFY_PUBLISH_CORRECTION_NOT_CONVERGED") extra.action = details.action;
16734
+ if ((details.code === "SHOPIFY_PUBLISH_CORRECTION_NOT_CONVERGED" || details.code === "SHOPIFY_PUBLISH_VERIFICATION_FAILED" || details.code === "SHOPIFY_PUBLISH_INPUT_REQUIRED" && details.correctionRejected) && details.message) extra.reason = details.message.slice(0, 500);
16675
16735
  if (details.code === "SHOPIFY_THEME_SETUP_REQUIRED") extra.themeEditorDeepLink = details.themeEditorDeepLink;
16676
16736
  const saved = "commerceCorrectionSaved" in details && details.commerceCorrectionSaved === true;
16677
16737
  if (saved) extra.commerceCorrectionSaved = true;
@@ -20029,7 +20089,7 @@ var TikTokInput = z77.object({
20029
20089
  });
20030
20090
  function registerPublishTools(server, api, opts = {}) {
20031
20091
  const transport = opts.transport ?? "http";
20032
- server.tool("publish_to_shopify", publishToShopifyGuidance.summary + " Precondici\xF3n: la tarea debe estar en status='complete' \u2014 usa `get_task_status` antes. Por defecto `publicationMode=auto`: si esta tarea ya fue publicada en esa tienda, actualiza ESE MISMO producto y conserva URL, precio, variantes, inventario y SKU; si no, crea uno. Usa `existing` + `targetProductId` cuando el usuario elija otro producto y `new` s\xF3lo si pide crear uno nuevo. ANTES de llamar preg\xFAntale \"\xBFTu producto tiene variantes (talla, color, etc.)?\" y pasa `showVariantPicker: true` si responde que s\xED. Por defecto publica TODAS las im\xE1genes generadas; pasa `numLandingImages` s\xF3lo si el usuario pide menos. En code landings con una oferta expl\xEDcita de packs, ProductMaker s\xF3lo publica el selector si variantes reales de Shopify cobran los totales exactos; la respuesta explica si las cre\xF3, verific\xF3 o descart\xF3 por no existir ahorro. Si devuelve SHOPIFY_PUBLISH_INPUT_REQUIRED, muestra todos los problemas de recovery y solicita los importes faltantes. Solo env\xEDa commerceCorrection con su revisi\xF3n/huella y confirmed:true despu\xE9s de confirmar los valores exactos; nunca inventes importes, conviertas moneda ni elimines ofertas. Un contexto obsoleto requiere volver a revisar; open_editor requiere el editor de ProductMaker. Si devuelve SHOPIFY_THEME_SETUP_REQUIRED, el tema de la tienda no tiene el bloque Code Landing y nada se public\xF3: env\xEDa al usuario el themeEditorDeepLink del error para abrir el editor del tema de Shopify, p\xEDdele que agregue el bloque y pulse Guardar (una sola vez por tienda), y reintenta s\xF3lo despu\xE9s. Ante resultado incierto, verifica la tarea/tienda antes de volver a publicar, especialmente con new; commerceCorrectionSaved no prueba publicaci\xF3n. La respuesta es markdown con el link a la landing y al producto.", ShopifyInput.shape, OVERWRITES_EXTERNAL, async (input, extra) => {
20092
+ server.tool("publish_to_shopify", publishToShopifyGuidance.summary + " Precondici\xF3n: la tarea debe estar en status='complete' \u2014 usa `get_task_status` antes. Por defecto `publicationMode=auto`: si esta tarea ya fue publicada en esa tienda, actualiza ESE MISMO producto y conserva URL, precio, variantes, inventario y SKU; si no, crea uno. Usa `existing` + `targetProductId` cuando el usuario elija otro producto y `new` s\xF3lo si pide crear uno nuevo. ANTES de llamar preg\xFAntale \"\xBFTu producto tiene variantes (talla, color, etc.)?\" y pasa `showVariantPicker: true` si responde que s\xED. Por defecto publica TODAS las im\xE1genes generadas; pasa `numLandingImages` s\xF3lo si el usuario pide menos. En code landings con una oferta expl\xEDcita de packs, ProductMaker s\xF3lo publica el selector si variantes reales de Shopify cobran los totales exactos; la respuesta explica si las cre\xF3, verific\xF3 o descart\xF3 por no existir ahorro. Si devuelve SHOPIFY_PUBLISH_INPUT_REQUIRED, muestra todos los problemas de recovery y solicita los importes faltantes. Solo env\xEDa commerceCorrection con su revisi\xF3n/huella y confirmed:true despu\xE9s de confirmar los valores exactos; nunca inventes importes, conviertas moneda ni elimines ofertas. Si adem\xE1s trae correctionRejected, lo que el vendedor confirm\xF3 fue rechazado: l\xE9ele `reason` ANTES de volver a pedir datos. Un contexto obsoleto requiere volver a revisar; open_editor (incluido SHOPIFY_PUBLISH_CORRECTION_NOT_CONVERGED) requiere el editor de ProductMaker y NO se reenv\xEDa la misma correcci\xF3n. SHOPIFY_PUBLISH_VERIFICATION_FAILED trae en `reason` el siguiente paso: reintenta una vez y si se repite sigue ese paso. Si devuelve SHOPIFY_THEME_SETUP_REQUIRED, el tema de la tienda no tiene el bloque Code Landing y nada se public\xF3: env\xEDa al usuario el themeEditorDeepLink del error para abrir el editor del tema de Shopify, p\xEDdele que agregue el bloque y pulse Guardar (una sola vez por tienda), y reintenta s\xF3lo despu\xE9s. Ante resultado incierto, verifica la tarea/tienda antes de volver a publicar, especialmente con new; commerceCorrectionSaved no prueba publicaci\xF3n. La respuesta es markdown con el link a la landing y al producto.", ShopifyInput.shape, OVERWRITES_EXTERNAL, async (input, extra) => {
20033
20093
  try {
20034
20094
  const parsed = parseShopifyInput(input);
20035
20095
  if (parsed.publicationMode === "existing" && !parsed.targetProductId) {
@@ -20046,7 +20106,7 @@ function registerPublishTools(server, api, opts = {}) {
20046
20106
  const variantIndex = parsed.variantIndex ?? 0;
20047
20107
  const availableImages = await fetchLandingImageCount(api, parsed.taskId);
20048
20108
  const effectiveAvailable = availableImages > 0 ? availableImages : 3;
20049
- const numImages = Math.min(parsed.numLandingImages ?? effectiveAvailable, effectiveAvailable);
20109
+ const numImages = Math.min(parsed.numLandingImages ?? effectiveAvailable, effectiveAvailable, SHOPIFY_MAX_LANDING_IMAGES);
20050
20110
  const selectedLandingImages = Array.from({
20051
20111
  length: numImages
20052
20112
  }, (_, i) => ({
@@ -20077,6 +20137,8 @@ function registerPublishTools(server, api, opts = {}) {
20077
20137
  "SHOPIFY_PUBLISH_INPUT_REQUIRED",
20078
20138
  "SHOPIFY_PUBLISH_CONTEXT_CHANGED",
20079
20139
  "SHOPIFY_PUBLISH_CORRECTION_REQUIRES_EDITOR",
20140
+ "SHOPIFY_PUBLISH_CORRECTION_NOT_CONVERGED",
20141
+ "SHOPIFY_PUBLISH_VERIFICATION_FAILED",
20080
20142
  "SHOPIFY_PUBLISH_TARGET_INCOMPATIBLE",
20081
20143
  "SHOPIFY_THEME_SETUP_REQUIRED",
20082
20144
  "SHOPIFY_PUBLISH_FAILED"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@productmaker/mcp",
3
- "version": "7.1.0",
3
+ "version": "7.2.0",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "description": "Product Maker MCP server — cost products, generate dropshipping landing pages, UGC videos, and ad creatives, and publish to Shopify and Meta Ads from Claude, ChatGPT, or Gemini.",