@productmaker/mcp 6.3.0 → 6.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/http.js +156 -11
- package/dist/stdio.js +156 -11
- package/package.json +1 -1
package/dist/http.js
CHANGED
|
@@ -619,7 +619,25 @@ var AdaptiveProductReferenceStateSchema = z5.object({
|
|
|
619
619
|
* may enrich the candidate later without changing the photos the user
|
|
620
620
|
* approved; that must not invalidate the answer and reopen the question.
|
|
621
621
|
*/
|
|
622
|
-
lastAnsweredSelectedImageUrlsHash: z5.string().min(1).max(128).optional()
|
|
622
|
+
lastAnsweredSelectedImageUrlsHash: z5.string().min(1).max(128).optional(),
|
|
623
|
+
/**
|
|
624
|
+
* SHE PRESSED "SIGUE CON ESTAS FOTOS".
|
|
625
|
+
*
|
|
626
|
+
* The evidence version she pressed it at. The guard is
|
|
627
|
+
* `keptSelectionAtEvidenceVersion <= referenceEvidenceVersion`, so the
|
|
628
|
+
* retraction SURVIVES a later photo change — exactly like every other answer
|
|
629
|
+
* since answers stopped being filtered by evidence version. Recording the
|
|
630
|
+
* version is what makes a retraction taken at a FUTURE version (impossible
|
|
631
|
+
* today, but the ordering is asserted rather than assumed) not count.
|
|
632
|
+
*
|
|
633
|
+
* This is a RETRACTION, and it needs its own field because it cannot be
|
|
634
|
+
* expressed as an answer: the `different_products` answer that opened visual
|
|
635
|
+
* recovery short-circuits `buildAdaptiveProductReferenceDecision` before any
|
|
636
|
+
* later answer is read, so recording one more answer could never close the
|
|
637
|
+
* gate. Bumping the evidence version instead would drop every answer and
|
|
638
|
+
* restart the interview from `sale_structure` — the opposite of "continue".
|
|
639
|
+
*/
|
|
640
|
+
keptSelectionAtEvidenceVersion: z5.number().int().positive().optional()
|
|
623
641
|
});
|
|
624
642
|
var AdaptiveProductReferenceQuestionOptionSchema = z5.object({
|
|
625
643
|
value: ProductReferenceUserChoiceSchema,
|
|
@@ -3078,7 +3096,8 @@ var ShopifyPublishResponse = z22.object({
|
|
|
3078
3096
|
var ShopifyPublishErrorResponse = z22.object({
|
|
3079
3097
|
code: z22.enum([
|
|
3080
3098
|
"SHOPIFY_NOT_CONNECTED",
|
|
3081
|
-
"SHOPIFY_PUBLISH_FAILED"
|
|
3099
|
+
"SHOPIFY_PUBLISH_FAILED",
|
|
3100
|
+
"SHOPIFY_NO_PUBLISHED_THEME"
|
|
3082
3101
|
]),
|
|
3083
3102
|
message: z22.string().optional(),
|
|
3084
3103
|
authUrl: z22.string().url().optional(),
|
|
@@ -3220,9 +3239,16 @@ var ConfirmProductReferencesRequest = z23.object({
|
|
|
3220
3239
|
referenceEvidenceVersion: z23.number().int().positive().optional(),
|
|
3221
3240
|
expectedEvidenceHash: z23.string().min(1).max(128).optional(),
|
|
3222
3241
|
recovery: z23.object({
|
|
3242
|
+
/**
|
|
3243
|
+
* `keep_selection` is NOT a recovery in the billing sense: it commits
|
|
3244
|
+
* no new evidence, re-enqueues no extract, and costs nothing. It rides
|
|
3245
|
+
* on this field because it answers the same card, and the server
|
|
3246
|
+
* branches on it BEFORE the billed recovery path.
|
|
3247
|
+
*/
|
|
3223
3248
|
action: z23.enum([
|
|
3224
3249
|
"replace_selection",
|
|
3225
|
-
"refine_selection"
|
|
3250
|
+
"refine_selection",
|
|
3251
|
+
"keep_selection"
|
|
3226
3252
|
]),
|
|
3227
3253
|
affectedRefKeys: z23.array(z23.string().min(1).max(200)).min(1).max(5).optional(),
|
|
3228
3254
|
expectedReferenceVersionId: z23.string().uuid().optional(),
|
|
@@ -3230,6 +3256,16 @@ var ConfirmProductReferencesRequest = z23.object({
|
|
|
3230
3256
|
uploadedImages: z23.array(ProductReferenceRecoveryUpload).max(INGEST_MAX_REFERENCE_IMAGES).optional()
|
|
3231
3257
|
}).optional()
|
|
3232
3258
|
}).superRefine((value, context) => {
|
|
3259
|
+
if (value.recovery?.action === "keep_selection" && value.recovery.uploadedImages?.length) {
|
|
3260
|
+
context.addIssue({
|
|
3261
|
+
code: z23.ZodIssueCode.custom,
|
|
3262
|
+
path: [
|
|
3263
|
+
"recovery",
|
|
3264
|
+
"uploadedImages"
|
|
3265
|
+
],
|
|
3266
|
+
message: "keep_selection_does_not_accept_uploads"
|
|
3267
|
+
});
|
|
3268
|
+
}
|
|
3233
3269
|
if (value.recovery?.action === "refine_selection" && value.recovery.uploadedImages?.length) {
|
|
3234
3270
|
context.addIssue({
|
|
3235
3271
|
code: z23.ZodIssueCode.custom,
|
|
@@ -3797,6 +3833,16 @@ var PendingUserActionSchema = z26.discriminatedUnion("kind", [
|
|
|
3797
3833
|
referenceEvidenceVersion: z26.number().int().positive().optional(),
|
|
3798
3834
|
expectedEvidenceHash: z26.string().min(1).max(128).optional(),
|
|
3799
3835
|
question: AdaptiveProductReferenceQuestionSchema.optional(),
|
|
3836
|
+
/**
|
|
3837
|
+
* TRUE when "sigue con estas fotos" is a real exit from this card.
|
|
3838
|
+
*
|
|
3839
|
+
* Absent means the seller genuinely has to supply pixels we do not have,
|
|
3840
|
+
* and offering to continue would be a lie: nothing is selected at all, or
|
|
3841
|
+
* a combo component was never photographed. Every client reads this — web,
|
|
3842
|
+
* MCP and WhatsApp — so no surface can invent an exit the server did not
|
|
3843
|
+
* offer, nor omit one it did.
|
|
3844
|
+
*/
|
|
3845
|
+
canKeepSelection: z26.boolean().optional(),
|
|
3800
3846
|
recovery: z26.object({
|
|
3801
3847
|
kind: z26.literal("combo_component_evidence"),
|
|
3802
3848
|
minimumImageCount: z26.number().int().min(2).max(5),
|
|
@@ -3973,6 +4019,28 @@ var StatusResponse = z26.object({
|
|
|
3973
4019
|
* narrower fact than `needsReview` — see the identical field on `TaskSummary`
|
|
3974
4020
|
* below for why conflating the two produced a call to action that led nowhere. */
|
|
3975
4021
|
hasAnswerableGate: z26.boolean().optional(),
|
|
4022
|
+
/**
|
|
4023
|
+
* "Is the ANGLE gate — and only that gate — still open?"
|
|
4024
|
+
*
|
|
4025
|
+
* `needsReview` is an OR over every gate (reference, angles, SCRIPT review,
|
|
4026
|
+
* grouping). The chat used to read that OR as proof that the server was
|
|
4027
|
+
* waiting on ANGLES, because the two conditions it paired it with
|
|
4028
|
+
* (`stageStatuses.video_generator === 'pending'` + `status === 'processing'`)
|
|
4029
|
+
* are ALSO true throughout script review. So the moment a script became
|
|
4030
|
+
* ready — a gate the seller had already passed the angle card to reach — the
|
|
4031
|
+
* chat "recovered" the angle review card over a run that was already
|
|
4032
|
+
* dispatched, released its own submit guard, and re-enabled «Generar». The
|
|
4033
|
+
* seller pressed it again; the second resume lost the compare-and-clear and
|
|
4034
|
+
* answered 201 with a plain status body, so the no-op read as success and the
|
|
4035
|
+
* loop repeated (measured 2026-08-27: three resumes in 16s, two of them
|
|
4036
|
+
* no-ops).
|
|
4037
|
+
*
|
|
4038
|
+
* Named rather than inferred, for the same reason as `hasAnswerableGate`: a
|
|
4039
|
+
* gate added tomorrow widens `needsReview` and would silently re-arm that
|
|
4040
|
+
* recovery path. Absent (undefined) means "an older API that cannot answer" —
|
|
4041
|
+
* clients must fall back to `pauseForAngleReview` alone, never to the OR.
|
|
4042
|
+
*/
|
|
4043
|
+
awaitingAngleReview: z26.boolean().optional(),
|
|
3976
4044
|
/** Which landing pipeline produced this task's landing. 'code' tasks have no
|
|
3977
4045
|
* classic LandingImage rows — the result lives in the inline code editor. */
|
|
3978
4046
|
landingMode: LandingModeSchema.optional(),
|
|
@@ -10516,6 +10584,10 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
10516
10584
|
defineCapability("cost_product", "6d1f85a999dbdabae4eda406ff4689cdff2016df375b5b6741b91f4308d819af", "none", "read_only", [
|
|
10517
10585
|
"read"
|
|
10518
10586
|
], [
|
|
10587
|
+
slot("product_reference", false, [], [
|
|
10588
|
+
"selectedProductTitle",
|
|
10589
|
+
"selectedProductUrl"
|
|
10590
|
+
], "server"),
|
|
10519
10591
|
slot("market", true, [
|
|
10520
10592
|
"country"
|
|
10521
10593
|
]),
|
|
@@ -10817,7 +10889,8 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
10817
10889
|
"images"
|
|
10818
10890
|
], [
|
|
10819
10891
|
"imageMessageIds",
|
|
10820
|
-
"projectId"
|
|
10892
|
+
"projectId",
|
|
10893
|
+
"productUrl"
|
|
10821
10894
|
]),
|
|
10822
10895
|
slot("product_identity", true, [
|
|
10823
10896
|
"productTitle"
|
|
@@ -10889,7 +10962,8 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
10889
10962
|
"images"
|
|
10890
10963
|
], [
|
|
10891
10964
|
"imageMessageIds",
|
|
10892
|
-
"projectId"
|
|
10965
|
+
"projectId",
|
|
10966
|
+
"productUrl"
|
|
10893
10967
|
]),
|
|
10894
10968
|
slot("locale", true, [
|
|
10895
10969
|
"language",
|
|
@@ -10956,6 +11030,15 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
10956
11030
|
explicitOmissionAllowed: false,
|
|
10957
11031
|
fieldCompletion: "all"
|
|
10958
11032
|
},
|
|
11033
|
+
{
|
|
11034
|
+
slot: "sales_context",
|
|
11035
|
+
askBeforePaidExecution: true,
|
|
11036
|
+
explicitOmissionAllowed: false,
|
|
11037
|
+
fieldCompletion: "any",
|
|
11038
|
+
completionFields: [
|
|
11039
|
+
"salesAngle"
|
|
11040
|
+
]
|
|
11041
|
+
},
|
|
10959
11042
|
{
|
|
10960
11043
|
slot: "commercial_offer",
|
|
10961
11044
|
askBeforePaidExecution: true,
|
|
@@ -10971,7 +11054,8 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
10971
11054
|
"images"
|
|
10972
11055
|
], [
|
|
10973
11056
|
"imageMessageIds",
|
|
10974
|
-
"projectId"
|
|
11057
|
+
"projectId",
|
|
11058
|
+
"productUrl"
|
|
10975
11059
|
]),
|
|
10976
11060
|
slot("product_identity", true, [
|
|
10977
11061
|
"title"
|
|
@@ -11024,6 +11108,25 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
11024
11108
|
"product_reference"
|
|
11025
11109
|
], [
|
|
11026
11110
|
"video"
|
|
11111
|
+
], SHARED_CHANNEL_EXPOSURE, [
|
|
11112
|
+
{
|
|
11113
|
+
slot: "product_reference",
|
|
11114
|
+
askBeforePaidExecution: true,
|
|
11115
|
+
explicitOmissionAllowed: false,
|
|
11116
|
+
fieldCompletion: "any"
|
|
11117
|
+
},
|
|
11118
|
+
{
|
|
11119
|
+
slot: "locale",
|
|
11120
|
+
askBeforePaidExecution: true,
|
|
11121
|
+
explicitOmissionAllowed: false,
|
|
11122
|
+
fieldCompletion: "all"
|
|
11123
|
+
},
|
|
11124
|
+
{
|
|
11125
|
+
slot: "commercial_offer",
|
|
11126
|
+
askBeforePaidExecution: true,
|
|
11127
|
+
explicitOmissionAllowed: true,
|
|
11128
|
+
fieldCompletion: "all"
|
|
11129
|
+
}
|
|
11027
11130
|
]),
|
|
11028
11131
|
defineCapability("get_meta_campaign_status", "92077241cf4e9d14edefca410e85715cc383b1433081c3461c925f8fc3d6e8ab", "none", "read_only", [
|
|
11029
11132
|
"read"
|
|
@@ -11908,6 +12011,10 @@ function parsePublicHttpsOrigin(value) {
|
|
|
11908
12011
|
}
|
|
11909
12012
|
__name(parsePublicHttpsOrigin, "parsePublicHttpsOrigin");
|
|
11910
12013
|
|
|
12014
|
+
// ../../packages/types/src/whatsapp.ts
|
|
12015
|
+
var WHATSAPP_MAX_AUDIO_BYTES = 16 * 1024 * 1024;
|
|
12016
|
+
var WHATSAPP_MAX_AUDIO_DURATION_SECONDS = 10 * 60;
|
|
12017
|
+
|
|
11911
12018
|
// src/http.ts
|
|
11912
12019
|
import express from "express";
|
|
11913
12020
|
import rateLimit from "express-rate-limit";
|
|
@@ -13167,7 +13274,7 @@ var ApiClient = class {
|
|
|
13167
13274
|
};
|
|
13168
13275
|
|
|
13169
13276
|
// src/index.ts
|
|
13170
|
-
var MCP_VERSION = "6.3.
|
|
13277
|
+
var MCP_VERSION = "6.3.1";
|
|
13171
13278
|
|
|
13172
13279
|
// src/meta-upload-app.ts
|
|
13173
13280
|
import { readFileSync } from "fs";
|
|
@@ -14266,6 +14373,7 @@ var McpErrorCode = {
|
|
|
14266
14373
|
SHOPIFY_TOKEN_EXPIRED: "SHOPIFY_TOKEN_EXPIRED",
|
|
14267
14374
|
SHOPIFY_SCOPE_UPGRADE_REQUIRED: "SHOPIFY_SCOPE_UPGRADE_REQUIRED",
|
|
14268
14375
|
SHOPIFY_PUBLISH_FAILED: "SHOPIFY_PUBLISH_FAILED",
|
|
14376
|
+
SHOPIFY_NO_PUBLISHED_THEME: "SHOPIFY_NO_PUBLISHED_THEME",
|
|
14269
14377
|
// Meta / Facebook Ads
|
|
14270
14378
|
META_NO_FB_PAGE: "META_NO_FB_PAGE",
|
|
14271
14379
|
META_PORTFOLIO_MISMATCH: "META_PORTFOLIO_MISMATCH",
|
|
@@ -14333,6 +14441,7 @@ var CODE_MESSAGES = {
|
|
|
14333
14441
|
SHOPIFY_TOKEN_EXPIRED: "The Shopify session expired. Reconnect the store at https://productmaker.app/integrations.",
|
|
14334
14442
|
SHOPIFY_SCOPE_UPGRADE_REQUIRED: "The Shopify connection needs additional scopes. Reconnect it at https://productmaker.app/integrations to approve the new permissions.",
|
|
14335
14443
|
SHOPIFY_PUBLISH_FAILED: "Shopify rejected the publish. Check that the product/landing does not already exist on the store and that your Shopify plan allows additional products.",
|
|
14444
|
+
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.",
|
|
14336
14445
|
// Meta — high-frequency
|
|
14337
14446
|
META_NO_FB_PAGE: "No Facebook Page is associated with this account. Create one at https://www.facebook.com/pages/create before publishing (Meta requires a Page to run ads).",
|
|
14338
14447
|
META_PORTFOLIO_MISMATCH: "The selected Meta portfolio does not contain the selected ad account. Call `list_meta_destinations` again and choose an account from that portfolio.",
|
|
@@ -14518,6 +14627,7 @@ var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
|
|
|
14518
14627
|
McpErrorCode.SHOPIFY_TOKEN_EXPIRED,
|
|
14519
14628
|
McpErrorCode.SHOPIFY_SCOPE_UPGRADE_REQUIRED,
|
|
14520
14629
|
McpErrorCode.SHOPIFY_NOT_CONNECTED,
|
|
14630
|
+
McpErrorCode.SHOPIFY_NO_PUBLISHED_THEME,
|
|
14521
14631
|
McpErrorCode.TIKTOK_NOT_CONNECTED,
|
|
14522
14632
|
McpErrorCode.NO_SHOPIFY_SHOPS,
|
|
14523
14633
|
McpErrorCode.NO_META_AD_ACCOUNTS
|
|
@@ -14543,6 +14653,7 @@ var PASSTHROUGH_CODES = /* @__PURE__ */ new Set([
|
|
|
14543
14653
|
McpErrorCode.SHOPIFY_TOKEN_EXPIRED,
|
|
14544
14654
|
McpErrorCode.SHOPIFY_SCOPE_UPGRADE_REQUIRED,
|
|
14545
14655
|
McpErrorCode.SHOPIFY_PUBLISH_FAILED,
|
|
14656
|
+
McpErrorCode.SHOPIFY_NO_PUBLISHED_THEME,
|
|
14546
14657
|
// Meta
|
|
14547
14658
|
McpErrorCode.META_NO_FB_PAGE,
|
|
14548
14659
|
McpErrorCode.META_NO_ADS_CREATED,
|
|
@@ -14970,9 +15081,14 @@ function renderAngleReview(pending, authorizedVideoCount) {
|
|
|
14970
15081
|
__name(renderAngleReview, "renderAngleReview");
|
|
14971
15082
|
function renderReferenceConfirmation(pending) {
|
|
14972
15083
|
if (pending.mode === "visual_recovery") {
|
|
14973
|
-
|
|
15084
|
+
const body = escapeMarkdownText(pending.reason ?? "Necesitamos revisar las fotos antes de continuar.");
|
|
15085
|
+
return pending.canKeepSelection ? `## Revisa las fotos del producto
|
|
15086
|
+
|
|
15087
|
+
${body}
|
|
15088
|
+
|
|
15089
|
+
Puedes **seguir con estas fotos** (sin costo adicional) llamando \`resolve_pending_action\` con \`kind: "product_reference_confirmation"\`, o abrir la tarea en ProductMaker Web para subir otras.` : `## Faltan fotos del producto
|
|
14974
15090
|
|
|
14975
|
-
${
|
|
15091
|
+
${body}
|
|
14976
15092
|
|
|
14977
15093
|
Abre esta tarea en ProductMaker Web para reemplazar o completar las fotos.`;
|
|
14978
15094
|
}
|
|
@@ -15461,6 +15577,8 @@ var StatusForResolveSchema = z54.object({
|
|
|
15461
15577
|
"question",
|
|
15462
15578
|
"visual_recovery"
|
|
15463
15579
|
]).optional(),
|
|
15580
|
+
/** Hand-mirrored from packages/types result.ts — see CLAUDE.md § MCP Server Sync. */
|
|
15581
|
+
canKeepSelection: z54.boolean().optional(),
|
|
15464
15582
|
decisionKey: z54.string().optional(),
|
|
15465
15583
|
questionOrdinal: z54.number().int().min(1).max(5).optional(),
|
|
15466
15584
|
questionCeiling: z54.union([
|
|
@@ -15959,7 +16077,19 @@ ANTES de invocar, resume outputs, cantidad de videos y datos comerciales, explic
|
|
|
15959
16077
|
const candidateGrouping = pending.candidateGrouping;
|
|
15960
16078
|
const selectedImageUrls = pending.selectedImageUrls ?? [];
|
|
15961
16079
|
if (pending.mode === "visual_recovery") {
|
|
15962
|
-
|
|
16080
|
+
if (!pending.canKeepSelection) {
|
|
16081
|
+
throw new Error("Esta recuperaci\xF3n necesita subir fotos con un ticket ligado al usuario. \xC1brela en ProductMaker Web; MCP no acepta URLs arbitrarias.");
|
|
16082
|
+
}
|
|
16083
|
+
const kept = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/confirm-product-references`, {
|
|
16084
|
+
selectedImageUrls: pending.selectedImageUrls ?? [],
|
|
16085
|
+
recovery: {
|
|
16086
|
+
action: "keep_selection",
|
|
16087
|
+
...pending.expectedEvidenceHash ? {
|
|
16088
|
+
expectedEvidenceHash: pending.expectedEvidenceHash
|
|
16089
|
+
} : {}
|
|
16090
|
+
}
|
|
16091
|
+
});
|
|
16092
|
+
return asText(kept);
|
|
15963
16093
|
}
|
|
15964
16094
|
if (!candidateGrouping || selectedImageUrls.length === 0) {
|
|
15965
16095
|
throw new Error("Estado inconsistente: la tarea est\xE1 pausada pero falta candidateGrouping o selectedImageUrls.");
|
|
@@ -18677,6 +18807,16 @@ function renderMeta(meta) {
|
|
|
18677
18807
|
].filter(Boolean);
|
|
18678
18808
|
}
|
|
18679
18809
|
__name(renderMeta, "renderMeta");
|
|
18810
|
+
function renderDiscoveryLinks(links) {
|
|
18811
|
+
if (!links) return [];
|
|
18812
|
+
return [
|
|
18813
|
+
links.originalProductUrl ? `- [Producto original en TikTok Shop](${links.originalProductUrl})` : "- Producto original en TikTok Shop: no disponible",
|
|
18814
|
+
`- [Buscar d\xF3nde comprarlo](${links.googleSearchUrl})`,
|
|
18815
|
+
links.metaAdUrl ? `- [Anuncio relacionado encontrado en Meta](${links.metaAdUrl}) \u2014 comp\xE1ralo; puede ser una variante o un producto parecido` : "- Anuncio relacionado en Meta: no disponible",
|
|
18816
|
+
links.kaloDataUrl ? `- [Datos en KaloData](${links.kaloDataUrl})` : "- Datos en KaloData: no disponibles"
|
|
18817
|
+
];
|
|
18818
|
+
}
|
|
18819
|
+
__name(renderDiscoveryLinks, "renderDiscoveryLinks");
|
|
18680
18820
|
function renderCandidate(candidate, index) {
|
|
18681
18821
|
const name = candidate.nameLocal ?? candidate.nameOriginal;
|
|
18682
18822
|
const lines = [
|
|
@@ -18692,7 +18832,12 @@ function renderCandidate(candidate, index) {
|
|
|
18692
18832
|
]);
|
|
18693
18833
|
lines.push("", ...renderMeta(candidate.meta));
|
|
18694
18834
|
lines.push("", `- **Venta (30d):** ${num(candidate.units30d)} unidades \xB7 ${num(candidate.revenueUsd30d, " USD")} \xB7 crecimiento ${num(candidate.growthPct30d, "%")} \xB7 precio ${num(candidate.priceUsd, " USD")}`);
|
|
18695
|
-
|
|
18835
|
+
const discoveryLines = renderDiscoveryLinks(candidate.discoveryLinks);
|
|
18836
|
+
if (discoveryLines.length > 0) {
|
|
18837
|
+
lines.push("", "**D\xF3nde encontrarlo:**", ...discoveryLines);
|
|
18838
|
+
} else if (candidate.productUrl) {
|
|
18839
|
+
lines.push(`- [Ver producto](${candidate.productUrl})`);
|
|
18840
|
+
}
|
|
18696
18841
|
return lines.join("\n");
|
|
18697
18842
|
}
|
|
18698
18843
|
__name(renderCandidate, "renderCandidate");
|
package/dist/stdio.js
CHANGED
|
@@ -264,7 +264,7 @@ var ApiClient = class {
|
|
|
264
264
|
};
|
|
265
265
|
|
|
266
266
|
// src/index.ts
|
|
267
|
-
var MCP_VERSION = "6.3.
|
|
267
|
+
var MCP_VERSION = "6.3.1";
|
|
268
268
|
|
|
269
269
|
// src/meta-upload-app.ts
|
|
270
270
|
import { readFileSync } from "fs";
|
|
@@ -950,7 +950,25 @@ var AdaptiveProductReferenceStateSchema = z5.object({
|
|
|
950
950
|
* may enrich the candidate later without changing the photos the user
|
|
951
951
|
* approved; that must not invalidate the answer and reopen the question.
|
|
952
952
|
*/
|
|
953
|
-
lastAnsweredSelectedImageUrlsHash: z5.string().min(1).max(128).optional()
|
|
953
|
+
lastAnsweredSelectedImageUrlsHash: z5.string().min(1).max(128).optional(),
|
|
954
|
+
/**
|
|
955
|
+
* SHE PRESSED "SIGUE CON ESTAS FOTOS".
|
|
956
|
+
*
|
|
957
|
+
* The evidence version she pressed it at. The guard is
|
|
958
|
+
* `keptSelectionAtEvidenceVersion <= referenceEvidenceVersion`, so the
|
|
959
|
+
* retraction SURVIVES a later photo change — exactly like every other answer
|
|
960
|
+
* since answers stopped being filtered by evidence version. Recording the
|
|
961
|
+
* version is what makes a retraction taken at a FUTURE version (impossible
|
|
962
|
+
* today, but the ordering is asserted rather than assumed) not count.
|
|
963
|
+
*
|
|
964
|
+
* This is a RETRACTION, and it needs its own field because it cannot be
|
|
965
|
+
* expressed as an answer: the `different_products` answer that opened visual
|
|
966
|
+
* recovery short-circuits `buildAdaptiveProductReferenceDecision` before any
|
|
967
|
+
* later answer is read, so recording one more answer could never close the
|
|
968
|
+
* gate. Bumping the evidence version instead would drop every answer and
|
|
969
|
+
* restart the interview from `sale_structure` — the opposite of "continue".
|
|
970
|
+
*/
|
|
971
|
+
keptSelectionAtEvidenceVersion: z5.number().int().positive().optional()
|
|
954
972
|
});
|
|
955
973
|
var AdaptiveProductReferenceQuestionOptionSchema = z5.object({
|
|
956
974
|
value: ProductReferenceUserChoiceSchema,
|
|
@@ -3409,7 +3427,8 @@ var ShopifyPublishResponse = z22.object({
|
|
|
3409
3427
|
var ShopifyPublishErrorResponse = z22.object({
|
|
3410
3428
|
code: z22.enum([
|
|
3411
3429
|
"SHOPIFY_NOT_CONNECTED",
|
|
3412
|
-
"SHOPIFY_PUBLISH_FAILED"
|
|
3430
|
+
"SHOPIFY_PUBLISH_FAILED",
|
|
3431
|
+
"SHOPIFY_NO_PUBLISHED_THEME"
|
|
3413
3432
|
]),
|
|
3414
3433
|
message: z22.string().optional(),
|
|
3415
3434
|
authUrl: z22.string().url().optional(),
|
|
@@ -3551,9 +3570,16 @@ var ConfirmProductReferencesRequest = z23.object({
|
|
|
3551
3570
|
referenceEvidenceVersion: z23.number().int().positive().optional(),
|
|
3552
3571
|
expectedEvidenceHash: z23.string().min(1).max(128).optional(),
|
|
3553
3572
|
recovery: z23.object({
|
|
3573
|
+
/**
|
|
3574
|
+
* `keep_selection` is NOT a recovery in the billing sense: it commits
|
|
3575
|
+
* no new evidence, re-enqueues no extract, and costs nothing. It rides
|
|
3576
|
+
* on this field because it answers the same card, and the server
|
|
3577
|
+
* branches on it BEFORE the billed recovery path.
|
|
3578
|
+
*/
|
|
3554
3579
|
action: z23.enum([
|
|
3555
3580
|
"replace_selection",
|
|
3556
|
-
"refine_selection"
|
|
3581
|
+
"refine_selection",
|
|
3582
|
+
"keep_selection"
|
|
3557
3583
|
]),
|
|
3558
3584
|
affectedRefKeys: z23.array(z23.string().min(1).max(200)).min(1).max(5).optional(),
|
|
3559
3585
|
expectedReferenceVersionId: z23.string().uuid().optional(),
|
|
@@ -3561,6 +3587,16 @@ var ConfirmProductReferencesRequest = z23.object({
|
|
|
3561
3587
|
uploadedImages: z23.array(ProductReferenceRecoveryUpload).max(INGEST_MAX_REFERENCE_IMAGES).optional()
|
|
3562
3588
|
}).optional()
|
|
3563
3589
|
}).superRefine((value, context) => {
|
|
3590
|
+
if (value.recovery?.action === "keep_selection" && value.recovery.uploadedImages?.length) {
|
|
3591
|
+
context.addIssue({
|
|
3592
|
+
code: z23.ZodIssueCode.custom,
|
|
3593
|
+
path: [
|
|
3594
|
+
"recovery",
|
|
3595
|
+
"uploadedImages"
|
|
3596
|
+
],
|
|
3597
|
+
message: "keep_selection_does_not_accept_uploads"
|
|
3598
|
+
});
|
|
3599
|
+
}
|
|
3564
3600
|
if (value.recovery?.action === "refine_selection" && value.recovery.uploadedImages?.length) {
|
|
3565
3601
|
context.addIssue({
|
|
3566
3602
|
code: z23.ZodIssueCode.custom,
|
|
@@ -4128,6 +4164,16 @@ var PendingUserActionSchema = z26.discriminatedUnion("kind", [
|
|
|
4128
4164
|
referenceEvidenceVersion: z26.number().int().positive().optional(),
|
|
4129
4165
|
expectedEvidenceHash: z26.string().min(1).max(128).optional(),
|
|
4130
4166
|
question: AdaptiveProductReferenceQuestionSchema.optional(),
|
|
4167
|
+
/**
|
|
4168
|
+
* TRUE when "sigue con estas fotos" is a real exit from this card.
|
|
4169
|
+
*
|
|
4170
|
+
* Absent means the seller genuinely has to supply pixels we do not have,
|
|
4171
|
+
* and offering to continue would be a lie: nothing is selected at all, or
|
|
4172
|
+
* a combo component was never photographed. Every client reads this — web,
|
|
4173
|
+
* MCP and WhatsApp — so no surface can invent an exit the server did not
|
|
4174
|
+
* offer, nor omit one it did.
|
|
4175
|
+
*/
|
|
4176
|
+
canKeepSelection: z26.boolean().optional(),
|
|
4131
4177
|
recovery: z26.object({
|
|
4132
4178
|
kind: z26.literal("combo_component_evidence"),
|
|
4133
4179
|
minimumImageCount: z26.number().int().min(2).max(5),
|
|
@@ -4304,6 +4350,28 @@ var StatusResponse = z26.object({
|
|
|
4304
4350
|
* narrower fact than `needsReview` — see the identical field on `TaskSummary`
|
|
4305
4351
|
* below for why conflating the two produced a call to action that led nowhere. */
|
|
4306
4352
|
hasAnswerableGate: z26.boolean().optional(),
|
|
4353
|
+
/**
|
|
4354
|
+
* "Is the ANGLE gate — and only that gate — still open?"
|
|
4355
|
+
*
|
|
4356
|
+
* `needsReview` is an OR over every gate (reference, angles, SCRIPT review,
|
|
4357
|
+
* grouping). The chat used to read that OR as proof that the server was
|
|
4358
|
+
* waiting on ANGLES, because the two conditions it paired it with
|
|
4359
|
+
* (`stageStatuses.video_generator === 'pending'` + `status === 'processing'`)
|
|
4360
|
+
* are ALSO true throughout script review. So the moment a script became
|
|
4361
|
+
* ready — a gate the seller had already passed the angle card to reach — the
|
|
4362
|
+
* chat "recovered" the angle review card over a run that was already
|
|
4363
|
+
* dispatched, released its own submit guard, and re-enabled «Generar». The
|
|
4364
|
+
* seller pressed it again; the second resume lost the compare-and-clear and
|
|
4365
|
+
* answered 201 with a plain status body, so the no-op read as success and the
|
|
4366
|
+
* loop repeated (measured 2026-08-27: three resumes in 16s, two of them
|
|
4367
|
+
* no-ops).
|
|
4368
|
+
*
|
|
4369
|
+
* Named rather than inferred, for the same reason as `hasAnswerableGate`: a
|
|
4370
|
+
* gate added tomorrow widens `needsReview` and would silently re-arm that
|
|
4371
|
+
* recovery path. Absent (undefined) means "an older API that cannot answer" —
|
|
4372
|
+
* clients must fall back to `pauseForAngleReview` alone, never to the OR.
|
|
4373
|
+
*/
|
|
4374
|
+
awaitingAngleReview: z26.boolean().optional(),
|
|
4307
4375
|
/** Which landing pipeline produced this task's landing. 'code' tasks have no
|
|
4308
4376
|
* classic LandingImage rows — the result lives in the inline code editor. */
|
|
4309
4377
|
landingMode: LandingModeSchema.optional(),
|
|
@@ -10847,6 +10915,10 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
10847
10915
|
defineCapability("cost_product", "6d1f85a999dbdabae4eda406ff4689cdff2016df375b5b6741b91f4308d819af", "none", "read_only", [
|
|
10848
10916
|
"read"
|
|
10849
10917
|
], [
|
|
10918
|
+
slot("product_reference", false, [], [
|
|
10919
|
+
"selectedProductTitle",
|
|
10920
|
+
"selectedProductUrl"
|
|
10921
|
+
], "server"),
|
|
10850
10922
|
slot("market", true, [
|
|
10851
10923
|
"country"
|
|
10852
10924
|
]),
|
|
@@ -11148,7 +11220,8 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
11148
11220
|
"images"
|
|
11149
11221
|
], [
|
|
11150
11222
|
"imageMessageIds",
|
|
11151
|
-
"projectId"
|
|
11223
|
+
"projectId",
|
|
11224
|
+
"productUrl"
|
|
11152
11225
|
]),
|
|
11153
11226
|
slot("product_identity", true, [
|
|
11154
11227
|
"productTitle"
|
|
@@ -11220,7 +11293,8 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
11220
11293
|
"images"
|
|
11221
11294
|
], [
|
|
11222
11295
|
"imageMessageIds",
|
|
11223
|
-
"projectId"
|
|
11296
|
+
"projectId",
|
|
11297
|
+
"productUrl"
|
|
11224
11298
|
]),
|
|
11225
11299
|
slot("locale", true, [
|
|
11226
11300
|
"language",
|
|
@@ -11287,6 +11361,15 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
11287
11361
|
explicitOmissionAllowed: false,
|
|
11288
11362
|
fieldCompletion: "all"
|
|
11289
11363
|
},
|
|
11364
|
+
{
|
|
11365
|
+
slot: "sales_context",
|
|
11366
|
+
askBeforePaidExecution: true,
|
|
11367
|
+
explicitOmissionAllowed: false,
|
|
11368
|
+
fieldCompletion: "any",
|
|
11369
|
+
completionFields: [
|
|
11370
|
+
"salesAngle"
|
|
11371
|
+
]
|
|
11372
|
+
},
|
|
11290
11373
|
{
|
|
11291
11374
|
slot: "commercial_offer",
|
|
11292
11375
|
askBeforePaidExecution: true,
|
|
@@ -11302,7 +11385,8 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
11302
11385
|
"images"
|
|
11303
11386
|
], [
|
|
11304
11387
|
"imageMessageIds",
|
|
11305
|
-
"projectId"
|
|
11388
|
+
"projectId",
|
|
11389
|
+
"productUrl"
|
|
11306
11390
|
]),
|
|
11307
11391
|
slot("product_identity", true, [
|
|
11308
11392
|
"title"
|
|
@@ -11355,6 +11439,25 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
11355
11439
|
"product_reference"
|
|
11356
11440
|
], [
|
|
11357
11441
|
"video"
|
|
11442
|
+
], SHARED_CHANNEL_EXPOSURE, [
|
|
11443
|
+
{
|
|
11444
|
+
slot: "product_reference",
|
|
11445
|
+
askBeforePaidExecution: true,
|
|
11446
|
+
explicitOmissionAllowed: false,
|
|
11447
|
+
fieldCompletion: "any"
|
|
11448
|
+
},
|
|
11449
|
+
{
|
|
11450
|
+
slot: "locale",
|
|
11451
|
+
askBeforePaidExecution: true,
|
|
11452
|
+
explicitOmissionAllowed: false,
|
|
11453
|
+
fieldCompletion: "all"
|
|
11454
|
+
},
|
|
11455
|
+
{
|
|
11456
|
+
slot: "commercial_offer",
|
|
11457
|
+
askBeforePaidExecution: true,
|
|
11458
|
+
explicitOmissionAllowed: true,
|
|
11459
|
+
fieldCompletion: "all"
|
|
11460
|
+
}
|
|
11358
11461
|
]),
|
|
11359
11462
|
defineCapability("get_meta_campaign_status", "92077241cf4e9d14edefca410e85715cc383b1433081c3461c925f8fc3d6e8ab", "none", "read_only", [
|
|
11360
11463
|
"read"
|
|
@@ -11983,6 +12086,10 @@ var ReferralDashboardOverviewSchema = z51.object({
|
|
|
11983
12086
|
monthlyRegistrations: z51.array(ReferralDashboardMonthlyRegistrationSchema).refine((months) => months.length === 6, "Expected six monthly registration buckets")
|
|
11984
12087
|
});
|
|
11985
12088
|
|
|
12089
|
+
// ../../packages/types/src/whatsapp.ts
|
|
12090
|
+
var WHATSAPP_MAX_AUDIO_BYTES = 16 * 1024 * 1024;
|
|
12091
|
+
var WHATSAPP_MAX_AUDIO_DURATION_SECONDS = 10 * 60;
|
|
12092
|
+
|
|
11986
12093
|
// src/tools/tasks.ts
|
|
11987
12094
|
import { z as z53 } from "zod";
|
|
11988
12095
|
|
|
@@ -13011,6 +13118,7 @@ var McpErrorCode = {
|
|
|
13011
13118
|
SHOPIFY_TOKEN_EXPIRED: "SHOPIFY_TOKEN_EXPIRED",
|
|
13012
13119
|
SHOPIFY_SCOPE_UPGRADE_REQUIRED: "SHOPIFY_SCOPE_UPGRADE_REQUIRED",
|
|
13013
13120
|
SHOPIFY_PUBLISH_FAILED: "SHOPIFY_PUBLISH_FAILED",
|
|
13121
|
+
SHOPIFY_NO_PUBLISHED_THEME: "SHOPIFY_NO_PUBLISHED_THEME",
|
|
13014
13122
|
// Meta / Facebook Ads
|
|
13015
13123
|
META_NO_FB_PAGE: "META_NO_FB_PAGE",
|
|
13016
13124
|
META_PORTFOLIO_MISMATCH: "META_PORTFOLIO_MISMATCH",
|
|
@@ -13078,6 +13186,7 @@ var CODE_MESSAGES = {
|
|
|
13078
13186
|
SHOPIFY_TOKEN_EXPIRED: "The Shopify session expired. Reconnect the store at https://productmaker.app/integrations.",
|
|
13079
13187
|
SHOPIFY_SCOPE_UPGRADE_REQUIRED: "The Shopify connection needs additional scopes. Reconnect it at https://productmaker.app/integrations to approve the new permissions.",
|
|
13080
13188
|
SHOPIFY_PUBLISH_FAILED: "Shopify rejected the publish. Check that the product/landing does not already exist on the store and that your Shopify plan allows additional products.",
|
|
13189
|
+
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.",
|
|
13081
13190
|
// Meta — high-frequency
|
|
13082
13191
|
META_NO_FB_PAGE: "No Facebook Page is associated with this account. Create one at https://www.facebook.com/pages/create before publishing (Meta requires a Page to run ads).",
|
|
13083
13192
|
META_PORTFOLIO_MISMATCH: "The selected Meta portfolio does not contain the selected ad account. Call `list_meta_destinations` again and choose an account from that portfolio.",
|
|
@@ -13263,6 +13372,7 @@ var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
|
|
|
13263
13372
|
McpErrorCode.SHOPIFY_TOKEN_EXPIRED,
|
|
13264
13373
|
McpErrorCode.SHOPIFY_SCOPE_UPGRADE_REQUIRED,
|
|
13265
13374
|
McpErrorCode.SHOPIFY_NOT_CONNECTED,
|
|
13375
|
+
McpErrorCode.SHOPIFY_NO_PUBLISHED_THEME,
|
|
13266
13376
|
McpErrorCode.TIKTOK_NOT_CONNECTED,
|
|
13267
13377
|
McpErrorCode.NO_SHOPIFY_SHOPS,
|
|
13268
13378
|
McpErrorCode.NO_META_AD_ACCOUNTS
|
|
@@ -13288,6 +13398,7 @@ var PASSTHROUGH_CODES = /* @__PURE__ */ new Set([
|
|
|
13288
13398
|
McpErrorCode.SHOPIFY_TOKEN_EXPIRED,
|
|
13289
13399
|
McpErrorCode.SHOPIFY_SCOPE_UPGRADE_REQUIRED,
|
|
13290
13400
|
McpErrorCode.SHOPIFY_PUBLISH_FAILED,
|
|
13401
|
+
McpErrorCode.SHOPIFY_NO_PUBLISHED_THEME,
|
|
13291
13402
|
// Meta
|
|
13292
13403
|
McpErrorCode.META_NO_FB_PAGE,
|
|
13293
13404
|
McpErrorCode.META_NO_ADS_CREATED,
|
|
@@ -13768,9 +13879,14 @@ function renderAngleReview(pending, authorizedVideoCount) {
|
|
|
13768
13879
|
__name(renderAngleReview, "renderAngleReview");
|
|
13769
13880
|
function renderReferenceConfirmation(pending) {
|
|
13770
13881
|
if (pending.mode === "visual_recovery") {
|
|
13771
|
-
|
|
13882
|
+
const body = escapeMarkdownText(pending.reason ?? "Necesitamos revisar las fotos antes de continuar.");
|
|
13883
|
+
return pending.canKeepSelection ? `## Revisa las fotos del producto
|
|
13884
|
+
|
|
13885
|
+
${body}
|
|
13886
|
+
|
|
13887
|
+
Puedes **seguir con estas fotos** (sin costo adicional) llamando \`resolve_pending_action\` con \`kind: "product_reference_confirmation"\`, o abrir la tarea en ProductMaker Web para subir otras.` : `## Faltan fotos del producto
|
|
13772
13888
|
|
|
13773
|
-
${
|
|
13889
|
+
${body}
|
|
13774
13890
|
|
|
13775
13891
|
Abre esta tarea en ProductMaker Web para reemplazar o completar las fotos.`;
|
|
13776
13892
|
}
|
|
@@ -14259,6 +14375,8 @@ var StatusForResolveSchema = z53.object({
|
|
|
14259
14375
|
"question",
|
|
14260
14376
|
"visual_recovery"
|
|
14261
14377
|
]).optional(),
|
|
14378
|
+
/** Hand-mirrored from packages/types result.ts — see CLAUDE.md § MCP Server Sync. */
|
|
14379
|
+
canKeepSelection: z53.boolean().optional(),
|
|
14262
14380
|
decisionKey: z53.string().optional(),
|
|
14263
14381
|
questionOrdinal: z53.number().int().min(1).max(5).optional(),
|
|
14264
14382
|
questionCeiling: z53.union([
|
|
@@ -14757,7 +14875,19 @@ ANTES de invocar, resume outputs, cantidad de videos y datos comerciales, explic
|
|
|
14757
14875
|
const candidateGrouping = pending.candidateGrouping;
|
|
14758
14876
|
const selectedImageUrls = pending.selectedImageUrls ?? [];
|
|
14759
14877
|
if (pending.mode === "visual_recovery") {
|
|
14760
|
-
|
|
14878
|
+
if (!pending.canKeepSelection) {
|
|
14879
|
+
throw new Error("Esta recuperaci\xF3n necesita subir fotos con un ticket ligado al usuario. \xC1brela en ProductMaker Web; MCP no acepta URLs arbitrarias.");
|
|
14880
|
+
}
|
|
14881
|
+
const kept = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/confirm-product-references`, {
|
|
14882
|
+
selectedImageUrls: pending.selectedImageUrls ?? [],
|
|
14883
|
+
recovery: {
|
|
14884
|
+
action: "keep_selection",
|
|
14885
|
+
...pending.expectedEvidenceHash ? {
|
|
14886
|
+
expectedEvidenceHash: pending.expectedEvidenceHash
|
|
14887
|
+
} : {}
|
|
14888
|
+
}
|
|
14889
|
+
});
|
|
14890
|
+
return asText(kept);
|
|
14761
14891
|
}
|
|
14762
14892
|
if (!candidateGrouping || selectedImageUrls.length === 0) {
|
|
14763
14893
|
throw new Error("Estado inconsistente: la tarea est\xE1 pausada pero falta candidateGrouping o selectedImageUrls.");
|
|
@@ -17475,6 +17605,16 @@ function renderMeta(meta) {
|
|
|
17475
17605
|
].filter(Boolean);
|
|
17476
17606
|
}
|
|
17477
17607
|
__name(renderMeta, "renderMeta");
|
|
17608
|
+
function renderDiscoveryLinks(links) {
|
|
17609
|
+
if (!links) return [];
|
|
17610
|
+
return [
|
|
17611
|
+
links.originalProductUrl ? `- [Producto original en TikTok Shop](${links.originalProductUrl})` : "- Producto original en TikTok Shop: no disponible",
|
|
17612
|
+
`- [Buscar d\xF3nde comprarlo](${links.googleSearchUrl})`,
|
|
17613
|
+
links.metaAdUrl ? `- [Anuncio relacionado encontrado en Meta](${links.metaAdUrl}) \u2014 comp\xE1ralo; puede ser una variante o un producto parecido` : "- Anuncio relacionado en Meta: no disponible",
|
|
17614
|
+
links.kaloDataUrl ? `- [Datos en KaloData](${links.kaloDataUrl})` : "- Datos en KaloData: no disponibles"
|
|
17615
|
+
];
|
|
17616
|
+
}
|
|
17617
|
+
__name(renderDiscoveryLinks, "renderDiscoveryLinks");
|
|
17478
17618
|
function renderCandidate(candidate, index) {
|
|
17479
17619
|
const name = candidate.nameLocal ?? candidate.nameOriginal;
|
|
17480
17620
|
const lines = [
|
|
@@ -17490,7 +17630,12 @@ function renderCandidate(candidate, index) {
|
|
|
17490
17630
|
]);
|
|
17491
17631
|
lines.push("", ...renderMeta(candidate.meta));
|
|
17492
17632
|
lines.push("", `- **Venta (30d):** ${num(candidate.units30d)} unidades \xB7 ${num(candidate.revenueUsd30d, " USD")} \xB7 crecimiento ${num(candidate.growthPct30d, "%")} \xB7 precio ${num(candidate.priceUsd, " USD")}`);
|
|
17493
|
-
|
|
17633
|
+
const discoveryLines = renderDiscoveryLinks(candidate.discoveryLinks);
|
|
17634
|
+
if (discoveryLines.length > 0) {
|
|
17635
|
+
lines.push("", "**D\xF3nde encontrarlo:**", ...discoveryLines);
|
|
17636
|
+
} else if (candidate.productUrl) {
|
|
17637
|
+
lines.push(`- [Ver producto](${candidate.productUrl})`);
|
|
17638
|
+
}
|
|
17494
17639
|
return lines.join("\n");
|
|
17495
17640
|
}
|
|
17496
17641
|
__name(renderCandidate, "renderCandidate");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@productmaker/mcp",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.1",
|
|
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.",
|