@productmaker/mcp 3.4.2 → 3.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/http.js +126 -30
- package/dist/stdio.js +126 -30
- package/package.json +1 -1
package/dist/http.js
CHANGED
|
@@ -3509,7 +3509,15 @@ var TaskCreativeVariantResult = TaskCreativeVariantBaseResult.extend({
|
|
|
3509
3509
|
var MVPResult = z24.object({
|
|
3510
3510
|
taskId: z24.string().uuid(),
|
|
3511
3511
|
product: Product,
|
|
3512
|
-
|
|
3512
|
+
// READ contract, NOT a generation cap. The generator writes exactly 3 angles
|
|
3513
|
+
// (`replaceAngles` renumbers positions 0..2), but a duplicated angles run
|
|
3514
|
+
// leaves TWO full sets on the task — alert on task a1954a35 (2026-08-21):
|
|
3515
|
+
// 6 rows, positions 0,0,1,1,2,2. Because the controller guards the whole
|
|
3516
|
+
// response with `MVPResult.parse()`, a cap here turns extra-but-valid rows
|
|
3517
|
+
// into a 500 on GET /v1/results/:taskId and hides EVERY deliverable of a
|
|
3518
|
+
// task that already finished. Same failure shape as the variant landing cap
|
|
3519
|
+
// below (alert 88badc8). The write path is what must stay at 3.
|
|
3520
|
+
angles: z24.array(Angle),
|
|
3513
3521
|
topVideos: z24.array(AdVideo).max(20),
|
|
3514
3522
|
landing: z24.object({
|
|
3515
3523
|
// Allow legacy data (10 sections × 2 variants) and new data (7 sections × 2)
|
|
@@ -3613,7 +3621,7 @@ var PendingUserActionSchema = z24.discriminatedUnion("kind", [
|
|
|
3613
3621
|
actor: z24.string(),
|
|
3614
3622
|
/** Saved custom actor (Actor.id) backing this angle, when one was chosen. */
|
|
3615
3623
|
actorId: z24.string().optional()
|
|
3616
|
-
})).min(1)
|
|
3624
|
+
})).min(1)
|
|
3617
3625
|
}),
|
|
3618
3626
|
z24.object({
|
|
3619
3627
|
kind: z24.literal("service_script_approval"),
|
|
@@ -4901,6 +4909,7 @@ var MetaCampaignType = z33.enum([
|
|
|
4901
4909
|
"traffic",
|
|
4902
4910
|
"engagement"
|
|
4903
4911
|
]);
|
|
4912
|
+
var MetaPublishAttemptId = z33.string().uuid();
|
|
4904
4913
|
var MetaOptimizationMode = z33.enum([
|
|
4905
4914
|
"advantage_plus",
|
|
4906
4915
|
"manual"
|
|
@@ -5084,6 +5093,8 @@ var MetaCampaignDefaults = MetaCampaignConfigBase.omit({
|
|
|
5084
5093
|
currency: z33.string().length(3).regex(/^[A-Z]{3}$/).nullable().optional()
|
|
5085
5094
|
});
|
|
5086
5095
|
var UploadToMetaRequest = z33.object({
|
|
5096
|
+
/** Caller-owned retry fence. Required before any provider operation. */
|
|
5097
|
+
publishAttemptId: MetaPublishAttemptId,
|
|
5087
5098
|
/** Array of creatives to upload */
|
|
5088
5099
|
creatives: z33.array(z33.object({
|
|
5089
5100
|
creativeId: z33.string().min(1).optional(),
|
|
@@ -5240,6 +5251,8 @@ var MetaCampaignOverridesSchema = z34.object({
|
|
|
5240
5251
|
destination: MetaCampaignDestination.optional()
|
|
5241
5252
|
});
|
|
5242
5253
|
var FacebookCreateCampaignBodySchema = z34.object({
|
|
5254
|
+
/** Caller-owned retry fence. Required before any provider operation. */
|
|
5255
|
+
publishAttemptId: MetaPublishAttemptId,
|
|
5243
5256
|
adAccountId: MetaAdAccountId,
|
|
5244
5257
|
portfolioId: z34.string().min(1).optional(),
|
|
5245
5258
|
pageId: z34.string().min(1).optional(),
|
|
@@ -5309,6 +5322,8 @@ var FacebookCreateCampaignResponseSchema = z34.object({
|
|
|
5309
5322
|
appliedBudget: z34.number().int().positive()
|
|
5310
5323
|
});
|
|
5311
5324
|
var FacebookCreateCampaignFromCreativesBodySchema = z34.object({
|
|
5325
|
+
/** Caller-owned retry fence. Required before any provider operation. */
|
|
5326
|
+
publishAttemptId: MetaPublishAttemptId,
|
|
5312
5327
|
creatives: z34.array(z34.object({
|
|
5313
5328
|
type: z34.enum([
|
|
5314
5329
|
"image",
|
|
@@ -5456,6 +5471,8 @@ var FacebookCreativeSourceSchema = z34.discriminatedUnion("type", [
|
|
|
5456
5471
|
FacebookMediaCreativeSourceSchema
|
|
5457
5472
|
]);
|
|
5458
5473
|
var FacebookCreateCampaignFromTasksBodySchema = z34.object({
|
|
5474
|
+
/** Caller-owned retry fence. Required before any provider operation. */
|
|
5475
|
+
publishAttemptId: MetaPublishAttemptId,
|
|
5459
5476
|
creatives: z34.array(z34.object({
|
|
5460
5477
|
type: z34.enum([
|
|
5461
5478
|
"image",
|
|
@@ -11318,8 +11335,9 @@ var OAuthOboCredential = class {
|
|
|
11318
11335
|
// src/guidance/productmaker-guidance.ts
|
|
11319
11336
|
var START_DROPSHIPPING_PROMPT_NAME = "start_dropshipping";
|
|
11320
11337
|
var PRODUCTMAKER_SERVER_INSTRUCTIONS = [
|
|
11321
|
-
"
|
|
11338
|
+
"Whenever a ProductMaker request needs a new image or video and no reusable ProductMaker assetId is available, call request_media_upload immediately in the same response\u2014whether media is the next missing input or the user attached, pasted, mentioned, or has the file locally. The inline uploader is how ProductMaker asks for the file. Never merely ask the user to attach or upload it in chat and wait for a later turn. Call it before asking for any missing business inputs, even when the user says not to generate or publish yet: uploading only stages the file and does not generate, publish, or consume credits, unless the user explicitly says not to upload, share, or store the file. Treat natural requests such as 'create a landing with this product', 'make creatives with this photo', or 'upload this video to Meta' as upload intent; never require the user to name the uploader.",
|
|
11322
11339
|
"For requests that do not need a new media upload, understand the user's intent before choosing a ProductMaker tool.",
|
|
11340
|
+
"For every concrete ProductMaker request to calculate how much to charge, profitability, or margin for a dropshipping product, you must call cost_product after collecting its missing country and real supplier cost. Never calculate or reproduce ProductMaker's costing formula yourself; answer from the tool result.",
|
|
11323
11341
|
"For remote MCP hosts, never ask for a local path, CDN URL, bytes, or base64. Pass supported host file references to request_media_upload; otherwise let its inline App open the file picker. The external upload page is fallback only.",
|
|
11324
11342
|
"After the upload App sends its OK continuation, resume the original request with the verified assetId values, do not ask for another upload, and ask only for remaining business inputs in chat. The host-inserted continuation may contain opaque asset IDs; do not repeat them in narrative prose or expose session IDs, tokens, private URLs, file names, or JSON handoff payloads.",
|
|
11325
11343
|
"Keep focused requests direct: creatives, landing pages, Shopify publication, Meta publication, and status checks do not require a DropshippingProject.",
|
|
@@ -11678,7 +11696,7 @@ var ApiClient = class {
|
|
|
11678
11696
|
};
|
|
11679
11697
|
|
|
11680
11698
|
// src/index.ts
|
|
11681
|
-
var MCP_VERSION = "3.4.
|
|
11699
|
+
var MCP_VERSION = "3.4.4";
|
|
11682
11700
|
|
|
11683
11701
|
// src/meta-upload-app.ts
|
|
11684
11702
|
import { readFileSync } from "fs";
|
|
@@ -12724,6 +12742,12 @@ var McpErrorCode = {
|
|
|
12724
12742
|
META_STAGED_ASSET_AMBIGUOUS: "META_STAGED_ASSET_AMBIGUOUS",
|
|
12725
12743
|
META_STAGED_ASSET_TYPE_MISMATCH: "META_STAGED_ASSET_TYPE_MISMATCH",
|
|
12726
12744
|
META_STAGED_ASSET_INVALID: "META_STAGED_ASSET_INVALID",
|
|
12745
|
+
META_STAGING_QUOTA_EXCEEDED: "META_STAGING_QUOTA_EXCEEDED",
|
|
12746
|
+
CAPACITY_UNAVAILABLE: "CAPACITY_UNAVAILABLE",
|
|
12747
|
+
UPLOAD_INCOMPLETE: "UPLOAD_INCOMPLETE",
|
|
12748
|
+
UPLOAD_EXPIRED: "UPLOAD_EXPIRED",
|
|
12749
|
+
META_UPLOAD_SESSION_EXPIRED: "META_UPLOAD_SESSION_EXPIRED",
|
|
12750
|
+
META_PUBLICATION_LEASE_STALE: "META_PUBLICATION_LEASE_STALE",
|
|
12727
12751
|
META_PUBLICATION_RECONCILIATION_REQUIRED: "META_PUBLICATION_RECONCILIATION_REQUIRED",
|
|
12728
12752
|
META_PUBLICATION_IN_PROGRESS: "META_PUBLICATION_IN_PROGRESS",
|
|
12729
12753
|
META_NO_ADS_CREATED: "META_NO_ADS_CREATED",
|
|
@@ -12785,8 +12809,14 @@ var CODE_MESSAGES = {
|
|
|
12785
12809
|
META_STAGED_ASSET_AMBIGUOUS: "The temporary media upload could not be uniquely resolved. Re-upload the media and retry the Meta publication.",
|
|
12786
12810
|
META_STAGED_ASSET_TYPE_MISMATCH: "The temporary media type does not match the creative. Re-upload the correct image or video and retry.",
|
|
12787
12811
|
META_STAGED_ASSET_INVALID: "The temporary media is empty or exceeds the server size limit. Re-upload the file and retry.",
|
|
12788
|
-
|
|
12789
|
-
|
|
12812
|
+
META_STAGING_QUOTA_EXCEEDED: "The temporary Meta staging quota was reached. Keep completed files and retry only pending files after retryAt.",
|
|
12813
|
+
CAPACITY_UNAVAILABLE: "Temporary Meta staging capacity is unavailable. Keep completed files and retry only pending files later.",
|
|
12814
|
+
UPLOAD_INCOMPLETE: "A temporary Meta upload is incomplete. Keep completed files and retry only pending files.",
|
|
12815
|
+
UPLOAD_EXPIRED: "The temporary Meta upload expired. Re-upload this file before publishing.",
|
|
12816
|
+
META_UPLOAD_SESSION_EXPIRED: "The Meta upload session expired. Open a new session and retry only pending files.",
|
|
12817
|
+
META_PUBLICATION_LEASE_STALE: "The Meta publication lease expired or was replaced. Do not continue this stale publication; start a new explicit attempt only after confirming Meta did not receive it.",
|
|
12818
|
+
META_PUBLICATION_RECONCILIATION_REQUIRED: "Meta may already have received this publication. Do not resend it; check Meta Ads Manager.",
|
|
12819
|
+
META_PUBLICATION_IN_PROGRESS: "This Meta publication is already in progress. Do not resend it.",
|
|
12790
12820
|
META_NO_ADS_CREATED: "No ads could be created. Most likely: the Meta Ads account has no payment method, no Facebook Page is linked, or the Facebook app lost permissions. Verify all three at https://business.facebook.com.",
|
|
12791
12821
|
META_NO_ASSETS_READY: "The task has no videos or ad creative ready to publish yet. Wait for generation to finish or regenerate the assets.",
|
|
12792
12822
|
META_NO_THUMBNAIL_SOURCE: "No image is available to use as the video thumbnail. Upload a product image or generate the ad creative before publishing.",
|
|
@@ -12935,6 +12965,11 @@ var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
|
|
|
12935
12965
|
McpErrorCode.META_STAGED_ASSET_AMBIGUOUS,
|
|
12936
12966
|
McpErrorCode.META_STAGED_ASSET_TYPE_MISMATCH,
|
|
12937
12967
|
McpErrorCode.META_STAGED_ASSET_INVALID,
|
|
12968
|
+
McpErrorCode.META_STAGING_QUOTA_EXCEEDED,
|
|
12969
|
+
McpErrorCode.UPLOAD_INCOMPLETE,
|
|
12970
|
+
McpErrorCode.UPLOAD_EXPIRED,
|
|
12971
|
+
McpErrorCode.META_UPLOAD_SESSION_EXPIRED,
|
|
12972
|
+
McpErrorCode.META_PUBLICATION_LEASE_STALE,
|
|
12938
12973
|
McpErrorCode.META_PUBLICATION_RECONCILIATION_REQUIRED,
|
|
12939
12974
|
McpErrorCode.META_PUBLICATION_IN_PROGRESS,
|
|
12940
12975
|
McpErrorCode.TIKTOK_TOKEN_EXPIRED,
|
|
@@ -12994,6 +13029,14 @@ var PASSTHROUGH_CODES = /* @__PURE__ */ new Set([
|
|
|
12994
13029
|
McpErrorCode.META_CAMPAIGN_CONFIG_INVALID,
|
|
12995
13030
|
McpErrorCode.META_WHATSAPP_INVALID_NUMBER,
|
|
12996
13031
|
McpErrorCode.META_INVALID_CTA,
|
|
13032
|
+
McpErrorCode.META_STAGING_QUOTA_EXCEEDED,
|
|
13033
|
+
McpErrorCode.CAPACITY_UNAVAILABLE,
|
|
13034
|
+
McpErrorCode.UPLOAD_INCOMPLETE,
|
|
13035
|
+
McpErrorCode.UPLOAD_EXPIRED,
|
|
13036
|
+
McpErrorCode.META_UPLOAD_SESSION_EXPIRED,
|
|
13037
|
+
McpErrorCode.META_PUBLICATION_LEASE_STALE,
|
|
13038
|
+
McpErrorCode.META_PUBLICATION_RECONCILIATION_REQUIRED,
|
|
13039
|
+
McpErrorCode.META_PUBLICATION_IN_PROGRESS,
|
|
12997
13040
|
// TikTok
|
|
12998
13041
|
McpErrorCode.TIKTOK_NOT_CONNECTED,
|
|
12999
13042
|
McpErrorCode.TIKTOK_TOKEN_EXPIRED,
|
|
@@ -13082,6 +13125,10 @@ function translateError(err) {
|
|
|
13082
13125
|
if (body.helpUrl !== void 0) extra.helpUrl = body.helpUrl;
|
|
13083
13126
|
if (typeof body.requiredCredits === "number") extra.requiredCredits = body.requiredCredits;
|
|
13084
13127
|
if (typeof body.availableCredits === "number") extra.availableCredits = body.availableCredits;
|
|
13128
|
+
if (typeof body.dimension === "string") extra.dimension = body.dimension;
|
|
13129
|
+
if (typeof body.usage === "number") extra.usage = body.usage;
|
|
13130
|
+
if (typeof body.limit === "number") extra.limit = body.limit;
|
|
13131
|
+
if (typeof body.retryAt === "string") extra.retryAt = body.retryAt;
|
|
13085
13132
|
return mcp(code, resolveMessage(code, body.message), extra);
|
|
13086
13133
|
}
|
|
13087
13134
|
if (err.status === 401) {
|
|
@@ -14755,6 +14802,57 @@ __name(registerActorTools, "registerActorTools");
|
|
|
14755
14802
|
|
|
14756
14803
|
// src/tools/publish.ts
|
|
14757
14804
|
import { z as z56 } from "zod";
|
|
14805
|
+
|
|
14806
|
+
// src/tools/stable-uuid.ts
|
|
14807
|
+
import { createHash as createHash3 } from "crypto";
|
|
14808
|
+
function canonicalize(value) {
|
|
14809
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
14810
|
+
if (value === null || typeof value !== "object") return value;
|
|
14811
|
+
const record = value;
|
|
14812
|
+
return Object.fromEntries(Object.keys(record).sort().filter((key) => record[key] !== void 0).map((key) => [
|
|
14813
|
+
key,
|
|
14814
|
+
canonicalize(record[key])
|
|
14815
|
+
]));
|
|
14816
|
+
}
|
|
14817
|
+
__name(canonicalize, "canonicalize");
|
|
14818
|
+
function canonicalJson(value) {
|
|
14819
|
+
return JSON.stringify(canonicalize(value));
|
|
14820
|
+
}
|
|
14821
|
+
__name(canonicalJson, "canonicalJson");
|
|
14822
|
+
function deriveUuidV5(name, namespace) {
|
|
14823
|
+
const namespaceBytes = Buffer.from(namespace.replaceAll("-", ""), "hex");
|
|
14824
|
+
const digest = createHash3("sha1").update(namespaceBytes).update(name, "utf8").digest();
|
|
14825
|
+
const bytes = Buffer.from(digest.subarray(0, 16));
|
|
14826
|
+
bytes[6] = (bytes[6] ?? 0) & 15 | 80;
|
|
14827
|
+
bytes[8] = (bytes[8] ?? 0) & 63 | 128;
|
|
14828
|
+
const hex = bytes.toString("hex");
|
|
14829
|
+
return [
|
|
14830
|
+
hex.slice(0, 8),
|
|
14831
|
+
hex.slice(8, 12),
|
|
14832
|
+
hex.slice(12, 16),
|
|
14833
|
+
hex.slice(16, 20),
|
|
14834
|
+
hex.slice(20)
|
|
14835
|
+
].join("-");
|
|
14836
|
+
}
|
|
14837
|
+
__name(deriveUuidV5, "deriveUuidV5");
|
|
14838
|
+
|
|
14839
|
+
// src/tools/meta-publish-attempt-id.ts
|
|
14840
|
+
var META_PUBLISH_NAMESPACE = "7b19c9ac-b4eb-4db1-970e-c2f24ce30b98";
|
|
14841
|
+
function deriveMetaPublishAttemptId(input) {
|
|
14842
|
+
const operationIdentity = canonicalJson([
|
|
14843
|
+
input.fingerprint,
|
|
14844
|
+
input.toolName,
|
|
14845
|
+
[
|
|
14846
|
+
typeof input.requestId,
|
|
14847
|
+
input.requestId
|
|
14848
|
+
],
|
|
14849
|
+
input.canonicalInput
|
|
14850
|
+
]);
|
|
14851
|
+
return deriveUuidV5(operationIdentity, META_PUBLISH_NAMESPACE);
|
|
14852
|
+
}
|
|
14853
|
+
__name(deriveMetaPublishAttemptId, "deriveMetaPublishAttemptId");
|
|
14854
|
+
|
|
14855
|
+
// src/tools/publish.ts
|
|
14758
14856
|
var ShopifyInput = z56.object({
|
|
14759
14857
|
taskId: z56.string().min(1),
|
|
14760
14858
|
shopDomain: z56.string().min(1),
|
|
@@ -15082,10 +15180,19 @@ function registerPublishTools(server, api, opts = {}) {
|
|
|
15082
15180
|
throw translateError(e);
|
|
15083
15181
|
}
|
|
15084
15182
|
});
|
|
15085
|
-
server.tool("publish_to_meta", "Crea una campa\xF1a de Meta Ads (Facebook/Instagram) desde UNA sola tarea. Antes llama `list_meta_destinations`, deja que el usuario elija el `portfolioId` (Business portfolio), el `adAccountId` y el `pageId` exactos, y env\xEDa esos tres valores; no adivines ni uses la primera p\xE1gina. Si quieres juntar creativos de VARIAS tareas usa `publish_creatives_to_meta`. La campa\xF1a se crea SIEMPRE PAUSED y el usuario la activa manualmente. `pixelId` es opcional. `angleIndexes` permite seleccionar \xE1ngulos. `campaignConfig` admite presupuesto en USD, pa\xEDses ISO-2 y nombre. `primaryText` permite reemplazar el copy y `phoneNumber` configura WhatsApp. Despu\xE9s usa `get_meta_campaign_status`.", MetaInput.shape, CREATES_EXTERNAL, async (
|
|
15183
|
+
server.tool("publish_to_meta", "Publica tus creativos en Meta sin gastar cr\xE9ditos de ProductMaker. El presupuesto de anuncios se paga directamente desde tu cuenta de Meta. Crear creativos o copy con IA sigue usando cr\xE9ditos. Crea una campa\xF1a de Meta Ads (Facebook/Instagram) desde UNA sola tarea. Antes llama `list_meta_destinations`, deja que el usuario elija el `portfolioId` (Business portfolio), el `adAccountId` y el `pageId` exactos, y env\xEDa esos tres valores; no adivines ni uses la primera p\xE1gina. Si quieres juntar creativos de VARIAS tareas usa `publish_creatives_to_meta`. La campa\xF1a se crea SIEMPRE PAUSED y el usuario la activa manualmente. `pixelId` es opcional. `angleIndexes` permite seleccionar \xE1ngulos. `campaignConfig` admite presupuesto en USD, pa\xEDses ISO-2 y nombre. `primaryText` permite reemplazar el copy y `phoneNumber` configura WhatsApp. Despu\xE9s usa `get_meta_campaign_status`. Si el resultado es ambiguo, NO reenv\xEDes: revisa Meta Ads Manager.", MetaInput.shape, CREATES_EXTERNAL, async (rawInput, extra) => {
|
|
15086
15184
|
try {
|
|
15185
|
+
const parsed = MetaInput.safeParse(rawInput);
|
|
15186
|
+
if (!parsed.success) throw invalidInputFromZod(parsed.error);
|
|
15187
|
+
const { taskId, campaignConfig, primaryText, phoneNumber, ...rest } = parsed.data;
|
|
15087
15188
|
const body = {
|
|
15088
|
-
...rest
|
|
15189
|
+
...rest,
|
|
15190
|
+
publishAttemptId: deriveMetaPublishAttemptId({
|
|
15191
|
+
fingerprint: api.fingerprint(),
|
|
15192
|
+
toolName: "publish_to_meta",
|
|
15193
|
+
requestId: extra.requestId,
|
|
15194
|
+
canonicalInput: parsed.data
|
|
15195
|
+
})
|
|
15089
15196
|
};
|
|
15090
15197
|
if (campaignConfig) {
|
|
15091
15198
|
body.campaignOverrides = toMetaCampaignOverrides(campaignConfig);
|
|
@@ -15110,7 +15217,7 @@ function registerPublishTools(server, api, opts = {}) {
|
|
|
15110
15217
|
throw translateError(e);
|
|
15111
15218
|
}
|
|
15112
15219
|
});
|
|
15113
|
-
server.tool("publish_creatives_to_meta", "Crea UNA campa\xF1a de Meta Ads PAUSED agrupando hasta " + CAMPAIGN_LIMITS.MAX_CREATIVES + ' creativos. Si la petici\xF3n natural adjunta o menciona archivos locales, llama primero `request_media_upload`; tras OK usa cada resultado como `source.type="asset"` con su `assetId` y el `type` que coincide con `mediaKind`. No pidas path ni URL. Despu\xE9s llama `list_meta_destinations` y pregunta en el chat por el `portfolioId`, `adAccountId` y `pageId` exactos; el App es solo para archivos. Si la carga fue parcial,
|
|
15220
|
+
server.tool("publish_creatives_to_meta", "Crea UNA campa\xF1a de Meta Ads PAUSED agrupando hasta " + CAMPAIGN_LIMITS.MAX_CREATIVES + ' creativos. Publica tus creativos en Meta sin gastar cr\xE9ditos de ProductMaker. El presupuesto de anuncios se paga directamente desde tu cuenta de Meta. Crear creativos o copy con IA sigue usando cr\xE9ditos. Si la petici\xF3n natural adjunta o menciona archivos locales, llama primero `request_media_upload`; tras OK usa cada resultado como `source.type="asset"` con su `assetId` y el `type` que coincide con `mediaKind`. No pidas path ni URL. Despu\xE9s llama `list_meta_destinations` y pregunta en el chat por el `portfolioId`, `adAccountId` y `pageId` exactos; el App es solo para archivos. Si la carga fue parcial, conserva los assets confirmados, procesa s\xF3lo los pendientes y publica \xFAnicamente assets confirmados; nunca inventes assets ni superes diez creativos. Las rutas locales s\xF3lo funcionan con MCP stdio. Los medios propios se eliminan despu\xE9s de publicar correctamente; si falla antes de enviar una mutaci\xF3n a Meta, quedan durante el TTL. Para videos la miniatura es opcional y ProductMaker puede generarla con FFmpeg. `campaignConfig.destination` acepta landing o WhatsApp. Para landing basta `campaignConfig.destination.landingUrl`; no dupliques la URL arriba. Si omites `budgetMode`, se infiere `ad_set` cuando cada ad set trae `dailyBudget`, o `campaign` cuando env\xEDas `campaignDailyBudget`. No actives la campa\xF1a autom\xE1ticamente. Si el resultado es ambiguo, NO reenv\xEDes: revisa Meta Ads Manager.', PublishCreativesToMetaInputBase.shape, CREATES_EXTERNAL, async (rawInput, extra) => {
|
|
15114
15221
|
try {
|
|
15115
15222
|
const parsed = PublishCreativesToMetaInput.safeParse(rawInput);
|
|
15116
15223
|
if (!parsed.success) throw invalidInputFromZod(parsed.error);
|
|
@@ -15190,7 +15297,13 @@ function registerPublishTools(server, api, opts = {}) {
|
|
|
15190
15297
|
landingUrl: input.landingUrl,
|
|
15191
15298
|
targetCountry: input.targetCountry,
|
|
15192
15299
|
campaignName: input.campaignName,
|
|
15193
|
-
campaignConfig: input.campaignConfig
|
|
15300
|
+
campaignConfig: input.campaignConfig,
|
|
15301
|
+
publishAttemptId: deriveMetaPublishAttemptId({
|
|
15302
|
+
fingerprint: api.fingerprint(),
|
|
15303
|
+
toolName: "publish_creatives_to_meta",
|
|
15304
|
+
requestId: extra.requestId,
|
|
15305
|
+
canonicalInput: input
|
|
15306
|
+
})
|
|
15194
15307
|
});
|
|
15195
15308
|
return asText({
|
|
15196
15309
|
...r,
|
|
@@ -15424,7 +15537,7 @@ __name(resolveEndpointForSession, "resolveEndpointForSession");
|
|
|
15424
15537
|
function registerMediaUploadTools(server, api) {
|
|
15425
15538
|
registerAppTool(server, "request_media_upload", {
|
|
15426
15539
|
title: "Subir archivos",
|
|
15427
|
-
description: "Abre el selector universal de ProductMaker para im\xE1genes o videos del usuario. Ll\xE1malo autom\xE1ticamente y antes de preguntar por campos faltantes cuando una petici\xF3n natural adjunte o mencione un archivo local, por ejemplo \u2018crea una landing con este producto\u2019, \u2018haz creativos con esta foto\u2019 o \u2018sube este video a Meta\u2019; hazlo aunque el usuario diga que todav\xEDa no generes ni publiques, porque esta herramienta solo prepara el archivo y no consume cr\xE9ditos, salvo que proh\xEDba expl\xEDcitamente subir, compartir o almacenar el archivo; no pidas una ruta local ni URL. Es la \xFAnica ruta de carga para create_product_task, generate_image_creatives, generate_video_creative, generate_free_video, generate_landing y publish_creatives_to_meta. Pasa las referencias opcionales de ChatGPT en files; si el host no puede transferirlas, el mismo App muestra el selector inline. Usa uploadPurpose=product_reference para fotos de producto y landings, creative para creativos o anuncios, y generic solo cuando ninguno aplique. Usa mediaKind=image para referencias de producto y landings; mixed solo cuando el consumidor acepte ambos tipos. El App NO te avisa cuando termina: en cuanto el usuario diga que ya subi\xF3 el archivo o que sigas, llama `check_media_upload` con este sessionId para obtener los assetId, y reanuda la intenci\xF3n original usando \xFAnicamente assetId y mediaKind, sin pedir que vuelva a subir ni mostrar datos t\xE9cnicos. El enlace externo es fallback solo si el App inline falla. Esta herramienta no genera, publica ni consume cr\xE9ditos.",
|
|
15540
|
+
description: "Abre el selector universal de ProductMaker para im\xE1genes o videos del usuario. Cuando la foto o el video sea el siguiente dato faltante, ll\xE1malo en esa misma respuesta: no te limites a pedir que lo adjunte en el chat y esperar otro turno. Ll\xE1malo tambi\xE9n autom\xE1ticamente y antes de preguntar por campos faltantes adicionales cuando una petici\xF3n natural adjunte o mencione un archivo local, por ejemplo \u2018crea una landing con este producto\u2019, \u2018haz creativos con esta foto\u2019 o \u2018sube este video a Meta\u2019; hazlo aunque el usuario diga que todav\xEDa no generes ni publiques, porque esta herramienta solo prepara el archivo y no consume cr\xE9ditos, salvo que proh\xEDba expl\xEDcitamente subir, compartir o almacenar el archivo; no pidas una ruta local ni URL. Es la \xFAnica ruta de carga para create_product_task, generate_image_creatives, generate_video_creative, generate_free_video, generate_landing y publish_creatives_to_meta. Pasa las referencias opcionales de ChatGPT en files; si el host no puede transferirlas, el mismo App muestra el selector inline. Usa uploadPurpose=product_reference para fotos de producto y landings, creative para creativos o anuncios, y generic solo cuando ninguno aplique. Usa mediaKind=image para referencias de producto y landings; mixed solo cuando el consumidor acepte ambos tipos. El App NO te avisa cuando termina: en cuanto el usuario diga que ya subi\xF3 el archivo o que sigas, llama `check_media_upload` con este sessionId para obtener los assetId, y reanuda la intenci\xF3n original usando \xFAnicamente assetId y mediaKind, sin pedir que vuelva a subir ni mostrar datos t\xE9cnicos. El enlace externo es fallback solo si el App inline falla. Esta herramienta no genera, publica ni consume cr\xE9ditos.",
|
|
15428
15541
|
inputSchema: UploadSessionToolInputShape,
|
|
15429
15542
|
annotations: CREATES_INTERNAL,
|
|
15430
15543
|
outputSchema: UniversalUploadStructuredResponse.shape,
|
|
@@ -15953,7 +16066,7 @@ var ProductCostingToolInput = z61.object({
|
|
|
15953
16066
|
desiredMarginPercent: optionalPercent("Margen de ganancia deseado. Ejemplo: 10 significa 10%. Omite para usar el valor gu\xEDa editable del pa\xEDs.")
|
|
15954
16067
|
});
|
|
15955
16068
|
function registerProductCostingTools(server) {
|
|
15956
|
-
server.tool("cost_product", "Calcula GRATIS y sin cr\xE9ditos cu\xE1nto cobrar por un producto de dropshipping en cualquiera de los pa\xEDses disponibles en ProductMaker. Es READ-ONLY: no crea tareas ni publica nada. Pide country y el costo real del proveedor; los dem\xE1s campos pueden usar valores gu\xEDa editables del pa\xEDs. Devuelve `price` y `compareAtPrice` listos para `create_product_task`. No inventes un costo de proveedor.", ProductCostingToolInput.shape, READS_INTERNAL, async (input) => {
|
|
16069
|
+
server.tool("cost_product", "Calcula GRATIS y sin cr\xE9ditos cu\xE1nto cobrar por un producto de dropshipping en cualquiera de los pa\xEDses disponibles en ProductMaker. Ll\xE1mala siempre que el usuario pida calcular cu\xE1nto cobrar, la rentabilidad o el margen de un producto concreto; no hagas el c\xE1lculo ni reconstruyas la f\xF3rmula por tu cuenta. Es READ-ONLY: no crea tareas ni publica nada. Pide country y el costo real del proveedor; los dem\xE1s campos pueden usar valores gu\xEDa editables del pa\xEDs. Devuelve `price` y `compareAtPrice` listos para `create_product_task`. No inventes un costo de proveedor.", ProductCostingToolInput.shape, READS_INTERNAL, async (input) => {
|
|
15957
16070
|
if (input.country !== void 0 && input.country !== "CO" && [
|
|
15958
16071
|
input.supplierCostCop,
|
|
15959
16072
|
input.shippingCostCop,
|
|
@@ -16077,29 +16190,12 @@ __name(formatPercent, "formatPercent");
|
|
|
16077
16190
|
import { z as z62 } from "zod";
|
|
16078
16191
|
|
|
16079
16192
|
// src/tools/product-research-idempotency.ts
|
|
16080
|
-
import { createHash as createHash3 } from "crypto";
|
|
16081
16193
|
var RESEARCH_IDEMPOTENCY_WINDOW_MS = 2 * 60 * 60 * 1e3;
|
|
16082
16194
|
var NAMESPACE = "6f9d3a1e-8c47-4b52-9f10-2d7e5a3c8b64";
|
|
16083
|
-
function uuidV5(name, namespace) {
|
|
16084
|
-
const namespaceBytes = Buffer.from(namespace.replace(/-/g, ""), "hex");
|
|
16085
|
-
const digest = createHash3("sha1").update(namespaceBytes).update(name, "utf8").digest();
|
|
16086
|
-
const bytes = Buffer.from(digest.subarray(0, 16));
|
|
16087
|
-
bytes[6] = (bytes[6] ?? 0) & 15 | 80;
|
|
16088
|
-
bytes[8] = (bytes[8] ?? 0) & 63 | 128;
|
|
16089
|
-
const hex = bytes.toString("hex");
|
|
16090
|
-
return [
|
|
16091
|
-
hex.slice(0, 8),
|
|
16092
|
-
hex.slice(8, 12),
|
|
16093
|
-
hex.slice(12, 16),
|
|
16094
|
-
hex.slice(16, 20),
|
|
16095
|
-
hex.slice(20)
|
|
16096
|
-
].join("-");
|
|
16097
|
-
}
|
|
16098
|
-
__name(uuidV5, "uuidV5");
|
|
16099
16195
|
function deriveIdempotencyKey(input) {
|
|
16100
16196
|
const window = Math.floor(input.nowMs / RESEARCH_IDEMPOTENCY_WINDOW_MS);
|
|
16101
16197
|
const name = `${input.fingerprint}:${input.targetCountry}:${input.categoryKey}:${window}`;
|
|
16102
|
-
return
|
|
16198
|
+
return deriveUuidV5(name, NAMESPACE);
|
|
16103
16199
|
}
|
|
16104
16200
|
__name(deriveIdempotencyKey, "deriveIdempotencyKey");
|
|
16105
16201
|
|
package/dist/stdio.js
CHANGED
|
@@ -264,7 +264,7 @@ var ApiClient = class {
|
|
|
264
264
|
};
|
|
265
265
|
|
|
266
266
|
// src/index.ts
|
|
267
|
-
var MCP_VERSION = "3.4.
|
|
267
|
+
var MCP_VERSION = "3.4.4";
|
|
268
268
|
|
|
269
269
|
// src/meta-upload-app.ts
|
|
270
270
|
import { readFileSync } from "fs";
|
|
@@ -4131,7 +4131,15 @@ var TaskCreativeVariantResult = TaskCreativeVariantBaseResult.extend({
|
|
|
4131
4131
|
var MVPResult = z24.object({
|
|
4132
4132
|
taskId: z24.string().uuid(),
|
|
4133
4133
|
product: Product,
|
|
4134
|
-
|
|
4134
|
+
// READ contract, NOT a generation cap. The generator writes exactly 3 angles
|
|
4135
|
+
// (`replaceAngles` renumbers positions 0..2), but a duplicated angles run
|
|
4136
|
+
// leaves TWO full sets on the task — alert on task a1954a35 (2026-08-21):
|
|
4137
|
+
// 6 rows, positions 0,0,1,1,2,2. Because the controller guards the whole
|
|
4138
|
+
// response with `MVPResult.parse()`, a cap here turns extra-but-valid rows
|
|
4139
|
+
// into a 500 on GET /v1/results/:taskId and hides EVERY deliverable of a
|
|
4140
|
+
// task that already finished. Same failure shape as the variant landing cap
|
|
4141
|
+
// below (alert 88badc8). The write path is what must stay at 3.
|
|
4142
|
+
angles: z24.array(Angle),
|
|
4135
4143
|
topVideos: z24.array(AdVideo).max(20),
|
|
4136
4144
|
landing: z24.object({
|
|
4137
4145
|
// Allow legacy data (10 sections × 2 variants) and new data (7 sections × 2)
|
|
@@ -4235,7 +4243,7 @@ var PendingUserActionSchema = z24.discriminatedUnion("kind", [
|
|
|
4235
4243
|
actor: z24.string(),
|
|
4236
4244
|
/** Saved custom actor (Actor.id) backing this angle, when one was chosen. */
|
|
4237
4245
|
actorId: z24.string().optional()
|
|
4238
|
-
})).min(1)
|
|
4246
|
+
})).min(1)
|
|
4239
4247
|
}),
|
|
4240
4248
|
z24.object({
|
|
4241
4249
|
kind: z24.literal("service_script_approval"),
|
|
@@ -5523,6 +5531,7 @@ var MetaCampaignType = z33.enum([
|
|
|
5523
5531
|
"traffic",
|
|
5524
5532
|
"engagement"
|
|
5525
5533
|
]);
|
|
5534
|
+
var MetaPublishAttemptId = z33.string().uuid();
|
|
5526
5535
|
var MetaOptimizationMode = z33.enum([
|
|
5527
5536
|
"advantage_plus",
|
|
5528
5537
|
"manual"
|
|
@@ -5706,6 +5715,8 @@ var MetaCampaignDefaults = MetaCampaignConfigBase.omit({
|
|
|
5706
5715
|
currency: z33.string().length(3).regex(/^[A-Z]{3}$/).nullable().optional()
|
|
5707
5716
|
});
|
|
5708
5717
|
var UploadToMetaRequest = z33.object({
|
|
5718
|
+
/** Caller-owned retry fence. Required before any provider operation. */
|
|
5719
|
+
publishAttemptId: MetaPublishAttemptId,
|
|
5709
5720
|
/** Array of creatives to upload */
|
|
5710
5721
|
creatives: z33.array(z33.object({
|
|
5711
5722
|
creativeId: z33.string().min(1).optional(),
|
|
@@ -5862,6 +5873,8 @@ var MetaCampaignOverridesSchema = z34.object({
|
|
|
5862
5873
|
destination: MetaCampaignDestination.optional()
|
|
5863
5874
|
});
|
|
5864
5875
|
var FacebookCreateCampaignBodySchema = z34.object({
|
|
5876
|
+
/** Caller-owned retry fence. Required before any provider operation. */
|
|
5877
|
+
publishAttemptId: MetaPublishAttemptId,
|
|
5865
5878
|
adAccountId: MetaAdAccountId,
|
|
5866
5879
|
portfolioId: z34.string().min(1).optional(),
|
|
5867
5880
|
pageId: z34.string().min(1).optional(),
|
|
@@ -5931,6 +5944,8 @@ var FacebookCreateCampaignResponseSchema = z34.object({
|
|
|
5931
5944
|
appliedBudget: z34.number().int().positive()
|
|
5932
5945
|
});
|
|
5933
5946
|
var FacebookCreateCampaignFromCreativesBodySchema = z34.object({
|
|
5947
|
+
/** Caller-owned retry fence. Required before any provider operation. */
|
|
5948
|
+
publishAttemptId: MetaPublishAttemptId,
|
|
5934
5949
|
creatives: z34.array(z34.object({
|
|
5935
5950
|
type: z34.enum([
|
|
5936
5951
|
"image",
|
|
@@ -6078,6 +6093,8 @@ var FacebookCreativeSourceSchema = z34.discriminatedUnion("type", [
|
|
|
6078
6093
|
FacebookMediaCreativeSourceSchema
|
|
6079
6094
|
]);
|
|
6080
6095
|
var FacebookCreateCampaignFromTasksBodySchema = z34.object({
|
|
6096
|
+
/** Caller-owned retry fence. Required before any provider operation. */
|
|
6097
|
+
publishAttemptId: MetaPublishAttemptId,
|
|
6081
6098
|
creatives: z34.array(z34.object({
|
|
6082
6099
|
type: z34.enum([
|
|
6083
6100
|
"image",
|
|
@@ -11473,6 +11490,12 @@ var McpErrorCode = {
|
|
|
11473
11490
|
META_STAGED_ASSET_AMBIGUOUS: "META_STAGED_ASSET_AMBIGUOUS",
|
|
11474
11491
|
META_STAGED_ASSET_TYPE_MISMATCH: "META_STAGED_ASSET_TYPE_MISMATCH",
|
|
11475
11492
|
META_STAGED_ASSET_INVALID: "META_STAGED_ASSET_INVALID",
|
|
11493
|
+
META_STAGING_QUOTA_EXCEEDED: "META_STAGING_QUOTA_EXCEEDED",
|
|
11494
|
+
CAPACITY_UNAVAILABLE: "CAPACITY_UNAVAILABLE",
|
|
11495
|
+
UPLOAD_INCOMPLETE: "UPLOAD_INCOMPLETE",
|
|
11496
|
+
UPLOAD_EXPIRED: "UPLOAD_EXPIRED",
|
|
11497
|
+
META_UPLOAD_SESSION_EXPIRED: "META_UPLOAD_SESSION_EXPIRED",
|
|
11498
|
+
META_PUBLICATION_LEASE_STALE: "META_PUBLICATION_LEASE_STALE",
|
|
11476
11499
|
META_PUBLICATION_RECONCILIATION_REQUIRED: "META_PUBLICATION_RECONCILIATION_REQUIRED",
|
|
11477
11500
|
META_PUBLICATION_IN_PROGRESS: "META_PUBLICATION_IN_PROGRESS",
|
|
11478
11501
|
META_NO_ADS_CREATED: "META_NO_ADS_CREATED",
|
|
@@ -11534,8 +11557,14 @@ var CODE_MESSAGES = {
|
|
|
11534
11557
|
META_STAGED_ASSET_AMBIGUOUS: "The temporary media upload could not be uniquely resolved. Re-upload the media and retry the Meta publication.",
|
|
11535
11558
|
META_STAGED_ASSET_TYPE_MISMATCH: "The temporary media type does not match the creative. Re-upload the correct image or video and retry.",
|
|
11536
11559
|
META_STAGED_ASSET_INVALID: "The temporary media is empty or exceeds the server size limit. Re-upload the file and retry.",
|
|
11537
|
-
|
|
11538
|
-
|
|
11560
|
+
META_STAGING_QUOTA_EXCEEDED: "The temporary Meta staging quota was reached. Keep completed files and retry only pending files after retryAt.",
|
|
11561
|
+
CAPACITY_UNAVAILABLE: "Temporary Meta staging capacity is unavailable. Keep completed files and retry only pending files later.",
|
|
11562
|
+
UPLOAD_INCOMPLETE: "A temporary Meta upload is incomplete. Keep completed files and retry only pending files.",
|
|
11563
|
+
UPLOAD_EXPIRED: "The temporary Meta upload expired. Re-upload this file before publishing.",
|
|
11564
|
+
META_UPLOAD_SESSION_EXPIRED: "The Meta upload session expired. Open a new session and retry only pending files.",
|
|
11565
|
+
META_PUBLICATION_LEASE_STALE: "The Meta publication lease expired or was replaced. Do not continue this stale publication; start a new explicit attempt only after confirming Meta did not receive it.",
|
|
11566
|
+
META_PUBLICATION_RECONCILIATION_REQUIRED: "Meta may already have received this publication. Do not resend it; check Meta Ads Manager.",
|
|
11567
|
+
META_PUBLICATION_IN_PROGRESS: "This Meta publication is already in progress. Do not resend it.",
|
|
11539
11568
|
META_NO_ADS_CREATED: "No ads could be created. Most likely: the Meta Ads account has no payment method, no Facebook Page is linked, or the Facebook app lost permissions. Verify all three at https://business.facebook.com.",
|
|
11540
11569
|
META_NO_ASSETS_READY: "The task has no videos or ad creative ready to publish yet. Wait for generation to finish or regenerate the assets.",
|
|
11541
11570
|
META_NO_THUMBNAIL_SOURCE: "No image is available to use as the video thumbnail. Upload a product image or generate the ad creative before publishing.",
|
|
@@ -11684,6 +11713,11 @@ var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
|
|
|
11684
11713
|
McpErrorCode.META_STAGED_ASSET_AMBIGUOUS,
|
|
11685
11714
|
McpErrorCode.META_STAGED_ASSET_TYPE_MISMATCH,
|
|
11686
11715
|
McpErrorCode.META_STAGED_ASSET_INVALID,
|
|
11716
|
+
McpErrorCode.META_STAGING_QUOTA_EXCEEDED,
|
|
11717
|
+
McpErrorCode.UPLOAD_INCOMPLETE,
|
|
11718
|
+
McpErrorCode.UPLOAD_EXPIRED,
|
|
11719
|
+
McpErrorCode.META_UPLOAD_SESSION_EXPIRED,
|
|
11720
|
+
McpErrorCode.META_PUBLICATION_LEASE_STALE,
|
|
11687
11721
|
McpErrorCode.META_PUBLICATION_RECONCILIATION_REQUIRED,
|
|
11688
11722
|
McpErrorCode.META_PUBLICATION_IN_PROGRESS,
|
|
11689
11723
|
McpErrorCode.TIKTOK_TOKEN_EXPIRED,
|
|
@@ -11743,6 +11777,14 @@ var PASSTHROUGH_CODES = /* @__PURE__ */ new Set([
|
|
|
11743
11777
|
McpErrorCode.META_CAMPAIGN_CONFIG_INVALID,
|
|
11744
11778
|
McpErrorCode.META_WHATSAPP_INVALID_NUMBER,
|
|
11745
11779
|
McpErrorCode.META_INVALID_CTA,
|
|
11780
|
+
McpErrorCode.META_STAGING_QUOTA_EXCEEDED,
|
|
11781
|
+
McpErrorCode.CAPACITY_UNAVAILABLE,
|
|
11782
|
+
McpErrorCode.UPLOAD_INCOMPLETE,
|
|
11783
|
+
McpErrorCode.UPLOAD_EXPIRED,
|
|
11784
|
+
McpErrorCode.META_UPLOAD_SESSION_EXPIRED,
|
|
11785
|
+
McpErrorCode.META_PUBLICATION_LEASE_STALE,
|
|
11786
|
+
McpErrorCode.META_PUBLICATION_RECONCILIATION_REQUIRED,
|
|
11787
|
+
McpErrorCode.META_PUBLICATION_IN_PROGRESS,
|
|
11746
11788
|
// TikTok
|
|
11747
11789
|
McpErrorCode.TIKTOK_NOT_CONNECTED,
|
|
11748
11790
|
McpErrorCode.TIKTOK_TOKEN_EXPIRED,
|
|
@@ -11831,6 +11873,10 @@ function translateError(err) {
|
|
|
11831
11873
|
if (body.helpUrl !== void 0) extra.helpUrl = body.helpUrl;
|
|
11832
11874
|
if (typeof body.requiredCredits === "number") extra.requiredCredits = body.requiredCredits;
|
|
11833
11875
|
if (typeof body.availableCredits === "number") extra.availableCredits = body.availableCredits;
|
|
11876
|
+
if (typeof body.dimension === "string") extra.dimension = body.dimension;
|
|
11877
|
+
if (typeof body.usage === "number") extra.usage = body.usage;
|
|
11878
|
+
if (typeof body.limit === "number") extra.limit = body.limit;
|
|
11879
|
+
if (typeof body.retryAt === "string") extra.retryAt = body.retryAt;
|
|
11834
11880
|
return mcp(code, resolveMessage(code, body.message), extra);
|
|
11835
11881
|
}
|
|
11836
11882
|
if (err.status === 401) {
|
|
@@ -13557,6 +13603,57 @@ __name(registerActorTools, "registerActorTools");
|
|
|
13557
13603
|
|
|
13558
13604
|
// src/tools/publish.ts
|
|
13559
13605
|
import { z as z55 } from "zod";
|
|
13606
|
+
|
|
13607
|
+
// src/tools/stable-uuid.ts
|
|
13608
|
+
import { createHash as createHash2 } from "crypto";
|
|
13609
|
+
function canonicalize(value) {
|
|
13610
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
13611
|
+
if (value === null || typeof value !== "object") return value;
|
|
13612
|
+
const record = value;
|
|
13613
|
+
return Object.fromEntries(Object.keys(record).sort().filter((key) => record[key] !== void 0).map((key) => [
|
|
13614
|
+
key,
|
|
13615
|
+
canonicalize(record[key])
|
|
13616
|
+
]));
|
|
13617
|
+
}
|
|
13618
|
+
__name(canonicalize, "canonicalize");
|
|
13619
|
+
function canonicalJson(value) {
|
|
13620
|
+
return JSON.stringify(canonicalize(value));
|
|
13621
|
+
}
|
|
13622
|
+
__name(canonicalJson, "canonicalJson");
|
|
13623
|
+
function deriveUuidV5(name, namespace) {
|
|
13624
|
+
const namespaceBytes = Buffer.from(namespace.replaceAll("-", ""), "hex");
|
|
13625
|
+
const digest = createHash2("sha1").update(namespaceBytes).update(name, "utf8").digest();
|
|
13626
|
+
const bytes = Buffer.from(digest.subarray(0, 16));
|
|
13627
|
+
bytes[6] = (bytes[6] ?? 0) & 15 | 80;
|
|
13628
|
+
bytes[8] = (bytes[8] ?? 0) & 63 | 128;
|
|
13629
|
+
const hex = bytes.toString("hex");
|
|
13630
|
+
return [
|
|
13631
|
+
hex.slice(0, 8),
|
|
13632
|
+
hex.slice(8, 12),
|
|
13633
|
+
hex.slice(12, 16),
|
|
13634
|
+
hex.slice(16, 20),
|
|
13635
|
+
hex.slice(20)
|
|
13636
|
+
].join("-");
|
|
13637
|
+
}
|
|
13638
|
+
__name(deriveUuidV5, "deriveUuidV5");
|
|
13639
|
+
|
|
13640
|
+
// src/tools/meta-publish-attempt-id.ts
|
|
13641
|
+
var META_PUBLISH_NAMESPACE = "7b19c9ac-b4eb-4db1-970e-c2f24ce30b98";
|
|
13642
|
+
function deriveMetaPublishAttemptId(input) {
|
|
13643
|
+
const operationIdentity = canonicalJson([
|
|
13644
|
+
input.fingerprint,
|
|
13645
|
+
input.toolName,
|
|
13646
|
+
[
|
|
13647
|
+
typeof input.requestId,
|
|
13648
|
+
input.requestId
|
|
13649
|
+
],
|
|
13650
|
+
input.canonicalInput
|
|
13651
|
+
]);
|
|
13652
|
+
return deriveUuidV5(operationIdentity, META_PUBLISH_NAMESPACE);
|
|
13653
|
+
}
|
|
13654
|
+
__name(deriveMetaPublishAttemptId, "deriveMetaPublishAttemptId");
|
|
13655
|
+
|
|
13656
|
+
// src/tools/publish.ts
|
|
13560
13657
|
var ShopifyInput = z55.object({
|
|
13561
13658
|
taskId: z55.string().min(1),
|
|
13562
13659
|
shopDomain: z55.string().min(1),
|
|
@@ -13884,10 +13981,19 @@ function registerPublishTools(server, api, opts = {}) {
|
|
|
13884
13981
|
throw translateError(e);
|
|
13885
13982
|
}
|
|
13886
13983
|
});
|
|
13887
|
-
server.tool("publish_to_meta", "Crea una campa\xF1a de Meta Ads (Facebook/Instagram) desde UNA sola tarea. Antes llama `list_meta_destinations`, deja que el usuario elija el `portfolioId` (Business portfolio), el `adAccountId` y el `pageId` exactos, y env\xEDa esos tres valores; no adivines ni uses la primera p\xE1gina. Si quieres juntar creativos de VARIAS tareas usa `publish_creatives_to_meta`. La campa\xF1a se crea SIEMPRE PAUSED y el usuario la activa manualmente. `pixelId` es opcional. `angleIndexes` permite seleccionar \xE1ngulos. `campaignConfig` admite presupuesto en USD, pa\xEDses ISO-2 y nombre. `primaryText` permite reemplazar el copy y `phoneNumber` configura WhatsApp. Despu\xE9s usa `get_meta_campaign_status`.", MetaInput.shape, CREATES_EXTERNAL, async (
|
|
13984
|
+
server.tool("publish_to_meta", "Publica tus creativos en Meta sin gastar cr\xE9ditos de ProductMaker. El presupuesto de anuncios se paga directamente desde tu cuenta de Meta. Crear creativos o copy con IA sigue usando cr\xE9ditos. Crea una campa\xF1a de Meta Ads (Facebook/Instagram) desde UNA sola tarea. Antes llama `list_meta_destinations`, deja que el usuario elija el `portfolioId` (Business portfolio), el `adAccountId` y el `pageId` exactos, y env\xEDa esos tres valores; no adivines ni uses la primera p\xE1gina. Si quieres juntar creativos de VARIAS tareas usa `publish_creatives_to_meta`. La campa\xF1a se crea SIEMPRE PAUSED y el usuario la activa manualmente. `pixelId` es opcional. `angleIndexes` permite seleccionar \xE1ngulos. `campaignConfig` admite presupuesto en USD, pa\xEDses ISO-2 y nombre. `primaryText` permite reemplazar el copy y `phoneNumber` configura WhatsApp. Despu\xE9s usa `get_meta_campaign_status`. Si el resultado es ambiguo, NO reenv\xEDes: revisa Meta Ads Manager.", MetaInput.shape, CREATES_EXTERNAL, async (rawInput, extra) => {
|
|
13888
13985
|
try {
|
|
13986
|
+
const parsed = MetaInput.safeParse(rawInput);
|
|
13987
|
+
if (!parsed.success) throw invalidInputFromZod(parsed.error);
|
|
13988
|
+
const { taskId, campaignConfig, primaryText, phoneNumber, ...rest } = parsed.data;
|
|
13889
13989
|
const body = {
|
|
13890
|
-
...rest
|
|
13990
|
+
...rest,
|
|
13991
|
+
publishAttemptId: deriveMetaPublishAttemptId({
|
|
13992
|
+
fingerprint: api.fingerprint(),
|
|
13993
|
+
toolName: "publish_to_meta",
|
|
13994
|
+
requestId: extra.requestId,
|
|
13995
|
+
canonicalInput: parsed.data
|
|
13996
|
+
})
|
|
13891
13997
|
};
|
|
13892
13998
|
if (campaignConfig) {
|
|
13893
13999
|
body.campaignOverrides = toMetaCampaignOverrides(campaignConfig);
|
|
@@ -13912,7 +14018,7 @@ function registerPublishTools(server, api, opts = {}) {
|
|
|
13912
14018
|
throw translateError(e);
|
|
13913
14019
|
}
|
|
13914
14020
|
});
|
|
13915
|
-
server.tool("publish_creatives_to_meta", "Crea UNA campa\xF1a de Meta Ads PAUSED agrupando hasta " + CAMPAIGN_LIMITS.MAX_CREATIVES + ' creativos. Si la petici\xF3n natural adjunta o menciona archivos locales, llama primero `request_media_upload`; tras OK usa cada resultado como `source.type="asset"` con su `assetId` y el `type` que coincide con `mediaKind`. No pidas path ni URL. Despu\xE9s llama `list_meta_destinations` y pregunta en el chat por el `portfolioId`, `adAccountId` y `pageId` exactos; el App es solo para archivos. Si la carga fue parcial,
|
|
14021
|
+
server.tool("publish_creatives_to_meta", "Crea UNA campa\xF1a de Meta Ads PAUSED agrupando hasta " + CAMPAIGN_LIMITS.MAX_CREATIVES + ' creativos. Publica tus creativos en Meta sin gastar cr\xE9ditos de ProductMaker. El presupuesto de anuncios se paga directamente desde tu cuenta de Meta. Crear creativos o copy con IA sigue usando cr\xE9ditos. Si la petici\xF3n natural adjunta o menciona archivos locales, llama primero `request_media_upload`; tras OK usa cada resultado como `source.type="asset"` con su `assetId` y el `type` que coincide con `mediaKind`. No pidas path ni URL. Despu\xE9s llama `list_meta_destinations` y pregunta en el chat por el `portfolioId`, `adAccountId` y `pageId` exactos; el App es solo para archivos. Si la carga fue parcial, conserva los assets confirmados, procesa s\xF3lo los pendientes y publica \xFAnicamente assets confirmados; nunca inventes assets ni superes diez creativos. Las rutas locales s\xF3lo funcionan con MCP stdio. Los medios propios se eliminan despu\xE9s de publicar correctamente; si falla antes de enviar una mutaci\xF3n a Meta, quedan durante el TTL. Para videos la miniatura es opcional y ProductMaker puede generarla con FFmpeg. `campaignConfig.destination` acepta landing o WhatsApp. Para landing basta `campaignConfig.destination.landingUrl`; no dupliques la URL arriba. Si omites `budgetMode`, se infiere `ad_set` cuando cada ad set trae `dailyBudget`, o `campaign` cuando env\xEDas `campaignDailyBudget`. No actives la campa\xF1a autom\xE1ticamente. Si el resultado es ambiguo, NO reenv\xEDes: revisa Meta Ads Manager.', PublishCreativesToMetaInputBase.shape, CREATES_EXTERNAL, async (rawInput, extra) => {
|
|
13916
14022
|
try {
|
|
13917
14023
|
const parsed = PublishCreativesToMetaInput.safeParse(rawInput);
|
|
13918
14024
|
if (!parsed.success) throw invalidInputFromZod(parsed.error);
|
|
@@ -13992,7 +14098,13 @@ function registerPublishTools(server, api, opts = {}) {
|
|
|
13992
14098
|
landingUrl: input.landingUrl,
|
|
13993
14099
|
targetCountry: input.targetCountry,
|
|
13994
14100
|
campaignName: input.campaignName,
|
|
13995
|
-
campaignConfig: input.campaignConfig
|
|
14101
|
+
campaignConfig: input.campaignConfig,
|
|
14102
|
+
publishAttemptId: deriveMetaPublishAttemptId({
|
|
14103
|
+
fingerprint: api.fingerprint(),
|
|
14104
|
+
toolName: "publish_creatives_to_meta",
|
|
14105
|
+
requestId: extra.requestId,
|
|
14106
|
+
canonicalInput: input
|
|
14107
|
+
})
|
|
13996
14108
|
});
|
|
13997
14109
|
return asText({
|
|
13998
14110
|
...r,
|
|
@@ -14226,7 +14338,7 @@ __name(resolveEndpointForSession, "resolveEndpointForSession");
|
|
|
14226
14338
|
function registerMediaUploadTools(server, api) {
|
|
14227
14339
|
registerAppTool(server, "request_media_upload", {
|
|
14228
14340
|
title: "Subir archivos",
|
|
14229
|
-
description: "Abre el selector universal de ProductMaker para im\xE1genes o videos del usuario. Ll\xE1malo autom\xE1ticamente y antes de preguntar por campos faltantes cuando una petici\xF3n natural adjunte o mencione un archivo local, por ejemplo \u2018crea una landing con este producto\u2019, \u2018haz creativos con esta foto\u2019 o \u2018sube este video a Meta\u2019; hazlo aunque el usuario diga que todav\xEDa no generes ni publiques, porque esta herramienta solo prepara el archivo y no consume cr\xE9ditos, salvo que proh\xEDba expl\xEDcitamente subir, compartir o almacenar el archivo; no pidas una ruta local ni URL. Es la \xFAnica ruta de carga para create_product_task, generate_image_creatives, generate_video_creative, generate_free_video, generate_landing y publish_creatives_to_meta. Pasa las referencias opcionales de ChatGPT en files; si el host no puede transferirlas, el mismo App muestra el selector inline. Usa uploadPurpose=product_reference para fotos de producto y landings, creative para creativos o anuncios, y generic solo cuando ninguno aplique. Usa mediaKind=image para referencias de producto y landings; mixed solo cuando el consumidor acepte ambos tipos. El App NO te avisa cuando termina: en cuanto el usuario diga que ya subi\xF3 el archivo o que sigas, llama `check_media_upload` con este sessionId para obtener los assetId, y reanuda la intenci\xF3n original usando \xFAnicamente assetId y mediaKind, sin pedir que vuelva a subir ni mostrar datos t\xE9cnicos. El enlace externo es fallback solo si el App inline falla. Esta herramienta no genera, publica ni consume cr\xE9ditos.",
|
|
14341
|
+
description: "Abre el selector universal de ProductMaker para im\xE1genes o videos del usuario. Cuando la foto o el video sea el siguiente dato faltante, ll\xE1malo en esa misma respuesta: no te limites a pedir que lo adjunte en el chat y esperar otro turno. Ll\xE1malo tambi\xE9n autom\xE1ticamente y antes de preguntar por campos faltantes adicionales cuando una petici\xF3n natural adjunte o mencione un archivo local, por ejemplo \u2018crea una landing con este producto\u2019, \u2018haz creativos con esta foto\u2019 o \u2018sube este video a Meta\u2019; hazlo aunque el usuario diga que todav\xEDa no generes ni publiques, porque esta herramienta solo prepara el archivo y no consume cr\xE9ditos, salvo que proh\xEDba expl\xEDcitamente subir, compartir o almacenar el archivo; no pidas una ruta local ni URL. Es la \xFAnica ruta de carga para create_product_task, generate_image_creatives, generate_video_creative, generate_free_video, generate_landing y publish_creatives_to_meta. Pasa las referencias opcionales de ChatGPT en files; si el host no puede transferirlas, el mismo App muestra el selector inline. Usa uploadPurpose=product_reference para fotos de producto y landings, creative para creativos o anuncios, y generic solo cuando ninguno aplique. Usa mediaKind=image para referencias de producto y landings; mixed solo cuando el consumidor acepte ambos tipos. El App NO te avisa cuando termina: en cuanto el usuario diga que ya subi\xF3 el archivo o que sigas, llama `check_media_upload` con este sessionId para obtener los assetId, y reanuda la intenci\xF3n original usando \xFAnicamente assetId y mediaKind, sin pedir que vuelva a subir ni mostrar datos t\xE9cnicos. El enlace externo es fallback solo si el App inline falla. Esta herramienta no genera, publica ni consume cr\xE9ditos.",
|
|
14230
14342
|
inputSchema: UploadSessionToolInputShape,
|
|
14231
14343
|
annotations: CREATES_INTERNAL,
|
|
14232
14344
|
outputSchema: UniversalUploadStructuredResponse.shape,
|
|
@@ -14755,7 +14867,7 @@ var ProductCostingToolInput = z60.object({
|
|
|
14755
14867
|
desiredMarginPercent: optionalPercent("Margen de ganancia deseado. Ejemplo: 10 significa 10%. Omite para usar el valor gu\xEDa editable del pa\xEDs.")
|
|
14756
14868
|
});
|
|
14757
14869
|
function registerProductCostingTools(server) {
|
|
14758
|
-
server.tool("cost_product", "Calcula GRATIS y sin cr\xE9ditos cu\xE1nto cobrar por un producto de dropshipping en cualquiera de los pa\xEDses disponibles en ProductMaker. Es READ-ONLY: no crea tareas ni publica nada. Pide country y el costo real del proveedor; los dem\xE1s campos pueden usar valores gu\xEDa editables del pa\xEDs. Devuelve `price` y `compareAtPrice` listos para `create_product_task`. No inventes un costo de proveedor.", ProductCostingToolInput.shape, READS_INTERNAL, async (input) => {
|
|
14870
|
+
server.tool("cost_product", "Calcula GRATIS y sin cr\xE9ditos cu\xE1nto cobrar por un producto de dropshipping en cualquiera de los pa\xEDses disponibles en ProductMaker. Ll\xE1mala siempre que el usuario pida calcular cu\xE1nto cobrar, la rentabilidad o el margen de un producto concreto; no hagas el c\xE1lculo ni reconstruyas la f\xF3rmula por tu cuenta. Es READ-ONLY: no crea tareas ni publica nada. Pide country y el costo real del proveedor; los dem\xE1s campos pueden usar valores gu\xEDa editables del pa\xEDs. Devuelve `price` y `compareAtPrice` listos para `create_product_task`. No inventes un costo de proveedor.", ProductCostingToolInput.shape, READS_INTERNAL, async (input) => {
|
|
14759
14871
|
if (input.country !== void 0 && input.country !== "CO" && [
|
|
14760
14872
|
input.supplierCostCop,
|
|
14761
14873
|
input.shippingCostCop,
|
|
@@ -14879,29 +14991,12 @@ __name(formatPercent, "formatPercent");
|
|
|
14879
14991
|
import { z as z61 } from "zod";
|
|
14880
14992
|
|
|
14881
14993
|
// src/tools/product-research-idempotency.ts
|
|
14882
|
-
import { createHash as createHash2 } from "crypto";
|
|
14883
14994
|
var RESEARCH_IDEMPOTENCY_WINDOW_MS = 2 * 60 * 60 * 1e3;
|
|
14884
14995
|
var NAMESPACE = "6f9d3a1e-8c47-4b52-9f10-2d7e5a3c8b64";
|
|
14885
|
-
function uuidV5(name, namespace) {
|
|
14886
|
-
const namespaceBytes = Buffer.from(namespace.replace(/-/g, ""), "hex");
|
|
14887
|
-
const digest = createHash2("sha1").update(namespaceBytes).update(name, "utf8").digest();
|
|
14888
|
-
const bytes = Buffer.from(digest.subarray(0, 16));
|
|
14889
|
-
bytes[6] = (bytes[6] ?? 0) & 15 | 80;
|
|
14890
|
-
bytes[8] = (bytes[8] ?? 0) & 63 | 128;
|
|
14891
|
-
const hex = bytes.toString("hex");
|
|
14892
|
-
return [
|
|
14893
|
-
hex.slice(0, 8),
|
|
14894
|
-
hex.slice(8, 12),
|
|
14895
|
-
hex.slice(12, 16),
|
|
14896
|
-
hex.slice(16, 20),
|
|
14897
|
-
hex.slice(20)
|
|
14898
|
-
].join("-");
|
|
14899
|
-
}
|
|
14900
|
-
__name(uuidV5, "uuidV5");
|
|
14901
14996
|
function deriveIdempotencyKey(input) {
|
|
14902
14997
|
const window = Math.floor(input.nowMs / RESEARCH_IDEMPOTENCY_WINDOW_MS);
|
|
14903
14998
|
const name = `${input.fingerprint}:${input.targetCountry}:${input.categoryKey}:${window}`;
|
|
14904
|
-
return
|
|
14999
|
+
return deriveUuidV5(name, NAMESPACE);
|
|
14905
15000
|
}
|
|
14906
15001
|
__name(deriveIdempotencyKey, "deriveIdempotencyKey");
|
|
14907
15002
|
|
|
@@ -15377,8 +15472,9 @@ __name(registerDropshippingProjectTools, "registerDropshippingProjectTools");
|
|
|
15377
15472
|
// src/guidance/productmaker-guidance.ts
|
|
15378
15473
|
var START_DROPSHIPPING_PROMPT_NAME = "start_dropshipping";
|
|
15379
15474
|
var PRODUCTMAKER_SERVER_INSTRUCTIONS = [
|
|
15380
|
-
"
|
|
15475
|
+
"Whenever a ProductMaker request needs a new image or video and no reusable ProductMaker assetId is available, call request_media_upload immediately in the same response\u2014whether media is the next missing input or the user attached, pasted, mentioned, or has the file locally. The inline uploader is how ProductMaker asks for the file. Never merely ask the user to attach or upload it in chat and wait for a later turn. Call it before asking for any missing business inputs, even when the user says not to generate or publish yet: uploading only stages the file and does not generate, publish, or consume credits, unless the user explicitly says not to upload, share, or store the file. Treat natural requests such as 'create a landing with this product', 'make creatives with this photo', or 'upload this video to Meta' as upload intent; never require the user to name the uploader.",
|
|
15381
15476
|
"For requests that do not need a new media upload, understand the user's intent before choosing a ProductMaker tool.",
|
|
15477
|
+
"For every concrete ProductMaker request to calculate how much to charge, profitability, or margin for a dropshipping product, you must call cost_product after collecting its missing country and real supplier cost. Never calculate or reproduce ProductMaker's costing formula yourself; answer from the tool result.",
|
|
15382
15478
|
"For remote MCP hosts, never ask for a local path, CDN URL, bytes, or base64. Pass supported host file references to request_media_upload; otherwise let its inline App open the file picker. The external upload page is fallback only.",
|
|
15383
15479
|
"After the upload App sends its OK continuation, resume the original request with the verified assetId values, do not ask for another upload, and ask only for remaining business inputs in chat. The host-inserted continuation may contain opaque asset IDs; do not repeat them in narrative prose or expose session IDs, tokens, private URLs, file names, or JSON handoff payloads.",
|
|
15384
15480
|
"Keep focused requests direct: creatives, landing pages, Shopify publication, Meta publication, and status checks do not require a DropshippingProject.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@productmaker/mcp",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Product Maker MCP server — cost products, generate dropshipping landing pages, UGC videos, and ad creatives, and publish to Shopify and Meta Ads from Claude, ChatGPT, or Gemini.",
|