@productmaker/mcp 8.13.1 → 9.0.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/README.md CHANGED
@@ -137,7 +137,7 @@ separate release step.
137
137
  | `get_task_status` | Check task progress (optional `waitSeconds` for near-sync polling) |
138
138
  | `list_tasks` | List recent tasks |
139
139
  | `edit_task_draft` | Edit angle / pain point / persona before generation |
140
- | `create_service_video` | Video for a _service_ built from its landing page URL. **Paid action** — `automatic` generates directly, `guided` returns concepts to approve via `resolve_pending_action` |
140
+ | `create_service_video` | **Hidden from the MCP since 9.0.0** (owner decision 2026-10-08, still on WhatsApp): use `create_animated_video` for a service video. |
141
141
  | `create_animated_video` | Animated _service_ video ("Video animado", motion graphics: text, shapes and the site's visual identity, music, no presenter) from its landing page URL. Choose `format` (vertical/square/horizontal), `durationSec` (10/15/30/45/60), `language` (es/en/pt) and optional `notes`. **Paid action** — flat price by duration (listed in the tool description, refunded on failure); takes minutes in a queue. Follow it with `get_task_status` (stage, queue position, render %, activity, preview, final MP4). In closed testing: other accounts get `FEATURE_NOT_AVAILABLE` |
142
142
  | `generate_video_creative` | Standalone UGC video creative |
143
143
  | `generate_free_video` | One free-form video from an exact prompt/script, optionally with a reference image (async: returns a `taskId`) |
@@ -390,7 +390,7 @@ Release metadata synchronized with per-store Meta controls. MCP tool inputs, res
390
390
 
391
391
  ### 8.3.0 — Idempotencia de tools pagadas + refresh OAuth
392
392
 
393
- - Tools pagadas (`create_product_task`, `create_service_video`, `create_animated_video` (sin `notes` en la llave), `generate_video_creative`, `generate_free_video`, `generate_image_creatives`, `generate_landing`, `create_creative_variant`): envían un `Idempotency-Key` derivado de los argumentos semánticos (ventana de 15 min). Un reintento con los mismos argumentos devuelve la misma tarea (`replayed: true`) sin segundo cargo; una tarea en `error` sí se reintenta.
393
+ - Tools pagadas (`create_product_task`, `create_animated_video` (sin `notes` en la llave), `generate_video_creative`, `generate_free_video`, `generate_image_creatives`, `generate_landing`, `create_creative_variant`): envían un `Idempotency-Key` derivado de los argumentos semánticos (ventana de 15 min). Un reintento con los mismos argumentos devuelve la misma tarea (`replayed: true`) sin segundo cargo; una tarea en `error` sí se reintenta.
394
394
  - OAuth alojado: se anuncia `offline_access` + grant `refresh_token`; un token a punto de expirar responde `401` + `WWW-Authenticate` para que el host refresque en silencio (`MCP_SUBJECT_TOKEN_REAUTH_WINDOW_MS`, por defecto 180000; `0` lo desactiva).
395
395
 
396
396
  ### 8.2.0 — Fase 3 (modelos)
package/dist/http.js CHANGED
@@ -13912,7 +13912,7 @@ var CAPABILITY_MODEL_GUIDANCE = {
13912
13912
  "mes en curso"
13913
13913
  ],
13914
13914
  disclosures: [
13915
- "sin publicidad leida la utilidad esta INCOMPLETA: dilo"
13915
+ "ventas, costos o publicidad desconocidos: utilidad INCOMPLETA"
13916
13916
  ]
13917
13917
  },
13918
13918
  sync_shopify_orders: {
@@ -13988,7 +13988,7 @@ var CAPABILITY_MODEL_GUIDANCE = {
13988
13988
  "es gasto de anuncios: usa set_ad_spend"
13989
13989
  ],
13990
13990
  defaults: [
13991
- "mes en curso; la misma etiqueta REEMPLAZA, no suma"
13991
+ "mes de la operaci\xF3n; misma etiqueta REEMPLAZA el total compartido y conserva el reparto"
13992
13992
  ],
13993
13993
  disclosures: [
13994
13994
  "requiere confirmaci\xF3n; sin gastos escritos la utilidad sale mas alta"
@@ -14318,6 +14318,73 @@ var LinkableCampaignsResponseSchema = z66.object({
14318
14318
 
14319
14319
  // ../../packages/types/src/finance-operation.ts
14320
14320
  import { z as z67 } from "zod";
14321
+
14322
+ // ../../packages/types/src/dropi-countries.ts
14323
+ var DROPI_COUNTRY_CODES = [
14324
+ "CO",
14325
+ "EC",
14326
+ "VE",
14327
+ "MX",
14328
+ "PE",
14329
+ "PA",
14330
+ "PY",
14331
+ "GT",
14332
+ "CL",
14333
+ "AR",
14334
+ "CR"
14335
+ ];
14336
+
14337
+ // ../../packages/types/src/finance-country-timezones.ts
14338
+ var FINANCE_COUNTRY_TIME_ZONES = Object.freeze({
14339
+ US: "America/New_York",
14340
+ CA: "America/Toronto",
14341
+ BZ: "America/Belize",
14342
+ SV: "America/El_Salvador",
14343
+ HN: "America/Tegucigalpa",
14344
+ NI: "America/Managua",
14345
+ CU: "America/Havana",
14346
+ DO: "America/Santo_Domingo",
14347
+ HT: "America/Port-au-Prince",
14348
+ JM: "America/Jamaica",
14349
+ PR: "America/Puerto_Rico",
14350
+ TT: "America/Port_of_Spain",
14351
+ BO: "America/La_Paz",
14352
+ BR: "America/Sao_Paulo",
14353
+ UY: "America/Montevideo",
14354
+ ES: "Europe/Madrid",
14355
+ FR: "Europe/Paris",
14356
+ DE: "Europe/Berlin",
14357
+ IT: "Europe/Rome",
14358
+ PT: "Europe/Lisbon",
14359
+ NL: "Europe/Amsterdam",
14360
+ BE: "Europe/Brussels",
14361
+ AT: "Europe/Vienna",
14362
+ IE: "Europe/Dublin",
14363
+ LU: "Europe/Luxembourg",
14364
+ GB: "Europe/London",
14365
+ CH: "Europe/Zurich",
14366
+ SE: "Europe/Stockholm",
14367
+ NO: "Europe/Oslo",
14368
+ DK: "Europe/Copenhagen",
14369
+ FI: "Europe/Helsinki",
14370
+ IS: "Atlantic/Reykjavik",
14371
+ PL: "Europe/Warsaw",
14372
+ CZ: "Europe/Prague",
14373
+ HU: "Europe/Budapest",
14374
+ RO: "Europe/Bucharest",
14375
+ BG: "Europe/Sofia",
14376
+ HR: "Europe/Zagreb",
14377
+ SI: "Europe/Ljubljana",
14378
+ SK: "Europe/Bratislava",
14379
+ GR: "Europe/Athens",
14380
+ MT: "Europe/Malta",
14381
+ CY: "Asia/Nicosia",
14382
+ EE: "Europe/Tallinn",
14383
+ LV: "Europe/Riga",
14384
+ LT: "Europe/Vilnius"
14385
+ });
14386
+
14387
+ // ../../packages/types/src/finance-operation.ts
14321
14388
  var CreateFinanceOperationSchema = z67.object({
14322
14389
  countryCode: z67.string().length(2).toUpperCase(),
14323
14390
  /** Cómo la llama ella. Vacío ⇒ se usa el nombre del país. */
@@ -14342,6 +14409,7 @@ var MonthlyPLQuerySchema = z67.object({
14342
14409
  var RESERVED_EXPENSE_LABEL_PREFIX = "__ad_spend__";
14343
14410
  var isReservedExpenseLabel = /* @__PURE__ */ __name((label) => label.trim().startsWith(RESERVED_EXPENSE_LABEL_PREFIX), "isReservedExpenseLabel");
14344
14411
  var SaveMonthlyExpenseSchema = z67.object({
14412
+ operationId: z67.string().uuid().optional(),
14345
14413
  month: z67.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional(),
14346
14414
  label: z67.string().trim().min(1).max(80).refine((label) => !isReservedExpenseLabel(label), {
14347
14415
  message: "reserved_expense_label"
@@ -14351,13 +14419,14 @@ var SaveMonthlyExpenseSchema = z67.object({
14351
14419
  allocations: z67.array(z67.object({
14352
14420
  operationId: z67.string().uuid(),
14353
14421
  sharePct: z67.number().int().min(1).max(100)
14354
- })).min(1).refine((allocations) => allocations.reduce((sum, one) => sum + one.sharePct, 0) === 100, {
14422
+ })).min(1).refine((allocations) => allocations.reduce((sum, one) => sum + one.sharePct, 0) <= 100, {
14355
14423
  message: "allocations_must_sum_to_100"
14356
14424
  }).refine((allocations) => new Set(allocations.map((one) => one.operationId)).size === allocations.length, {
14357
14425
  message: "duplicate_operation_in_allocations"
14358
14426
  }).optional()
14359
14427
  }).strict();
14360
14428
  var DeleteMonthlyExpenseSchema = z67.object({
14429
+ operationId: z67.string().uuid().optional(),
14361
14430
  month: z67.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/),
14362
14431
  label: z67.string().trim().min(1).max(80).refine((label) => !isReservedExpenseLabel(label), {
14363
14432
  message: "reserved_expense_label"
@@ -14369,6 +14438,7 @@ var SetAdSpendSchema = z67.object({
14369
14438
  amount: z67.number().finite().min(0).nullable()
14370
14439
  }).strict();
14371
14440
  var MonthlyExpensesQuerySchema = z67.object({
14441
+ operationId: z67.string().uuid().optional(),
14372
14442
  month: z67.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional()
14373
14443
  }).strict();
14374
14444
 
@@ -15274,44 +15344,58 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
15274
15344
  "product"
15275
15345
  ]
15276
15346
  ),
15277
- defineCapability("create_service_video", "c3d81a4f698e01658fa04ebb613a91c8ba43320d21cacda522e72d198036eb21", "credit_consuming", "creative", [
15347
+ defineCapability(
15348
+ "create_service_video",
15349
+ "c3d81a4f698e01658fa04ebb613a91c8ba43320d21cacda522e72d198036eb21",
15278
15350
  "credit_consuming",
15279
- "state_change"
15280
- ], [
15281
- slot("service_landing", true, [
15282
- "landingUrl"
15283
- ]),
15284
- slot("locale", true, [
15285
- "language"
15286
- ]),
15287
- slot("service_context", false, [
15288
- "additionalContext"
15289
- ]),
15290
- slot("voice", false, [
15291
- "voiceId"
15292
- ]),
15293
- slot("brand", false, [
15294
- "brandId"
15295
- ]),
15296
- slot("review_mode", false, [
15297
- "reviewMode"
15298
- ]),
15299
- slot("video_preset", false, [
15300
- "videoPreset"
15301
- ]),
15302
- // MCP-only for now: WhatsApp's service flow keeps the economy default.
15303
- slot("video_quality", false, [
15304
- "videoQuality"
15305
- ], []),
15306
- slot("video_resolution", false, [
15307
- "videoResolution"
15308
- ], [])
15309
- ], [
15310
- "service_context"
15311
- ], [
15312
- "task",
15313
- "video"
15314
- ]),
15351
+ "creative",
15352
+ [
15353
+ "credit_consuming",
15354
+ "state_change"
15355
+ ],
15356
+ [
15357
+ slot("service_landing", true, [
15358
+ "landingUrl"
15359
+ ]),
15360
+ slot("locale", true, [
15361
+ "language"
15362
+ ]),
15363
+ slot("service_context", false, [
15364
+ "additionalContext"
15365
+ ]),
15366
+ slot("voice", false, [
15367
+ "voiceId"
15368
+ ]),
15369
+ slot("brand", false, [
15370
+ "brandId"
15371
+ ]),
15372
+ slot("review_mode", false, [
15373
+ "reviewMode"
15374
+ ]),
15375
+ slot("video_preset", false, [
15376
+ "videoPreset"
15377
+ ]),
15378
+ // MCP-only for now: WhatsApp's service flow keeps the economy default.
15379
+ slot("video_quality", false, [
15380
+ "videoQuality"
15381
+ ], []),
15382
+ slot("video_resolution", false, [
15383
+ "videoResolution"
15384
+ ], [])
15385
+ ],
15386
+ [
15387
+ "service_context"
15388
+ ],
15389
+ [
15390
+ "task",
15391
+ "video"
15392
+ ],
15393
+ // Owner decision 2026-10-08: the presenter service video is hidden from the
15394
+ // MCP for now — dropshipping sellers ordered it for products and cancelled.
15395
+ // MCP clients get `create_animated_video` instead. Flip back to
15396
+ // SHARED_CHANNEL_EXPOSURE to restore it.
15397
+ WHATSAPP_ONLY_CHANNEL_EXPOSURE
15398
+ ),
15315
15399
  defineCapability("edit_image_creative", "50eca473829328dfb63b9cbae03a9835b79c510e29528563e09b7eb3e5e9bca4", "credit_consuming", "creative", [
15316
15400
  "credit_consuming",
15317
15401
  "state_change"
@@ -18066,10 +18150,16 @@ var RegenerateVideoScriptCapabilityRequest = z80.object({
18066
18150
 
18067
18151
  // ../../packages/types/src/dropi-financial.ts
18068
18152
  import { z as z81 } from "zod";
18153
+ var DropiCountrySchema = z81.enum(DROPI_COUNTRY_CODES);
18154
+ var DropiCountryQuerySchema = z81.object({
18155
+ countryCode: DropiCountrySchema.default("CO")
18156
+ }).strict();
18069
18157
  var ConnectDropiFinancialSchema = z81.object({
18158
+ countryCode: DropiCountrySchema.default("CO"),
18070
18159
  integrationKey: z81.string().trim().min(1).max(4096).regex(/^[\x21-\x7e]+$/)
18071
18160
  }).strict();
18072
18161
  var SyncDropiFinancialSchema = z81.object({
18162
+ countryCode: DropiCountrySchema.optional(),
18073
18163
  operationId: z81.string().uuid().optional(),
18074
18164
  since: z81.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
18075
18165
  until: z81.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional()
@@ -18077,7 +18167,8 @@ var SyncDropiFinancialSchema = z81.object({
18077
18167
  var DropiFinancialStatusSchema = z81.object({
18078
18168
  enabled: z81.boolean(),
18079
18169
  connected: z81.boolean(),
18080
- countryCode: z81.literal("CO"),
18170
+ countryCode: DropiCountrySchema,
18171
+ enabledCountries: z81.array(DropiCountrySchema).optional(),
18081
18172
  environment: z81.enum([
18082
18173
  "sandbox",
18083
18174
  "production"
@@ -19789,7 +19880,7 @@ var ApiClient = class {
19789
19880
  };
19790
19881
 
19791
19882
  // src/index.ts
19792
- var MCP_VERSION = "8.13.1";
19883
+ var MCP_VERSION = "9.0.1";
19793
19884
 
19794
19885
  // src/meta-upload-app.ts
19795
19886
  import { readFileSync } from "fs";
@@ -22338,7 +22429,7 @@ function renderAnimatedVideoStatus(result) {
22338
22429
  __name(renderAnimatedVideoStatus, "renderAnimatedVideoStatus");
22339
22430
  function registerAnimatedVideoTools(server, api) {
22340
22431
  server.registerTool("create_animated_video", {
22341
- description: guidance.summary + ` Hace un video animado (motion graphics) de un SERVICIO a partir del enlace de su p\xE1gina: textos, formas y la identidad visual del sitio, con m\xFAsica y efectos, sin presentador ni voz en off. Esta acci\xF3n consume cr\xE9ditos: confirma formato, duraci\xF3n, idioma y precio con el usuario antes de llamarla. Precio fijo por duraci\xF3n: ${describeAnimatedVideoPrices()}; si falla, se devuelve. Tarda varios minutos, m\xE1s si hay otros videos en la cola. Devuelve un taskId: sigue el avance con get_task_status (etapa, posici\xF3n en la cola, % de render, lo que va haciendo y vista previa) hasta done, que trae el enlace del MP4, o error, que trae el motivo. Para un video con presentador usa create_service_video. Puede no estar disponible para todas las cuentas: si responde que la funci\xF3n no est\xE1 disponible, dilo y no ofrezcas alternativas pagadas.`,
22432
+ description: guidance.summary + ` Hace un video animado (motion graphics) de un SERVICIO a partir del enlace de su p\xE1gina: textos, formas y la identidad visual del sitio, con m\xFAsica y efectos, sin presentador ni voz en off. Esta acci\xF3n consume cr\xE9ditos: confirma formato, duraci\xF3n, idioma y precio con el usuario antes de llamarla. Precio fijo por duraci\xF3n: ${describeAnimatedVideoPrices()}; si falla, se devuelve. Tarda varios minutos, m\xE1s si hay otros videos en la cola. Devuelve un taskId: sigue el avance con get_task_status (etapa, posici\xF3n en la cola, % de render, lo que va haciendo y vista previa) hasta done, que trae el enlace del MP4, o error, que trae el motivo. Es el \xFAnico video de servicio disponible por ahora: no ofrezcas un video con presentador. Puede no estar disponible para todas las cuentas: si responde que la funci\xF3n no est\xE1 disponible, dilo y no ofrezcas alternativas pagadas.`,
22342
22433
  inputSchema: CreateAnimatedVideoInput.shape,
22343
22434
  annotations: CREATES_PAID_OPEN_WORLD
22344
22435
  }, async (input) => {
@@ -22443,7 +22534,7 @@ function renderCreateProductTaskAvoidWhenForMcp(fragment) {
22443
22534
  __name(renderCreateProductTaskAvoidWhenForMcp, "renderCreateProductTaskAvoidWhenForMcp");
22444
22535
  var createProductTaskDefaultDescription = renderCreateProductTaskDefaultForMcp(createProductTaskGuidance.defaults[0]);
22445
22536
  var createProductTaskSingleOutputDescription = renderCreateProductTaskAvoidWhenForMcp(createProductTaskGuidance.avoidWhen[0]);
22446
- var createServiceVideoGuidance = getProductMakerCapabilityModelGuidance("create_service_video");
22537
+ var CREATE_SERVICE_VIDEO_ON_MCP = PRODUCTMAKER_MCP_CAPABILITY_NAMES.includes("create_service_video");
22447
22538
  var listTasksGuidance = getProductMakerCapabilityModelGuidance("list_tasks");
22448
22539
  var editTaskDraftGuidance = getProductMakerCapabilityModelGuidance("edit_task_draft");
22449
22540
  var CreateProductTaskInput = z87.object({
@@ -22878,7 +22969,7 @@ async function fetchAnimatedVideoResult(api, taskId, signal, serviceMode = void
22878
22969
  }
22879
22970
  }
22880
22971
  __name(fetchAnimatedVideoResult, "fetchAnimatedVideoResult");
22881
- function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http" } = {}) {
22972
+ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http", serviceVideo = CREATE_SERVICE_VIDEO_ON_MCP } = {}) {
22882
22973
  server.registerTool("create_product_task", {
22883
22974
  description: createProductTaskGuidance.summary + ` ${createProductTaskDefaultDescription} \xDAsala cuando el usuario pide todo o varios outputs, no creando tareas individuales. Usa images para solicitudes directas. Usa projectId \xFAnicamente si el usuario pidi\xF3 expl\xEDcitamente continuar el proyecto guiado que acaba de costear; nunca busques, infieras ni menciones proyectos previos.
22884
22975
 
@@ -23001,97 +23092,100 @@ ANTES de invocar, resume outputs, cantidad de videos y datos comerciales, explic
23001
23092
  throw translateError(e);
23002
23093
  }
23003
23094
  });
23004
- server.registerTool("create_service_video", {
23005
- description: createServiceVideoGuidance.summary + " Esta acci\xF3n consume cr\xE9ditos: confirma la intenci\xF3n del usuario antes de llamarla. `automatic` genera directamente; `guided` devuelve conceptos para que el usuario apruebe uno mediante `resolve_pending_action`. La web tambi\xE9n acepta demos y anuncios de referencia; el transporte MCP de video adjunto todav\xEDa falla cerrado hasta poder subir bytes de forma segura.",
23006
- inputSchema: CreateServiceVideoInput.shape,
23007
- annotations: CREATES_PAID_OPEN_WORLD
23008
- }, async (input) => {
23009
- try {
23010
- if (input.videoQuality === "medium" && input.videoResolution === "720p") {
23011
- throw mcp(McpErrorCode.INVALID_INPUT, 'videoResolution "720p" no est\xE1 disponible con videoQuality "medium" en videos de servicio (Medio = 768P). Usa "480p" o elige "economy"/"pro".');
23012
- }
23013
- await requirePaidCreativeConfirmation(server, `Video de servicio en ${input.language}. Modo: ${input.reviewMode === "automatic" ? "la IA decide todo" : "reviews conversacionales"}.`);
23014
- const created = await api.postJson("/v2/ingest", {
23015
- taskType: "service",
23016
- serviceLandingUrl: input.landingUrl,
23017
- ...input.additionalContext ? {
23018
- additionalContext: input.additionalContext
23019
- } : {},
23020
- lang: input.language,
23021
- ...input.voiceId ? {
23022
- voiceId: input.voiceId
23023
- } : {},
23024
- videoPreset: input.videoPreset,
23025
- videoQuality: input.videoQuality,
23026
- videoResolution: input.videoResolution,
23027
- reviewMode: input.reviewMode,
23028
- ...input.brandId ? {
23029
- brandId: input.brandId
23030
- } : {},
23031
- media: {
23032
- assets: []
23033
- },
23034
- appDemoVideos: []
23035
- }, {
23036
- // The 2026-09-25 incident: four retries of this exact tool created
23037
- // four charged service tasks because the host rephrased
23038
- // `additionalContext` and flipped the landing between `/` and
23039
- // `/es`. The derived key is stable across both rewrites (see
23040
- // request-key.ts).
23041
- idempotencyKey: derivePaidRequestKey({
23042
- fingerprint: api.fingerprint(),
23043
- tool: "create_service_video",
23044
- input,
23045
- nowMs: Date.now()
23046
- })
23047
- });
23048
- const angles = await api.postJson(`/v1/tasks/${encodeURIComponent(created.taskId)}/service-angles`, {});
23049
- const first = angles[0];
23050
- const replayed = created.replayed === true;
23051
- if (input.reviewMode === "automatic") {
23052
- if (!first) throw new Error("ProductMaker no pudo crear un concepto para este servicio.");
23053
- if (!(replayed && created.status !== "queued")) {
23054
- await api.postJson(`/v1/tasks/${encodeURIComponent(created.taskId)}/service-angles/select`, {
23055
- angleIds: [
23056
- first.id
23057
- ]
23095
+ if (serviceVideo) {
23096
+ const createServiceVideoGuidance = getProductMakerCapabilityModelGuidance("create_service_video");
23097
+ server.registerTool("create_service_video", {
23098
+ description: createServiceVideoGuidance.summary + " Esta acci\xF3n consume cr\xE9ditos: confirma la intenci\xF3n del usuario antes de llamarla. `automatic` genera directamente; `guided` devuelve conceptos para que el usuario apruebe uno mediante `resolve_pending_action`. La web tambi\xE9n acepta demos y anuncios de referencia; el transporte MCP de video adjunto todav\xEDa falla cerrado hasta poder subir bytes de forma segura.",
23099
+ inputSchema: CreateServiceVideoInput.shape,
23100
+ annotations: CREATES_PAID_OPEN_WORLD
23101
+ }, async (input) => {
23102
+ try {
23103
+ if (input.videoQuality === "medium" && input.videoResolution === "720p") {
23104
+ throw mcp(McpErrorCode.INVALID_INPUT, 'videoResolution "720p" no est\xE1 disponible con videoQuality "medium" en videos de servicio (Medio = 768P). Usa "480p" o elige "economy"/"pro".');
23105
+ }
23106
+ await requirePaidCreativeConfirmation(server, `Video de servicio en ${input.language}. Modo: ${input.reviewMode === "automatic" ? "la IA decide todo" : "reviews conversacionales"}.`);
23107
+ const created = await api.postJson("/v2/ingest", {
23108
+ taskType: "service",
23109
+ serviceLandingUrl: input.landingUrl,
23110
+ ...input.additionalContext ? {
23111
+ additionalContext: input.additionalContext
23112
+ } : {},
23113
+ lang: input.language,
23114
+ ...input.voiceId ? {
23115
+ voiceId: input.voiceId
23116
+ } : {},
23117
+ videoPreset: input.videoPreset,
23118
+ videoQuality: input.videoQuality,
23119
+ videoResolution: input.videoResolution,
23120
+ reviewMode: input.reviewMode,
23121
+ ...input.brandId ? {
23122
+ brandId: input.brandId
23123
+ } : {},
23124
+ media: {
23125
+ assets: []
23126
+ },
23127
+ appDemoVideos: []
23128
+ }, {
23129
+ // The 2026-09-25 incident: four retries of this exact tool created
23130
+ // four charged service tasks because the host rephrased
23131
+ // `additionalContext` and flipped the landing between `/` and
23132
+ // `/es`. The derived key is stable across both rewrites (see
23133
+ // request-key.ts).
23134
+ idempotencyKey: derivePaidRequestKey({
23135
+ fingerprint: api.fingerprint(),
23136
+ tool: "create_service_video",
23137
+ input,
23138
+ nowMs: Date.now()
23139
+ })
23140
+ });
23141
+ const angles = await api.postJson(`/v1/tasks/${encodeURIComponent(created.taskId)}/service-angles`, {});
23142
+ const first = angles[0];
23143
+ const replayed = created.replayed === true;
23144
+ if (input.reviewMode === "automatic") {
23145
+ if (!first) throw new Error("ProductMaker no pudo crear un concepto para este servicio.");
23146
+ if (!(replayed && created.status !== "queued")) {
23147
+ await api.postJson(`/v1/tasks/${encodeURIComponent(created.taskId)}/service-angles/select`, {
23148
+ angleIds: [
23149
+ first.id
23150
+ ]
23151
+ });
23152
+ }
23153
+ return asText({
23154
+ taskId: created.taskId,
23155
+ status: replayed ? created.status ?? "processing" : "processing",
23156
+ ...replayed ? {
23157
+ replayed: true
23158
+ } : {},
23159
+ reviewMode: input.reviewMode,
23160
+ selectedConcept: first
23161
+ });
23162
+ }
23163
+ if (replayed && created.status !== "queued") {
23164
+ return asText({
23165
+ taskId: created.taskId,
23166
+ status: created.status,
23167
+ replayed: true,
23168
+ reviewMode: input.reviewMode,
23169
+ angles
23058
23170
  });
23059
23171
  }
23060
23172
  return asText({
23061
23173
  taskId: created.taskId,
23062
- status: replayed ? created.status ?? "processing" : "processing",
23174
+ status: "waiting_for_user",
23063
23175
  ...replayed ? {
23064
23176
  replayed: true
23065
23177
  } : {},
23066
23178
  reviewMode: input.reviewMode,
23067
- selectedConcept: first
23068
- });
23069
- }
23070
- if (replayed && created.status !== "queued") {
23071
- return asText({
23072
- taskId: created.taskId,
23073
- status: created.status,
23074
- replayed: true,
23075
- reviewMode: input.reviewMode,
23076
- angles
23179
+ pendingUserAction: {
23180
+ kind: "service_angle_approval",
23181
+ angles
23182
+ }
23077
23183
  });
23184
+ } catch (e) {
23185
+ throw translateError(e);
23078
23186
  }
23079
- return asText({
23080
- taskId: created.taskId,
23081
- status: "waiting_for_user",
23082
- ...replayed ? {
23083
- replayed: true
23084
- } : {},
23085
- reviewMode: input.reviewMode,
23086
- pendingUserAction: {
23087
- kind: "service_angle_approval",
23088
- angles
23089
- }
23090
- });
23091
- } catch (e) {
23092
- throw translateError(e);
23093
- }
23094
- });
23187
+ });
23188
+ }
23095
23189
  server.registerTool("verify_landing_commerce", {
23096
23190
  description: getProductMakerCapabilityModelGuidance("verify_landing_commerce").summary + " Verifica una correcci\xF3n manual de precios, tachados o packs de una landing editable existente. Solo relee Shopify y los recibos externos; no cambia precios, inventario, SKU ni compra una edici\xF3n. Pasa taskId y editId de la edici\xF3n ya conocida; nunca inventes IDs ni pidas repetir el cambio. Devuelve el estado can\xF3nico por proveedor: conserva el aviso pendiente hasta confirmar los valores reales. Solo despu\xE9s de confirmar todos los valores, el servidor puede volver a habilitar la compra. Web guardada no demuestra publicaci\xF3n p\xFAblica ni checkout; muestra las correcciones y enlaces pendientes sin c\xF3digos t\xE9cnicos. La disponibilidad del editor conserva sus permisos actuales.",
23097
23191
  inputSchema: {
@@ -27441,7 +27535,7 @@ function registerProductControlTools(server, api, { transport = "http" } = {}) {
27441
27535
  }
27442
27536
  });
27443
27537
  server.registerTool("get_finances", {
27444
- description: getFinancesGuidance.summary + ' Contesta la pregunta que de verdad importa --- \xABeste negocio gana plata?\xBB --- con el mes armado: ventas totales y entregadas, fletes, costo de los productos, utilidad bruta, publicidad, gastos fijos y utilidad NETA. Esa utilidad es ANTES de impuestos y hay que decirlo asi: el contador es un gasto mas, no un calculo nuestro. Trae tambien los porcentajes contra sus metas. Si `costsKnown` viene en `false`, esos pedidos llegaron de una fuente que NO sabe cuanto costaron \u2014la tienda, o la API del proveedor\u2014 y la utilidad BRUTA esta incompleta: no es que el producto salga gratis. Si `adSpendKnown` viene en `false`, la publicidad NO se pudo leer y la utilidad esta INCOMPLETA: dilo antes de que alguien celebre un mes que perdio plata. `adSpendSource: "user"` significa que ella corrigio la cifra a mano, y eso se revisa distinto de una leida. Si `unconfirmedExpenses` es mayor que cero, ese monto viene heredado del mes pasado y ella no lo ha revisado. Es READ-ONLY: nunca pausa ni modifica una campana.',
27538
+ description: getFinancesGuidance.summary + ' Contesta la pregunta que de verdad importa --- \xABeste negocio gana plata?\xBB --- con el mes armado: ventas totales y entregadas, fletes, costo de los productos, utilidad bruta, publicidad, gastos fijos y utilidad NETA. Esa utilidad es ANTES de impuestos y hay que decirlo asi: el contador es un gasto mas, no un calculo nuestro. Trae tambien los porcentajes contra sus metas. Si `deliveredSalesKnown` no es true, faltan importes de ventas entregadas: la utilidad y los m\xE1rgenes no est\xE1n completos. `grossSalesKnown` distinto de true indica ventas totales parciales. Si `costsKnown` viene en `false`, esos pedidos llegaron de una fuente que NO sabe cuanto costaron \u2014la tienda, o la API del proveedor\u2014 y la utilidad BRUTA esta incompleta: no es que el producto salga gratis. Si `adSpendKnown` viene en `false`, la publicidad NO se pudo leer y la utilidad esta INCOMPLETA: dilo antes de que alguien celebre un mes que perdio plata. `adSpendSource: "user"` significa que ella corrigio la cifra a mano, y eso se revisa distinto de una leida. Si `unconfirmedExpenses` es mayor que cero, ese monto viene heredado del mes pasado y ella no lo ha revisado. Es READ-ONLY: nunca pausa ni modifica una campana.',
27445
27539
  inputSchema: GetFinancesInput.shape,
27446
27540
  annotations: READS_INTERNAL
27447
27541
  }, async (input) => {
@@ -27528,23 +27622,16 @@ function registerProductControlTools(server, api, { transport = "http" } = {}) {
27528
27622
  }
27529
27623
  });
27530
27624
  server.registerTool("set_monthly_expense", {
27531
- description: setMonthlyExpenseGuidance.summary + " Es un gasto FIJO: arriendo, sueldos, contador, plataformas \u2014 lo que se paga venda o no. Escribir la misma etiqueta otra vez REEMPLAZA el monto, no suma otro gasto. Sin gastos fijos escritos, la utilidad del mes sale siempre M\xC1S ALTA de lo que es, y ese es el error que nadie reclama. No lo uses para el gasto en anuncios: eso es set_ad_spend.",
27625
+ description: setMonthlyExpenseGuidance.summary + " El importe indicado es el TOTAL del gasto compartido existente; la parte de la operaci\xF3n se calcula conservando el reparto. Es un gasto FIJO: arriendo, sueldos, contador, plataformas \u2014 lo que se paga venda o no. Escribir la misma etiqueta otra vez REEMPLAZA el monto, no suma otro gasto. Sin gastos fijos escritos, la utilidad del mes sale siempre M\xC1S ALTA de lo que es, y ese es el error que nadie reclama. No lo uses para el gasto en anuncios: eso es set_ad_spend.",
27532
27626
  inputSchema: SetMonthlyExpenseInput.shape,
27533
27627
  annotations: OVERWRITES_INTERNAL
27534
27628
  }, async (input) => {
27535
27629
  try {
27536
- const month = input.month ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 7);
27537
- const operationId = input.operationId ?? (await api.getJson("/v1/product-control/operations"))[0]?.id;
27538
27630
  return asText(await api.putJson("/v1/product-control/expenses", {
27539
- month,
27631
+ month: input.month,
27632
+ operationId: input.operationId,
27540
27633
  label: input.label,
27541
- amount: input.amount,
27542
- allocations: operationId ? [
27543
- {
27544
- operationId,
27545
- sharePct: 100
27546
- }
27547
- ] : void 0
27634
+ amount: input.amount
27548
27635
  }));
27549
27636
  } catch (error) {
27550
27637
  throw translateError(error);
@@ -27557,7 +27644,7 @@ function registerProductControlTools(server, api, { transport = "http" } = {}) {
27557
27644
  }, async (input) => {
27558
27645
  try {
27559
27646
  return asText(await api.putJson("/v1/product-control/ad-spend", {
27560
- month: input.month ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 7),
27647
+ month: input.month,
27561
27648
  operationId: input.operationId,
27562
27649
  amount: input.amount
27563
27650
  }));
package/dist/stdio.js CHANGED
@@ -13934,7 +13934,7 @@ var CAPABILITY_MODEL_GUIDANCE = {
13934
13934
  "mes en curso"
13935
13935
  ],
13936
13936
  disclosures: [
13937
- "sin publicidad leida la utilidad esta INCOMPLETA: dilo"
13937
+ "ventas, costos o publicidad desconocidos: utilidad INCOMPLETA"
13938
13938
  ]
13939
13939
  },
13940
13940
  sync_shopify_orders: {
@@ -14010,7 +14010,7 @@ var CAPABILITY_MODEL_GUIDANCE = {
14010
14010
  "es gasto de anuncios: usa set_ad_spend"
14011
14011
  ],
14012
14012
  defaults: [
14013
- "mes en curso; la misma etiqueta REEMPLAZA, no suma"
14013
+ "mes de la operaci\xF3n; misma etiqueta REEMPLAZA el total compartido y conserva el reparto"
14014
14014
  ],
14015
14015
  disclosures: [
14016
14016
  "requiere confirmaci\xF3n; sin gastos escritos la utilidad sale mas alta"
@@ -14340,6 +14340,73 @@ var LinkableCampaignsResponseSchema = z66.object({
14340
14340
 
14341
14341
  // ../../packages/types/src/finance-operation.ts
14342
14342
  import { z as z67 } from "zod";
14343
+
14344
+ // ../../packages/types/src/dropi-countries.ts
14345
+ var DROPI_COUNTRY_CODES = [
14346
+ "CO",
14347
+ "EC",
14348
+ "VE",
14349
+ "MX",
14350
+ "PE",
14351
+ "PA",
14352
+ "PY",
14353
+ "GT",
14354
+ "CL",
14355
+ "AR",
14356
+ "CR"
14357
+ ];
14358
+
14359
+ // ../../packages/types/src/finance-country-timezones.ts
14360
+ var FINANCE_COUNTRY_TIME_ZONES = Object.freeze({
14361
+ US: "America/New_York",
14362
+ CA: "America/Toronto",
14363
+ BZ: "America/Belize",
14364
+ SV: "America/El_Salvador",
14365
+ HN: "America/Tegucigalpa",
14366
+ NI: "America/Managua",
14367
+ CU: "America/Havana",
14368
+ DO: "America/Santo_Domingo",
14369
+ HT: "America/Port-au-Prince",
14370
+ JM: "America/Jamaica",
14371
+ PR: "America/Puerto_Rico",
14372
+ TT: "America/Port_of_Spain",
14373
+ BO: "America/La_Paz",
14374
+ BR: "America/Sao_Paulo",
14375
+ UY: "America/Montevideo",
14376
+ ES: "Europe/Madrid",
14377
+ FR: "Europe/Paris",
14378
+ DE: "Europe/Berlin",
14379
+ IT: "Europe/Rome",
14380
+ PT: "Europe/Lisbon",
14381
+ NL: "Europe/Amsterdam",
14382
+ BE: "Europe/Brussels",
14383
+ AT: "Europe/Vienna",
14384
+ IE: "Europe/Dublin",
14385
+ LU: "Europe/Luxembourg",
14386
+ GB: "Europe/London",
14387
+ CH: "Europe/Zurich",
14388
+ SE: "Europe/Stockholm",
14389
+ NO: "Europe/Oslo",
14390
+ DK: "Europe/Copenhagen",
14391
+ FI: "Europe/Helsinki",
14392
+ IS: "Atlantic/Reykjavik",
14393
+ PL: "Europe/Warsaw",
14394
+ CZ: "Europe/Prague",
14395
+ HU: "Europe/Budapest",
14396
+ RO: "Europe/Bucharest",
14397
+ BG: "Europe/Sofia",
14398
+ HR: "Europe/Zagreb",
14399
+ SI: "Europe/Ljubljana",
14400
+ SK: "Europe/Bratislava",
14401
+ GR: "Europe/Athens",
14402
+ MT: "Europe/Malta",
14403
+ CY: "Asia/Nicosia",
14404
+ EE: "Europe/Tallinn",
14405
+ LV: "Europe/Riga",
14406
+ LT: "Europe/Vilnius"
14407
+ });
14408
+
14409
+ // ../../packages/types/src/finance-operation.ts
14343
14410
  var CreateFinanceOperationSchema = z67.object({
14344
14411
  countryCode: z67.string().length(2).toUpperCase(),
14345
14412
  /** Cómo la llama ella. Vacío ⇒ se usa el nombre del país. */
@@ -14364,6 +14431,7 @@ var MonthlyPLQuerySchema = z67.object({
14364
14431
  var RESERVED_EXPENSE_LABEL_PREFIX = "__ad_spend__";
14365
14432
  var isReservedExpenseLabel = /* @__PURE__ */ __name((label) => label.trim().startsWith(RESERVED_EXPENSE_LABEL_PREFIX), "isReservedExpenseLabel");
14366
14433
  var SaveMonthlyExpenseSchema = z67.object({
14434
+ operationId: z67.string().uuid().optional(),
14367
14435
  month: z67.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional(),
14368
14436
  label: z67.string().trim().min(1).max(80).refine((label) => !isReservedExpenseLabel(label), {
14369
14437
  message: "reserved_expense_label"
@@ -14373,13 +14441,14 @@ var SaveMonthlyExpenseSchema = z67.object({
14373
14441
  allocations: z67.array(z67.object({
14374
14442
  operationId: z67.string().uuid(),
14375
14443
  sharePct: z67.number().int().min(1).max(100)
14376
- })).min(1).refine((allocations) => allocations.reduce((sum, one) => sum + one.sharePct, 0) === 100, {
14444
+ })).min(1).refine((allocations) => allocations.reduce((sum, one) => sum + one.sharePct, 0) <= 100, {
14377
14445
  message: "allocations_must_sum_to_100"
14378
14446
  }).refine((allocations) => new Set(allocations.map((one) => one.operationId)).size === allocations.length, {
14379
14447
  message: "duplicate_operation_in_allocations"
14380
14448
  }).optional()
14381
14449
  }).strict();
14382
14450
  var DeleteMonthlyExpenseSchema = z67.object({
14451
+ operationId: z67.string().uuid().optional(),
14383
14452
  month: z67.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/),
14384
14453
  label: z67.string().trim().min(1).max(80).refine((label) => !isReservedExpenseLabel(label), {
14385
14454
  message: "reserved_expense_label"
@@ -14391,6 +14460,7 @@ var SetAdSpendSchema = z67.object({
14391
14460
  amount: z67.number().finite().min(0).nullable()
14392
14461
  }).strict();
14393
14462
  var MonthlyExpensesQuerySchema = z67.object({
14463
+ operationId: z67.string().uuid().optional(),
14394
14464
  month: z67.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional()
14395
14465
  }).strict();
14396
14466
 
@@ -15296,44 +15366,58 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
15296
15366
  "product"
15297
15367
  ]
15298
15368
  ),
15299
- defineCapability("create_service_video", "c3d81a4f698e01658fa04ebb613a91c8ba43320d21cacda522e72d198036eb21", "credit_consuming", "creative", [
15369
+ defineCapability(
15370
+ "create_service_video",
15371
+ "c3d81a4f698e01658fa04ebb613a91c8ba43320d21cacda522e72d198036eb21",
15300
15372
  "credit_consuming",
15301
- "state_change"
15302
- ], [
15303
- slot("service_landing", true, [
15304
- "landingUrl"
15305
- ]),
15306
- slot("locale", true, [
15307
- "language"
15308
- ]),
15309
- slot("service_context", false, [
15310
- "additionalContext"
15311
- ]),
15312
- slot("voice", false, [
15313
- "voiceId"
15314
- ]),
15315
- slot("brand", false, [
15316
- "brandId"
15317
- ]),
15318
- slot("review_mode", false, [
15319
- "reviewMode"
15320
- ]),
15321
- slot("video_preset", false, [
15322
- "videoPreset"
15323
- ]),
15324
- // MCP-only for now: WhatsApp's service flow keeps the economy default.
15325
- slot("video_quality", false, [
15326
- "videoQuality"
15327
- ], []),
15328
- slot("video_resolution", false, [
15329
- "videoResolution"
15330
- ], [])
15331
- ], [
15332
- "service_context"
15333
- ], [
15334
- "task",
15335
- "video"
15336
- ]),
15373
+ "creative",
15374
+ [
15375
+ "credit_consuming",
15376
+ "state_change"
15377
+ ],
15378
+ [
15379
+ slot("service_landing", true, [
15380
+ "landingUrl"
15381
+ ]),
15382
+ slot("locale", true, [
15383
+ "language"
15384
+ ]),
15385
+ slot("service_context", false, [
15386
+ "additionalContext"
15387
+ ]),
15388
+ slot("voice", false, [
15389
+ "voiceId"
15390
+ ]),
15391
+ slot("brand", false, [
15392
+ "brandId"
15393
+ ]),
15394
+ slot("review_mode", false, [
15395
+ "reviewMode"
15396
+ ]),
15397
+ slot("video_preset", false, [
15398
+ "videoPreset"
15399
+ ]),
15400
+ // MCP-only for now: WhatsApp's service flow keeps the economy default.
15401
+ slot("video_quality", false, [
15402
+ "videoQuality"
15403
+ ], []),
15404
+ slot("video_resolution", false, [
15405
+ "videoResolution"
15406
+ ], [])
15407
+ ],
15408
+ [
15409
+ "service_context"
15410
+ ],
15411
+ [
15412
+ "task",
15413
+ "video"
15414
+ ],
15415
+ // Owner decision 2026-10-08: the presenter service video is hidden from the
15416
+ // MCP for now — dropshipping sellers ordered it for products and cancelled.
15417
+ // MCP clients get `create_animated_video` instead. Flip back to
15418
+ // SHARED_CHANNEL_EXPOSURE to restore it.
15419
+ WHATSAPP_ONLY_CHANNEL_EXPOSURE
15420
+ ),
15337
15421
  defineCapability("edit_image_creative", "50eca473829328dfb63b9cbae03a9835b79c510e29528563e09b7eb3e5e9bca4", "credit_consuming", "creative", [
15338
15422
  "credit_consuming",
15339
15423
  "state_change"
@@ -17834,10 +17918,16 @@ var RegenerateVideoScriptCapabilityRequest = z80.object({
17834
17918
 
17835
17919
  // ../../packages/types/src/dropi-financial.ts
17836
17920
  import { z as z81 } from "zod";
17921
+ var DropiCountrySchema = z81.enum(DROPI_COUNTRY_CODES);
17922
+ var DropiCountryQuerySchema = z81.object({
17923
+ countryCode: DropiCountrySchema.default("CO")
17924
+ }).strict();
17837
17925
  var ConnectDropiFinancialSchema = z81.object({
17926
+ countryCode: DropiCountrySchema.default("CO"),
17838
17927
  integrationKey: z81.string().trim().min(1).max(4096).regex(/^[\x21-\x7e]+$/)
17839
17928
  }).strict();
17840
17929
  var SyncDropiFinancialSchema = z81.object({
17930
+ countryCode: DropiCountrySchema.optional(),
17841
17931
  operationId: z81.string().uuid().optional(),
17842
17932
  since: z81.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
17843
17933
  until: z81.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional()
@@ -17845,7 +17935,8 @@ var SyncDropiFinancialSchema = z81.object({
17845
17935
  var DropiFinancialStatusSchema = z81.object({
17846
17936
  enabled: z81.boolean(),
17847
17937
  connected: z81.boolean(),
17848
- countryCode: z81.literal("CO"),
17938
+ countryCode: DropiCountrySchema,
17939
+ enabledCountries: z81.array(DropiCountrySchema).optional(),
17849
17940
  environment: z81.enum([
17850
17941
  "sandbox",
17851
17942
  "production"
@@ -18121,7 +18212,7 @@ var ApiClient = class {
18121
18212
  };
18122
18213
 
18123
18214
  // src/index.ts
18124
- var MCP_VERSION = "8.13.1";
18215
+ var MCP_VERSION = "9.0.1";
18125
18216
 
18126
18217
  // src/meta-upload-app.ts
18127
18218
  import { readFileSync } from "fs";
@@ -20723,7 +20814,7 @@ function renderAnimatedVideoStatus(result) {
20723
20814
  __name(renderAnimatedVideoStatus, "renderAnimatedVideoStatus");
20724
20815
  function registerAnimatedVideoTools(server, api) {
20725
20816
  server.registerTool("create_animated_video", {
20726
- description: guidance.summary + ` Hace un video animado (motion graphics) de un SERVICIO a partir del enlace de su p\xE1gina: textos, formas y la identidad visual del sitio, con m\xFAsica y efectos, sin presentador ni voz en off. Esta acci\xF3n consume cr\xE9ditos: confirma formato, duraci\xF3n, idioma y precio con el usuario antes de llamarla. Precio fijo por duraci\xF3n: ${describeAnimatedVideoPrices()}; si falla, se devuelve. Tarda varios minutos, m\xE1s si hay otros videos en la cola. Devuelve un taskId: sigue el avance con get_task_status (etapa, posici\xF3n en la cola, % de render, lo que va haciendo y vista previa) hasta done, que trae el enlace del MP4, o error, que trae el motivo. Para un video con presentador usa create_service_video. Puede no estar disponible para todas las cuentas: si responde que la funci\xF3n no est\xE1 disponible, dilo y no ofrezcas alternativas pagadas.`,
20817
+ description: guidance.summary + ` Hace un video animado (motion graphics) de un SERVICIO a partir del enlace de su p\xE1gina: textos, formas y la identidad visual del sitio, con m\xFAsica y efectos, sin presentador ni voz en off. Esta acci\xF3n consume cr\xE9ditos: confirma formato, duraci\xF3n, idioma y precio con el usuario antes de llamarla. Precio fijo por duraci\xF3n: ${describeAnimatedVideoPrices()}; si falla, se devuelve. Tarda varios minutos, m\xE1s si hay otros videos en la cola. Devuelve un taskId: sigue el avance con get_task_status (etapa, posici\xF3n en la cola, % de render, lo que va haciendo y vista previa) hasta done, que trae el enlace del MP4, o error, que trae el motivo. Es el \xFAnico video de servicio disponible por ahora: no ofrezcas un video con presentador. Puede no estar disponible para todas las cuentas: si responde que la funci\xF3n no est\xE1 disponible, dilo y no ofrezcas alternativas pagadas.`,
20727
20818
  inputSchema: CreateAnimatedVideoInput.shape,
20728
20819
  annotations: CREATES_PAID_OPEN_WORLD
20729
20820
  }, async (input) => {
@@ -20828,7 +20919,7 @@ function renderCreateProductTaskAvoidWhenForMcp(fragment) {
20828
20919
  __name(renderCreateProductTaskAvoidWhenForMcp, "renderCreateProductTaskAvoidWhenForMcp");
20829
20920
  var createProductTaskDefaultDescription = renderCreateProductTaskDefaultForMcp(createProductTaskGuidance.defaults[0]);
20830
20921
  var createProductTaskSingleOutputDescription = renderCreateProductTaskAvoidWhenForMcp(createProductTaskGuidance.avoidWhen[0]);
20831
- var createServiceVideoGuidance = getProductMakerCapabilityModelGuidance("create_service_video");
20922
+ var CREATE_SERVICE_VIDEO_ON_MCP = PRODUCTMAKER_MCP_CAPABILITY_NAMES.includes("create_service_video");
20832
20923
  var listTasksGuidance = getProductMakerCapabilityModelGuidance("list_tasks");
20833
20924
  var editTaskDraftGuidance = getProductMakerCapabilityModelGuidance("edit_task_draft");
20834
20925
  var CreateProductTaskInput = z86.object({
@@ -21263,7 +21354,7 @@ async function fetchAnimatedVideoResult(api, taskId, signal, serviceMode = void
21263
21354
  }
21264
21355
  }
21265
21356
  __name(fetchAnimatedVideoResult, "fetchAnimatedVideoResult");
21266
- function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http" } = {}) {
21357
+ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http", serviceVideo = CREATE_SERVICE_VIDEO_ON_MCP } = {}) {
21267
21358
  server.registerTool("create_product_task", {
21268
21359
  description: createProductTaskGuidance.summary + ` ${createProductTaskDefaultDescription} \xDAsala cuando el usuario pide todo o varios outputs, no creando tareas individuales. Usa images para solicitudes directas. Usa projectId \xFAnicamente si el usuario pidi\xF3 expl\xEDcitamente continuar el proyecto guiado que acaba de costear; nunca busques, infieras ni menciones proyectos previos.
21269
21360
 
@@ -21386,97 +21477,100 @@ ANTES de invocar, resume outputs, cantidad de videos y datos comerciales, explic
21386
21477
  throw translateError(e);
21387
21478
  }
21388
21479
  });
21389
- server.registerTool("create_service_video", {
21390
- description: createServiceVideoGuidance.summary + " Esta acci\xF3n consume cr\xE9ditos: confirma la intenci\xF3n del usuario antes de llamarla. `automatic` genera directamente; `guided` devuelve conceptos para que el usuario apruebe uno mediante `resolve_pending_action`. La web tambi\xE9n acepta demos y anuncios de referencia; el transporte MCP de video adjunto todav\xEDa falla cerrado hasta poder subir bytes de forma segura.",
21391
- inputSchema: CreateServiceVideoInput.shape,
21392
- annotations: CREATES_PAID_OPEN_WORLD
21393
- }, async (input) => {
21394
- try {
21395
- if (input.videoQuality === "medium" && input.videoResolution === "720p") {
21396
- throw mcp(McpErrorCode.INVALID_INPUT, 'videoResolution "720p" no est\xE1 disponible con videoQuality "medium" en videos de servicio (Medio = 768P). Usa "480p" o elige "economy"/"pro".');
21397
- }
21398
- await requirePaidCreativeConfirmation(server, `Video de servicio en ${input.language}. Modo: ${input.reviewMode === "automatic" ? "la IA decide todo" : "reviews conversacionales"}.`);
21399
- const created = await api.postJson("/v2/ingest", {
21400
- taskType: "service",
21401
- serviceLandingUrl: input.landingUrl,
21402
- ...input.additionalContext ? {
21403
- additionalContext: input.additionalContext
21404
- } : {},
21405
- lang: input.language,
21406
- ...input.voiceId ? {
21407
- voiceId: input.voiceId
21408
- } : {},
21409
- videoPreset: input.videoPreset,
21410
- videoQuality: input.videoQuality,
21411
- videoResolution: input.videoResolution,
21412
- reviewMode: input.reviewMode,
21413
- ...input.brandId ? {
21414
- brandId: input.brandId
21415
- } : {},
21416
- media: {
21417
- assets: []
21418
- },
21419
- appDemoVideos: []
21420
- }, {
21421
- // The 2026-09-25 incident: four retries of this exact tool created
21422
- // four charged service tasks because the host rephrased
21423
- // `additionalContext` and flipped the landing between `/` and
21424
- // `/es`. The derived key is stable across both rewrites (see
21425
- // request-key.ts).
21426
- idempotencyKey: derivePaidRequestKey({
21427
- fingerprint: api.fingerprint(),
21428
- tool: "create_service_video",
21429
- input,
21430
- nowMs: Date.now()
21431
- })
21432
- });
21433
- const angles = await api.postJson(`/v1/tasks/${encodeURIComponent(created.taskId)}/service-angles`, {});
21434
- const first = angles[0];
21435
- const replayed = created.replayed === true;
21436
- if (input.reviewMode === "automatic") {
21437
- if (!first) throw new Error("ProductMaker no pudo crear un concepto para este servicio.");
21438
- if (!(replayed && created.status !== "queued")) {
21439
- await api.postJson(`/v1/tasks/${encodeURIComponent(created.taskId)}/service-angles/select`, {
21440
- angleIds: [
21441
- first.id
21442
- ]
21480
+ if (serviceVideo) {
21481
+ const createServiceVideoGuidance = getProductMakerCapabilityModelGuidance("create_service_video");
21482
+ server.registerTool("create_service_video", {
21483
+ description: createServiceVideoGuidance.summary + " Esta acci\xF3n consume cr\xE9ditos: confirma la intenci\xF3n del usuario antes de llamarla. `automatic` genera directamente; `guided` devuelve conceptos para que el usuario apruebe uno mediante `resolve_pending_action`. La web tambi\xE9n acepta demos y anuncios de referencia; el transporte MCP de video adjunto todav\xEDa falla cerrado hasta poder subir bytes de forma segura.",
21484
+ inputSchema: CreateServiceVideoInput.shape,
21485
+ annotations: CREATES_PAID_OPEN_WORLD
21486
+ }, async (input) => {
21487
+ try {
21488
+ if (input.videoQuality === "medium" && input.videoResolution === "720p") {
21489
+ throw mcp(McpErrorCode.INVALID_INPUT, 'videoResolution "720p" no est\xE1 disponible con videoQuality "medium" en videos de servicio (Medio = 768P). Usa "480p" o elige "economy"/"pro".');
21490
+ }
21491
+ await requirePaidCreativeConfirmation(server, `Video de servicio en ${input.language}. Modo: ${input.reviewMode === "automatic" ? "la IA decide todo" : "reviews conversacionales"}.`);
21492
+ const created = await api.postJson("/v2/ingest", {
21493
+ taskType: "service",
21494
+ serviceLandingUrl: input.landingUrl,
21495
+ ...input.additionalContext ? {
21496
+ additionalContext: input.additionalContext
21497
+ } : {},
21498
+ lang: input.language,
21499
+ ...input.voiceId ? {
21500
+ voiceId: input.voiceId
21501
+ } : {},
21502
+ videoPreset: input.videoPreset,
21503
+ videoQuality: input.videoQuality,
21504
+ videoResolution: input.videoResolution,
21505
+ reviewMode: input.reviewMode,
21506
+ ...input.brandId ? {
21507
+ brandId: input.brandId
21508
+ } : {},
21509
+ media: {
21510
+ assets: []
21511
+ },
21512
+ appDemoVideos: []
21513
+ }, {
21514
+ // The 2026-09-25 incident: four retries of this exact tool created
21515
+ // four charged service tasks because the host rephrased
21516
+ // `additionalContext` and flipped the landing between `/` and
21517
+ // `/es`. The derived key is stable across both rewrites (see
21518
+ // request-key.ts).
21519
+ idempotencyKey: derivePaidRequestKey({
21520
+ fingerprint: api.fingerprint(),
21521
+ tool: "create_service_video",
21522
+ input,
21523
+ nowMs: Date.now()
21524
+ })
21525
+ });
21526
+ const angles = await api.postJson(`/v1/tasks/${encodeURIComponent(created.taskId)}/service-angles`, {});
21527
+ const first = angles[0];
21528
+ const replayed = created.replayed === true;
21529
+ if (input.reviewMode === "automatic") {
21530
+ if (!first) throw new Error("ProductMaker no pudo crear un concepto para este servicio.");
21531
+ if (!(replayed && created.status !== "queued")) {
21532
+ await api.postJson(`/v1/tasks/${encodeURIComponent(created.taskId)}/service-angles/select`, {
21533
+ angleIds: [
21534
+ first.id
21535
+ ]
21536
+ });
21537
+ }
21538
+ return asText({
21539
+ taskId: created.taskId,
21540
+ status: replayed ? created.status ?? "processing" : "processing",
21541
+ ...replayed ? {
21542
+ replayed: true
21543
+ } : {},
21544
+ reviewMode: input.reviewMode,
21545
+ selectedConcept: first
21546
+ });
21547
+ }
21548
+ if (replayed && created.status !== "queued") {
21549
+ return asText({
21550
+ taskId: created.taskId,
21551
+ status: created.status,
21552
+ replayed: true,
21553
+ reviewMode: input.reviewMode,
21554
+ angles
21443
21555
  });
21444
21556
  }
21445
21557
  return asText({
21446
21558
  taskId: created.taskId,
21447
- status: replayed ? created.status ?? "processing" : "processing",
21559
+ status: "waiting_for_user",
21448
21560
  ...replayed ? {
21449
21561
  replayed: true
21450
21562
  } : {},
21451
21563
  reviewMode: input.reviewMode,
21452
- selectedConcept: first
21453
- });
21454
- }
21455
- if (replayed && created.status !== "queued") {
21456
- return asText({
21457
- taskId: created.taskId,
21458
- status: created.status,
21459
- replayed: true,
21460
- reviewMode: input.reviewMode,
21461
- angles
21564
+ pendingUserAction: {
21565
+ kind: "service_angle_approval",
21566
+ angles
21567
+ }
21462
21568
  });
21569
+ } catch (e) {
21570
+ throw translateError(e);
21463
21571
  }
21464
- return asText({
21465
- taskId: created.taskId,
21466
- status: "waiting_for_user",
21467
- ...replayed ? {
21468
- replayed: true
21469
- } : {},
21470
- reviewMode: input.reviewMode,
21471
- pendingUserAction: {
21472
- kind: "service_angle_approval",
21473
- angles
21474
- }
21475
- });
21476
- } catch (e) {
21477
- throw translateError(e);
21478
- }
21479
- });
21572
+ });
21573
+ }
21480
21574
  server.registerTool("verify_landing_commerce", {
21481
21575
  description: getProductMakerCapabilityModelGuidance("verify_landing_commerce").summary + " Verifica una correcci\xF3n manual de precios, tachados o packs de una landing editable existente. Solo relee Shopify y los recibos externos; no cambia precios, inventario, SKU ni compra una edici\xF3n. Pasa taskId y editId de la edici\xF3n ya conocida; nunca inventes IDs ni pidas repetir el cambio. Devuelve el estado can\xF3nico por proveedor: conserva el aviso pendiente hasta confirmar los valores reales. Solo despu\xE9s de confirmar todos los valores, el servidor puede volver a habilitar la compra. Web guardada no demuestra publicaci\xF3n p\xFAblica ni checkout; muestra las correcciones y enlaces pendientes sin c\xF3digos t\xE9cnicos. La disponibilidad del editor conserva sus permisos actuales.",
21482
21576
  inputSchema: {
@@ -25826,7 +25920,7 @@ function registerProductControlTools(server, api, { transport = "http" } = {}) {
25826
25920
  }
25827
25921
  });
25828
25922
  server.registerTool("get_finances", {
25829
- description: getFinancesGuidance.summary + ' Contesta la pregunta que de verdad importa --- \xABeste negocio gana plata?\xBB --- con el mes armado: ventas totales y entregadas, fletes, costo de los productos, utilidad bruta, publicidad, gastos fijos y utilidad NETA. Esa utilidad es ANTES de impuestos y hay que decirlo asi: el contador es un gasto mas, no un calculo nuestro. Trae tambien los porcentajes contra sus metas. Si `costsKnown` viene en `false`, esos pedidos llegaron de una fuente que NO sabe cuanto costaron \u2014la tienda, o la API del proveedor\u2014 y la utilidad BRUTA esta incompleta: no es que el producto salga gratis. Si `adSpendKnown` viene en `false`, la publicidad NO se pudo leer y la utilidad esta INCOMPLETA: dilo antes de que alguien celebre un mes que perdio plata. `adSpendSource: "user"` significa que ella corrigio la cifra a mano, y eso se revisa distinto de una leida. Si `unconfirmedExpenses` es mayor que cero, ese monto viene heredado del mes pasado y ella no lo ha revisado. Es READ-ONLY: nunca pausa ni modifica una campana.',
25923
+ description: getFinancesGuidance.summary + ' Contesta la pregunta que de verdad importa --- \xABeste negocio gana plata?\xBB --- con el mes armado: ventas totales y entregadas, fletes, costo de los productos, utilidad bruta, publicidad, gastos fijos y utilidad NETA. Esa utilidad es ANTES de impuestos y hay que decirlo asi: el contador es un gasto mas, no un calculo nuestro. Trae tambien los porcentajes contra sus metas. Si `deliveredSalesKnown` no es true, faltan importes de ventas entregadas: la utilidad y los m\xE1rgenes no est\xE1n completos. `grossSalesKnown` distinto de true indica ventas totales parciales. Si `costsKnown` viene en `false`, esos pedidos llegaron de una fuente que NO sabe cuanto costaron \u2014la tienda, o la API del proveedor\u2014 y la utilidad BRUTA esta incompleta: no es que el producto salga gratis. Si `adSpendKnown` viene en `false`, la publicidad NO se pudo leer y la utilidad esta INCOMPLETA: dilo antes de que alguien celebre un mes que perdio plata. `adSpendSource: "user"` significa que ella corrigio la cifra a mano, y eso se revisa distinto de una leida. Si `unconfirmedExpenses` es mayor que cero, ese monto viene heredado del mes pasado y ella no lo ha revisado. Es READ-ONLY: nunca pausa ni modifica una campana.',
25830
25924
  inputSchema: GetFinancesInput.shape,
25831
25925
  annotations: READS_INTERNAL
25832
25926
  }, async (input) => {
@@ -25913,23 +26007,16 @@ function registerProductControlTools(server, api, { transport = "http" } = {}) {
25913
26007
  }
25914
26008
  });
25915
26009
  server.registerTool("set_monthly_expense", {
25916
- description: setMonthlyExpenseGuidance.summary + " Es un gasto FIJO: arriendo, sueldos, contador, plataformas \u2014 lo que se paga venda o no. Escribir la misma etiqueta otra vez REEMPLAZA el monto, no suma otro gasto. Sin gastos fijos escritos, la utilidad del mes sale siempre M\xC1S ALTA de lo que es, y ese es el error que nadie reclama. No lo uses para el gasto en anuncios: eso es set_ad_spend.",
26010
+ description: setMonthlyExpenseGuidance.summary + " El importe indicado es el TOTAL del gasto compartido existente; la parte de la operaci\xF3n se calcula conservando el reparto. Es un gasto FIJO: arriendo, sueldos, contador, plataformas \u2014 lo que se paga venda o no. Escribir la misma etiqueta otra vez REEMPLAZA el monto, no suma otro gasto. Sin gastos fijos escritos, la utilidad del mes sale siempre M\xC1S ALTA de lo que es, y ese es el error que nadie reclama. No lo uses para el gasto en anuncios: eso es set_ad_spend.",
25917
26011
  inputSchema: SetMonthlyExpenseInput.shape,
25918
26012
  annotations: OVERWRITES_INTERNAL
25919
26013
  }, async (input) => {
25920
26014
  try {
25921
- const month = input.month ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 7);
25922
- const operationId = input.operationId ?? (await api.getJson("/v1/product-control/operations"))[0]?.id;
25923
26015
  return asText(await api.putJson("/v1/product-control/expenses", {
25924
- month,
26016
+ month: input.month,
26017
+ operationId: input.operationId,
25925
26018
  label: input.label,
25926
- amount: input.amount,
25927
- allocations: operationId ? [
25928
- {
25929
- operationId,
25930
- sharePct: 100
25931
- }
25932
- ] : void 0
26019
+ amount: input.amount
25933
26020
  }));
25934
26021
  } catch (error) {
25935
26022
  throw translateError(error);
@@ -25942,7 +26029,7 @@ function registerProductControlTools(server, api, { transport = "http" } = {}) {
25942
26029
  }, async (input) => {
25943
26030
  try {
25944
26031
  return asText(await api.putJson("/v1/product-control/ad-spend", {
25945
- month: input.month ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 7),
26032
+ month: input.month,
25946
26033
  operationId: input.operationId,
25947
26034
  amount: input.amount
25948
26035
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@productmaker/mcp",
3
- "version": "8.13.1",
3
+ "version": "9.0.1",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "description": "ProductMaker MCP server — cost products, generate landing pages, UGC videos, and ad creatives, and publish to Shopify, Meta Ads, and TikTok Ads.",