@productmaker/mcp 3.4.1 → 3.4.3
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 +169 -30
- package/dist/stdio.js +169 -30
- package/package.json +1 -1
package/dist/http.js
CHANGED
|
@@ -3692,6 +3692,32 @@ var StatusResponse = z24.object({
|
|
|
3692
3692
|
requiresAction: z24.boolean().optional(),
|
|
3693
3693
|
/** True when the task needs the user's attention rather than passive progress. */
|
|
3694
3694
|
needsReview: z24.boolean().optional(),
|
|
3695
|
+
/**
|
|
3696
|
+
* THE answer to "does this task still owe the seller a question — now OR later
|
|
3697
|
+
* in this run?". The chat leaves for /tasks/:id only on an explicit `false`,
|
|
3698
|
+
* and /tasks/:id bounces her back on `true`. ONE field drives BOTH directions
|
|
3699
|
+
* on purpose: deciding them on two different facts is what looped
|
|
3700
|
+
* /tasks/:id ↔ wizard before (see apps/web/lib/task-routing.ts).
|
|
3701
|
+
*
|
|
3702
|
+
* STRICTLY WIDER THAN `needsReview`, and the difference is the whole point:
|
|
3703
|
+
* · `needsReview` answers "is a gate open RIGHT NOW", and it under-reports —
|
|
3704
|
+
* `buildPendingUserAction` only serves the reference-grouping question when
|
|
3705
|
+
* it is RENDERABLE, so an unconfirmed target reports `false` while
|
|
3706
|
+
* `angles.worker` will still pause at the END of the angles stage.
|
|
3707
|
+
* · this field adds the gates that are GUARANTEED to open: both pause flags,
|
|
3708
|
+
* plus that same worker prediction, through the one shared helper.
|
|
3709
|
+
*
|
|
3710
|
+
* Absent (undefined) means "an older API that cannot answer" — the client must
|
|
3711
|
+
* treat that as `true` and stay put. Uncertainty resolves to staying: a false
|
|
3712
|
+
* positive costs her a wait, a false negative strands her on a screen that
|
|
3713
|
+
* cannot render the gate (prod bd4d0bb1 / 52dc15a7, both refunded and
|
|
3714
|
+
* undelivered).
|
|
3715
|
+
*
|
|
3716
|
+
* ALWAYS false on a terminal task. `pauseForAngleReview` SURVIVES on completed
|
|
3717
|
+
* tasks, so without that guard a finished landing would report `true` forever
|
|
3718
|
+
* and get bounced into a chat with nothing left to ask.
|
|
3719
|
+
*/
|
|
3720
|
+
willAskAgain: z24.boolean().optional(),
|
|
3695
3721
|
/** True when an open gate exists that somebody can actually ANSWER. A strictly
|
|
3696
3722
|
* narrower fact than `needsReview` — see the identical field on `TaskSummary`
|
|
3697
3723
|
* below for why conflating the two produced a call to action that led nowhere. */
|
|
@@ -3826,6 +3852,9 @@ var CostEstimationType = z25.enum([
|
|
|
3826
3852
|
"landing-regenerate",
|
|
3827
3853
|
"landing-creative",
|
|
3828
3854
|
"campaign-copy",
|
|
3855
|
+
"brand-extract",
|
|
3856
|
+
"persona-suggest",
|
|
3857
|
+
"chat-parse",
|
|
3829
3858
|
"service-creative",
|
|
3830
3859
|
"actor-grid"
|
|
3831
3860
|
]);
|
|
@@ -3991,11 +4020,14 @@ var LandingCreativeEstimationOptions = z25.object({
|
|
|
3991
4020
|
landingStrategy: LandingStrategySchema.optional()
|
|
3992
4021
|
});
|
|
3993
4022
|
var CampaignCopyEstimationOptions = z25.object({
|
|
3994
|
-
/** Number of creatives
|
|
3995
|
-
nCreatives: z25.number().int().min(1).max(
|
|
3996
|
-
/** Number of image creatives that
|
|
3997
|
-
nImages: z25.number().int().min(0).max(
|
|
3998
|
-
});
|
|
4023
|
+
/** Number of creatives in the one copy-completion request (1-10). */
|
|
4024
|
+
nCreatives: z25.number().int().min(1).max(10),
|
|
4025
|
+
/** Number of image creatives that can each use one vision analysis (0-10). */
|
|
4026
|
+
nImages: z25.number().int().min(0).max(10).default(0)
|
|
4027
|
+
});
|
|
4028
|
+
var BrandExtractEstimationOptions = z25.object({}).strict();
|
|
4029
|
+
var PersonaSuggestEstimationOptions = z25.object({}).strict();
|
|
4030
|
+
var ChatParseEstimationOptions = z25.object({}).strict();
|
|
3999
4031
|
var ServiceCreativeEstimationOptions = z25.object({
|
|
4000
4032
|
mediaAssetCount: z25.number().int().min(0).max(8).optional(),
|
|
4001
4033
|
uploadedVideoDurationSeconds: z25.number().min(0).max(240).optional()
|
|
@@ -4034,6 +4066,18 @@ var CostEstimationRequest = z25.discriminatedUnion("type", [
|
|
|
4034
4066
|
type: z25.literal("campaign-copy"),
|
|
4035
4067
|
options: CampaignCopyEstimationOptions
|
|
4036
4068
|
}),
|
|
4069
|
+
z25.object({
|
|
4070
|
+
type: z25.literal("brand-extract"),
|
|
4071
|
+
options: BrandExtractEstimationOptions
|
|
4072
|
+
}),
|
|
4073
|
+
z25.object({
|
|
4074
|
+
type: z25.literal("persona-suggest"),
|
|
4075
|
+
options: PersonaSuggestEstimationOptions
|
|
4076
|
+
}),
|
|
4077
|
+
z25.object({
|
|
4078
|
+
type: z25.literal("chat-parse"),
|
|
4079
|
+
options: ChatParseEstimationOptions
|
|
4080
|
+
}),
|
|
4037
4081
|
z25.object({
|
|
4038
4082
|
type: z25.literal("service-creative"),
|
|
4039
4083
|
options: ServiceCreativeEstimationOptions
|
|
@@ -4533,6 +4577,9 @@ var VideoMixQaJobSchema = z30.object({
|
|
|
4533
4577
|
});
|
|
4534
4578
|
var BrandExtractJobSchema = z30.object({
|
|
4535
4579
|
brandId: z30.string().uuid(),
|
|
4580
|
+
// Generation is part of the durable paid-spend identity. Default 1 keeps
|
|
4581
|
+
// pre-migration BullMQ payloads parseable; their DB state fails safe.
|
|
4582
|
+
generation: z30.number().int().positive().default(1),
|
|
4536
4583
|
url: z30.string().url().refine((value) => {
|
|
4537
4584
|
const protocol = new URL(value).protocol;
|
|
4538
4585
|
return protocol === "http:" || protocol === "https:";
|
|
@@ -4854,6 +4901,7 @@ var MetaCampaignType = z33.enum([
|
|
|
4854
4901
|
"traffic",
|
|
4855
4902
|
"engagement"
|
|
4856
4903
|
]);
|
|
4904
|
+
var MetaPublishAttemptId = z33.string().uuid();
|
|
4857
4905
|
var MetaOptimizationMode = z33.enum([
|
|
4858
4906
|
"advantage_plus",
|
|
4859
4907
|
"manual"
|
|
@@ -5037,6 +5085,8 @@ var MetaCampaignDefaults = MetaCampaignConfigBase.omit({
|
|
|
5037
5085
|
currency: z33.string().length(3).regex(/^[A-Z]{3}$/).nullable().optional()
|
|
5038
5086
|
});
|
|
5039
5087
|
var UploadToMetaRequest = z33.object({
|
|
5088
|
+
/** Caller-owned retry fence. Required before any provider operation. */
|
|
5089
|
+
publishAttemptId: MetaPublishAttemptId,
|
|
5040
5090
|
/** Array of creatives to upload */
|
|
5041
5091
|
creatives: z33.array(z33.object({
|
|
5042
5092
|
creativeId: z33.string().min(1).optional(),
|
|
@@ -5193,6 +5243,8 @@ var MetaCampaignOverridesSchema = z34.object({
|
|
|
5193
5243
|
destination: MetaCampaignDestination.optional()
|
|
5194
5244
|
});
|
|
5195
5245
|
var FacebookCreateCampaignBodySchema = z34.object({
|
|
5246
|
+
/** Caller-owned retry fence. Required before any provider operation. */
|
|
5247
|
+
publishAttemptId: MetaPublishAttemptId,
|
|
5196
5248
|
adAccountId: MetaAdAccountId,
|
|
5197
5249
|
portfolioId: z34.string().min(1).optional(),
|
|
5198
5250
|
pageId: z34.string().min(1).optional(),
|
|
@@ -5262,6 +5314,8 @@ var FacebookCreateCampaignResponseSchema = z34.object({
|
|
|
5262
5314
|
appliedBudget: z34.number().int().positive()
|
|
5263
5315
|
});
|
|
5264
5316
|
var FacebookCreateCampaignFromCreativesBodySchema = z34.object({
|
|
5317
|
+
/** Caller-owned retry fence. Required before any provider operation. */
|
|
5318
|
+
publishAttemptId: MetaPublishAttemptId,
|
|
5265
5319
|
creatives: z34.array(z34.object({
|
|
5266
5320
|
type: z34.enum([
|
|
5267
5321
|
"image",
|
|
@@ -5409,6 +5463,8 @@ var FacebookCreativeSourceSchema = z34.discriminatedUnion("type", [
|
|
|
5409
5463
|
FacebookMediaCreativeSourceSchema
|
|
5410
5464
|
]);
|
|
5411
5465
|
var FacebookCreateCampaignFromTasksBodySchema = z34.object({
|
|
5466
|
+
/** Caller-owned retry fence. Required before any provider operation. */
|
|
5467
|
+
publishAttemptId: MetaPublishAttemptId,
|
|
5412
5468
|
creatives: z34.array(z34.object({
|
|
5413
5469
|
type: z34.enum([
|
|
5414
5470
|
"image",
|
|
@@ -11631,7 +11687,7 @@ var ApiClient = class {
|
|
|
11631
11687
|
};
|
|
11632
11688
|
|
|
11633
11689
|
// src/index.ts
|
|
11634
|
-
var MCP_VERSION = "3.4.
|
|
11690
|
+
var MCP_VERSION = "3.4.3";
|
|
11635
11691
|
|
|
11636
11692
|
// src/meta-upload-app.ts
|
|
11637
11693
|
import { readFileSync } from "fs";
|
|
@@ -11943,6 +11999,7 @@ __name(resolveOne, "resolveOne");
|
|
|
11943
11999
|
|
|
11944
12000
|
// src/status-poller.ts
|
|
11945
12001
|
var TERMINAL = /* @__PURE__ */ new Set([
|
|
12002
|
+
"done",
|
|
11946
12003
|
"complete",
|
|
11947
12004
|
"error"
|
|
11948
12005
|
]);
|
|
@@ -12618,6 +12675,7 @@ var McpErrorCode = {
|
|
|
12618
12675
|
REVOKED_API_KEY: "REVOKED_API_KEY",
|
|
12619
12676
|
// Rate limit / quota
|
|
12620
12677
|
RATE_LIMITED: "RATE_LIMITED",
|
|
12678
|
+
CREDIT_ADMISSION_UNAVAILABLE: "CREDIT_ADMISSION_UNAVAILABLE",
|
|
12621
12679
|
// Input
|
|
12622
12680
|
INVALID_INPUT: "INVALID_INPUT",
|
|
12623
12681
|
INSUFFICIENT_CREDITS: "INSUFFICIENT_CREDITS",
|
|
@@ -12675,6 +12733,12 @@ var McpErrorCode = {
|
|
|
12675
12733
|
META_STAGED_ASSET_AMBIGUOUS: "META_STAGED_ASSET_AMBIGUOUS",
|
|
12676
12734
|
META_STAGED_ASSET_TYPE_MISMATCH: "META_STAGED_ASSET_TYPE_MISMATCH",
|
|
12677
12735
|
META_STAGED_ASSET_INVALID: "META_STAGED_ASSET_INVALID",
|
|
12736
|
+
META_STAGING_QUOTA_EXCEEDED: "META_STAGING_QUOTA_EXCEEDED",
|
|
12737
|
+
CAPACITY_UNAVAILABLE: "CAPACITY_UNAVAILABLE",
|
|
12738
|
+
UPLOAD_INCOMPLETE: "UPLOAD_INCOMPLETE",
|
|
12739
|
+
UPLOAD_EXPIRED: "UPLOAD_EXPIRED",
|
|
12740
|
+
META_UPLOAD_SESSION_EXPIRED: "META_UPLOAD_SESSION_EXPIRED",
|
|
12741
|
+
META_PUBLICATION_LEASE_STALE: "META_PUBLICATION_LEASE_STALE",
|
|
12678
12742
|
META_PUBLICATION_RECONCILIATION_REQUIRED: "META_PUBLICATION_RECONCILIATION_REQUIRED",
|
|
12679
12743
|
META_PUBLICATION_IN_PROGRESS: "META_PUBLICATION_IN_PROGRESS",
|
|
12680
12744
|
META_NO_ADS_CREATED: "META_NO_ADS_CREATED",
|
|
@@ -12736,8 +12800,14 @@ var CODE_MESSAGES = {
|
|
|
12736
12800
|
META_STAGED_ASSET_AMBIGUOUS: "The temporary media upload could not be uniquely resolved. Re-upload the media and retry the Meta publication.",
|
|
12737
12801
|
META_STAGED_ASSET_TYPE_MISMATCH: "The temporary media type does not match the creative. Re-upload the correct image or video and retry.",
|
|
12738
12802
|
META_STAGED_ASSET_INVALID: "The temporary media is empty or exceeds the server size limit. Re-upload the file and retry.",
|
|
12739
|
-
|
|
12740
|
-
|
|
12803
|
+
META_STAGING_QUOTA_EXCEEDED: "The temporary Meta staging quota was reached. Keep completed files and retry only pending files after retryAt.",
|
|
12804
|
+
CAPACITY_UNAVAILABLE: "Temporary Meta staging capacity is unavailable. Keep completed files and retry only pending files later.",
|
|
12805
|
+
UPLOAD_INCOMPLETE: "A temporary Meta upload is incomplete. Keep completed files and retry only pending files.",
|
|
12806
|
+
UPLOAD_EXPIRED: "The temporary Meta upload expired. Re-upload this file before publishing.",
|
|
12807
|
+
META_UPLOAD_SESSION_EXPIRED: "The Meta upload session expired. Open a new session and retry only pending files.",
|
|
12808
|
+
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.",
|
|
12809
|
+
META_PUBLICATION_RECONCILIATION_REQUIRED: "Meta may already have received this publication. Do not resend it; check Meta Ads Manager.",
|
|
12810
|
+
META_PUBLICATION_IN_PROGRESS: "This Meta publication is already in progress. Do not resend it.",
|
|
12741
12811
|
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.",
|
|
12742
12812
|
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.",
|
|
12743
12813
|
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.",
|
|
@@ -12806,6 +12876,7 @@ var CODE_MESSAGES = {
|
|
|
12806
12876
|
REVOKED_API_KEY: "API key revoked. Generate a new one at https://productmaker.app/account/api-keys.",
|
|
12807
12877
|
// Rate
|
|
12808
12878
|
RATE_LIMITED: "Too many requests. Wait a few seconds.",
|
|
12879
|
+
CREDIT_ADMISSION_UNAVAILABLE: "Credit admission is temporarily unavailable. Do not retry a paid action until ProductMaker can verify the balance.",
|
|
12809
12880
|
INSUFFICIENT_CREDITS: "Insufficient credits for this action. Check your balance and add credits at https://productmaker.app before retrying.",
|
|
12810
12881
|
TASK_QUOTA_EXCEEDED: "You reached the active-task limit. Wait for a task to finish before creating another variant.",
|
|
12811
12882
|
// Generic
|
|
@@ -12885,6 +12956,11 @@ var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
|
|
|
12885
12956
|
McpErrorCode.META_STAGED_ASSET_AMBIGUOUS,
|
|
12886
12957
|
McpErrorCode.META_STAGED_ASSET_TYPE_MISMATCH,
|
|
12887
12958
|
McpErrorCode.META_STAGED_ASSET_INVALID,
|
|
12959
|
+
McpErrorCode.META_STAGING_QUOTA_EXCEEDED,
|
|
12960
|
+
McpErrorCode.UPLOAD_INCOMPLETE,
|
|
12961
|
+
McpErrorCode.UPLOAD_EXPIRED,
|
|
12962
|
+
McpErrorCode.META_UPLOAD_SESSION_EXPIRED,
|
|
12963
|
+
McpErrorCode.META_PUBLICATION_LEASE_STALE,
|
|
12888
12964
|
McpErrorCode.META_PUBLICATION_RECONCILIATION_REQUIRED,
|
|
12889
12965
|
McpErrorCode.META_PUBLICATION_IN_PROGRESS,
|
|
12890
12966
|
McpErrorCode.TIKTOK_TOKEN_EXPIRED,
|
|
@@ -12900,6 +12976,7 @@ var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
|
|
|
12900
12976
|
McpErrorCode.NO_META_AD_ACCOUNTS
|
|
12901
12977
|
]);
|
|
12902
12978
|
var PASSTHROUGH_CODES = /* @__PURE__ */ new Set([
|
|
12979
|
+
McpErrorCode.CREDIT_ADMISSION_UNAVAILABLE,
|
|
12903
12980
|
McpErrorCode.INSUFFICIENT_CREDITS,
|
|
12904
12981
|
McpErrorCode.TASK_QUOTA_EXCEEDED,
|
|
12905
12982
|
McpErrorCode.DROPSHIPPING_PROJECT_NOT_FOUND,
|
|
@@ -12943,6 +13020,14 @@ var PASSTHROUGH_CODES = /* @__PURE__ */ new Set([
|
|
|
12943
13020
|
McpErrorCode.META_CAMPAIGN_CONFIG_INVALID,
|
|
12944
13021
|
McpErrorCode.META_WHATSAPP_INVALID_NUMBER,
|
|
12945
13022
|
McpErrorCode.META_INVALID_CTA,
|
|
13023
|
+
McpErrorCode.META_STAGING_QUOTA_EXCEEDED,
|
|
13024
|
+
McpErrorCode.CAPACITY_UNAVAILABLE,
|
|
13025
|
+
McpErrorCode.UPLOAD_INCOMPLETE,
|
|
13026
|
+
McpErrorCode.UPLOAD_EXPIRED,
|
|
13027
|
+
McpErrorCode.META_UPLOAD_SESSION_EXPIRED,
|
|
13028
|
+
McpErrorCode.META_PUBLICATION_LEASE_STALE,
|
|
13029
|
+
McpErrorCode.META_PUBLICATION_RECONCILIATION_REQUIRED,
|
|
13030
|
+
McpErrorCode.META_PUBLICATION_IN_PROGRESS,
|
|
12946
13031
|
// TikTok
|
|
12947
13032
|
McpErrorCode.TIKTOK_NOT_CONNECTED,
|
|
12948
13033
|
McpErrorCode.TIKTOK_TOKEN_EXPIRED,
|
|
@@ -12998,6 +13083,7 @@ __name(looksLikeMeaningfulBackendMessage, "looksLikeMeaningfulBackendMessage");
|
|
|
12998
13083
|
function resolveMessage(code, backendMessage) {
|
|
12999
13084
|
const mapped = CODE_MESSAGES[code];
|
|
13000
13085
|
const backend = typeof backendMessage === "string" ? backendMessage.trim() : "";
|
|
13086
|
+
if (code === McpErrorCode.CREDIT_ADMISSION_UNAVAILABLE && mapped) return mapped;
|
|
13001
13087
|
if (mapped && backend && looksLikeMeaningfulBackendMessage(backend, mapped)) {
|
|
13002
13088
|
return `${mapped} (Detail: ${backend})`;
|
|
13003
13089
|
}
|
|
@@ -13030,6 +13116,10 @@ function translateError(err) {
|
|
|
13030
13116
|
if (body.helpUrl !== void 0) extra.helpUrl = body.helpUrl;
|
|
13031
13117
|
if (typeof body.requiredCredits === "number") extra.requiredCredits = body.requiredCredits;
|
|
13032
13118
|
if (typeof body.availableCredits === "number") extra.availableCredits = body.availableCredits;
|
|
13119
|
+
if (typeof body.dimension === "string") extra.dimension = body.dimension;
|
|
13120
|
+
if (typeof body.usage === "number") extra.usage = body.usage;
|
|
13121
|
+
if (typeof body.limit === "number") extra.limit = body.limit;
|
|
13122
|
+
if (typeof body.retryAt === "string") extra.retryAt = body.retryAt;
|
|
13033
13123
|
return mcp(code, resolveMessage(code, body.message), extra);
|
|
13034
13124
|
}
|
|
13035
13125
|
if (err.status === 401) {
|
|
@@ -14703,6 +14793,57 @@ __name(registerActorTools, "registerActorTools");
|
|
|
14703
14793
|
|
|
14704
14794
|
// src/tools/publish.ts
|
|
14705
14795
|
import { z as z56 } from "zod";
|
|
14796
|
+
|
|
14797
|
+
// src/tools/stable-uuid.ts
|
|
14798
|
+
import { createHash as createHash3 } from "crypto";
|
|
14799
|
+
function canonicalize(value) {
|
|
14800
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
14801
|
+
if (value === null || typeof value !== "object") return value;
|
|
14802
|
+
const record = value;
|
|
14803
|
+
return Object.fromEntries(Object.keys(record).sort().filter((key) => record[key] !== void 0).map((key) => [
|
|
14804
|
+
key,
|
|
14805
|
+
canonicalize(record[key])
|
|
14806
|
+
]));
|
|
14807
|
+
}
|
|
14808
|
+
__name(canonicalize, "canonicalize");
|
|
14809
|
+
function canonicalJson(value) {
|
|
14810
|
+
return JSON.stringify(canonicalize(value));
|
|
14811
|
+
}
|
|
14812
|
+
__name(canonicalJson, "canonicalJson");
|
|
14813
|
+
function deriveUuidV5(name, namespace) {
|
|
14814
|
+
const namespaceBytes = Buffer.from(namespace.replaceAll("-", ""), "hex");
|
|
14815
|
+
const digest = createHash3("sha1").update(namespaceBytes).update(name, "utf8").digest();
|
|
14816
|
+
const bytes = Buffer.from(digest.subarray(0, 16));
|
|
14817
|
+
bytes[6] = (bytes[6] ?? 0) & 15 | 80;
|
|
14818
|
+
bytes[8] = (bytes[8] ?? 0) & 63 | 128;
|
|
14819
|
+
const hex = bytes.toString("hex");
|
|
14820
|
+
return [
|
|
14821
|
+
hex.slice(0, 8),
|
|
14822
|
+
hex.slice(8, 12),
|
|
14823
|
+
hex.slice(12, 16),
|
|
14824
|
+
hex.slice(16, 20),
|
|
14825
|
+
hex.slice(20)
|
|
14826
|
+
].join("-");
|
|
14827
|
+
}
|
|
14828
|
+
__name(deriveUuidV5, "deriveUuidV5");
|
|
14829
|
+
|
|
14830
|
+
// src/tools/meta-publish-attempt-id.ts
|
|
14831
|
+
var META_PUBLISH_NAMESPACE = "7b19c9ac-b4eb-4db1-970e-c2f24ce30b98";
|
|
14832
|
+
function deriveMetaPublishAttemptId(input) {
|
|
14833
|
+
const operationIdentity = canonicalJson([
|
|
14834
|
+
input.fingerprint,
|
|
14835
|
+
input.toolName,
|
|
14836
|
+
[
|
|
14837
|
+
typeof input.requestId,
|
|
14838
|
+
input.requestId
|
|
14839
|
+
],
|
|
14840
|
+
input.canonicalInput
|
|
14841
|
+
]);
|
|
14842
|
+
return deriveUuidV5(operationIdentity, META_PUBLISH_NAMESPACE);
|
|
14843
|
+
}
|
|
14844
|
+
__name(deriveMetaPublishAttemptId, "deriveMetaPublishAttemptId");
|
|
14845
|
+
|
|
14846
|
+
// src/tools/publish.ts
|
|
14706
14847
|
var ShopifyInput = z56.object({
|
|
14707
14848
|
taskId: z56.string().min(1),
|
|
14708
14849
|
shopDomain: z56.string().min(1),
|
|
@@ -15030,10 +15171,19 @@ function registerPublishTools(server, api, opts = {}) {
|
|
|
15030
15171
|
throw translateError(e);
|
|
15031
15172
|
}
|
|
15032
15173
|
});
|
|
15033
|
-
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 (
|
|
15174
|
+
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) => {
|
|
15034
15175
|
try {
|
|
15176
|
+
const parsed = MetaInput.safeParse(rawInput);
|
|
15177
|
+
if (!parsed.success) throw invalidInputFromZod(parsed.error);
|
|
15178
|
+
const { taskId, campaignConfig, primaryText, phoneNumber, ...rest } = parsed.data;
|
|
15035
15179
|
const body = {
|
|
15036
|
-
...rest
|
|
15180
|
+
...rest,
|
|
15181
|
+
publishAttemptId: deriveMetaPublishAttemptId({
|
|
15182
|
+
fingerprint: api.fingerprint(),
|
|
15183
|
+
toolName: "publish_to_meta",
|
|
15184
|
+
requestId: extra.requestId,
|
|
15185
|
+
canonicalInput: parsed.data
|
|
15186
|
+
})
|
|
15037
15187
|
};
|
|
15038
15188
|
if (campaignConfig) {
|
|
15039
15189
|
body.campaignOverrides = toMetaCampaignOverrides(campaignConfig);
|
|
@@ -15058,7 +15208,7 @@ function registerPublishTools(server, api, opts = {}) {
|
|
|
15058
15208
|
throw translateError(e);
|
|
15059
15209
|
}
|
|
15060
15210
|
});
|
|
15061
|
-
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,
|
|
15211
|
+
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) => {
|
|
15062
15212
|
try {
|
|
15063
15213
|
const parsed = PublishCreativesToMetaInput.safeParse(rawInput);
|
|
15064
15214
|
if (!parsed.success) throw invalidInputFromZod(parsed.error);
|
|
@@ -15138,7 +15288,13 @@ function registerPublishTools(server, api, opts = {}) {
|
|
|
15138
15288
|
landingUrl: input.landingUrl,
|
|
15139
15289
|
targetCountry: input.targetCountry,
|
|
15140
15290
|
campaignName: input.campaignName,
|
|
15141
|
-
campaignConfig: input.campaignConfig
|
|
15291
|
+
campaignConfig: input.campaignConfig,
|
|
15292
|
+
publishAttemptId: deriveMetaPublishAttemptId({
|
|
15293
|
+
fingerprint: api.fingerprint(),
|
|
15294
|
+
toolName: "publish_creatives_to_meta",
|
|
15295
|
+
requestId: extra.requestId,
|
|
15296
|
+
canonicalInput: input
|
|
15297
|
+
})
|
|
15142
15298
|
});
|
|
15143
15299
|
return asText({
|
|
15144
15300
|
...r,
|
|
@@ -16025,29 +16181,12 @@ __name(formatPercent, "formatPercent");
|
|
|
16025
16181
|
import { z as z62 } from "zod";
|
|
16026
16182
|
|
|
16027
16183
|
// src/tools/product-research-idempotency.ts
|
|
16028
|
-
import { createHash as createHash3 } from "crypto";
|
|
16029
16184
|
var RESEARCH_IDEMPOTENCY_WINDOW_MS = 2 * 60 * 60 * 1e3;
|
|
16030
16185
|
var NAMESPACE = "6f9d3a1e-8c47-4b52-9f10-2d7e5a3c8b64";
|
|
16031
|
-
function uuidV5(name, namespace) {
|
|
16032
|
-
const namespaceBytes = Buffer.from(namespace.replace(/-/g, ""), "hex");
|
|
16033
|
-
const digest = createHash3("sha1").update(namespaceBytes).update(name, "utf8").digest();
|
|
16034
|
-
const bytes = Buffer.from(digest.subarray(0, 16));
|
|
16035
|
-
bytes[6] = (bytes[6] ?? 0) & 15 | 80;
|
|
16036
|
-
bytes[8] = (bytes[8] ?? 0) & 63 | 128;
|
|
16037
|
-
const hex = bytes.toString("hex");
|
|
16038
|
-
return [
|
|
16039
|
-
hex.slice(0, 8),
|
|
16040
|
-
hex.slice(8, 12),
|
|
16041
|
-
hex.slice(12, 16),
|
|
16042
|
-
hex.slice(16, 20),
|
|
16043
|
-
hex.slice(20)
|
|
16044
|
-
].join("-");
|
|
16045
|
-
}
|
|
16046
|
-
__name(uuidV5, "uuidV5");
|
|
16047
16186
|
function deriveIdempotencyKey(input) {
|
|
16048
16187
|
const window = Math.floor(input.nowMs / RESEARCH_IDEMPOTENCY_WINDOW_MS);
|
|
16049
16188
|
const name = `${input.fingerprint}:${input.targetCountry}:${input.categoryKey}:${window}`;
|
|
16050
|
-
return
|
|
16189
|
+
return deriveUuidV5(name, NAMESPACE);
|
|
16051
16190
|
}
|
|
16052
16191
|
__name(deriveIdempotencyKey, "deriveIdempotencyKey");
|
|
16053
16192
|
|
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.3";
|
|
268
268
|
|
|
269
269
|
// src/meta-upload-app.ts
|
|
270
270
|
import { readFileSync } from "fs";
|
|
@@ -576,6 +576,7 @@ __name(resolveOne, "resolveOne");
|
|
|
576
576
|
|
|
577
577
|
// src/status-poller.ts
|
|
578
578
|
var TERMINAL = /* @__PURE__ */ new Set([
|
|
579
|
+
"done",
|
|
579
580
|
"complete",
|
|
580
581
|
"error"
|
|
581
582
|
]);
|
|
@@ -4313,6 +4314,32 @@ var StatusResponse = z24.object({
|
|
|
4313
4314
|
requiresAction: z24.boolean().optional(),
|
|
4314
4315
|
/** True when the task needs the user's attention rather than passive progress. */
|
|
4315
4316
|
needsReview: z24.boolean().optional(),
|
|
4317
|
+
/**
|
|
4318
|
+
* THE answer to "does this task still owe the seller a question — now OR later
|
|
4319
|
+
* in this run?". The chat leaves for /tasks/:id only on an explicit `false`,
|
|
4320
|
+
* and /tasks/:id bounces her back on `true`. ONE field drives BOTH directions
|
|
4321
|
+
* on purpose: deciding them on two different facts is what looped
|
|
4322
|
+
* /tasks/:id ↔ wizard before (see apps/web/lib/task-routing.ts).
|
|
4323
|
+
*
|
|
4324
|
+
* STRICTLY WIDER THAN `needsReview`, and the difference is the whole point:
|
|
4325
|
+
* · `needsReview` answers "is a gate open RIGHT NOW", and it under-reports —
|
|
4326
|
+
* `buildPendingUserAction` only serves the reference-grouping question when
|
|
4327
|
+
* it is RENDERABLE, so an unconfirmed target reports `false` while
|
|
4328
|
+
* `angles.worker` will still pause at the END of the angles stage.
|
|
4329
|
+
* · this field adds the gates that are GUARANTEED to open: both pause flags,
|
|
4330
|
+
* plus that same worker prediction, through the one shared helper.
|
|
4331
|
+
*
|
|
4332
|
+
* Absent (undefined) means "an older API that cannot answer" — the client must
|
|
4333
|
+
* treat that as `true` and stay put. Uncertainty resolves to staying: a false
|
|
4334
|
+
* positive costs her a wait, a false negative strands her on a screen that
|
|
4335
|
+
* cannot render the gate (prod bd4d0bb1 / 52dc15a7, both refunded and
|
|
4336
|
+
* undelivered).
|
|
4337
|
+
*
|
|
4338
|
+
* ALWAYS false on a terminal task. `pauseForAngleReview` SURVIVES on completed
|
|
4339
|
+
* tasks, so without that guard a finished landing would report `true` forever
|
|
4340
|
+
* and get bounced into a chat with nothing left to ask.
|
|
4341
|
+
*/
|
|
4342
|
+
willAskAgain: z24.boolean().optional(),
|
|
4316
4343
|
/** True when an open gate exists that somebody can actually ANSWER. A strictly
|
|
4317
4344
|
* narrower fact than `needsReview` — see the identical field on `TaskSummary`
|
|
4318
4345
|
* below for why conflating the two produced a call to action that led nowhere. */
|
|
@@ -4447,6 +4474,9 @@ var CostEstimationType = z25.enum([
|
|
|
4447
4474
|
"landing-regenerate",
|
|
4448
4475
|
"landing-creative",
|
|
4449
4476
|
"campaign-copy",
|
|
4477
|
+
"brand-extract",
|
|
4478
|
+
"persona-suggest",
|
|
4479
|
+
"chat-parse",
|
|
4450
4480
|
"service-creative",
|
|
4451
4481
|
"actor-grid"
|
|
4452
4482
|
]);
|
|
@@ -4612,11 +4642,14 @@ var LandingCreativeEstimationOptions = z25.object({
|
|
|
4612
4642
|
landingStrategy: LandingStrategySchema.optional()
|
|
4613
4643
|
});
|
|
4614
4644
|
var CampaignCopyEstimationOptions = z25.object({
|
|
4615
|
-
/** Number of creatives
|
|
4616
|
-
nCreatives: z25.number().int().min(1).max(
|
|
4617
|
-
/** Number of image creatives that
|
|
4618
|
-
nImages: z25.number().int().min(0).max(
|
|
4619
|
-
});
|
|
4645
|
+
/** Number of creatives in the one copy-completion request (1-10). */
|
|
4646
|
+
nCreatives: z25.number().int().min(1).max(10),
|
|
4647
|
+
/** Number of image creatives that can each use one vision analysis (0-10). */
|
|
4648
|
+
nImages: z25.number().int().min(0).max(10).default(0)
|
|
4649
|
+
});
|
|
4650
|
+
var BrandExtractEstimationOptions = z25.object({}).strict();
|
|
4651
|
+
var PersonaSuggestEstimationOptions = z25.object({}).strict();
|
|
4652
|
+
var ChatParseEstimationOptions = z25.object({}).strict();
|
|
4620
4653
|
var ServiceCreativeEstimationOptions = z25.object({
|
|
4621
4654
|
mediaAssetCount: z25.number().int().min(0).max(8).optional(),
|
|
4622
4655
|
uploadedVideoDurationSeconds: z25.number().min(0).max(240).optional()
|
|
@@ -4655,6 +4688,18 @@ var CostEstimationRequest = z25.discriminatedUnion("type", [
|
|
|
4655
4688
|
type: z25.literal("campaign-copy"),
|
|
4656
4689
|
options: CampaignCopyEstimationOptions
|
|
4657
4690
|
}),
|
|
4691
|
+
z25.object({
|
|
4692
|
+
type: z25.literal("brand-extract"),
|
|
4693
|
+
options: BrandExtractEstimationOptions
|
|
4694
|
+
}),
|
|
4695
|
+
z25.object({
|
|
4696
|
+
type: z25.literal("persona-suggest"),
|
|
4697
|
+
options: PersonaSuggestEstimationOptions
|
|
4698
|
+
}),
|
|
4699
|
+
z25.object({
|
|
4700
|
+
type: z25.literal("chat-parse"),
|
|
4701
|
+
options: ChatParseEstimationOptions
|
|
4702
|
+
}),
|
|
4658
4703
|
z25.object({
|
|
4659
4704
|
type: z25.literal("service-creative"),
|
|
4660
4705
|
options: ServiceCreativeEstimationOptions
|
|
@@ -5154,6 +5199,9 @@ var VideoMixQaJobSchema = z30.object({
|
|
|
5154
5199
|
});
|
|
5155
5200
|
var BrandExtractJobSchema = z30.object({
|
|
5156
5201
|
brandId: z30.string().uuid(),
|
|
5202
|
+
// Generation is part of the durable paid-spend identity. Default 1 keeps
|
|
5203
|
+
// pre-migration BullMQ payloads parseable; their DB state fails safe.
|
|
5204
|
+
generation: z30.number().int().positive().default(1),
|
|
5157
5205
|
url: z30.string().url().refine((value) => {
|
|
5158
5206
|
const protocol = new URL(value).protocol;
|
|
5159
5207
|
return protocol === "http:" || protocol === "https:";
|
|
@@ -5475,6 +5523,7 @@ var MetaCampaignType = z33.enum([
|
|
|
5475
5523
|
"traffic",
|
|
5476
5524
|
"engagement"
|
|
5477
5525
|
]);
|
|
5526
|
+
var MetaPublishAttemptId = z33.string().uuid();
|
|
5478
5527
|
var MetaOptimizationMode = z33.enum([
|
|
5479
5528
|
"advantage_plus",
|
|
5480
5529
|
"manual"
|
|
@@ -5658,6 +5707,8 @@ var MetaCampaignDefaults = MetaCampaignConfigBase.omit({
|
|
|
5658
5707
|
currency: z33.string().length(3).regex(/^[A-Z]{3}$/).nullable().optional()
|
|
5659
5708
|
});
|
|
5660
5709
|
var UploadToMetaRequest = z33.object({
|
|
5710
|
+
/** Caller-owned retry fence. Required before any provider operation. */
|
|
5711
|
+
publishAttemptId: MetaPublishAttemptId,
|
|
5661
5712
|
/** Array of creatives to upload */
|
|
5662
5713
|
creatives: z33.array(z33.object({
|
|
5663
5714
|
creativeId: z33.string().min(1).optional(),
|
|
@@ -5814,6 +5865,8 @@ var MetaCampaignOverridesSchema = z34.object({
|
|
|
5814
5865
|
destination: MetaCampaignDestination.optional()
|
|
5815
5866
|
});
|
|
5816
5867
|
var FacebookCreateCampaignBodySchema = z34.object({
|
|
5868
|
+
/** Caller-owned retry fence. Required before any provider operation. */
|
|
5869
|
+
publishAttemptId: MetaPublishAttemptId,
|
|
5817
5870
|
adAccountId: MetaAdAccountId,
|
|
5818
5871
|
portfolioId: z34.string().min(1).optional(),
|
|
5819
5872
|
pageId: z34.string().min(1).optional(),
|
|
@@ -5883,6 +5936,8 @@ var FacebookCreateCampaignResponseSchema = z34.object({
|
|
|
5883
5936
|
appliedBudget: z34.number().int().positive()
|
|
5884
5937
|
});
|
|
5885
5938
|
var FacebookCreateCampaignFromCreativesBodySchema = z34.object({
|
|
5939
|
+
/** Caller-owned retry fence. Required before any provider operation. */
|
|
5940
|
+
publishAttemptId: MetaPublishAttemptId,
|
|
5886
5941
|
creatives: z34.array(z34.object({
|
|
5887
5942
|
type: z34.enum([
|
|
5888
5943
|
"image",
|
|
@@ -6030,6 +6085,8 @@ var FacebookCreativeSourceSchema = z34.discriminatedUnion("type", [
|
|
|
6030
6085
|
FacebookMediaCreativeSourceSchema
|
|
6031
6086
|
]);
|
|
6032
6087
|
var FacebookCreateCampaignFromTasksBodySchema = z34.object({
|
|
6088
|
+
/** Caller-owned retry fence. Required before any provider operation. */
|
|
6089
|
+
publishAttemptId: MetaPublishAttemptId,
|
|
6033
6090
|
creatives: z34.array(z34.object({
|
|
6034
6091
|
type: z34.enum([
|
|
6035
6092
|
"image",
|
|
@@ -11367,6 +11424,7 @@ var McpErrorCode = {
|
|
|
11367
11424
|
REVOKED_API_KEY: "REVOKED_API_KEY",
|
|
11368
11425
|
// Rate limit / quota
|
|
11369
11426
|
RATE_LIMITED: "RATE_LIMITED",
|
|
11427
|
+
CREDIT_ADMISSION_UNAVAILABLE: "CREDIT_ADMISSION_UNAVAILABLE",
|
|
11370
11428
|
// Input
|
|
11371
11429
|
INVALID_INPUT: "INVALID_INPUT",
|
|
11372
11430
|
INSUFFICIENT_CREDITS: "INSUFFICIENT_CREDITS",
|
|
@@ -11424,6 +11482,12 @@ var McpErrorCode = {
|
|
|
11424
11482
|
META_STAGED_ASSET_AMBIGUOUS: "META_STAGED_ASSET_AMBIGUOUS",
|
|
11425
11483
|
META_STAGED_ASSET_TYPE_MISMATCH: "META_STAGED_ASSET_TYPE_MISMATCH",
|
|
11426
11484
|
META_STAGED_ASSET_INVALID: "META_STAGED_ASSET_INVALID",
|
|
11485
|
+
META_STAGING_QUOTA_EXCEEDED: "META_STAGING_QUOTA_EXCEEDED",
|
|
11486
|
+
CAPACITY_UNAVAILABLE: "CAPACITY_UNAVAILABLE",
|
|
11487
|
+
UPLOAD_INCOMPLETE: "UPLOAD_INCOMPLETE",
|
|
11488
|
+
UPLOAD_EXPIRED: "UPLOAD_EXPIRED",
|
|
11489
|
+
META_UPLOAD_SESSION_EXPIRED: "META_UPLOAD_SESSION_EXPIRED",
|
|
11490
|
+
META_PUBLICATION_LEASE_STALE: "META_PUBLICATION_LEASE_STALE",
|
|
11427
11491
|
META_PUBLICATION_RECONCILIATION_REQUIRED: "META_PUBLICATION_RECONCILIATION_REQUIRED",
|
|
11428
11492
|
META_PUBLICATION_IN_PROGRESS: "META_PUBLICATION_IN_PROGRESS",
|
|
11429
11493
|
META_NO_ADS_CREATED: "META_NO_ADS_CREATED",
|
|
@@ -11485,8 +11549,14 @@ var CODE_MESSAGES = {
|
|
|
11485
11549
|
META_STAGED_ASSET_AMBIGUOUS: "The temporary media upload could not be uniquely resolved. Re-upload the media and retry the Meta publication.",
|
|
11486
11550
|
META_STAGED_ASSET_TYPE_MISMATCH: "The temporary media type does not match the creative. Re-upload the correct image or video and retry.",
|
|
11487
11551
|
META_STAGED_ASSET_INVALID: "The temporary media is empty or exceeds the server size limit. Re-upload the file and retry.",
|
|
11488
|
-
|
|
11489
|
-
|
|
11552
|
+
META_STAGING_QUOTA_EXCEEDED: "The temporary Meta staging quota was reached. Keep completed files and retry only pending files after retryAt.",
|
|
11553
|
+
CAPACITY_UNAVAILABLE: "Temporary Meta staging capacity is unavailable. Keep completed files and retry only pending files later.",
|
|
11554
|
+
UPLOAD_INCOMPLETE: "A temporary Meta upload is incomplete. Keep completed files and retry only pending files.",
|
|
11555
|
+
UPLOAD_EXPIRED: "The temporary Meta upload expired. Re-upload this file before publishing.",
|
|
11556
|
+
META_UPLOAD_SESSION_EXPIRED: "The Meta upload session expired. Open a new session and retry only pending files.",
|
|
11557
|
+
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.",
|
|
11558
|
+
META_PUBLICATION_RECONCILIATION_REQUIRED: "Meta may already have received this publication. Do not resend it; check Meta Ads Manager.",
|
|
11559
|
+
META_PUBLICATION_IN_PROGRESS: "This Meta publication is already in progress. Do not resend it.",
|
|
11490
11560
|
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.",
|
|
11491
11561
|
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.",
|
|
11492
11562
|
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.",
|
|
@@ -11555,6 +11625,7 @@ var CODE_MESSAGES = {
|
|
|
11555
11625
|
REVOKED_API_KEY: "API key revoked. Generate a new one at https://productmaker.app/account/api-keys.",
|
|
11556
11626
|
// Rate
|
|
11557
11627
|
RATE_LIMITED: "Too many requests. Wait a few seconds.",
|
|
11628
|
+
CREDIT_ADMISSION_UNAVAILABLE: "Credit admission is temporarily unavailable. Do not retry a paid action until ProductMaker can verify the balance.",
|
|
11558
11629
|
INSUFFICIENT_CREDITS: "Insufficient credits for this action. Check your balance and add credits at https://productmaker.app before retrying.",
|
|
11559
11630
|
TASK_QUOTA_EXCEEDED: "You reached the active-task limit. Wait for a task to finish before creating another variant.",
|
|
11560
11631
|
// Generic
|
|
@@ -11634,6 +11705,11 @@ var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
|
|
|
11634
11705
|
McpErrorCode.META_STAGED_ASSET_AMBIGUOUS,
|
|
11635
11706
|
McpErrorCode.META_STAGED_ASSET_TYPE_MISMATCH,
|
|
11636
11707
|
McpErrorCode.META_STAGED_ASSET_INVALID,
|
|
11708
|
+
McpErrorCode.META_STAGING_QUOTA_EXCEEDED,
|
|
11709
|
+
McpErrorCode.UPLOAD_INCOMPLETE,
|
|
11710
|
+
McpErrorCode.UPLOAD_EXPIRED,
|
|
11711
|
+
McpErrorCode.META_UPLOAD_SESSION_EXPIRED,
|
|
11712
|
+
McpErrorCode.META_PUBLICATION_LEASE_STALE,
|
|
11637
11713
|
McpErrorCode.META_PUBLICATION_RECONCILIATION_REQUIRED,
|
|
11638
11714
|
McpErrorCode.META_PUBLICATION_IN_PROGRESS,
|
|
11639
11715
|
McpErrorCode.TIKTOK_TOKEN_EXPIRED,
|
|
@@ -11649,6 +11725,7 @@ var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
|
|
|
11649
11725
|
McpErrorCode.NO_META_AD_ACCOUNTS
|
|
11650
11726
|
]);
|
|
11651
11727
|
var PASSTHROUGH_CODES = /* @__PURE__ */ new Set([
|
|
11728
|
+
McpErrorCode.CREDIT_ADMISSION_UNAVAILABLE,
|
|
11652
11729
|
McpErrorCode.INSUFFICIENT_CREDITS,
|
|
11653
11730
|
McpErrorCode.TASK_QUOTA_EXCEEDED,
|
|
11654
11731
|
McpErrorCode.DROPSHIPPING_PROJECT_NOT_FOUND,
|
|
@@ -11692,6 +11769,14 @@ var PASSTHROUGH_CODES = /* @__PURE__ */ new Set([
|
|
|
11692
11769
|
McpErrorCode.META_CAMPAIGN_CONFIG_INVALID,
|
|
11693
11770
|
McpErrorCode.META_WHATSAPP_INVALID_NUMBER,
|
|
11694
11771
|
McpErrorCode.META_INVALID_CTA,
|
|
11772
|
+
McpErrorCode.META_STAGING_QUOTA_EXCEEDED,
|
|
11773
|
+
McpErrorCode.CAPACITY_UNAVAILABLE,
|
|
11774
|
+
McpErrorCode.UPLOAD_INCOMPLETE,
|
|
11775
|
+
McpErrorCode.UPLOAD_EXPIRED,
|
|
11776
|
+
McpErrorCode.META_UPLOAD_SESSION_EXPIRED,
|
|
11777
|
+
McpErrorCode.META_PUBLICATION_LEASE_STALE,
|
|
11778
|
+
McpErrorCode.META_PUBLICATION_RECONCILIATION_REQUIRED,
|
|
11779
|
+
McpErrorCode.META_PUBLICATION_IN_PROGRESS,
|
|
11695
11780
|
// TikTok
|
|
11696
11781
|
McpErrorCode.TIKTOK_NOT_CONNECTED,
|
|
11697
11782
|
McpErrorCode.TIKTOK_TOKEN_EXPIRED,
|
|
@@ -11747,6 +11832,7 @@ __name(looksLikeMeaningfulBackendMessage, "looksLikeMeaningfulBackendMessage");
|
|
|
11747
11832
|
function resolveMessage(code, backendMessage) {
|
|
11748
11833
|
const mapped = CODE_MESSAGES[code];
|
|
11749
11834
|
const backend = typeof backendMessage === "string" ? backendMessage.trim() : "";
|
|
11835
|
+
if (code === McpErrorCode.CREDIT_ADMISSION_UNAVAILABLE && mapped) return mapped;
|
|
11750
11836
|
if (mapped && backend && looksLikeMeaningfulBackendMessage(backend, mapped)) {
|
|
11751
11837
|
return `${mapped} (Detail: ${backend})`;
|
|
11752
11838
|
}
|
|
@@ -11779,6 +11865,10 @@ function translateError(err) {
|
|
|
11779
11865
|
if (body.helpUrl !== void 0) extra.helpUrl = body.helpUrl;
|
|
11780
11866
|
if (typeof body.requiredCredits === "number") extra.requiredCredits = body.requiredCredits;
|
|
11781
11867
|
if (typeof body.availableCredits === "number") extra.availableCredits = body.availableCredits;
|
|
11868
|
+
if (typeof body.dimension === "string") extra.dimension = body.dimension;
|
|
11869
|
+
if (typeof body.usage === "number") extra.usage = body.usage;
|
|
11870
|
+
if (typeof body.limit === "number") extra.limit = body.limit;
|
|
11871
|
+
if (typeof body.retryAt === "string") extra.retryAt = body.retryAt;
|
|
11782
11872
|
return mcp(code, resolveMessage(code, body.message), extra);
|
|
11783
11873
|
}
|
|
11784
11874
|
if (err.status === 401) {
|
|
@@ -13505,6 +13595,57 @@ __name(registerActorTools, "registerActorTools");
|
|
|
13505
13595
|
|
|
13506
13596
|
// src/tools/publish.ts
|
|
13507
13597
|
import { z as z55 } from "zod";
|
|
13598
|
+
|
|
13599
|
+
// src/tools/stable-uuid.ts
|
|
13600
|
+
import { createHash as createHash2 } from "crypto";
|
|
13601
|
+
function canonicalize(value) {
|
|
13602
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
13603
|
+
if (value === null || typeof value !== "object") return value;
|
|
13604
|
+
const record = value;
|
|
13605
|
+
return Object.fromEntries(Object.keys(record).sort().filter((key) => record[key] !== void 0).map((key) => [
|
|
13606
|
+
key,
|
|
13607
|
+
canonicalize(record[key])
|
|
13608
|
+
]));
|
|
13609
|
+
}
|
|
13610
|
+
__name(canonicalize, "canonicalize");
|
|
13611
|
+
function canonicalJson(value) {
|
|
13612
|
+
return JSON.stringify(canonicalize(value));
|
|
13613
|
+
}
|
|
13614
|
+
__name(canonicalJson, "canonicalJson");
|
|
13615
|
+
function deriveUuidV5(name, namespace) {
|
|
13616
|
+
const namespaceBytes = Buffer.from(namespace.replaceAll("-", ""), "hex");
|
|
13617
|
+
const digest = createHash2("sha1").update(namespaceBytes).update(name, "utf8").digest();
|
|
13618
|
+
const bytes = Buffer.from(digest.subarray(0, 16));
|
|
13619
|
+
bytes[6] = (bytes[6] ?? 0) & 15 | 80;
|
|
13620
|
+
bytes[8] = (bytes[8] ?? 0) & 63 | 128;
|
|
13621
|
+
const hex = bytes.toString("hex");
|
|
13622
|
+
return [
|
|
13623
|
+
hex.slice(0, 8),
|
|
13624
|
+
hex.slice(8, 12),
|
|
13625
|
+
hex.slice(12, 16),
|
|
13626
|
+
hex.slice(16, 20),
|
|
13627
|
+
hex.slice(20)
|
|
13628
|
+
].join("-");
|
|
13629
|
+
}
|
|
13630
|
+
__name(deriveUuidV5, "deriveUuidV5");
|
|
13631
|
+
|
|
13632
|
+
// src/tools/meta-publish-attempt-id.ts
|
|
13633
|
+
var META_PUBLISH_NAMESPACE = "7b19c9ac-b4eb-4db1-970e-c2f24ce30b98";
|
|
13634
|
+
function deriveMetaPublishAttemptId(input) {
|
|
13635
|
+
const operationIdentity = canonicalJson([
|
|
13636
|
+
input.fingerprint,
|
|
13637
|
+
input.toolName,
|
|
13638
|
+
[
|
|
13639
|
+
typeof input.requestId,
|
|
13640
|
+
input.requestId
|
|
13641
|
+
],
|
|
13642
|
+
input.canonicalInput
|
|
13643
|
+
]);
|
|
13644
|
+
return deriveUuidV5(operationIdentity, META_PUBLISH_NAMESPACE);
|
|
13645
|
+
}
|
|
13646
|
+
__name(deriveMetaPublishAttemptId, "deriveMetaPublishAttemptId");
|
|
13647
|
+
|
|
13648
|
+
// src/tools/publish.ts
|
|
13508
13649
|
var ShopifyInput = z55.object({
|
|
13509
13650
|
taskId: z55.string().min(1),
|
|
13510
13651
|
shopDomain: z55.string().min(1),
|
|
@@ -13832,10 +13973,19 @@ function registerPublishTools(server, api, opts = {}) {
|
|
|
13832
13973
|
throw translateError(e);
|
|
13833
13974
|
}
|
|
13834
13975
|
});
|
|
13835
|
-
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 (
|
|
13976
|
+
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) => {
|
|
13836
13977
|
try {
|
|
13978
|
+
const parsed = MetaInput.safeParse(rawInput);
|
|
13979
|
+
if (!parsed.success) throw invalidInputFromZod(parsed.error);
|
|
13980
|
+
const { taskId, campaignConfig, primaryText, phoneNumber, ...rest } = parsed.data;
|
|
13837
13981
|
const body = {
|
|
13838
|
-
...rest
|
|
13982
|
+
...rest,
|
|
13983
|
+
publishAttemptId: deriveMetaPublishAttemptId({
|
|
13984
|
+
fingerprint: api.fingerprint(),
|
|
13985
|
+
toolName: "publish_to_meta",
|
|
13986
|
+
requestId: extra.requestId,
|
|
13987
|
+
canonicalInput: parsed.data
|
|
13988
|
+
})
|
|
13839
13989
|
};
|
|
13840
13990
|
if (campaignConfig) {
|
|
13841
13991
|
body.campaignOverrides = toMetaCampaignOverrides(campaignConfig);
|
|
@@ -13860,7 +14010,7 @@ function registerPublishTools(server, api, opts = {}) {
|
|
|
13860
14010
|
throw translateError(e);
|
|
13861
14011
|
}
|
|
13862
14012
|
});
|
|
13863
|
-
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,
|
|
14013
|
+
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) => {
|
|
13864
14014
|
try {
|
|
13865
14015
|
const parsed = PublishCreativesToMetaInput.safeParse(rawInput);
|
|
13866
14016
|
if (!parsed.success) throw invalidInputFromZod(parsed.error);
|
|
@@ -13940,7 +14090,13 @@ function registerPublishTools(server, api, opts = {}) {
|
|
|
13940
14090
|
landingUrl: input.landingUrl,
|
|
13941
14091
|
targetCountry: input.targetCountry,
|
|
13942
14092
|
campaignName: input.campaignName,
|
|
13943
|
-
campaignConfig: input.campaignConfig
|
|
14093
|
+
campaignConfig: input.campaignConfig,
|
|
14094
|
+
publishAttemptId: deriveMetaPublishAttemptId({
|
|
14095
|
+
fingerprint: api.fingerprint(),
|
|
14096
|
+
toolName: "publish_creatives_to_meta",
|
|
14097
|
+
requestId: extra.requestId,
|
|
14098
|
+
canonicalInput: input
|
|
14099
|
+
})
|
|
13944
14100
|
});
|
|
13945
14101
|
return asText({
|
|
13946
14102
|
...r,
|
|
@@ -14827,29 +14983,12 @@ __name(formatPercent, "formatPercent");
|
|
|
14827
14983
|
import { z as z61 } from "zod";
|
|
14828
14984
|
|
|
14829
14985
|
// src/tools/product-research-idempotency.ts
|
|
14830
|
-
import { createHash as createHash2 } from "crypto";
|
|
14831
14986
|
var RESEARCH_IDEMPOTENCY_WINDOW_MS = 2 * 60 * 60 * 1e3;
|
|
14832
14987
|
var NAMESPACE = "6f9d3a1e-8c47-4b52-9f10-2d7e5a3c8b64";
|
|
14833
|
-
function uuidV5(name, namespace) {
|
|
14834
|
-
const namespaceBytes = Buffer.from(namespace.replace(/-/g, ""), "hex");
|
|
14835
|
-
const digest = createHash2("sha1").update(namespaceBytes).update(name, "utf8").digest();
|
|
14836
|
-
const bytes = Buffer.from(digest.subarray(0, 16));
|
|
14837
|
-
bytes[6] = (bytes[6] ?? 0) & 15 | 80;
|
|
14838
|
-
bytes[8] = (bytes[8] ?? 0) & 63 | 128;
|
|
14839
|
-
const hex = bytes.toString("hex");
|
|
14840
|
-
return [
|
|
14841
|
-
hex.slice(0, 8),
|
|
14842
|
-
hex.slice(8, 12),
|
|
14843
|
-
hex.slice(12, 16),
|
|
14844
|
-
hex.slice(16, 20),
|
|
14845
|
-
hex.slice(20)
|
|
14846
|
-
].join("-");
|
|
14847
|
-
}
|
|
14848
|
-
__name(uuidV5, "uuidV5");
|
|
14849
14988
|
function deriveIdempotencyKey(input) {
|
|
14850
14989
|
const window = Math.floor(input.nowMs / RESEARCH_IDEMPOTENCY_WINDOW_MS);
|
|
14851
14990
|
const name = `${input.fingerprint}:${input.targetCountry}:${input.categoryKey}:${window}`;
|
|
14852
|
-
return
|
|
14991
|
+
return deriveUuidV5(name, NAMESPACE);
|
|
14853
14992
|
}
|
|
14854
14993
|
__name(deriveIdempotencyKey, "deriveIdempotencyKey");
|
|
14855
14994
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@productmaker/mcp",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.3",
|
|
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.",
|