@productmaker/mcp 2.9.2 → 2.9.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 +35 -14
- package/dist/stdio.js +35 -14
- package/dist/ui/meta-upload-app.html +104 -575
- package/package.json +3 -3
package/dist/http.js
CHANGED
|
@@ -153,7 +153,7 @@ var ApiClient = class {
|
|
|
153
153
|
};
|
|
154
154
|
|
|
155
155
|
// src/index.ts
|
|
156
|
-
var MCP_VERSION = "2.9.
|
|
156
|
+
var MCP_VERSION = "2.9.3";
|
|
157
157
|
|
|
158
158
|
// src/meta-upload-app.ts
|
|
159
159
|
import { readFileSync } from "fs";
|
|
@@ -8052,7 +8052,7 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
|
|
|
8052
8052
|
{
|
|
8053
8053
|
name: "publish_creatives_to_meta",
|
|
8054
8054
|
confirmation: "external_mutation",
|
|
8055
|
-
schemaHash: "
|
|
8055
|
+
schemaHash: "f712fe12baee19de6ac5ed94b23de630247a345ed13c3a431a647d421480280f"
|
|
8056
8056
|
},
|
|
8057
8057
|
{
|
|
8058
8058
|
name: "publish_to_meta",
|
|
@@ -8915,7 +8915,7 @@ var CODE_MESSAGES = {
|
|
|
8915
8915
|
META_TOKEN_EXPIRED: "Your Meta session expired. Reconnect Facebook at https://productmaker.app/integrations.",
|
|
8916
8916
|
META_ACCOUNT_RESTRICTED: "Your Meta Ads account is temporarily restricted from creating ads. Check the notices at https://business.facebook.com and complete the required verification.",
|
|
8917
8917
|
META_CAPABILITY_MISSING: "The Facebook app lacks required permissions for this ad account. Reconnect Facebook at https://productmaker.app/integrations and accept ALL requested permissions (including business_management).",
|
|
8918
|
-
META_PERMISSION_DENIED: "
|
|
8918
|
+
META_PERMISSION_DENIED: "Meta denied write access to the selected ad account. Ask a Business Manager admin to grant this user Advertiser role or higher for that account, then reconnect Facebook at https://productmaker.app/integrations and accept every requested permission, including ads_management.",
|
|
8919
8919
|
META_PIXEL_NOT_FOUND: "Pixel not found or not associated with the ad account. Verify the `pixelId` at https://business.facebook.com/events_manager.",
|
|
8920
8920
|
META_OBJECT_NOT_FOUND: "The requested object (campaign/ad set/ad) does not exist in Meta. Verify the ID or create a new one.",
|
|
8921
8921
|
META_AI_DISCLOSURE_REQUIRED: "Meta rejected the ad for undisclosed AI content. The system adds the disclosure automatically; if it persists, edit the copy to avoid language Meta flags as misleading.",
|
|
@@ -10901,10 +10901,10 @@ var GroupedCreative = z52.object({
|
|
|
10901
10901
|
cta: MetaCtaType.optional(),
|
|
10902
10902
|
creativeId: z52.string().min(1).optional()
|
|
10903
10903
|
});
|
|
10904
|
-
var
|
|
10904
|
+
var GroupedCampaignConfigBase = z52.object({
|
|
10905
10905
|
campaignType: MetaCampaignType.default("sales"),
|
|
10906
10906
|
optimizationMode: MetaOptimizationMode.default("advantage_plus"),
|
|
10907
|
-
budgetMode: MetaBudgetMode.
|
|
10907
|
+
budgetMode: MetaBudgetMode.optional(),
|
|
10908
10908
|
campaignDailyBudget: z52.number().int().positive().optional(),
|
|
10909
10909
|
adSets: z52.array(z52.object({
|
|
10910
10910
|
tempId: z52.string().min(1).max(80),
|
|
@@ -10930,8 +10930,17 @@ var GroupedCampaignConfig = z52.object({
|
|
|
10930
10930
|
]),
|
|
10931
10931
|
saveAsDefault: z52.boolean().default(false),
|
|
10932
10932
|
budgetPolicy: MetaBudgetPolicy.optional()
|
|
10933
|
-
})
|
|
10934
|
-
|
|
10933
|
+
});
|
|
10934
|
+
function resolveBudgetMode(value) {
|
|
10935
|
+
if (value.budgetMode) return value.budgetMode;
|
|
10936
|
+
if (value.campaignDailyBudget !== void 0) return "campaign";
|
|
10937
|
+
if (value.adSets.every((adSet) => adSet.dailyBudget !== void 0)) return "ad_set";
|
|
10938
|
+
return "campaign";
|
|
10939
|
+
}
|
|
10940
|
+
__name(resolveBudgetMode, "resolveBudgetMode");
|
|
10941
|
+
var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) => {
|
|
10942
|
+
const budgetMode = resolveBudgetMode(value);
|
|
10943
|
+
if (budgetMode === "campaign" && value.campaignDailyBudget === void 0) {
|
|
10935
10944
|
ctx.addIssue({
|
|
10936
10945
|
code: z52.ZodIssueCode.custom,
|
|
10937
10946
|
path: [
|
|
@@ -10940,7 +10949,7 @@ var GroupedCampaignConfig = z52.object({
|
|
|
10940
10949
|
message: "campaignDailyBudget is required when budgetMode is campaign"
|
|
10941
10950
|
});
|
|
10942
10951
|
}
|
|
10943
|
-
if (
|
|
10952
|
+
if (budgetMode === "ad_set") {
|
|
10944
10953
|
value.adSets.forEach((adSet, index) => {
|
|
10945
10954
|
if (adSet.dailyBudget === void 0) {
|
|
10946
10955
|
ctx.addIssue({
|
|
@@ -10955,7 +10964,10 @@ var GroupedCampaignConfig = z52.object({
|
|
|
10955
10964
|
}
|
|
10956
10965
|
});
|
|
10957
10966
|
}
|
|
10958
|
-
})
|
|
10967
|
+
}).transform((value) => ({
|
|
10968
|
+
...value,
|
|
10969
|
+
budgetMode: resolveBudgetMode(value)
|
|
10970
|
+
}));
|
|
10959
10971
|
var PublishCreativesToMetaInputBase = z52.object({
|
|
10960
10972
|
creatives: z52.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
|
|
10961
10973
|
/** Exact Meta Business portfolio selected from `list_meta_destinations`. */
|
|
@@ -10973,7 +10985,7 @@ var PublishCreativesToMetaInputBase = z52.object({
|
|
|
10973
10985
|
});
|
|
10974
10986
|
var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((value, ctx) => {
|
|
10975
10987
|
const destination = value.campaignConfig?.destination;
|
|
10976
|
-
if (destination?.type !== "whatsapp" && !value.landingUrl) {
|
|
10988
|
+
if (destination?.type !== "whatsapp" && destination?.type !== "landing" && !value.landingUrl) {
|
|
10977
10989
|
ctx.addIssue({
|
|
10978
10990
|
code: z52.ZodIssueCode.custom,
|
|
10979
10991
|
path: [
|
|
@@ -10982,7 +10994,7 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
|
|
|
10982
10994
|
message: "landingUrl is required unless campaignConfig.destination.type is whatsapp"
|
|
10983
10995
|
});
|
|
10984
10996
|
}
|
|
10985
|
-
if (destination?.type === "landing" && destination.landingUrl !== value.landingUrl) {
|
|
10997
|
+
if (destination?.type === "landing" && value.landingUrl && destination.landingUrl !== value.landingUrl) {
|
|
10986
10998
|
ctx.addIssue({
|
|
10987
10999
|
code: z52.ZodIssueCode.custom,
|
|
10988
11000
|
path: [
|
|
@@ -10993,6 +11005,15 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
|
|
|
10993
11005
|
message: "campaignConfig.destination.landingUrl must match landingUrl"
|
|
10994
11006
|
});
|
|
10995
11007
|
}
|
|
11008
|
+
}).transform((value) => {
|
|
11009
|
+
const destination = value.campaignConfig?.destination;
|
|
11010
|
+
if (destination?.type === "landing" && !value.landingUrl) {
|
|
11011
|
+
return {
|
|
11012
|
+
...value,
|
|
11013
|
+
landingUrl: destination.landingUrl
|
|
11014
|
+
};
|
|
11015
|
+
}
|
|
11016
|
+
return value;
|
|
10996
11017
|
});
|
|
10997
11018
|
var PresignUploadResponseSchema = z52.object({
|
|
10998
11019
|
uploadUrl: z52.string().url().refine((value) => {
|
|
@@ -11104,7 +11125,7 @@ function registerPublishTools(server, api, opts = {}) {
|
|
|
11104
11125
|
throw translateError(e);
|
|
11105
11126
|
}
|
|
11106
11127
|
});
|
|
11107
|
-
server.tool("publish_creatives_to_meta", "Crea UNA campa\xF1a de Meta Ads PAUSED agrupando hasta " + CAMPAIGN_LIMITS.MAX_CREATIVES + ' creativos. Antes llama `list_meta_destinations` y pide al usuario elegir el `portfolioId`, `adAccountId` y `pageId` exactos. Para archivos en la laptop con MCP remoto: llama `request_meta_media_upload`, espera al usuario y consulta `check_meta_media_upload`; por CADA entrada de `assets[]` crea un creativo `source.type="staged"`, con `type` igual a su `mediaKind`. Si la carga fue parcial, publica s\xF3lo los assets que ese estado confirm\xF3; nunca inventes assets ni superes diez creativos. Las rutas locales s\xF3lo funcionan con MCP stdio. Los medios propios se suben temporalmente al CDN de ProductMaker y se eliminan despu\xE9s de publicar correctamente; si falla, quedan s\xF3lo para reintento
|
|
11128
|
+
server.tool("publish_creatives_to_meta", "Crea UNA campa\xF1a de Meta Ads PAUSED agrupando hasta " + CAMPAIGN_LIMITS.MAX_CREATIVES + ' creativos. Antes llama `list_meta_destinations` y pide al usuario elegir el `portfolioId`, `adAccountId` y `pageId` exactos. Para archivos en la laptop con MCP remoto: llama `request_meta_media_upload`, espera al usuario y consulta `check_meta_media_upload`; por CADA entrada de `assets[]` crea un creativo `source.type="staged"`, con `type` igual a su `mediaKind`. Si la carga fue parcial, publica s\xF3lo los assets que ese estado confirm\xF3; nunca inventes assets ni superes diez creativos. Las rutas locales s\xF3lo funcionan con MCP stdio. Los medios propios se suben temporalmente al CDN de ProductMaker y se eliminan despu\xE9s de publicar correctamente; si falla, quedan s\xF3lo durante el TTL para reintento: nunca afirmes que una sesi\xF3n no expira. 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.', PublishCreativesToMetaInputBase.shape, async (rawInput) => {
|
|
11108
11129
|
try {
|
|
11109
11130
|
const parsed = PublishCreativesToMetaInput.safeParse(rawInput);
|
|
11110
11131
|
if (!parsed.success) throw invalidInputFromZod(parsed.error);
|
|
@@ -11377,7 +11398,7 @@ __name(uploadSessionText, "uploadSessionText");
|
|
|
11377
11398
|
function registerMetaUploadTools(server, api) {
|
|
11378
11399
|
server.registerTool("request_meta_media_upload", {
|
|
11379
11400
|
title: "Solicitar carga segura de Meta",
|
|
11380
|
-
description: "Inicia una carga segura desde la laptop del usuario para creativos propios de Meta. Por defecto crea una sesi\xF3n `mixed` de hasta diez archivos; tambi\xE9n admite una sola imagen o video. Si el usuario adjunta una foto o video en el mismo mensaje de ChatGPT, pasa esas referencias opcionales en `files` para que el MCP App inline las consuma dentro del chat. ProductMaker conserva `files` solo como metadata del UI bridge: NO los descarga y NO los reenv\xEDa. No pidas URL CDN, ruta local, bytes ni base64. El
|
|
11401
|
+
description: "Inicia una carga segura desde la laptop del usuario para creativos propios de Meta. Por defecto crea una sesi\xF3n `mixed` de hasta diez archivos; tambi\xE9n admite una sola imagen o video. Si el usuario adjunta una foto o video en el mismo mensaje de ChatGPT, pasa esas referencias opcionales en `files` para que el MCP App inline las consuma dentro del chat. ProductMaker conserva `files` solo como metadata del UI bridge: NO los descarga y NO los reenv\xEDa. No pidas URL CDN, ruta local, bytes ni base64. El App es exclusivamente para cargar archivos: nunca debe pedir ni mostrar portafolio, cuenta publicitaria, p\xE1gina, IDs o JSON. El enlace externo de ProductMaker queda como fallback para hosts sin bridge de archivos. Conserva privadamente el `sessionId`; cuando el usuario termine, llama `check_meta_media_upload`, luego `list_meta_destinations` y pregunta en el chat qu\xE9 `portfolioId`, `adAccountId` y `pageId` desea usar. No muestres el `sessionId`, `stagingAssetId` ni un handoff JSON al usuario. Esta herramienta no publica ni consume cr\xE9ditos.",
|
|
11381
11402
|
inputSchema: MetaUploadSessionToolInputShape,
|
|
11382
11403
|
outputSchema: MetaUploadSessionResponse2,
|
|
11383
11404
|
_meta: {
|
|
@@ -11410,7 +11431,7 @@ function registerMetaUploadTools(server, api) {
|
|
|
11410
11431
|
}
|
|
11411
11432
|
});
|
|
11412
11433
|
server.registerTool("check_meta_media_upload", {
|
|
11413
|
-
description: 'Comprueba si el usuario ya termin\xF3 de subir desde el enlace de ProductMaker. Si status=uploaded,
|
|
11434
|
+
description: 'Comprueba si el usuario ya termin\xF3 de subir desde el App o enlace de ProductMaker. Si status=uploaded, conserva `assets[]` privadamente, llama `list_meta_destinations` y pregunta en lenguaje natural dentro del chat qu\xE9 portafolio, cuenta publicitaria y p\xE1gina desea usar; nunca muestres `sessionId`, `stagingAssetId`, IDs t\xE9cnicos ni un bloque JSON. Despu\xE9s crea un creativo `source.type="staged"` por cada asset, usando su `mediaKind` como tipo, hasta diez en el batch. Si la carga fue parcial, usa \xFAnicamente los assets confirmados. Conserva el `stagingAssetId` legado si aparece. Si status=pending, pide al usuario que termine; no publiques todav\xEDa.',
|
|
11414
11435
|
inputSchema: MetaUploadStatusInput.shape,
|
|
11415
11436
|
_meta: {
|
|
11416
11437
|
"openai/widgetAccessible": true
|
package/dist/stdio.js
CHANGED
|
@@ -150,7 +150,7 @@ var ApiClient = class {
|
|
|
150
150
|
};
|
|
151
151
|
|
|
152
152
|
// src/index.ts
|
|
153
|
-
var MCP_VERSION = "2.9.
|
|
153
|
+
var MCP_VERSION = "2.9.3";
|
|
154
154
|
|
|
155
155
|
// src/meta-upload-app.ts
|
|
156
156
|
import { readFileSync } from "fs";
|
|
@@ -8049,7 +8049,7 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
|
|
|
8049
8049
|
{
|
|
8050
8050
|
name: "publish_creatives_to_meta",
|
|
8051
8051
|
confirmation: "external_mutation",
|
|
8052
|
-
schemaHash: "
|
|
8052
|
+
schemaHash: "f712fe12baee19de6ac5ed94b23de630247a345ed13c3a431a647d421480280f"
|
|
8053
8053
|
},
|
|
8054
8054
|
{
|
|
8055
8055
|
name: "publish_to_meta",
|
|
@@ -8912,7 +8912,7 @@ var CODE_MESSAGES = {
|
|
|
8912
8912
|
META_TOKEN_EXPIRED: "Your Meta session expired. Reconnect Facebook at https://productmaker.app/integrations.",
|
|
8913
8913
|
META_ACCOUNT_RESTRICTED: "Your Meta Ads account is temporarily restricted from creating ads. Check the notices at https://business.facebook.com and complete the required verification.",
|
|
8914
8914
|
META_CAPABILITY_MISSING: "The Facebook app lacks required permissions for this ad account. Reconnect Facebook at https://productmaker.app/integrations and accept ALL requested permissions (including business_management).",
|
|
8915
|
-
META_PERMISSION_DENIED: "
|
|
8915
|
+
META_PERMISSION_DENIED: "Meta denied write access to the selected ad account. Ask a Business Manager admin to grant this user Advertiser role or higher for that account, then reconnect Facebook at https://productmaker.app/integrations and accept every requested permission, including ads_management.",
|
|
8916
8916
|
META_PIXEL_NOT_FOUND: "Pixel not found or not associated with the ad account. Verify the `pixelId` at https://business.facebook.com/events_manager.",
|
|
8917
8917
|
META_OBJECT_NOT_FOUND: "The requested object (campaign/ad set/ad) does not exist in Meta. Verify the ID or create a new one.",
|
|
8918
8918
|
META_AI_DISCLOSURE_REQUIRED: "Meta rejected the ad for undisclosed AI content. The system adds the disclosure automatically; if it persists, edit the copy to avoid language Meta flags as misleading.",
|
|
@@ -10857,10 +10857,10 @@ var GroupedCreative = z52.object({
|
|
|
10857
10857
|
cta: MetaCtaType.optional(),
|
|
10858
10858
|
creativeId: z52.string().min(1).optional()
|
|
10859
10859
|
});
|
|
10860
|
-
var
|
|
10860
|
+
var GroupedCampaignConfigBase = z52.object({
|
|
10861
10861
|
campaignType: MetaCampaignType.default("sales"),
|
|
10862
10862
|
optimizationMode: MetaOptimizationMode.default("advantage_plus"),
|
|
10863
|
-
budgetMode: MetaBudgetMode.
|
|
10863
|
+
budgetMode: MetaBudgetMode.optional(),
|
|
10864
10864
|
campaignDailyBudget: z52.number().int().positive().optional(),
|
|
10865
10865
|
adSets: z52.array(z52.object({
|
|
10866
10866
|
tempId: z52.string().min(1).max(80),
|
|
@@ -10886,8 +10886,17 @@ var GroupedCampaignConfig = z52.object({
|
|
|
10886
10886
|
]),
|
|
10887
10887
|
saveAsDefault: z52.boolean().default(false),
|
|
10888
10888
|
budgetPolicy: MetaBudgetPolicy.optional()
|
|
10889
|
-
})
|
|
10890
|
-
|
|
10889
|
+
});
|
|
10890
|
+
function resolveBudgetMode(value) {
|
|
10891
|
+
if (value.budgetMode) return value.budgetMode;
|
|
10892
|
+
if (value.campaignDailyBudget !== void 0) return "campaign";
|
|
10893
|
+
if (value.adSets.every((adSet) => adSet.dailyBudget !== void 0)) return "ad_set";
|
|
10894
|
+
return "campaign";
|
|
10895
|
+
}
|
|
10896
|
+
__name(resolveBudgetMode, "resolveBudgetMode");
|
|
10897
|
+
var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) => {
|
|
10898
|
+
const budgetMode = resolveBudgetMode(value);
|
|
10899
|
+
if (budgetMode === "campaign" && value.campaignDailyBudget === void 0) {
|
|
10891
10900
|
ctx.addIssue({
|
|
10892
10901
|
code: z52.ZodIssueCode.custom,
|
|
10893
10902
|
path: [
|
|
@@ -10896,7 +10905,7 @@ var GroupedCampaignConfig = z52.object({
|
|
|
10896
10905
|
message: "campaignDailyBudget is required when budgetMode is campaign"
|
|
10897
10906
|
});
|
|
10898
10907
|
}
|
|
10899
|
-
if (
|
|
10908
|
+
if (budgetMode === "ad_set") {
|
|
10900
10909
|
value.adSets.forEach((adSet, index) => {
|
|
10901
10910
|
if (adSet.dailyBudget === void 0) {
|
|
10902
10911
|
ctx.addIssue({
|
|
@@ -10911,7 +10920,10 @@ var GroupedCampaignConfig = z52.object({
|
|
|
10911
10920
|
}
|
|
10912
10921
|
});
|
|
10913
10922
|
}
|
|
10914
|
-
})
|
|
10923
|
+
}).transform((value) => ({
|
|
10924
|
+
...value,
|
|
10925
|
+
budgetMode: resolveBudgetMode(value)
|
|
10926
|
+
}));
|
|
10915
10927
|
var PublishCreativesToMetaInputBase = z52.object({
|
|
10916
10928
|
creatives: z52.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
|
|
10917
10929
|
/** Exact Meta Business portfolio selected from `list_meta_destinations`. */
|
|
@@ -10929,7 +10941,7 @@ var PublishCreativesToMetaInputBase = z52.object({
|
|
|
10929
10941
|
});
|
|
10930
10942
|
var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((value, ctx) => {
|
|
10931
10943
|
const destination = value.campaignConfig?.destination;
|
|
10932
|
-
if (destination?.type !== "whatsapp" && !value.landingUrl) {
|
|
10944
|
+
if (destination?.type !== "whatsapp" && destination?.type !== "landing" && !value.landingUrl) {
|
|
10933
10945
|
ctx.addIssue({
|
|
10934
10946
|
code: z52.ZodIssueCode.custom,
|
|
10935
10947
|
path: [
|
|
@@ -10938,7 +10950,7 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
|
|
|
10938
10950
|
message: "landingUrl is required unless campaignConfig.destination.type is whatsapp"
|
|
10939
10951
|
});
|
|
10940
10952
|
}
|
|
10941
|
-
if (destination?.type === "landing" && destination.landingUrl !== value.landingUrl) {
|
|
10953
|
+
if (destination?.type === "landing" && value.landingUrl && destination.landingUrl !== value.landingUrl) {
|
|
10942
10954
|
ctx.addIssue({
|
|
10943
10955
|
code: z52.ZodIssueCode.custom,
|
|
10944
10956
|
path: [
|
|
@@ -10949,6 +10961,15 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
|
|
|
10949
10961
|
message: "campaignConfig.destination.landingUrl must match landingUrl"
|
|
10950
10962
|
});
|
|
10951
10963
|
}
|
|
10964
|
+
}).transform((value) => {
|
|
10965
|
+
const destination = value.campaignConfig?.destination;
|
|
10966
|
+
if (destination?.type === "landing" && !value.landingUrl) {
|
|
10967
|
+
return {
|
|
10968
|
+
...value,
|
|
10969
|
+
landingUrl: destination.landingUrl
|
|
10970
|
+
};
|
|
10971
|
+
}
|
|
10972
|
+
return value;
|
|
10952
10973
|
});
|
|
10953
10974
|
var PresignUploadResponseSchema = z52.object({
|
|
10954
10975
|
uploadUrl: z52.string().url().refine((value) => {
|
|
@@ -11060,7 +11081,7 @@ function registerPublishTools(server, api, opts = {}) {
|
|
|
11060
11081
|
throw translateError(e);
|
|
11061
11082
|
}
|
|
11062
11083
|
});
|
|
11063
|
-
server.tool("publish_creatives_to_meta", "Crea UNA campa\xF1a de Meta Ads PAUSED agrupando hasta " + CAMPAIGN_LIMITS.MAX_CREATIVES + ' creativos. Antes llama `list_meta_destinations` y pide al usuario elegir el `portfolioId`, `adAccountId` y `pageId` exactos. Para archivos en la laptop con MCP remoto: llama `request_meta_media_upload`, espera al usuario y consulta `check_meta_media_upload`; por CADA entrada de `assets[]` crea un creativo `source.type="staged"`, con `type` igual a su `mediaKind`. Si la carga fue parcial, publica s\xF3lo los assets que ese estado confirm\xF3; nunca inventes assets ni superes diez creativos. Las rutas locales s\xF3lo funcionan con MCP stdio. Los medios propios se suben temporalmente al CDN de ProductMaker y se eliminan despu\xE9s de publicar correctamente; si falla, quedan s\xF3lo para reintento
|
|
11084
|
+
server.tool("publish_creatives_to_meta", "Crea UNA campa\xF1a de Meta Ads PAUSED agrupando hasta " + CAMPAIGN_LIMITS.MAX_CREATIVES + ' creativos. Antes llama `list_meta_destinations` y pide al usuario elegir el `portfolioId`, `adAccountId` y `pageId` exactos. Para archivos en la laptop con MCP remoto: llama `request_meta_media_upload`, espera al usuario y consulta `check_meta_media_upload`; por CADA entrada de `assets[]` crea un creativo `source.type="staged"`, con `type` igual a su `mediaKind`. Si la carga fue parcial, publica s\xF3lo los assets que ese estado confirm\xF3; nunca inventes assets ni superes diez creativos. Las rutas locales s\xF3lo funcionan con MCP stdio. Los medios propios se suben temporalmente al CDN de ProductMaker y se eliminan despu\xE9s de publicar correctamente; si falla, quedan s\xF3lo durante el TTL para reintento: nunca afirmes que una sesi\xF3n no expira. 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.', PublishCreativesToMetaInputBase.shape, async (rawInput) => {
|
|
11064
11085
|
try {
|
|
11065
11086
|
const parsed = PublishCreativesToMetaInput.safeParse(rawInput);
|
|
11066
11087
|
if (!parsed.success) throw invalidInputFromZod(parsed.error);
|
|
@@ -11333,7 +11354,7 @@ __name(uploadSessionText, "uploadSessionText");
|
|
|
11333
11354
|
function registerMetaUploadTools(server, api) {
|
|
11334
11355
|
server.registerTool("request_meta_media_upload", {
|
|
11335
11356
|
title: "Solicitar carga segura de Meta",
|
|
11336
|
-
description: "Inicia una carga segura desde la laptop del usuario para creativos propios de Meta. Por defecto crea una sesi\xF3n `mixed` de hasta diez archivos; tambi\xE9n admite una sola imagen o video. Si el usuario adjunta una foto o video en el mismo mensaje de ChatGPT, pasa esas referencias opcionales en `files` para que el MCP App inline las consuma dentro del chat. ProductMaker conserva `files` solo como metadata del UI bridge: NO los descarga y NO los reenv\xEDa. No pidas URL CDN, ruta local, bytes ni base64. El
|
|
11357
|
+
description: "Inicia una carga segura desde la laptop del usuario para creativos propios de Meta. Por defecto crea una sesi\xF3n `mixed` de hasta diez archivos; tambi\xE9n admite una sola imagen o video. Si el usuario adjunta una foto o video en el mismo mensaje de ChatGPT, pasa esas referencias opcionales en `files` para que el MCP App inline las consuma dentro del chat. ProductMaker conserva `files` solo como metadata del UI bridge: NO los descarga y NO los reenv\xEDa. No pidas URL CDN, ruta local, bytes ni base64. El App es exclusivamente para cargar archivos: nunca debe pedir ni mostrar portafolio, cuenta publicitaria, p\xE1gina, IDs o JSON. El enlace externo de ProductMaker queda como fallback para hosts sin bridge de archivos. Conserva privadamente el `sessionId`; cuando el usuario termine, llama `check_meta_media_upload`, luego `list_meta_destinations` y pregunta en el chat qu\xE9 `portfolioId`, `adAccountId` y `pageId` desea usar. No muestres el `sessionId`, `stagingAssetId` ni un handoff JSON al usuario. Esta herramienta no publica ni consume cr\xE9ditos.",
|
|
11337
11358
|
inputSchema: MetaUploadSessionToolInputShape,
|
|
11338
11359
|
outputSchema: MetaUploadSessionResponse2,
|
|
11339
11360
|
_meta: {
|
|
@@ -11366,7 +11387,7 @@ function registerMetaUploadTools(server, api) {
|
|
|
11366
11387
|
}
|
|
11367
11388
|
});
|
|
11368
11389
|
server.registerTool("check_meta_media_upload", {
|
|
11369
|
-
description: 'Comprueba si el usuario ya termin\xF3 de subir desde el enlace de ProductMaker. Si status=uploaded,
|
|
11390
|
+
description: 'Comprueba si el usuario ya termin\xF3 de subir desde el App o enlace de ProductMaker. Si status=uploaded, conserva `assets[]` privadamente, llama `list_meta_destinations` y pregunta en lenguaje natural dentro del chat qu\xE9 portafolio, cuenta publicitaria y p\xE1gina desea usar; nunca muestres `sessionId`, `stagingAssetId`, IDs t\xE9cnicos ni un bloque JSON. Despu\xE9s crea un creativo `source.type="staged"` por cada asset, usando su `mediaKind` como tipo, hasta diez en el batch. Si la carga fue parcial, usa \xFAnicamente los assets confirmados. Conserva el `stagingAssetId` legado si aparece. Si status=pending, pide al usuario que termine; no publiques todav\xEDa.',
|
|
11370
11391
|
inputSchema: MetaUploadStatusInput.shape,
|
|
11371
11392
|
_meta: {
|
|
11372
11393
|
"openai/widgetAccessible": true
|
|
@@ -66,21 +66,16 @@
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
button,
|
|
69
|
-
input
|
|
70
|
-
select,
|
|
71
|
-
textarea {
|
|
69
|
+
input {
|
|
72
70
|
font: inherit;
|
|
73
71
|
}
|
|
74
72
|
|
|
75
73
|
button,
|
|
76
|
-
select,
|
|
77
74
|
a {
|
|
78
75
|
-webkit-tap-highlight-color: transparent;
|
|
79
76
|
}
|
|
80
77
|
|
|
81
78
|
button:focus-visible,
|
|
82
|
-
select:focus-visible,
|
|
83
|
-
textarea:focus-visible,
|
|
84
79
|
a:focus-visible,
|
|
85
80
|
[tabindex]:focus-visible {
|
|
86
81
|
outline: 3px solid
|
|
@@ -102,73 +97,12 @@
|
|
|
102
97
|
box-shadow: var(--pm-shadow);
|
|
103
98
|
}
|
|
104
99
|
|
|
105
|
-
.
|
|
106
|
-
padding: 19px 22px 14px;
|
|
107
|
-
border-bottom: 1px solid
|
|
108
|
-
color-mix(in srgb, var(--pm-line) 65%, transparent);
|
|
109
|
-
background:
|
|
110
|
-
radial-gradient(
|
|
111
|
-
circle at 100% 0%,
|
|
112
|
-
var(--pm-primary-soft),
|
|
113
|
-
transparent 48%
|
|
114
|
-
),
|
|
115
|
-
var(--pm-card);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
.upload-header {
|
|
100
|
+
.upload-feedback {
|
|
119
101
|
display: flex;
|
|
120
102
|
align-items: center;
|
|
121
|
-
|
|
122
|
-
gap: 12px;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
.upload-title {
|
|
126
|
-
min-width: 0;
|
|
127
|
-
margin: 0;
|
|
128
|
-
overflow: hidden;
|
|
129
|
-
font-size: clamp(17px, 3vw, 24px);
|
|
130
|
-
font-weight: 760;
|
|
131
|
-
letter-spacing: -0.025em;
|
|
132
|
-
text-overflow: ellipsis;
|
|
133
|
-
white-space: nowrap;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
.brand-mark {
|
|
137
|
-
display: grid;
|
|
138
|
-
width: 34px;
|
|
139
|
-
height: 34px;
|
|
140
|
-
flex: 0 0 auto;
|
|
141
|
-
place-items: center;
|
|
142
|
-
border: 1px solid
|
|
143
|
-
color-mix(in srgb, var(--pm-primary) 20%, var(--pm-line));
|
|
144
|
-
border-radius: 50%;
|
|
145
|
-
background: color-mix(
|
|
146
|
-
in srgb,
|
|
147
|
-
var(--pm-primary-soft) 70%,
|
|
148
|
-
var(--pm-card)
|
|
149
|
-
);
|
|
150
|
-
color: var(--pm-primary-strong);
|
|
151
|
-
font-size: 20px;
|
|
152
|
-
font-weight: 800;
|
|
153
|
-
line-height: 1;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
.session-row {
|
|
157
|
-
display: flex;
|
|
158
|
-
min-height: 0;
|
|
159
|
-
align-items: center;
|
|
103
|
+
flex-wrap: wrap;
|
|
160
104
|
gap: 10px;
|
|
161
|
-
margin-top:
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
.session-id {
|
|
165
|
-
overflow: hidden;
|
|
166
|
-
max-width: 100%;
|
|
167
|
-
color: var(--pm-muted);
|
|
168
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
169
|
-
font-size: 10px;
|
|
170
|
-
text-overflow: ellipsis;
|
|
171
|
-
white-space: nowrap;
|
|
105
|
+
margin-top: 10px;
|
|
172
106
|
}
|
|
173
107
|
|
|
174
108
|
.fallback-link {
|
|
@@ -212,20 +146,23 @@
|
|
|
212
146
|
}
|
|
213
147
|
|
|
214
148
|
.section {
|
|
215
|
-
padding:
|
|
149
|
+
padding: 18px;
|
|
216
150
|
}
|
|
217
151
|
|
|
218
|
-
.
|
|
219
|
-
|
|
152
|
+
.media-grid {
|
|
153
|
+
display: grid;
|
|
154
|
+
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
|
155
|
+
gap: 12px;
|
|
156
|
+
align-items: start;
|
|
220
157
|
}
|
|
221
158
|
|
|
222
159
|
.dropzone {
|
|
223
160
|
display: grid;
|
|
224
|
-
|
|
161
|
+
aspect-ratio: 1;
|
|
162
|
+
min-width: 0;
|
|
225
163
|
place-items: center;
|
|
226
|
-
padding: 20px;
|
|
227
164
|
border: 2px dashed var(--pm-line);
|
|
228
|
-
border-radius:
|
|
165
|
+
border-radius: 16px;
|
|
229
166
|
background: color-mix(
|
|
230
167
|
in srgb,
|
|
231
168
|
var(--pm-card-muted) 62%,
|
|
@@ -251,13 +188,11 @@
|
|
|
251
188
|
|
|
252
189
|
.drop-trigger {
|
|
253
190
|
display: grid;
|
|
254
|
-
width:
|
|
255
|
-
|
|
191
|
+
width: 100%;
|
|
192
|
+
height: 100%;
|
|
256
193
|
place-items: center;
|
|
257
|
-
align-content: center;
|
|
258
|
-
gap: 16px;
|
|
259
194
|
border: 0;
|
|
260
|
-
border-radius:
|
|
195
|
+
border-radius: 14px;
|
|
261
196
|
background: transparent;
|
|
262
197
|
color: var(--pm-text);
|
|
263
198
|
cursor: pointer;
|
|
@@ -269,26 +204,19 @@
|
|
|
269
204
|
|
|
270
205
|
.drop-icon {
|
|
271
206
|
display: grid;
|
|
272
|
-
width:
|
|
273
|
-
height:
|
|
207
|
+
width: 48px;
|
|
208
|
+
height: 48px;
|
|
274
209
|
place-items: center;
|
|
275
210
|
border: 1px solid var(--pm-line);
|
|
276
211
|
border-radius: 50%;
|
|
277
212
|
background: var(--pm-card);
|
|
278
213
|
box-shadow: 0 7px 20px rgb(27 36 66 / 8%);
|
|
279
214
|
color: var(--pm-text);
|
|
280
|
-
font-size:
|
|
215
|
+
font-size: 29px;
|
|
281
216
|
font-weight: 350;
|
|
282
217
|
line-height: 1;
|
|
283
218
|
}
|
|
284
219
|
|
|
285
|
-
.drop-title {
|
|
286
|
-
margin: 0;
|
|
287
|
-
font-size: 18px;
|
|
288
|
-
font-weight: 520;
|
|
289
|
-
letter-spacing: -0.02em;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
220
|
.drop-help {
|
|
293
221
|
position: absolute;
|
|
294
222
|
width: 1px;
|
|
@@ -309,47 +237,6 @@
|
|
|
309
237
|
clip-path: inset(50%);
|
|
310
238
|
}
|
|
311
239
|
|
|
312
|
-
.button {
|
|
313
|
-
display: inline-flex;
|
|
314
|
-
min-height: 42px;
|
|
315
|
-
align-items: center;
|
|
316
|
-
justify-content: center;
|
|
317
|
-
gap: 8px;
|
|
318
|
-
padding: 10px 16px;
|
|
319
|
-
border: 1px solid transparent;
|
|
320
|
-
border-radius: 12px;
|
|
321
|
-
background: var(--pm-primary);
|
|
322
|
-
color: #ffffff;
|
|
323
|
-
cursor: pointer;
|
|
324
|
-
font-size: 13px;
|
|
325
|
-
font-weight: 780;
|
|
326
|
-
line-height: 1;
|
|
327
|
-
transition:
|
|
328
|
-
background 140ms ease,
|
|
329
|
-
opacity 140ms ease,
|
|
330
|
-
transform 140ms ease;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
.button:hover:not(:disabled) {
|
|
334
|
-
background: var(--pm-primary-strong);
|
|
335
|
-
transform: translateY(-1px);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
.button:disabled {
|
|
339
|
-
cursor: not-allowed;
|
|
340
|
-
opacity: 0.5;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
.button.secondary {
|
|
344
|
-
border-color: var(--pm-line);
|
|
345
|
-
background: var(--pm-card);
|
|
346
|
-
color: var(--pm-text);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
.button.secondary:hover:not(:disabled) {
|
|
350
|
-
background: var(--pm-card-muted);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
240
|
.batch-error {
|
|
354
241
|
display: none;
|
|
355
242
|
margin: 10px 0 0;
|
|
@@ -362,10 +249,7 @@
|
|
|
362
249
|
}
|
|
363
250
|
|
|
364
251
|
.file-list {
|
|
365
|
-
display:
|
|
366
|
-
grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
|
|
367
|
-
gap: 12px;
|
|
368
|
-
margin-top: 14px;
|
|
252
|
+
display: contents;
|
|
369
253
|
}
|
|
370
254
|
|
|
371
255
|
.file-card {
|
|
@@ -501,64 +385,11 @@
|
|
|
501
385
|
transition: width 120ms linear;
|
|
502
386
|
}
|
|
503
387
|
|
|
504
|
-
.destination-grid {
|
|
505
|
-
display: grid;
|
|
506
|
-
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
507
|
-
gap: 9px;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
.field {
|
|
511
|
-
display: grid;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
select,
|
|
515
|
-
textarea {
|
|
516
|
-
width: 100%;
|
|
517
|
-
border: 1px solid var(--pm-line);
|
|
518
|
-
border-radius: 11px;
|
|
519
|
-
background: var(--pm-card);
|
|
520
|
-
color: var(--pm-text);
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
select {
|
|
524
|
-
min-height: 42px;
|
|
525
|
-
padding: 8px 30px 8px 10px;
|
|
526
|
-
font-size: 12px;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
textarea {
|
|
530
|
-
min-height: 135px;
|
|
531
|
-
padding: 12px;
|
|
532
|
-
resize: vertical;
|
|
533
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
534
|
-
font-size: 11px;
|
|
535
|
-
line-height: 1.5;
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
.destination-actions,
|
|
539
|
-
.handoff-actions {
|
|
540
|
-
display: flex;
|
|
541
|
-
flex-wrap: wrap;
|
|
542
|
-
gap: 10px;
|
|
543
|
-
margin-top: 12px;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
388
|
.compatibility-control {
|
|
547
389
|
/* Retained for older hosts; recovery is visible only through the secure fallback. */
|
|
548
390
|
display: none !important;
|
|
549
391
|
}
|
|
550
392
|
|
|
551
|
-
.sr-only {
|
|
552
|
-
position: absolute;
|
|
553
|
-
width: 1px;
|
|
554
|
-
height: 1px;
|
|
555
|
-
padding: 0;
|
|
556
|
-
overflow: hidden;
|
|
557
|
-
clip: rect(0, 0, 0, 0);
|
|
558
|
-
white-space: nowrap;
|
|
559
|
-
border: 0;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
393
|
[hidden] {
|
|
563
394
|
display: none !important;
|
|
564
395
|
}
|
|
@@ -572,23 +403,13 @@
|
|
|
572
403
|
border-radius: 20px;
|
|
573
404
|
}
|
|
574
405
|
|
|
575
|
-
.hero,
|
|
576
406
|
.section {
|
|
577
407
|
padding-right: 14px;
|
|
578
408
|
padding-left: 14px;
|
|
579
409
|
}
|
|
580
410
|
|
|
581
|
-
.
|
|
582
|
-
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
.destination-grid {
|
|
586
|
-
grid-template-columns: 1fr;
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
.destination-actions .button,
|
|
590
|
-
.handoff-actions .button {
|
|
591
|
-
width: 100%;
|
|
411
|
+
.media-grid {
|
|
412
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
592
413
|
}
|
|
593
414
|
}
|
|
594
415
|
|
|
@@ -605,71 +426,46 @@
|
|
|
605
426
|
<body>
|
|
606
427
|
<main class="app" data-testid="meta-upload-root">
|
|
607
428
|
<article class="card">
|
|
608
|
-
<header class="hero">
|
|
609
|
-
<div class="upload-header">
|
|
610
|
-
<p class="upload-title" data-testid="meta-upload-title">
|
|
611
|
-
Upload image
|
|
612
|
-
</p>
|
|
613
|
-
<span class="brand-mark" aria-label="ProductMaker" role="img"
|
|
614
|
-
>↗</span
|
|
615
|
-
>
|
|
616
|
-
</div>
|
|
617
|
-
<div class="session-row">
|
|
618
|
-
<code class="session-id" data-testid="meta-upload-session-id" hidden
|
|
619
|
-
>Esperando sesión…</code
|
|
620
|
-
>
|
|
621
|
-
<a
|
|
622
|
-
class="fallback-link"
|
|
623
|
-
data-testid="meta-upload-fallback-link"
|
|
624
|
-
href="#"
|
|
625
|
-
target="_blank"
|
|
626
|
-
rel="noopener noreferrer"
|
|
627
|
-
hidden
|
|
628
|
-
>Abrir carga segura en otra pestaña</a
|
|
629
|
-
>
|
|
630
|
-
</div>
|
|
631
|
-
<p
|
|
632
|
-
class="status"
|
|
633
|
-
data-testid="meta-upload-session-status"
|
|
634
|
-
aria-live="polite"
|
|
635
|
-
></p>
|
|
636
|
-
</header>
|
|
637
|
-
|
|
638
429
|
<section class="section" aria-label="Upload media">
|
|
639
|
-
<div
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
430
|
+
<div class="media-grid">
|
|
431
|
+
<div
|
|
432
|
+
class="file-list"
|
|
433
|
+
data-testid="meta-upload-file-list"
|
|
434
|
+
aria-live="polite"
|
|
435
|
+
></div>
|
|
436
|
+
<div
|
|
437
|
+
class="dropzone"
|
|
438
|
+
data-testid="meta-upload-dropzone"
|
|
439
|
+
data-dragging="false"
|
|
440
|
+
aria-disabled="true"
|
|
441
|
+
aria-describedby="meta-upload-constraints"
|
|
651
442
|
>
|
|
652
|
-
<
|
|
653
|
-
|
|
654
|
-
|
|
443
|
+
<button
|
|
444
|
+
class="drop-trigger"
|
|
445
|
+
type="button"
|
|
446
|
+
data-testid="meta-upload-add-tile"
|
|
447
|
+
aria-label="Agregar otro archivo"
|
|
448
|
+
disabled
|
|
655
449
|
>
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
450
|
+
<span class="drop-icon" aria-hidden="true">+</span>
|
|
451
|
+
</button>
|
|
452
|
+
<button
|
|
453
|
+
class="compatibility-control"
|
|
454
|
+
type="button"
|
|
455
|
+
data-testid="meta-upload-library-btn"
|
|
456
|
+
hidden
|
|
457
|
+
disabled
|
|
458
|
+
aria-label="Choose from ChatGPT library"
|
|
459
|
+
></button>
|
|
460
|
+
<input
|
|
461
|
+
class="native-input"
|
|
462
|
+
data-testid="meta-upload-input"
|
|
463
|
+
type="file"
|
|
464
|
+
accept="image/jpeg,image/png,image/webp,video/mp4,video/quicktime,video/webm"
|
|
465
|
+
multiple
|
|
466
|
+
disabled
|
|
467
|
+
/>
|
|
468
|
+
</div>
|
|
673
469
|
</div>
|
|
674
470
|
<p class="drop-help" id="meta-upload-constraints">
|
|
675
471
|
JPEG, PNG, WebP, MP4, MOV o WebM. Hasta 10 archivos de 350 MB; las
|
|
@@ -681,76 +477,21 @@
|
|
|
681
477
|
role="alert"
|
|
682
478
|
aria-live="assertive"
|
|
683
479
|
></p>
|
|
684
|
-
<div
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
data-testid="meta-upload-destination-section"
|
|
694
|
-
aria-label="Meta destination"
|
|
695
|
-
hidden
|
|
696
|
-
>
|
|
697
|
-
<div class="destination-grid">
|
|
698
|
-
<div class="field">
|
|
699
|
-
<label class="sr-only" for="portfolio-select">Portfolio</label>
|
|
700
|
-
<select
|
|
701
|
-
id="portfolio-select"
|
|
702
|
-
data-testid="meta-upload-portfolio-select"
|
|
703
|
-
>
|
|
704
|
-
<option value="">Portfolio</option>
|
|
705
|
-
</select>
|
|
706
|
-
</div>
|
|
707
|
-
<div class="field">
|
|
708
|
-
<label class="sr-only" for="ad-account-select">Ad account</label>
|
|
709
|
-
<select
|
|
710
|
-
id="ad-account-select"
|
|
711
|
-
data-testid="meta-upload-ad-account-select"
|
|
712
|
-
disabled
|
|
713
|
-
>
|
|
714
|
-
<option value="">Ad account</option>
|
|
715
|
-
</select>
|
|
716
|
-
</div>
|
|
717
|
-
<div class="field">
|
|
718
|
-
<label class="sr-only" for="page-select">Facebook Page</label>
|
|
719
|
-
<select id="page-select" data-testid="meta-upload-page-select">
|
|
720
|
-
<option value="">Facebook Page</option>
|
|
721
|
-
</select>
|
|
722
|
-
</div>
|
|
723
|
-
</div>
|
|
724
|
-
<div class="destination-actions">
|
|
725
|
-
<button
|
|
726
|
-
class="button"
|
|
727
|
-
type="button"
|
|
728
|
-
data-testid="meta-upload-submit-destination"
|
|
729
|
-
disabled
|
|
730
|
-
>
|
|
731
|
-
Continue
|
|
732
|
-
</button>
|
|
733
|
-
</div>
|
|
734
|
-
</section>
|
|
735
|
-
|
|
736
|
-
<section
|
|
737
|
-
class="section"
|
|
738
|
-
data-testid="meta-upload-handoff-section"
|
|
739
|
-
aria-labelledby="handoff-heading"
|
|
740
|
-
hidden
|
|
741
|
-
>
|
|
742
|
-
<p id="handoff-heading" class="section-copy muted">
|
|
743
|
-
Copy this handoff into the chat.
|
|
744
|
-
</p>
|
|
745
|
-
<textarea data-testid="meta-upload-handoff-copy" readonly></textarea>
|
|
746
|
-
<div class="handoff-actions">
|
|
747
|
-
<button
|
|
748
|
-
class="button secondary"
|
|
749
|
-
type="button"
|
|
750
|
-
data-testid="meta-upload-copy-btn"
|
|
480
|
+
<div class="upload-feedback">
|
|
481
|
+
<a
|
|
482
|
+
class="fallback-link"
|
|
483
|
+
data-testid="meta-upload-fallback-link"
|
|
484
|
+
href="#"
|
|
485
|
+
target="_blank"
|
|
486
|
+
rel="noopener noreferrer"
|
|
487
|
+
hidden
|
|
488
|
+
>Abrir carga segura en otra pestaña</a
|
|
751
489
|
>
|
|
752
|
-
|
|
753
|
-
|
|
490
|
+
<p
|
|
491
|
+
class="status"
|
|
492
|
+
data-testid="meta-upload-session-status"
|
|
493
|
+
aria-live="polite"
|
|
494
|
+
></p>
|
|
754
495
|
</div>
|
|
755
496
|
</section>
|
|
756
497
|
</article>
|
|
@@ -788,10 +529,6 @@
|
|
|
788
529
|
|
|
789
530
|
const elements = {
|
|
790
531
|
root: document.querySelector('[data-testid="meta-upload-root"]'),
|
|
791
|
-
title: document.querySelector('[data-testid="meta-upload-title"]'),
|
|
792
|
-
sessionId: document.querySelector(
|
|
793
|
-
'[data-testid="meta-upload-session-id"]',
|
|
794
|
-
),
|
|
795
532
|
fallbackLink: document.querySelector(
|
|
796
533
|
'[data-testid="meta-upload-fallback-link"]',
|
|
797
534
|
),
|
|
@@ -801,11 +538,8 @@
|
|
|
801
538
|
dropzone: document.querySelector(
|
|
802
539
|
'[data-testid="meta-upload-dropzone"]',
|
|
803
540
|
),
|
|
804
|
-
dropTitle: document.querySelector(
|
|
805
|
-
'[data-testid="meta-upload-drop-title"]',
|
|
806
|
-
),
|
|
807
541
|
pickerButton: document.querySelector(
|
|
808
|
-
'[data-testid="meta-upload-
|
|
542
|
+
'[data-testid="meta-upload-add-tile"]',
|
|
809
543
|
),
|
|
810
544
|
libraryPickerButton: document.querySelector(
|
|
811
545
|
'[data-testid="meta-upload-library-btn"]',
|
|
@@ -817,30 +551,6 @@
|
|
|
817
551
|
fileList: document.querySelector(
|
|
818
552
|
'[data-testid="meta-upload-file-list"]',
|
|
819
553
|
),
|
|
820
|
-
destinationSection: document.querySelector(
|
|
821
|
-
'[data-testid="meta-upload-destination-section"]',
|
|
822
|
-
),
|
|
823
|
-
portfolio: document.querySelector(
|
|
824
|
-
'[data-testid="meta-upload-portfolio-select"]',
|
|
825
|
-
),
|
|
826
|
-
adAccount: document.querySelector(
|
|
827
|
-
'[data-testid="meta-upload-ad-account-select"]',
|
|
828
|
-
),
|
|
829
|
-
page: document.querySelector(
|
|
830
|
-
'[data-testid="meta-upload-page-select"]',
|
|
831
|
-
),
|
|
832
|
-
submitDestination: document.querySelector(
|
|
833
|
-
'[data-testid="meta-upload-submit-destination"]',
|
|
834
|
-
),
|
|
835
|
-
handoffSection: document.querySelector(
|
|
836
|
-
'[data-testid="meta-upload-handoff-section"]',
|
|
837
|
-
),
|
|
838
|
-
handoffCopy: document.querySelector(
|
|
839
|
-
'[data-testid="meta-upload-handoff-copy"]',
|
|
840
|
-
),
|
|
841
|
-
copyButton: document.querySelector(
|
|
842
|
-
'[data-testid="meta-upload-copy-btn"]',
|
|
843
|
-
),
|
|
844
554
|
};
|
|
845
555
|
|
|
846
556
|
const state = {
|
|
@@ -859,7 +569,8 @@
|
|
|
859
569
|
toolInput: {},
|
|
860
570
|
items: [],
|
|
861
571
|
assets: [],
|
|
862
|
-
|
|
572
|
+
completionNotified: false,
|
|
573
|
+
completionNotificationInFlight: false,
|
|
863
574
|
consumedBridgeIds: new Set(),
|
|
864
575
|
consumingBridgeFiles: false,
|
|
865
576
|
bridgeConsumptionQueued: false,
|
|
@@ -907,29 +618,6 @@
|
|
|
907
618
|
return null;
|
|
908
619
|
}
|
|
909
620
|
|
|
910
|
-
function defaultUploadTitle() {
|
|
911
|
-
return state.session?.mediaKind === "video"
|
|
912
|
-
? "Upload video"
|
|
913
|
-
: "Upload image";
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
function updateUploadTitle() {
|
|
917
|
-
const namedItems = state.items.filter(
|
|
918
|
-
(item) =>
|
|
919
|
-
(item.file && typeof item.file.name === "string") ||
|
|
920
|
-
(typeof item.displayName === "string" &&
|
|
921
|
-
item.displayName.length > 0),
|
|
922
|
-
);
|
|
923
|
-
const title =
|
|
924
|
-
namedItems.length === 1
|
|
925
|
-
? namedItems[0].file?.name || namedItems[0].displayName
|
|
926
|
-
: namedItems.length > 1
|
|
927
|
-
? `${namedItems.length} files`
|
|
928
|
-
: defaultUploadTitle();
|
|
929
|
-
elements.title.textContent = title;
|
|
930
|
-
elements.dropTitle.textContent = defaultUploadTitle();
|
|
931
|
-
}
|
|
932
|
-
|
|
933
621
|
function safeSessionLimit() {
|
|
934
622
|
const proposed =
|
|
935
623
|
state.session && Number.isInteger(state.session.maxFiles)
|
|
@@ -1262,24 +950,6 @@
|
|
|
1262
950
|
void processLocalFiles(files);
|
|
1263
951
|
});
|
|
1264
952
|
|
|
1265
|
-
elements.portfolio.addEventListener("change", () => {
|
|
1266
|
-
renderAdAccounts();
|
|
1267
|
-
updateDestinationSubmit();
|
|
1268
|
-
});
|
|
1269
|
-
elements.adAccount.addEventListener(
|
|
1270
|
-
"change",
|
|
1271
|
-
updateDestinationSubmit,
|
|
1272
|
-
);
|
|
1273
|
-
elements.page.addEventListener("change", updateDestinationSubmit);
|
|
1274
|
-
elements.submitDestination.addEventListener(
|
|
1275
|
-
"click",
|
|
1276
|
-
() => void submitDestination(),
|
|
1277
|
-
);
|
|
1278
|
-
elements.copyButton.addEventListener(
|
|
1279
|
-
"click",
|
|
1280
|
-
() => void copyHandoff(),
|
|
1281
|
-
);
|
|
1282
|
-
|
|
1283
953
|
state.hostMessageHandler = handleHostMessage;
|
|
1284
954
|
window.addEventListener("message", state.hostMessageHandler);
|
|
1285
955
|
}
|
|
@@ -1413,15 +1083,15 @@
|
|
|
1413
1083
|
if (sessionChanged) {
|
|
1414
1084
|
state.uploadBlocked = false;
|
|
1415
1085
|
state.fallbackRequired = false;
|
|
1086
|
+
state.completionNotified = false;
|
|
1087
|
+
state.completionNotificationInFlight = false;
|
|
1416
1088
|
state.workCancelled = false;
|
|
1417
1089
|
if (state.abortController?.signal.aborted) {
|
|
1418
1090
|
state.abortController = null;
|
|
1419
1091
|
}
|
|
1420
1092
|
}
|
|
1421
1093
|
state.session = session;
|
|
1422
|
-
elements.sessionId.textContent = session.sessionId;
|
|
1423
1094
|
elements.fallbackLink.href = session.uploadPageUrl;
|
|
1424
|
-
updateUploadTitle();
|
|
1425
1095
|
if (sessionExpired()) {
|
|
1426
1096
|
requireFallback(
|
|
1427
1097
|
"La sesión está expirada. Abre el enlace seguro para solicitar una nueva.",
|
|
@@ -2415,7 +2085,6 @@
|
|
|
2415
2085
|
fragment.append(card);
|
|
2416
2086
|
});
|
|
2417
2087
|
elements.fileList.replaceChildren(fragment);
|
|
2418
|
-
updateUploadTitle();
|
|
2419
2088
|
scheduleIntrinsicSizeReport(true);
|
|
2420
2089
|
}
|
|
2421
2090
|
|
|
@@ -2448,7 +2117,6 @@
|
|
|
2448
2117
|
"warning",
|
|
2449
2118
|
);
|
|
2450
2119
|
}
|
|
2451
|
-
elements.destinationSection.hidden = true;
|
|
2452
2120
|
return;
|
|
2453
2121
|
}
|
|
2454
2122
|
|
|
@@ -2467,15 +2135,10 @@
|
|
|
2467
2135
|
setSessionStatus(
|
|
2468
2136
|
partial
|
|
2469
2137
|
? "Carga parcial verificada. Continuaremos solo con los assets listos."
|
|
2470
|
-
: "Carga
|
|
2138
|
+
: "Carga completada.",
|
|
2471
2139
|
partial ? "warning" : "success",
|
|
2472
2140
|
);
|
|
2473
|
-
|
|
2474
|
-
"list_meta_destinations",
|
|
2475
|
-
{},
|
|
2476
|
-
);
|
|
2477
|
-
state.destinations = parseDestinationsResult(destinationsResult);
|
|
2478
|
-
renderDestinations();
|
|
2141
|
+
await notifyChatUploadComplete();
|
|
2479
2142
|
} catch (error) {
|
|
2480
2143
|
const expired =
|
|
2481
2144
|
sessionExpired() ||
|
|
@@ -2492,11 +2155,7 @@
|
|
|
2492
2155
|
}
|
|
2493
2156
|
|
|
2494
2157
|
async function callReadOnlyTool(name, args) {
|
|
2495
|
-
if (
|
|
2496
|
-
!["check_meta_media_upload", "list_meta_destinations"].includes(
|
|
2497
|
-
name,
|
|
2498
|
-
)
|
|
2499
|
-
) {
|
|
2158
|
+
if (name !== "check_meta_media_upload") {
|
|
2500
2159
|
throw new Error("tool_not_allowed");
|
|
2501
2160
|
}
|
|
2502
2161
|
const bridge = openAiBridge();
|
|
@@ -2587,173 +2246,43 @@
|
|
|
2587
2246
|
return { status, assets, legacyKindAmbiguous };
|
|
2588
2247
|
}
|
|
2589
2248
|
|
|
2590
|
-
function
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
currency: boundedString(entry.currency, 20),
|
|
2601
|
-
portfolioId: boundedString(entry.portfolioId, 300),
|
|
2602
|
-
accountStatus: Number(entry.accountStatus),
|
|
2603
|
-
})).filter(
|
|
2604
|
-
(entry) => entry.portfolioId && entry.accountStatus === 1,
|
|
2605
|
-
),
|
|
2606
|
-
pages: parseDestinationList(payload.pages, (entry) => ({
|
|
2607
|
-
id: boundedString(entry.id, 300),
|
|
2608
|
-
name: boundedString(entry.name, 300) || "Página sin nombre",
|
|
2609
|
-
})),
|
|
2610
|
-
};
|
|
2611
|
-
}
|
|
2612
|
-
|
|
2613
|
-
function parseDestinationList(value, mapper) {
|
|
2614
|
-
if (!Array.isArray(value)) return [];
|
|
2615
|
-
return value.slice(0, 1000).flatMap((entry) => {
|
|
2616
|
-
if (!isRecord(entry)) return [];
|
|
2617
|
-
const mapped = mapper(entry);
|
|
2618
|
-
return mapped.id ? [mapped] : [];
|
|
2619
|
-
});
|
|
2620
|
-
}
|
|
2621
|
-
|
|
2622
|
-
function renderDestinations() {
|
|
2623
|
-
fillSelect(
|
|
2624
|
-
elements.portfolio,
|
|
2625
|
-
state.destinations.portfolios,
|
|
2626
|
-
"Portfolio",
|
|
2627
|
-
(entry) => entry.name,
|
|
2628
|
-
);
|
|
2629
|
-
fillSelect(
|
|
2630
|
-
elements.page,
|
|
2631
|
-
state.destinations.pages,
|
|
2632
|
-
"Facebook Page",
|
|
2633
|
-
(entry) => entry.name,
|
|
2634
|
-
);
|
|
2635
|
-
renderAdAccounts();
|
|
2636
|
-
elements.destinationSection.hidden = false;
|
|
2637
|
-
updateDestinationSubmit();
|
|
2638
|
-
scheduleIntrinsicSizeReport(true);
|
|
2639
|
-
}
|
|
2640
|
-
|
|
2641
|
-
function renderAdAccounts() {
|
|
2642
|
-
const portfolioId = elements.portfolio.value;
|
|
2643
|
-
const accounts = portfolioId
|
|
2644
|
-
? state.destinations.adAccounts.filter(
|
|
2645
|
-
(account) => account.portfolioId === portfolioId,
|
|
2646
|
-
)
|
|
2647
|
-
: [];
|
|
2648
|
-
fillSelect(
|
|
2649
|
-
elements.adAccount,
|
|
2650
|
-
accounts,
|
|
2651
|
-
portfolioId ? "Ad account" : "Ad account",
|
|
2652
|
-
(entry) =>
|
|
2653
|
-
`${entry.name}${entry.currency ? ` · ${entry.currency}` : ""}`,
|
|
2654
|
-
);
|
|
2655
|
-
elements.adAccount.disabled = !portfolioId || !accounts.length;
|
|
2656
|
-
}
|
|
2657
|
-
|
|
2658
|
-
function fillSelect(select, entries, placeholder, labelFor) {
|
|
2659
|
-
const fragment = document.createDocumentFragment();
|
|
2660
|
-
const empty = document.createElement("option");
|
|
2661
|
-
empty.value = "";
|
|
2662
|
-
empty.textContent = placeholder;
|
|
2663
|
-
fragment.append(empty);
|
|
2664
|
-
for (const entry of entries) {
|
|
2665
|
-
const option = document.createElement("option");
|
|
2666
|
-
option.value = entry.id;
|
|
2667
|
-
option.textContent = labelFor(entry);
|
|
2668
|
-
fragment.append(option);
|
|
2669
|
-
}
|
|
2670
|
-
select.replaceChildren(fragment);
|
|
2671
|
-
select.value = "";
|
|
2672
|
-
}
|
|
2673
|
-
|
|
2674
|
-
function updateDestinationSubmit() {
|
|
2675
|
-
elements.submitDestination.disabled = !(
|
|
2676
|
-
elements.portfolio.value &&
|
|
2677
|
-
elements.adAccount.value &&
|
|
2678
|
-
elements.page.value &&
|
|
2679
|
-
state.assets.length
|
|
2680
|
-
);
|
|
2681
|
-
}
|
|
2682
|
-
|
|
2683
|
-
function exactHandoff() {
|
|
2684
|
-
return JSON.stringify({
|
|
2685
|
-
sessionId: state.session.sessionId,
|
|
2686
|
-
portfolioId: elements.portfolio.value,
|
|
2687
|
-
adAccountId: elements.adAccount.value,
|
|
2688
|
-
pageId: elements.page.value,
|
|
2689
|
-
assets: state.assets.slice(0, MAX_FILES).map((asset) => ({
|
|
2690
|
-
stagingAssetId: asset.stagingAssetId,
|
|
2691
|
-
mediaKind: asset.mediaKind,
|
|
2692
|
-
})),
|
|
2693
|
-
});
|
|
2694
|
-
}
|
|
2695
|
-
|
|
2696
|
-
async function submitDestination() {
|
|
2697
|
-
if (elements.submitDestination.disabled || !state.session) return;
|
|
2698
|
-
const handoff = exactHandoff();
|
|
2699
|
-
elements.submitDestination.disabled = true;
|
|
2249
|
+
async function notifyChatUploadComplete() {
|
|
2250
|
+
if (
|
|
2251
|
+
state.completionNotified ||
|
|
2252
|
+
state.completionNotificationInFlight ||
|
|
2253
|
+
!state.session
|
|
2254
|
+
)
|
|
2255
|
+
return;
|
|
2256
|
+
state.completionNotificationInFlight = true;
|
|
2257
|
+
const message =
|
|
2258
|
+
"Terminé de subir los archivos. Verifica esta misma sesión de carga y continúa en el chat: muéstrame las opciones disponibles y pregúntame por el portafolio, la cuenta publicitaria y la página de Meta. No publiques nada todavía.";
|
|
2700
2259
|
try {
|
|
2701
2260
|
const bridge = openAiBridge();
|
|
2702
2261
|
if (bridge && typeof bridge.sendFollowUpMessage === "function") {
|
|
2703
|
-
await bridge.sendFollowUpMessage({ prompt:
|
|
2704
|
-
|
|
2705
|
-
"Selección enviada al chat. Espera la confirmación antes de continuar.",
|
|
2706
|
-
"success",
|
|
2707
|
-
);
|
|
2262
|
+
await bridge.sendFollowUpMessage({ prompt: message });
|
|
2263
|
+
state.completionNotified = true;
|
|
2708
2264
|
return;
|
|
2709
2265
|
}
|
|
2710
2266
|
if (state.hostCanMessage) {
|
|
2711
2267
|
await rpcRequest("ui/message", {
|
|
2712
2268
|
role: "user",
|
|
2713
|
-
content: [{ type: "text", text:
|
|
2269
|
+
content: [{ type: "text", text: message }],
|
|
2714
2270
|
});
|
|
2715
|
-
|
|
2716
|
-
"Selección enviada al chat. Espera la confirmación antes de continuar.",
|
|
2717
|
-
"success",
|
|
2718
|
-
);
|
|
2271
|
+
state.completionNotified = true;
|
|
2719
2272
|
return;
|
|
2720
2273
|
}
|
|
2721
|
-
|
|
2274
|
+
setSessionStatus(
|
|
2275
|
+
"Carga completada. Escribe “listo” en el chat para continuar.",
|
|
2276
|
+
"success",
|
|
2277
|
+
);
|
|
2722
2278
|
} catch {
|
|
2723
|
-
|
|
2279
|
+
setSessionStatus(
|
|
2280
|
+
"Carga completada. Escribe “listo” en el chat para continuar.",
|
|
2281
|
+
"success",
|
|
2282
|
+
);
|
|
2724
2283
|
} finally {
|
|
2725
|
-
|
|
2726
|
-
}
|
|
2727
|
-
}
|
|
2728
|
-
|
|
2729
|
-
function showCopyableHandoff(handoff) {
|
|
2730
|
-
elements.handoffCopy.value = handoff;
|
|
2731
|
-
elements.handoffSection.hidden = false;
|
|
2732
|
-
setSessionStatus(
|
|
2733
|
-
"Copia el handoff exacto y pégalo en el chat.",
|
|
2734
|
-
"warning",
|
|
2735
|
-
);
|
|
2736
|
-
scheduleIntrinsicSizeReport(true);
|
|
2737
|
-
}
|
|
2738
|
-
|
|
2739
|
-
async function copyHandoff() {
|
|
2740
|
-
const handoff = elements.handoffCopy.value;
|
|
2741
|
-
if (!handoff) return;
|
|
2742
|
-
if (
|
|
2743
|
-
navigator.clipboard &&
|
|
2744
|
-
typeof navigator.clipboard.writeText === "function"
|
|
2745
|
-
) {
|
|
2746
|
-
try {
|
|
2747
|
-
await navigator.clipboard.writeText(handoff);
|
|
2748
|
-
elements.copyButton.textContent = "Copiado";
|
|
2749
|
-
return;
|
|
2750
|
-
} catch {
|
|
2751
|
-
// Selection fallback below.
|
|
2752
|
-
}
|
|
2284
|
+
state.completionNotificationInFlight = false;
|
|
2753
2285
|
}
|
|
2754
|
-
elements.handoffCopy.focus();
|
|
2755
|
-
elements.handoffCopy.select();
|
|
2756
|
-
elements.copyButton.textContent = "Seleccionado para copiar";
|
|
2757
2286
|
}
|
|
2758
2287
|
|
|
2759
2288
|
document.addEventListener("DOMContentLoaded", initialize, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@productmaker/mcp",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.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.",
|
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
"typescript": "^7.0.2",
|
|
66
66
|
"vitest": "^2.1.9",
|
|
67
67
|
"@packages/product-economics": "0.1.0",
|
|
68
|
-
"@packages/
|
|
69
|
-
"@packages/
|
|
68
|
+
"@packages/product-research": "0.1.0",
|
|
69
|
+
"@packages/types": "0.1.0"
|
|
70
70
|
},
|
|
71
71
|
"publishConfig": {
|
|
72
72
|
"access": "public"
|