@productmaker/mcp 1.16.3 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/http.js +571 -16
- package/dist/stdio.js +571 -16
- package/package.json +4 -3
package/dist/stdio.js
CHANGED
|
@@ -9,6 +9,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
9
9
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
10
|
|
|
11
11
|
// src/api-client.ts
|
|
12
|
+
import { createHash } from "crypto";
|
|
12
13
|
var ApiHttpError = class extends Error {
|
|
13
14
|
static {
|
|
14
15
|
__name(this, "ApiHttpError");
|
|
@@ -35,6 +36,15 @@ var ApiClient = class {
|
|
|
35
36
|
Authorization: `Bearer ${this.apiKey}`
|
|
36
37
|
};
|
|
37
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Stable per-key identifier. The MCP never sees the userId — the API key is
|
|
41
|
+
* the only per-user handle it holds — so this stands in as the user
|
|
42
|
+
* component of derived idempotency keys. Never log or return it raw to a
|
|
43
|
+
* caller: it is derived from a secret.
|
|
44
|
+
*/
|
|
45
|
+
fingerprint() {
|
|
46
|
+
return createHash("sha256").update(this.apiKey).digest("hex").slice(0, 32);
|
|
47
|
+
}
|
|
38
48
|
async getJson(path3) {
|
|
39
49
|
const res = await fetch(`${this.baseUrl}${path3}`, {
|
|
40
50
|
headers: this.authHeaders()
|
|
@@ -129,7 +139,7 @@ var ApiClient = class {
|
|
|
129
139
|
};
|
|
130
140
|
|
|
131
141
|
// src/index.ts
|
|
132
|
-
var MCP_VERSION = "
|
|
142
|
+
var MCP_VERSION = "2.0.0";
|
|
133
143
|
|
|
134
144
|
// src/tools/tasks.ts
|
|
135
145
|
import { z as z39 } from "zod";
|
|
@@ -437,9 +447,11 @@ var VideoModel = z.enum([
|
|
|
437
447
|
]);
|
|
438
448
|
var NarrativeStyle = z.enum([
|
|
439
449
|
"review",
|
|
450
|
+
"ugc_how_to",
|
|
440
451
|
"problem_product_cta",
|
|
441
452
|
"product_showcase",
|
|
442
453
|
"cinematic",
|
|
454
|
+
"wild_card",
|
|
443
455
|
"metaphor_product_cta",
|
|
444
456
|
"wearable_showcase",
|
|
445
457
|
"animated_problem_pixar",
|
|
@@ -458,9 +470,11 @@ var VideoResolution = z.enum([
|
|
|
458
470
|
var VideoPreset = z.enum([
|
|
459
471
|
"auto",
|
|
460
472
|
"ugc_dynamic",
|
|
473
|
+
"ugc_how_to",
|
|
461
474
|
"problem_solution",
|
|
462
475
|
"product_showcase",
|
|
463
476
|
"cinematic",
|
|
477
|
+
"wild_card",
|
|
464
478
|
"wearable_showcase",
|
|
465
479
|
"pixar_problem",
|
|
466
480
|
"pixar_sassy",
|
|
@@ -2448,6 +2462,7 @@ var TASK_CREATIVE_VARIANT_TEXT_LIMITS = {
|
|
|
2448
2462
|
recommendationRationale: 600
|
|
2449
2463
|
};
|
|
2450
2464
|
var TaskCreativeVariantVideoPreset = VideoPreset.exclude([
|
|
2465
|
+
"problem_solution",
|
|
2451
2466
|
"ugc_unboxing_chaos",
|
|
2452
2467
|
"ugc_curiosity_reveal",
|
|
2453
2468
|
"ugc_listicle_3",
|
|
@@ -2560,6 +2575,7 @@ var TaskVariantSummary = z21.object({
|
|
|
2560
2575
|
var MAX_LANDING_IMAGES = 20;
|
|
2561
2576
|
var AutoResolvedPresetSchema = z22.enum([
|
|
2562
2577
|
"ugc_dynamic",
|
|
2578
|
+
"ugc_how_to",
|
|
2563
2579
|
"ugc_demo_review",
|
|
2564
2580
|
"ugc_unboxing_chaos",
|
|
2565
2581
|
"ugc_curiosity_reveal",
|
|
@@ -2568,6 +2584,7 @@ var AutoResolvedPresetSchema = z22.enum([
|
|
|
2568
2584
|
"problem_solution",
|
|
2569
2585
|
"product_showcase",
|
|
2570
2586
|
"cinematic",
|
|
2587
|
+
"wild_card",
|
|
2571
2588
|
"wearable_showcase",
|
|
2572
2589
|
"pixar_problem",
|
|
2573
2590
|
"pixar_sassy",
|
|
@@ -3146,6 +3163,125 @@ var AdminQualityReportListResponse = z24.object({
|
|
|
3146
3163
|
var UpdateQualityReportStatusRequest = z24.object({
|
|
3147
3164
|
status: QualityReportStatus
|
|
3148
3165
|
});
|
|
3166
|
+
var AdminSupportConversationStatus = z24.enum([
|
|
3167
|
+
"bot_active",
|
|
3168
|
+
"human_pending",
|
|
3169
|
+
"human_active",
|
|
3170
|
+
"closed"
|
|
3171
|
+
]);
|
|
3172
|
+
var AdminSupportDeliveryStatus = z24.enum([
|
|
3173
|
+
"draft",
|
|
3174
|
+
"pending",
|
|
3175
|
+
"sent",
|
|
3176
|
+
"delivered",
|
|
3177
|
+
"read",
|
|
3178
|
+
"failed",
|
|
3179
|
+
"blocked"
|
|
3180
|
+
]);
|
|
3181
|
+
var AdminSupportMessage = z24.object({
|
|
3182
|
+
id: z24.string().uuid(),
|
|
3183
|
+
direction: z24.enum([
|
|
3184
|
+
"inbound",
|
|
3185
|
+
"outbound"
|
|
3186
|
+
]),
|
|
3187
|
+
senderKind: z24.enum([
|
|
3188
|
+
"user",
|
|
3189
|
+
"bot",
|
|
3190
|
+
"admin",
|
|
3191
|
+
"system"
|
|
3192
|
+
]),
|
|
3193
|
+
messageType: z24.enum([
|
|
3194
|
+
"text",
|
|
3195
|
+
"image",
|
|
3196
|
+
"audio",
|
|
3197
|
+
"document",
|
|
3198
|
+
"interactive",
|
|
3199
|
+
"status"
|
|
3200
|
+
]),
|
|
3201
|
+
bodyRedacted: z24.string().nullable(),
|
|
3202
|
+
deliveryStatus: AdminSupportDeliveryStatus.nullable(),
|
|
3203
|
+
createdAt: z24.string().datetime()
|
|
3204
|
+
});
|
|
3205
|
+
var AdminSupportTicket = z24.object({
|
|
3206
|
+
id: z24.string().uuid(),
|
|
3207
|
+
category: z24.enum([
|
|
3208
|
+
"how_to",
|
|
3209
|
+
"credits",
|
|
3210
|
+
"billing",
|
|
3211
|
+
"task_status",
|
|
3212
|
+
"task_error",
|
|
3213
|
+
"video_quality",
|
|
3214
|
+
"landing_quality",
|
|
3215
|
+
"image_quality",
|
|
3216
|
+
"feature_request",
|
|
3217
|
+
"other"
|
|
3218
|
+
]),
|
|
3219
|
+
failureMode: z24.string().nullable(),
|
|
3220
|
+
status: z24.enum([
|
|
3221
|
+
"open",
|
|
3222
|
+
"triaged",
|
|
3223
|
+
"resolved",
|
|
3224
|
+
"rejected"
|
|
3225
|
+
]),
|
|
3226
|
+
summary: z24.string(),
|
|
3227
|
+
taskId: z24.string().nullable(),
|
|
3228
|
+
mixTaskId: z24.string().nullable(),
|
|
3229
|
+
qualityReportId: z24.string().nullable(),
|
|
3230
|
+
handoffReason: z24.string().nullable(),
|
|
3231
|
+
createdAt: z24.string().datetime(),
|
|
3232
|
+
updatedAt: z24.string().datetime()
|
|
3233
|
+
});
|
|
3234
|
+
var AdminSupportConversation = z24.object({
|
|
3235
|
+
id: z24.string().uuid(),
|
|
3236
|
+
status: AdminSupportConversationStatus,
|
|
3237
|
+
priority: z24.enum([
|
|
3238
|
+
"low",
|
|
3239
|
+
"normal",
|
|
3240
|
+
"high",
|
|
3241
|
+
"urgent"
|
|
3242
|
+
]),
|
|
3243
|
+
currentIntent: z24.string().nullable(),
|
|
3244
|
+
assignedAdminId: z24.string().nullable(),
|
|
3245
|
+
customerServiceWindowEndsAt: z24.string().datetime().nullable(),
|
|
3246
|
+
lastInboundAt: z24.string().datetime().nullable(),
|
|
3247
|
+
createdAt: z24.string().datetime(),
|
|
3248
|
+
updatedAt: z24.string().datetime(),
|
|
3249
|
+
contact: z24.object({
|
|
3250
|
+
id: z24.string().uuid(),
|
|
3251
|
+
userId: z24.string().nullable(),
|
|
3252
|
+
verifiedAt: z24.string().datetime().nullable(),
|
|
3253
|
+
botMode: z24.enum([
|
|
3254
|
+
"enabled",
|
|
3255
|
+
"paused",
|
|
3256
|
+
"opted_out"
|
|
3257
|
+
]),
|
|
3258
|
+
locale: z24.string()
|
|
3259
|
+
}),
|
|
3260
|
+
messages: z24.array(AdminSupportMessage),
|
|
3261
|
+
tickets: z24.array(AdminSupportTicket)
|
|
3262
|
+
});
|
|
3263
|
+
var AdminSupportListQuery = z24.object({
|
|
3264
|
+
status: AdminSupportConversationStatus.optional(),
|
|
3265
|
+
take: z24.coerce.number().int().min(1).max(100).default(50),
|
|
3266
|
+
cursorId: z24.string().uuid().optional()
|
|
3267
|
+
}).strict();
|
|
3268
|
+
var AdminSupportListResponse = z24.object({
|
|
3269
|
+
conversations: z24.array(AdminSupportConversation),
|
|
3270
|
+
nextCursor: z24.string().uuid().nullable()
|
|
3271
|
+
});
|
|
3272
|
+
var AdminSupportReplyRequest = z24.object({
|
|
3273
|
+
body: z24.string().trim().min(1).max(4096),
|
|
3274
|
+
idempotencyKey: z24.string().regex(/^[A-Za-z0-9_-]{8,128}$/)
|
|
3275
|
+
}).strict();
|
|
3276
|
+
var AdminSupportEmptyActionRequest = z24.object({}).strict();
|
|
3277
|
+
var AdminSupportActionResponse = z24.object({
|
|
3278
|
+
conversation: AdminSupportConversation
|
|
3279
|
+
});
|
|
3280
|
+
var AdminSupportReplyResponse = z24.object({
|
|
3281
|
+
messageId: z24.string().uuid(),
|
|
3282
|
+
deliveryStatus: AdminSupportDeliveryStatus,
|
|
3283
|
+
duplicate: z24.boolean()
|
|
3284
|
+
});
|
|
3149
3285
|
|
|
3150
3286
|
// ../../packages/types/src/promo.ts
|
|
3151
3287
|
import { z as z25 } from "zod";
|
|
@@ -3981,6 +4117,23 @@ var DEFAULT_PRICING = {
|
|
|
3981
4117
|
ourCostCents: 0.4,
|
|
3982
4118
|
userPriceCents: 0.8
|
|
3983
4119
|
},
|
|
4120
|
+
// WhatsApp ProductMaker intent proposal. Real OpenRouter usage.cost wins;
|
|
4121
|
+
// this is only the fallback when the relay omits cost.
|
|
4122
|
+
"openrouter:whatsapp-product-agent-intent": {
|
|
4123
|
+
ourCostCents: 0.4,
|
|
4124
|
+
userPriceCents: 0.8
|
|
4125
|
+
},
|
|
4126
|
+
// Internal support calls are cost-only: callers explicitly override user
|
|
4127
|
+
// price to zero. Static COGS estimates apply because OpenAI Responses exposes
|
|
4128
|
+
// token counts but not the provider's dollar charge.
|
|
4129
|
+
"openai:whatsapp-support-classifier": {
|
|
4130
|
+
ourCostCents: 0.1,
|
|
4131
|
+
userPriceCents: 0.2
|
|
4132
|
+
},
|
|
4133
|
+
"openai:whatsapp-support-draft": {
|
|
4134
|
+
ourCostCents: 0.4,
|
|
4135
|
+
userPriceCents: 0.8
|
|
4136
|
+
},
|
|
3984
4137
|
// Task-derived creative recommendation (angle/persona/script/landing-section).
|
|
3985
4138
|
// OpenRouter usage normally supplies the exact COGS and the call layer charges
|
|
3986
4139
|
// 2x. This rule is the fail-closed fallback when usage.cost is unavailable.
|
|
@@ -6427,6 +6580,151 @@ var SceneSubjectContractSchema = z37.object({
|
|
|
6427
6580
|
}
|
|
6428
6581
|
});
|
|
6429
6582
|
|
|
6583
|
+
// ../../packages/types/src/productmaker-chat-capabilities.ts
|
|
6584
|
+
var PRODUCTMAKER_MCP_CAPABILITIES = [
|
|
6585
|
+
{
|
|
6586
|
+
name: "approve_video_script",
|
|
6587
|
+
confirmation: "credit_consuming",
|
|
6588
|
+
schemaHash: "bab794a9700a8d3f42b97f444a776e7ed4dcd833038c52588f951b50970ff3fa"
|
|
6589
|
+
},
|
|
6590
|
+
{
|
|
6591
|
+
name: "cost_product",
|
|
6592
|
+
confirmation: "none",
|
|
6593
|
+
schemaHash: "6d1f85a999dbdabae4eda406ff4689cdff2016df375b5b6741b91f4308d819af"
|
|
6594
|
+
},
|
|
6595
|
+
{
|
|
6596
|
+
name: "create_creative_variant",
|
|
6597
|
+
confirmation: "credit_consuming",
|
|
6598
|
+
schemaHash: "8feeed1e6e6a14bdcb0a1d9d8b03350ce7d8117d926db49f19ca5c5a8f1e7033"
|
|
6599
|
+
},
|
|
6600
|
+
{
|
|
6601
|
+
name: "create_product_task",
|
|
6602
|
+
confirmation: "credit_consuming",
|
|
6603
|
+
schemaHash: "5346684b007a40f6e8d53cc0b56ebb20aeb7e5ba48cdde43bc9f4e6163ed2d3f"
|
|
6604
|
+
},
|
|
6605
|
+
{
|
|
6606
|
+
name: "edit_task_draft",
|
|
6607
|
+
confirmation: "state_change",
|
|
6608
|
+
schemaHash: "f1763564083dcaac2d1f4d5e6c4625a2d4ec0ea3eea273aadc0fb9d4ab6c7d47"
|
|
6609
|
+
},
|
|
6610
|
+
{
|
|
6611
|
+
name: "estimate_creative_variant",
|
|
6612
|
+
confirmation: "none",
|
|
6613
|
+
schemaHash: "a5601adcb9578319528ca0a96f37a35b613370646a75932d1b3978d593f7e302"
|
|
6614
|
+
},
|
|
6615
|
+
{
|
|
6616
|
+
name: "find_winning_products",
|
|
6617
|
+
confirmation: "credit_consuming",
|
|
6618
|
+
schemaHash: "40f581e270ca668fb5fd1505a1f7cced6f629936e7d8394426aa0c655b9cef4f"
|
|
6619
|
+
},
|
|
6620
|
+
{
|
|
6621
|
+
name: "generate_free_video",
|
|
6622
|
+
confirmation: "credit_consuming",
|
|
6623
|
+
schemaHash: "a6f984d175c1ae5bc5f36ade27ee12c57763192000453c0387cd4cd6a9f1751d"
|
|
6624
|
+
},
|
|
6625
|
+
{
|
|
6626
|
+
name: "generate_image_creatives",
|
|
6627
|
+
confirmation: "credit_consuming",
|
|
6628
|
+
schemaHash: "fb6afcf8571887906340fa0b8dd17196d2814e480b734ad5d374e8810958152e"
|
|
6629
|
+
},
|
|
6630
|
+
{
|
|
6631
|
+
name: "generate_landing",
|
|
6632
|
+
confirmation: "credit_consuming",
|
|
6633
|
+
schemaHash: "539922b8fe611c73a7349b1c3f92632fe21f161d7874a0ef2172abfec5834759"
|
|
6634
|
+
},
|
|
6635
|
+
{
|
|
6636
|
+
name: "generate_video_creative",
|
|
6637
|
+
confirmation: "credit_consuming",
|
|
6638
|
+
schemaHash: "94e90d60ad5d68538c31d880a635c5be69d6d204fd2119139735af83ebf0b94a"
|
|
6639
|
+
},
|
|
6640
|
+
{
|
|
6641
|
+
name: "get_meta_campaign_status",
|
|
6642
|
+
confirmation: "none",
|
|
6643
|
+
schemaHash: "92077241cf4e9d14edefca410e85715cc383b1433081c3461c925f8fc3d6e8ab"
|
|
6644
|
+
},
|
|
6645
|
+
{
|
|
6646
|
+
name: "get_product_research_run",
|
|
6647
|
+
confirmation: "none",
|
|
6648
|
+
schemaHash: "3d0bfa4409daa4a8126d18787c3dc619dbdf2fbb679a2cffe9ffcd1c0d96402f"
|
|
6649
|
+
},
|
|
6650
|
+
{
|
|
6651
|
+
name: "get_task_status",
|
|
6652
|
+
confirmation: "none",
|
|
6653
|
+
schemaHash: "6876d865685dd371ae6c2da2d5a69b657731e5a032aff46ac95b6077e9a6d93e"
|
|
6654
|
+
},
|
|
6655
|
+
{
|
|
6656
|
+
name: "get_tiktok_campaign_status",
|
|
6657
|
+
confirmation: "none",
|
|
6658
|
+
schemaHash: "7a2f90a9a9ba91506fbb57a0a881d5a7b9ca1bb1074b0a754520f02fd81b2801"
|
|
6659
|
+
},
|
|
6660
|
+
{
|
|
6661
|
+
name: "get_video_script",
|
|
6662
|
+
confirmation: "none",
|
|
6663
|
+
schemaHash: "c96e7042368ba6f241773d26d4a443894b092801d1c81054f3798865138edfba"
|
|
6664
|
+
},
|
|
6665
|
+
{
|
|
6666
|
+
name: "list_meta_ad_accounts",
|
|
6667
|
+
confirmation: "none",
|
|
6668
|
+
schemaHash: "7a014b717a77aac971ea0ab5c0ff47bb13e6cae7ef9442d9dedb15ce36381b15"
|
|
6669
|
+
},
|
|
6670
|
+
{
|
|
6671
|
+
name: "list_product_research_runs",
|
|
6672
|
+
confirmation: "none",
|
|
6673
|
+
schemaHash: "3b8fac903cd9e30c5b6a6c584cc70a819af5392bbdbb32b69eab4c65dc52b556"
|
|
6674
|
+
},
|
|
6675
|
+
{
|
|
6676
|
+
name: "list_shopify_products",
|
|
6677
|
+
confirmation: "none",
|
|
6678
|
+
schemaHash: "152b137e01066f3b443987ad75f5be00de8696ce984144bd775416192be20667"
|
|
6679
|
+
},
|
|
6680
|
+
{
|
|
6681
|
+
name: "list_shopify_shops",
|
|
6682
|
+
confirmation: "none",
|
|
6683
|
+
schemaHash: "7a014b717a77aac971ea0ab5c0ff47bb13e6cae7ef9442d9dedb15ce36381b15"
|
|
6684
|
+
},
|
|
6685
|
+
{
|
|
6686
|
+
name: "list_tasks",
|
|
6687
|
+
confirmation: "none",
|
|
6688
|
+
schemaHash: "cac5e9dda34b90550024cc546976fab1d6de4fc8fca85a3ec6abde65223cb56c"
|
|
6689
|
+
},
|
|
6690
|
+
{
|
|
6691
|
+
name: "list_tiktok_advertisers",
|
|
6692
|
+
confirmation: "none",
|
|
6693
|
+
schemaHash: "7a014b717a77aac971ea0ab5c0ff47bb13e6cae7ef9442d9dedb15ce36381b15"
|
|
6694
|
+
},
|
|
6695
|
+
{
|
|
6696
|
+
name: "publish_creatives_to_meta",
|
|
6697
|
+
confirmation: "external_mutation",
|
|
6698
|
+
schemaHash: "ddb45071d61746b443124346e7a82095315a2a80bd393c73a45f12b895939cf5"
|
|
6699
|
+
},
|
|
6700
|
+
{
|
|
6701
|
+
name: "publish_to_meta",
|
|
6702
|
+
confirmation: "external_mutation",
|
|
6703
|
+
schemaHash: "4b6daa1a25e739ccf208a6acede067ec1c22b1ea5509f332361a17dd46f6bb0b"
|
|
6704
|
+
},
|
|
6705
|
+
{
|
|
6706
|
+
name: "publish_to_shopify",
|
|
6707
|
+
confirmation: "external_mutation",
|
|
6708
|
+
schemaHash: "ec37111aac872e6be22f34c8b547c1e4b58875f798c48e24ca9ca067a0a22d8b"
|
|
6709
|
+
},
|
|
6710
|
+
{
|
|
6711
|
+
name: "publish_to_tiktok",
|
|
6712
|
+
confirmation: "external_mutation",
|
|
6713
|
+
schemaHash: "4f8dccba51b99687f04e1a58e3fde003b6f52ec6c891afe74b1d81d4b6992908"
|
|
6714
|
+
},
|
|
6715
|
+
{
|
|
6716
|
+
name: "regenerate_video_script",
|
|
6717
|
+
confirmation: "credit_consuming",
|
|
6718
|
+
schemaHash: "745687cb41f904e293105f93bbe6bc0471f63fe097ab8f85d3df8d9833084844"
|
|
6719
|
+
},
|
|
6720
|
+
{
|
|
6721
|
+
name: "resolve_pending_action",
|
|
6722
|
+
confirmation: "credit_consuming",
|
|
6723
|
+
schemaHash: "3a9f33a26fce63b20bece3a528f8eb16e917dabc8a9ec5f04e746bad84757aa3"
|
|
6724
|
+
}
|
|
6725
|
+
];
|
|
6726
|
+
var PRODUCTMAKER_MCP_CAPABILITY_NAMES = PRODUCTMAKER_MCP_CAPABILITIES.map((capability) => capability.name);
|
|
6727
|
+
|
|
6430
6728
|
// src/resolve-media.ts
|
|
6431
6729
|
var MAX_MEDIA_BYTES = CAMPAIGN_LIMITS.MAX_FILE_SIZE_BYTES;
|
|
6432
6730
|
var MEDIA_FORMATS = [
|
|
@@ -6686,20 +6984,22 @@ var LandingImageFormatEnum = z38.enum([
|
|
|
6686
6984
|
var VideoPresetEnum = z38.enum([
|
|
6687
6985
|
"auto",
|
|
6688
6986
|
"ugc_dynamic",
|
|
6987
|
+
"ugc_how_to",
|
|
6689
6988
|
"ugc_unboxing_chaos",
|
|
6690
|
-
"problem_solution",
|
|
6691
6989
|
"product_showcase",
|
|
6692
6990
|
"wearable_showcase",
|
|
6693
6991
|
"cinematic",
|
|
6992
|
+
"wild_card",
|
|
6694
6993
|
"pixar_problem",
|
|
6695
6994
|
"pixar_sassy"
|
|
6696
6995
|
]);
|
|
6697
6996
|
var TaskCreativeVariantVideoPresetEnum = z38.enum([
|
|
6698
6997
|
"auto",
|
|
6699
6998
|
"ugc_dynamic",
|
|
6700
|
-
"
|
|
6999
|
+
"ugc_how_to",
|
|
6701
7000
|
"product_showcase",
|
|
6702
7001
|
"cinematic",
|
|
7002
|
+
"wild_card",
|
|
6703
7003
|
"wearable_showcase",
|
|
6704
7004
|
"pixar_problem",
|
|
6705
7005
|
"pixar_sassy",
|
|
@@ -6712,15 +7012,15 @@ var VIDEO_PRESET_RESOLUTION = {
|
|
|
6712
7012
|
narrativeStyle: "review",
|
|
6713
7013
|
videoModel: "veo"
|
|
6714
7014
|
},
|
|
6715
|
-
|
|
7015
|
+
ugc_how_to: {
|
|
6716
7016
|
videoStyle: "ai_dynamic",
|
|
6717
|
-
narrativeStyle: "
|
|
7017
|
+
narrativeStyle: "ugc_how_to",
|
|
6718
7018
|
videoModel: "veo"
|
|
6719
7019
|
},
|
|
6720
|
-
|
|
7020
|
+
ugc_unboxing_chaos: {
|
|
6721
7021
|
videoStyle: "ai_dynamic",
|
|
6722
|
-
narrativeStyle: "
|
|
6723
|
-
videoModel: "
|
|
7022
|
+
narrativeStyle: "ugc_unboxing_chaos",
|
|
7023
|
+
videoModel: "veo"
|
|
6724
7024
|
},
|
|
6725
7025
|
pixar_problem: {
|
|
6726
7026
|
videoStyle: "ai_dynamic",
|
|
@@ -6746,6 +7046,11 @@ var VIDEO_PRESET_RESOLUTION = {
|
|
|
6746
7046
|
videoStyle: "product_only",
|
|
6747
7047
|
narrativeStyle: "cinematic",
|
|
6748
7048
|
videoModel: "grok"
|
|
7049
|
+
},
|
|
7050
|
+
wild_card: {
|
|
7051
|
+
videoStyle: "product_only",
|
|
7052
|
+
narrativeStyle: "wild_card",
|
|
7053
|
+
videoModel: "grok"
|
|
6749
7054
|
}
|
|
6750
7055
|
};
|
|
6751
7056
|
function resolveVideoPreset(preset) {
|
|
@@ -6780,9 +7085,11 @@ var VideoStyleEnum = z38.enum([
|
|
|
6780
7085
|
]);
|
|
6781
7086
|
var NarrativeStyleEnum = z38.enum([
|
|
6782
7087
|
"review",
|
|
7088
|
+
"ugc_how_to",
|
|
6783
7089
|
"problem_product_cta",
|
|
6784
7090
|
"product_showcase",
|
|
6785
7091
|
"cinematic",
|
|
7092
|
+
"wild_card",
|
|
6786
7093
|
"metaphor_product_cta",
|
|
6787
7094
|
"wearable_showcase",
|
|
6788
7095
|
"animated_problem_pixar",
|
|
@@ -6795,11 +7102,12 @@ var NarrativeStyleEnum = z38.enum([
|
|
|
6795
7102
|
]);
|
|
6796
7103
|
var PublicNarrativeStyleEnum = z38.enum([
|
|
6797
7104
|
"review",
|
|
7105
|
+
"ugc_how_to",
|
|
6798
7106
|
"ugc_unboxing_chaos",
|
|
6799
|
-
"problem_product_cta",
|
|
6800
7107
|
"product_showcase",
|
|
6801
7108
|
"wearable_showcase",
|
|
6802
7109
|
"cinematic",
|
|
7110
|
+
"wild_card",
|
|
6803
7111
|
"animated_problem_pixar",
|
|
6804
7112
|
"sassy_product_pixar"
|
|
6805
7113
|
]);
|
|
@@ -6927,6 +7235,28 @@ async function submitCreative(api, opts) {
|
|
|
6927
7235
|
});
|
|
6928
7236
|
}
|
|
6929
7237
|
__name(submitCreative, "submitCreative");
|
|
7238
|
+
var ResearchTargetCountryEnum = z38.enum([
|
|
7239
|
+
"CO",
|
|
7240
|
+
"MX",
|
|
7241
|
+
"PE",
|
|
7242
|
+
"EC",
|
|
7243
|
+
"CL"
|
|
7244
|
+
]);
|
|
7245
|
+
var ResearchCategoryEnum = z38.enum([
|
|
7246
|
+
"auto",
|
|
7247
|
+
"automotive",
|
|
7248
|
+
"kitchen",
|
|
7249
|
+
"pets",
|
|
7250
|
+
"home_organization",
|
|
7251
|
+
"beauty",
|
|
7252
|
+
"wellness",
|
|
7253
|
+
"fitness",
|
|
7254
|
+
"baby",
|
|
7255
|
+
"tech",
|
|
7256
|
+
"tools",
|
|
7257
|
+
"garden",
|
|
7258
|
+
"cleaning"
|
|
7259
|
+
]);
|
|
6930
7260
|
|
|
6931
7261
|
// src/errors.ts
|
|
6932
7262
|
var McpErrorCode = {
|
|
@@ -6958,6 +7288,7 @@ var McpErrorCode = {
|
|
|
6958
7288
|
TASK_NOT_READY: "TASK_NOT_READY",
|
|
6959
7289
|
CREATIVE_VARIANT_FAILED: "CREATIVE_VARIANT_FAILED",
|
|
6960
7290
|
CREATIVE_VARIANT_DISPATCH_FAILED: "CREATIVE_VARIANT_DISPATCH_FAILED",
|
|
7291
|
+
PRODUCT_RESEARCH_IDEMPOTENCY_CONFLICT: "PRODUCT_RESEARCH_IDEMPOTENCY_CONFLICT",
|
|
6961
7292
|
// Connection presence
|
|
6962
7293
|
NO_SHOPIFY_SHOPS: "NO_SHOPIFY_SHOPS",
|
|
6963
7294
|
NO_META_AD_ACCOUNTS: "NO_META_AD_ACCOUNTS",
|
|
@@ -7079,12 +7410,13 @@ var CODE_MESSAGES = {
|
|
|
7079
7410
|
TASK_NOT_READY: "The task is not ready for this action yet. Wait for it to finish and retry.",
|
|
7080
7411
|
CREATIVE_VARIANT_FAILED: "The previous creative variant attempt failed. Ask for confirmation before starting a new paid attempt with a new clientRequestId.",
|
|
7081
7412
|
CREATIVE_VARIANT_DISPATCH_FAILED: "The creative variant could not be queued. Retry as a new paid attempt with a new clientRequestId.",
|
|
7413
|
+
PRODUCT_RESEARCH_IDEMPOTENCY_CONFLICT: "A product research run with the same country and category is already running. Check it with `list_product_research_runs` instead of starting a new paid run.",
|
|
7082
7414
|
// API key
|
|
7083
7415
|
INVALID_API_KEY: "Invalid API key. Check your PM_API_KEY in the MCP client configuration.",
|
|
7084
7416
|
REVOKED_API_KEY: "API key revoked. Generate a new one at https://productmaker.app/account/api-keys.",
|
|
7085
7417
|
// Rate
|
|
7086
7418
|
RATE_LIMITED: "Too many requests. Wait a few seconds.",
|
|
7087
|
-
INSUFFICIENT_CREDITS: "Insufficient credits
|
|
7419
|
+
INSUFFICIENT_CREDITS: "Insufficient credits for this action. Check your balance and add credits at https://productmaker.app before retrying.",
|
|
7088
7420
|
TASK_QUOTA_EXCEEDED: "You reached the active-task limit. Wait for a task to finish before creating another variant.",
|
|
7089
7421
|
// Generic
|
|
7090
7422
|
UPSTREAM_UNAVAILABLE: "The Product Maker service did not respond. Try again shortly.",
|
|
@@ -7097,6 +7429,7 @@ var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
|
|
|
7097
7429
|
McpErrorCode.INVALID_INPUT,
|
|
7098
7430
|
McpErrorCode.INSUFFICIENT_CREDITS,
|
|
7099
7431
|
McpErrorCode.TASK_QUOTA_EXCEEDED,
|
|
7432
|
+
McpErrorCode.PRODUCT_RESEARCH_IDEMPOTENCY_CONFLICT,
|
|
7100
7433
|
McpErrorCode.INVALID_API_KEY,
|
|
7101
7434
|
McpErrorCode.REVOKED_API_KEY,
|
|
7102
7435
|
McpErrorCode.IMAGE_PATH_NOT_FOUND,
|
|
@@ -7159,6 +7492,7 @@ var PASSTHROUGH_CODES = /* @__PURE__ */ new Set([
|
|
|
7159
7492
|
McpErrorCode.INSUFFICIENT_CREDITS,
|
|
7160
7493
|
McpErrorCode.TASK_QUOTA_EXCEEDED,
|
|
7161
7494
|
McpErrorCode.CREATIVE_VARIANT_DISPATCH_FAILED,
|
|
7495
|
+
McpErrorCode.PRODUCT_RESEARCH_IDEMPOTENCY_CONFLICT,
|
|
7162
7496
|
// Shopify
|
|
7163
7497
|
McpErrorCode.SHOPIFY_NOT_CONNECTED,
|
|
7164
7498
|
McpErrorCode.SHOPIFY_TOKEN_EXPIRED,
|
|
@@ -7272,9 +7606,11 @@ function translateError(err) {
|
|
|
7272
7606
|
const body = isRecord(err.body) ? err.body : {};
|
|
7273
7607
|
if (typeof body.code === "string" && PASSTHROUGH_CODES.has(body.code)) {
|
|
7274
7608
|
const code = body.code;
|
|
7275
|
-
|
|
7276
|
-
|
|
7277
|
-
|
|
7609
|
+
const extra = {};
|
|
7610
|
+
if (body.helpUrl !== void 0) extra.helpUrl = body.helpUrl;
|
|
7611
|
+
if (typeof body.requiredCredits === "number") extra.requiredCredits = body.requiredCredits;
|
|
7612
|
+
if (typeof body.availableCredits === "number") extra.availableCredits = body.availableCredits;
|
|
7613
|
+
return mcp(code, resolveMessage(code, body.message), extra);
|
|
7278
7614
|
}
|
|
7279
7615
|
if (err.status === 401) {
|
|
7280
7616
|
return mcp(McpErrorCode.INVALID_API_KEY, resolveMessage(McpErrorCode.INVALID_API_KEY, void 0));
|
|
@@ -7489,7 +7825,7 @@ var CreateProductTaskInput = z39.object({
|
|
|
7489
7825
|
paymentMethods: z39.string().min(1).max(240).optional().describe('Opcional. M\xE9todos de pago que puede mostrar la landing. Ej. "Tarjeta, PSE, Nequi, Contraentrega". Si se omite, la landing no muestra m\xE9todos de pago.'),
|
|
7490
7826
|
productContext: z39.string().max(3e3).optional().describe("Opcional. Informacion adicional del producto que la IA debe usar como contexto prioritario: uso real, comprador, restricciones, diferenciadores o direccion creativa. No reemplaza la investigacion de competencia; si hay competencia, guia el primer angulo y los outputs no deben contradecirlo."),
|
|
7491
7827
|
productLabel: z39.string().trim().max(80).optional().describe('Opcional. Qu\xE9 es el producto, en pocas palabras (ej. "quita pelusas para ropa"). Es AUTORITATIVO: si lo env\xEDas, la IA usa esta identidad y no la reemplaza por otra categor\xEDa. Si lo omites, la IA detecta el producto desde la imagen.'),
|
|
7492
|
-
videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo/narrativa del video \u2014 \xFAnico campo de "estilo" que el usuario controla
|
|
7828
|
+
videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo/narrativa del video \u2014 \xFAnico campo de "estilo" que el usuario controla. Default "auto" (la IA escoge tras analizar el producto). Los presets funcionan a 15s y 30s. Opciones: "auto", "ugc_dynamic" (review con experiencia y prueba), "ugc_how_to" (tutorial con pasos y resultado visible), "ugc_unboxing_chaos" (apertura y primera prueba), "product_showcase" (producto ejecutando una funci\xF3n real), "wearable_showcase" (prenda o accesorio modelado), "cinematic" (TV spot con receta cinematogr\xE1fica), "wild_card" (una regla visual experimental original), "pixar_problem" (problema animado 3D), "pixar_sassy" (producto que habla). Si el usuario pide algo fuera de esta lista, p\xEDdele una opci\xF3n v\xE1lida.'),
|
|
7493
7829
|
durationSeconds: z39.union([
|
|
7494
7830
|
z39.literal(15),
|
|
7495
7831
|
z39.literal(30)
|
|
@@ -7498,7 +7834,7 @@ var CreateProductTaskInput = z39.object({
|
|
|
7498
7834
|
videoTier: VideoTierEnum.optional().describe('Opcional. Estilo del video. "basic" = "Realista" para 15s; al pedir durationSeconds=30 el sistema selecciona internamente Multiescena. No se expone el tier interno directamente.'),
|
|
7499
7835
|
videoResolution: VideoResolutionEnum.optional().describe('Opcional. Resoluci\xF3n del render. En la web se pregunta "\xBFResoluci\xF3n del video?". "480p" = excelente calidad y m\xE1s econ\xF3mico (RECOMENDADO, default si se omite); "720p" = m\xE1xima nitidez (~el doble de precio). Si el usuario no menciona resoluci\xF3n, OMITE este campo (el backend usa 480p).'),
|
|
7500
7836
|
multiAngleVideos: z39.boolean().optional().describe("Opcional. Si true, genera m\xFAltiples \xE1ngulos de venta en paralelo (3-5 videos). Si false/omitido, genera 1 video del \xE1ngulo principal. Default false."),
|
|
7501
|
-
angleNarratives: PublicNarrativeStyleEnum.array().max(3).optional().describe('Opcional. Narrativa POR \xC1NGULO (posicional). Valores actuales: "review", "
|
|
7837
|
+
angleNarratives: PublicNarrativeStyleEnum.array().max(3).optional().describe('Opcional. Narrativa POR \xC1NGULO (posicional). Valores actuales: "review", "ugc_how_to", "ugc_unboxing_chaos", "product_showcase", "wearable_showcase", "cinematic", "wild_card", "animated_problem_pixar", "sassy_product_pixar". angleNarratives[i] fuerza la narrativa del \xE1ngulo i; las posiciones restantes las elige la IA. Requiere multiAngleVideos:true para varios \xE1ngulos.'),
|
|
7502
7838
|
outputs: z39.array(z39.enum([
|
|
7503
7839
|
"landing",
|
|
7504
7840
|
"video",
|
|
@@ -7984,7 +8320,7 @@ var VideoInput = z40.object({
|
|
|
7984
8320
|
z40.literal(15),
|
|
7985
8321
|
z40.literal(30)
|
|
7986
8322
|
]).default(15).describe("Opcional. Duraci\xF3n objetivo: 15s (single_call, default) o 30s (Multiescena, aproximadamente 30s y facturado por escena)."),
|
|
7987
|
-
videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo/narrativa del video \u2014 \xFAnico campo de "estilo" que el usuario controla
|
|
8323
|
+
videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo/narrativa del video \u2014 \xFAnico campo de "estilo" que el usuario controla. Default "auto" (la IA escoge tras analizar el producto). Los presets funcionan a 15s y 30s. Opciones: "auto", "ugc_dynamic" (review con experiencia y prueba), "ugc_how_to" (tutorial con pasos y resultado visible), "ugc_unboxing_chaos" (apertura y primera prueba), "product_showcase" (producto ejecutando una funci\xF3n real), "wearable_showcase" (prenda o accesorio modelado), "cinematic" (TV spot con receta cinematogr\xE1fica), "wild_card" (una regla visual experimental original), "pixar_problem" (problema animado 3D), "pixar_sassy" (producto que habla).'),
|
|
7988
8324
|
videoTier: VideoTierEnum.optional().describe('Opcional. Estilo del video. "basic" = "Realista"; para durationSeconds=30 el sistema selecciona internamente Multiescena. No se expone el tier interno directamente.'),
|
|
7989
8325
|
videoResolution: VideoResolutionEnum.optional().describe('Opcional. Resoluci\xF3n del render. En la web se pregunta "\xBFResoluci\xF3n del video?". "720p" = m\xE1xima nitidez (RECOMENDADO, default si se omite); "480p" = render m\xE1s econ\xF3mico. Si el usuario no menciona resoluci\xF3n, OMITE este campo (el backend usa 720p).'),
|
|
7990
8326
|
features: z40.array(z40.string()).optional().describe('Opcional. Lista de features/beneficios clave a mencionar. Ej. ["resistente al agua", "bater\xEDa 24h", "carga inal\xE1mbrica"].'),
|
|
@@ -9601,6 +9937,224 @@ function formatPercent(value) {
|
|
|
9601
9937
|
}
|
|
9602
9938
|
__name(formatPercent, "formatPercent");
|
|
9603
9939
|
|
|
9940
|
+
// src/tools/product-research.ts
|
|
9941
|
+
import { z as z49 } from "zod";
|
|
9942
|
+
|
|
9943
|
+
// src/tools/product-research-idempotency.ts
|
|
9944
|
+
import { createHash as createHash2 } from "crypto";
|
|
9945
|
+
var RESEARCH_IDEMPOTENCY_WINDOW_MS = 2 * 60 * 60 * 1e3;
|
|
9946
|
+
var NAMESPACE = "6f9d3a1e-8c47-4b52-9f10-2d7e5a3c8b64";
|
|
9947
|
+
function uuidV5(name, namespace) {
|
|
9948
|
+
const namespaceBytes = Buffer.from(namespace.replace(/-/g, ""), "hex");
|
|
9949
|
+
const digest = createHash2("sha1").update(namespaceBytes).update(name, "utf8").digest();
|
|
9950
|
+
const bytes = Buffer.from(digest.subarray(0, 16));
|
|
9951
|
+
bytes[6] = (bytes[6] ?? 0) & 15 | 80;
|
|
9952
|
+
bytes[8] = (bytes[8] ?? 0) & 63 | 128;
|
|
9953
|
+
const hex = bytes.toString("hex");
|
|
9954
|
+
return [
|
|
9955
|
+
hex.slice(0, 8),
|
|
9956
|
+
hex.slice(8, 12),
|
|
9957
|
+
hex.slice(12, 16),
|
|
9958
|
+
hex.slice(16, 20),
|
|
9959
|
+
hex.slice(20)
|
|
9960
|
+
].join("-");
|
|
9961
|
+
}
|
|
9962
|
+
__name(uuidV5, "uuidV5");
|
|
9963
|
+
function deriveIdempotencyKey(input) {
|
|
9964
|
+
const window = Math.floor(input.nowMs / RESEARCH_IDEMPOTENCY_WINDOW_MS);
|
|
9965
|
+
const name = `${input.fingerprint}:${input.targetCountry}:${input.categoryKey}:${window}`;
|
|
9966
|
+
return uuidV5(name, NAMESPACE);
|
|
9967
|
+
}
|
|
9968
|
+
__name(deriveIdempotencyKey, "deriveIdempotencyKey");
|
|
9969
|
+
|
|
9970
|
+
// src/tools/product-research-render.ts
|
|
9971
|
+
var num = /* @__PURE__ */ __name((value, suffix = "") => value === void 0 ? "\u2014" : `${Math.round(value * 100) / 100}${suffix}`, "num");
|
|
9972
|
+
function renderMeta(meta) {
|
|
9973
|
+
if (meta.status === "evidence_failed") {
|
|
9974
|
+
return [
|
|
9975
|
+
"- \u26A0\uFE0F **Sin evidencia publicitaria:** no se pudo consultar Meta Ads para este producto. La puntuaci\xF3n se apoya s\xF3lo en datos de venta."
|
|
9976
|
+
];
|
|
9977
|
+
}
|
|
9978
|
+
if (meta.status === "evidence_empty") {
|
|
9979
|
+
return [
|
|
9980
|
+
"- \u26A0\uFE0F **Sin evidencia publicitaria:** no se encontraron anuncios activos en Meta. Puede ser un mercado virgen o una b\xFAsqueda sin cobertura; la puntuaci\xF3n se apoya s\xF3lo en datos de venta."
|
|
9981
|
+
];
|
|
9982
|
+
}
|
|
9983
|
+
return [
|
|
9984
|
+
`- **Evidencia en Meta Ads:** ${meta.totalAds} anuncios de ${meta.distinctAdvertisers} anunciantes, m\xE1ximo ${meta.maxActiveDays} d\xEDas activos, oportunidad de \xE1ngulo ${meta.angleOpportunity}/100.`,
|
|
9985
|
+
meta.summary ? ` ${meta.summary}` : ""
|
|
9986
|
+
].filter(Boolean);
|
|
9987
|
+
}
|
|
9988
|
+
__name(renderMeta, "renderMeta");
|
|
9989
|
+
function renderCandidate(candidate, index) {
|
|
9990
|
+
const name = candidate.nameLocal ?? candidate.nameOriginal;
|
|
9991
|
+
const lines = [
|
|
9992
|
+
`### ${index + 1}. ${name}`,
|
|
9993
|
+
`**${candidate.score}/100 \xB7 ${candidate.label} \xB7 confianza ${candidate.confidence}**`
|
|
9994
|
+
];
|
|
9995
|
+
if (candidate.insight) lines.push("", candidate.insight);
|
|
9996
|
+
lines.push("", "**Por qu\xE9:**");
|
|
9997
|
+
lines.push(...candidate.reasons.map((reason) => `- ${reason}`));
|
|
9998
|
+
lines.push("", "**Riesgos:**");
|
|
9999
|
+
lines.push(...candidate.risks.length > 0 ? candidate.risks.map((risk) => `- ${risk}`) : [
|
|
10000
|
+
"- No se identificaron riesgos espec\xEDficos."
|
|
10001
|
+
]);
|
|
10002
|
+
lines.push("", ...renderMeta(candidate.meta));
|
|
10003
|
+
lines.push("", `- **Venta (30d):** ${num(candidate.units30d)} unidades \xB7 ${num(candidate.revenueUsd30d, " USD")} \xB7 crecimiento ${num(candidate.growthPct30d, "%")} \xB7 precio ${num(candidate.priceUsd, " USD")}`);
|
|
10004
|
+
if (candidate.productUrl) lines.push(`- [Ver producto](${candidate.productUrl})`);
|
|
10005
|
+
return lines.join("\n");
|
|
10006
|
+
}
|
|
10007
|
+
__name(renderCandidate, "renderCandidate");
|
|
10008
|
+
function renderResearchReport(run) {
|
|
10009
|
+
if (run.status === "failed" || run.status === "refunded") {
|
|
10010
|
+
return [
|
|
10011
|
+
`\u274C **La investigaci\xF3n no pudo completarse** (estado: ${run.status}).`,
|
|
10012
|
+
run.errorCode ? `C\xF3digo: \`${run.errorCode}\`` : "",
|
|
10013
|
+
run.refunded ? `Los ${run.priceCredits} cr\xE9ditos fueron reembolsados.` : "\u26A0\uFE0F No consta reembolso \u2014 rev\xEDsalo antes de reintentar."
|
|
10014
|
+
].filter(Boolean).join("\n");
|
|
10015
|
+
}
|
|
10016
|
+
if (run.status === "ready" && run.result === null) {
|
|
10017
|
+
return [
|
|
10018
|
+
`\u274C **No se pudo leer el reporte** (estado: ${run.status}).`,
|
|
10019
|
+
`El cargo de ${run.priceCredits} cr\xE9ditos ya se realiz\xF3, pero el reporte qued\xF3 corrupto y no puede recuperarse.`,
|
|
10020
|
+
`Contacta a soporte con este runId para revisar un reembolso: \`${run.id}\`.`
|
|
10021
|
+
].join("\n");
|
|
10022
|
+
}
|
|
10023
|
+
if (run.result === null) {
|
|
10024
|
+
return [
|
|
10025
|
+
`\u23F3 **La investigaci\xF3n sigue en curso** (estado: ${run.status}, etapa: ${run.progressStage}).`,
|
|
10026
|
+
`El cargo de ${run.priceCredits} cr\xE9ditos ya se realiz\xF3; el reporte no se pierde.`,
|
|
10027
|
+
`Cons\xFAltalo con \`get_product_research_run\` usando runId \`${run.id}\`.`
|
|
10028
|
+
].join("\n");
|
|
10029
|
+
}
|
|
10030
|
+
const { result } = run;
|
|
10031
|
+
const degraded = result.candidates.filter((candidate) => candidate.meta.status !== "evidence_available").length;
|
|
10032
|
+
const header = [
|
|
10033
|
+
`\u2705 **Productos ganadores \xB7 ${result.targetCountry} \xB7 ${result.categoryKey}**`,
|
|
10034
|
+
"",
|
|
10035
|
+
`Analizados ${result.methodology.kaloCandidateCount} candidatos del mercado ${result.sourceMarket}; ${result.methodology.metaCoveredCandidateCount} con cobertura de Meta Ads. Datos con ${num(result.freshnessHours, " h")} de antig\xFCedad.`
|
|
10036
|
+
];
|
|
10037
|
+
if (degraded > 0) {
|
|
10038
|
+
header.push("", `\u26A0\uFE0F ${degraded} de ${result.candidates.length} productos no tienen evidencia publicitaria. Su puntuaci\xF3n se apoya s\xF3lo en datos de venta.`);
|
|
10039
|
+
}
|
|
10040
|
+
return [
|
|
10041
|
+
...header,
|
|
10042
|
+
"",
|
|
10043
|
+
...result.candidates.map((candidate, index) => renderCandidate(candidate, index))
|
|
10044
|
+
].join("\n\n");
|
|
10045
|
+
}
|
|
10046
|
+
__name(renderResearchReport, "renderResearchReport");
|
|
10047
|
+
|
|
10048
|
+
// src/tools/product-research.ts
|
|
10049
|
+
var PRICE_CREDITS = 2e3;
|
|
10050
|
+
var TERMINAL2 = /* @__PURE__ */ new Set([
|
|
10051
|
+
"ready",
|
|
10052
|
+
"failed",
|
|
10053
|
+
"refunded"
|
|
10054
|
+
]);
|
|
10055
|
+
var POLL_INTERVAL_MS = 5e3;
|
|
10056
|
+
var MAX_WAIT_SECONDS = 120;
|
|
10057
|
+
var FindInput = z49.object({
|
|
10058
|
+
targetCountry: ResearchTargetCountryEnum.describe("Pa\xEDs objetivo. S\xF3lo CO, MX, PE, EC y CL tienen cobertura de investigaci\xF3n."),
|
|
10059
|
+
categoryKey: ResearchCategoryEnum.default("auto").describe('Categor\xEDa a investigar. Usa "auto" para que la IA elija seg\xFAn el mercado.'),
|
|
10060
|
+
confirmed: z49.boolean().describe(`Ponlo en true SOLO despu\xE9s de que el usuario haya aceptado el cargo de ${PRICE_CREDITS} cr\xE9ditos. No lo asumas.`),
|
|
10061
|
+
waitSeconds: z49.number().int().min(0).max(MAX_WAIT_SECONDS).optional().describe("Segundos a esperar el reporte antes de devolver el runId. M\xE1ximo 120.")
|
|
10062
|
+
});
|
|
10063
|
+
var GetInput = z49.object({
|
|
10064
|
+
runId: z49.string().uuid().describe("ID del run devuelto por find_winning_products.")
|
|
10065
|
+
});
|
|
10066
|
+
var ListInput = z49.object({
|
|
10067
|
+
limit: z49.number().int().min(1).max(20).default(10)
|
|
10068
|
+
});
|
|
10069
|
+
function textResult(text) {
|
|
10070
|
+
return {
|
|
10071
|
+
content: [
|
|
10072
|
+
{
|
|
10073
|
+
type: "text",
|
|
10074
|
+
text
|
|
10075
|
+
}
|
|
10076
|
+
]
|
|
10077
|
+
};
|
|
10078
|
+
}
|
|
10079
|
+
__name(textResult, "textResult");
|
|
10080
|
+
var sleep2 = /* @__PURE__ */ __name((ms) => new Promise((resolve3) => setTimeout(resolve3, ms)), "sleep");
|
|
10081
|
+
async function pollUntilTerminal(api, runId, waitSeconds) {
|
|
10082
|
+
const deadline = Date.now() + Math.min(waitSeconds, MAX_WAIT_SECONDS) * 1e3;
|
|
10083
|
+
let run = await api.getJson(`/v1/product-research/runs/${runId}`);
|
|
10084
|
+
while (!TERMINAL2.has(run.status) && Date.now() < deadline) {
|
|
10085
|
+
await sleep2(POLL_INTERVAL_MS);
|
|
10086
|
+
run = await api.getJson(`/v1/product-research/runs/${runId}`);
|
|
10087
|
+
}
|
|
10088
|
+
return run;
|
|
10089
|
+
}
|
|
10090
|
+
__name(pollUntilTerminal, "pollUntilTerminal");
|
|
10091
|
+
function registerProductResearchTools(server, api) {
|
|
10092
|
+
server.tool("find_winning_products", `Busca productos ganadores de dropshipping para un pa\xEDs y categor\xEDa, y explica POR QU\xC9 la IA eligi\xF3 cada uno (razones, riesgos y evidencia real de anuncios en Meta Ads). CUESTA ${PRICE_CREDITS} CR\xC9DITOS por ejecuci\xF3n. ANTES de invocar, dile al usuario el costo y espera su aceptaci\xF3n expl\xEDcita; s\xF3lo entonces pasa confirmed: true. Consulta primero list_product_research_runs (GRATIS) \u2014 puede que el usuario ya tenga un reporte pagado para ese pa\xEDs/categor\xEDa y no necesites cobrar de nuevo. Pa\xEDses disponibles: CO, MX, PE, EC, CL. Tarda varios minutos: usa waitSeconds para esperar, y si expira consulta el resultado con get_product_research_run \u2014 el reporte NO se pierde.`, FindInput.shape, async (input) => {
|
|
10093
|
+
const parsed = FindInput.parse(input);
|
|
10094
|
+
if (!parsed.confirmed) {
|
|
10095
|
+
throw mcp(McpErrorCode.INVALID_INPUT, `This research run costs ${PRICE_CREDITS} credits. Tell the user the cost, get an explicit yes, then call again with confirmed: true.`);
|
|
10096
|
+
}
|
|
10097
|
+
try {
|
|
10098
|
+
const idempotencyKey = deriveIdempotencyKey({
|
|
10099
|
+
fingerprint: api.fingerprint(),
|
|
10100
|
+
targetCountry: parsed.targetCountry,
|
|
10101
|
+
categoryKey: parsed.categoryKey,
|
|
10102
|
+
nowMs: Date.now()
|
|
10103
|
+
});
|
|
10104
|
+
const created = await api.postJson("/v1/product-research/runs", {
|
|
10105
|
+
targetCountry: parsed.targetCountry,
|
|
10106
|
+
categoryKey: parsed.categoryKey,
|
|
10107
|
+
idempotencyKey
|
|
10108
|
+
});
|
|
10109
|
+
const waitSeconds = parsed.waitSeconds ?? 0;
|
|
10110
|
+
let run = created;
|
|
10111
|
+
if (waitSeconds > 0 && !TERMINAL2.has(created.status)) {
|
|
10112
|
+
try {
|
|
10113
|
+
run = await pollUntilTerminal(api, created.id, waitSeconds);
|
|
10114
|
+
} catch {
|
|
10115
|
+
run = created;
|
|
10116
|
+
}
|
|
10117
|
+
}
|
|
10118
|
+
const notice = created.reused ? `
|
|
10119
|
+
|
|
10120
|
+
_Esta llamada no cobr\xF3 cr\xE9ditos: se reutiliz\xF3 una investigaci\xF3n reciente con los mismos par\xE1metros. Para investigar una categor\xEDa distinta, vuelve a llamar con un \`categoryKey\` expl\xEDcito \u2014 eso inicia una investigaci\xF3n NUEVA con un cargo NUEVO de ${PRICE_CREDITS} cr\xE9ditos, as\xED que pide confirmaci\xF3n expl\xEDcita al usuario antes de hacerlo._` : "";
|
|
10121
|
+
return textResult(`${renderResearchReport(run)}${notice}`);
|
|
10122
|
+
} catch (error) {
|
|
10123
|
+
throw translateError(error);
|
|
10124
|
+
}
|
|
10125
|
+
});
|
|
10126
|
+
server.tool("get_product_research_run", "Devuelve el reporte completo de una investigaci\xF3n de productos ganadores: score, veredicto, confianza, razones, riesgos y evidencia de Meta Ads por producto. GRATIS y read-only. \xDAsalo cuando find_winning_products expir\xF3 la espera, o para releer un reporte anterior encontrado con list_product_research_runs.", GetInput.shape, async (input) => {
|
|
10127
|
+
const parsed = GetInput.parse(input);
|
|
10128
|
+
try {
|
|
10129
|
+
const run = await api.getJson(`/v1/product-research/runs/${parsed.runId}`);
|
|
10130
|
+
return {
|
|
10131
|
+
content: [
|
|
10132
|
+
{
|
|
10133
|
+
type: "text",
|
|
10134
|
+
text: JSON.stringify(run)
|
|
10135
|
+
},
|
|
10136
|
+
{
|
|
10137
|
+
type: "text",
|
|
10138
|
+
text: renderResearchReport(run)
|
|
10139
|
+
}
|
|
10140
|
+
]
|
|
10141
|
+
};
|
|
10142
|
+
} catch (error) {
|
|
10143
|
+
throw translateError(error);
|
|
10144
|
+
}
|
|
10145
|
+
});
|
|
10146
|
+
server.tool("list_product_research_runs", "Lista las investigaciones de productos ganadores ya pagadas por el usuario, de la m\xE1s reciente a la m\xE1s antigua. GRATIS y read-only. Cons\xFAltalo ANTES de lanzar una nueva: un reporte ya pagado se relee gratis con get_product_research_run.", ListInput.shape, async (input) => {
|
|
10147
|
+
const parsed = ListInput.parse(input);
|
|
10148
|
+
try {
|
|
10149
|
+
const runs = await api.getJson(`/v1/product-research/runs?limit=${parsed.limit}`);
|
|
10150
|
+
return textResult(JSON.stringify(runs));
|
|
10151
|
+
} catch (error) {
|
|
10152
|
+
throw translateError(error);
|
|
10153
|
+
}
|
|
10154
|
+
});
|
|
10155
|
+
}
|
|
10156
|
+
__name(registerProductResearchTools, "registerProductResearchTools");
|
|
10157
|
+
|
|
9604
10158
|
// src/server.ts
|
|
9605
10159
|
function createServer(opts) {
|
|
9606
10160
|
const { apiKey, apiBaseUrl, proxyBaseUrl = null, transport = "http" } = opts;
|
|
@@ -9617,6 +10171,7 @@ function createServer(opts) {
|
|
|
9617
10171
|
});
|
|
9618
10172
|
const api = new ApiClient(apiBaseUrl, apiKey);
|
|
9619
10173
|
registerProductCostingTools(server);
|
|
10174
|
+
registerProductResearchTools(server, api);
|
|
9620
10175
|
registerTaskTools(server, api, {
|
|
9621
10176
|
proxyBaseUrl,
|
|
9622
10177
|
transport
|