@socialneuron/mcp-server 1.7.3 → 1.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +123 -17
- package/README.md +77 -19
- package/dist/http.js +1115 -169
- package/dist/index.js +1120 -170
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var MCP_VERSION;
|
|
|
14
14
|
var init_version = __esm({
|
|
15
15
|
"src/lib/version.ts"() {
|
|
16
16
|
"use strict";
|
|
17
|
-
MCP_VERSION = "1.7.
|
|
17
|
+
MCP_VERSION = "1.7.4";
|
|
18
18
|
}
|
|
19
19
|
});
|
|
20
20
|
|
|
@@ -895,6 +895,12 @@ var init_tool_catalog = __esm({
|
|
|
895
895
|
module: "content",
|
|
896
896
|
scope: "mcp:write"
|
|
897
897
|
},
|
|
898
|
+
{
|
|
899
|
+
name: "create_carousel",
|
|
900
|
+
description: "End-to-end carousel: generate text + kick off image jobs for each slide",
|
|
901
|
+
module: "carousel",
|
|
902
|
+
scope: "mcp:write"
|
|
903
|
+
},
|
|
898
904
|
// media
|
|
899
905
|
{
|
|
900
906
|
name: "upload_media",
|
|
@@ -1252,6 +1258,45 @@ var init_tool_catalog = __esm({
|
|
|
1252
1258
|
description: "Create a new autopilot configuration",
|
|
1253
1259
|
module: "autopilot",
|
|
1254
1260
|
scope: "mcp:autopilot"
|
|
1261
|
+
},
|
|
1262
|
+
// brand runtime (additions)
|
|
1263
|
+
{
|
|
1264
|
+
name: "audit_brand_colors",
|
|
1265
|
+
description: "Audit brand color palette for accessibility, contrast, and harmony",
|
|
1266
|
+
module: "brandRuntime",
|
|
1267
|
+
scope: "mcp:read"
|
|
1268
|
+
},
|
|
1269
|
+
{
|
|
1270
|
+
name: "export_design_tokens",
|
|
1271
|
+
description: "Export brand design tokens in CSS/Tailwind/JSON formats",
|
|
1272
|
+
module: "brandRuntime",
|
|
1273
|
+
scope: "mcp:read"
|
|
1274
|
+
},
|
|
1275
|
+
// carousel (already listed in content section above)
|
|
1276
|
+
// recipes
|
|
1277
|
+
{
|
|
1278
|
+
name: "list_recipes",
|
|
1279
|
+
description: "List available recipe templates for automated content workflows",
|
|
1280
|
+
module: "recipes",
|
|
1281
|
+
scope: "mcp:read"
|
|
1282
|
+
},
|
|
1283
|
+
{
|
|
1284
|
+
name: "get_recipe_details",
|
|
1285
|
+
description: "Get full details of a recipe template including steps and required inputs",
|
|
1286
|
+
module: "recipes",
|
|
1287
|
+
scope: "mcp:read"
|
|
1288
|
+
},
|
|
1289
|
+
{
|
|
1290
|
+
name: "execute_recipe",
|
|
1291
|
+
description: "Execute a recipe template with provided inputs to run a multi-step workflow",
|
|
1292
|
+
module: "recipes",
|
|
1293
|
+
scope: "mcp:write"
|
|
1294
|
+
},
|
|
1295
|
+
{
|
|
1296
|
+
name: "get_recipe_run_status",
|
|
1297
|
+
description: "Check the status and progress of a running recipe execution",
|
|
1298
|
+
module: "recipes",
|
|
1299
|
+
scope: "mcp:read"
|
|
1255
1300
|
}
|
|
1256
1301
|
];
|
|
1257
1302
|
}
|
|
@@ -3856,6 +3901,8 @@ var TOOL_SCOPES = {
|
|
|
3856
3901
|
get_brand_runtime: "mcp:read",
|
|
3857
3902
|
explain_brand_system: "mcp:read",
|
|
3858
3903
|
check_brand_consistency: "mcp:read",
|
|
3904
|
+
audit_brand_colors: "mcp:read",
|
|
3905
|
+
export_design_tokens: "mcp:read",
|
|
3859
3906
|
get_ideation_context: "mcp:read",
|
|
3860
3907
|
get_credit_balance: "mcp:read",
|
|
3861
3908
|
get_budget_status: "mcp:read",
|
|
@@ -3877,6 +3924,7 @@ var TOOL_SCOPES = {
|
|
|
3877
3924
|
create_storyboard: "mcp:write",
|
|
3878
3925
|
generate_voiceover: "mcp:write",
|
|
3879
3926
|
generate_carousel: "mcp:write",
|
|
3927
|
+
create_carousel: "mcp:write",
|
|
3880
3928
|
upload_media: "mcp:write",
|
|
3881
3929
|
// mcp:read (media)
|
|
3882
3930
|
get_media_url: "mcp:read",
|
|
@@ -3895,6 +3943,11 @@ var TOOL_SCOPES = {
|
|
|
3895
3943
|
list_autopilot_configs: "mcp:autopilot",
|
|
3896
3944
|
update_autopilot_config: "mcp:autopilot",
|
|
3897
3945
|
get_autopilot_status: "mcp:autopilot",
|
|
3946
|
+
// Recipes
|
|
3947
|
+
list_recipes: "mcp:read",
|
|
3948
|
+
get_recipe_details: "mcp:read",
|
|
3949
|
+
execute_recipe: "mcp:write",
|
|
3950
|
+
get_recipe_run_status: "mcp:read",
|
|
3898
3951
|
// mcp:read (content lifecycle — read-only tools)
|
|
3899
3952
|
extract_url_content: "mcp:read",
|
|
3900
3953
|
quality_check: "mcp:read",
|
|
@@ -10181,12 +10234,280 @@ Active: ${is_active}`
|
|
|
10181
10234
|
);
|
|
10182
10235
|
}
|
|
10183
10236
|
|
|
10184
|
-
// src/tools/
|
|
10237
|
+
// src/tools/recipes.ts
|
|
10185
10238
|
init_edge_function();
|
|
10239
|
+
init_version();
|
|
10186
10240
|
import { z as z17 } from "zod";
|
|
10241
|
+
function asEnvelope13(data) {
|
|
10242
|
+
return {
|
|
10243
|
+
_meta: {
|
|
10244
|
+
version: MCP_VERSION,
|
|
10245
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
10246
|
+
},
|
|
10247
|
+
data
|
|
10248
|
+
};
|
|
10249
|
+
}
|
|
10250
|
+
function registerRecipeTools(server2) {
|
|
10251
|
+
server2.tool(
|
|
10252
|
+
"list_recipes",
|
|
10253
|
+
'List available recipe templates. Recipes are pre-built multi-step workflows like "Weekly Instagram Calendar" or "Product Launch Sequence" that automate common content operations. Use this to discover what recipes are available before running one.',
|
|
10254
|
+
{
|
|
10255
|
+
category: z17.enum([
|
|
10256
|
+
"content_creation",
|
|
10257
|
+
"distribution",
|
|
10258
|
+
"repurposing",
|
|
10259
|
+
"analytics",
|
|
10260
|
+
"engagement",
|
|
10261
|
+
"general"
|
|
10262
|
+
]).optional().describe("Filter by category. Omit to list all."),
|
|
10263
|
+
featured_only: z17.boolean().optional().describe("If true, only return featured recipes. Defaults to false."),
|
|
10264
|
+
response_format: z17.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
10265
|
+
},
|
|
10266
|
+
async ({ category, featured_only, response_format }) => {
|
|
10267
|
+
const format = response_format ?? "text";
|
|
10268
|
+
const { data: result, error: efError } = await callEdgeFunction("mcp-data", {
|
|
10269
|
+
action: "list-recipes",
|
|
10270
|
+
category: category ?? null,
|
|
10271
|
+
featured_only: featured_only ?? false
|
|
10272
|
+
});
|
|
10273
|
+
if (efError) {
|
|
10274
|
+
return {
|
|
10275
|
+
content: [
|
|
10276
|
+
{
|
|
10277
|
+
type: "text",
|
|
10278
|
+
text: `Error fetching recipes: ${efError}`
|
|
10279
|
+
}
|
|
10280
|
+
],
|
|
10281
|
+
isError: true
|
|
10282
|
+
};
|
|
10283
|
+
}
|
|
10284
|
+
const recipes = result?.recipes ?? [];
|
|
10285
|
+
if (format === "json") {
|
|
10286
|
+
return {
|
|
10287
|
+
content: [
|
|
10288
|
+
{
|
|
10289
|
+
type: "text",
|
|
10290
|
+
text: JSON.stringify(asEnvelope13(recipes))
|
|
10291
|
+
}
|
|
10292
|
+
]
|
|
10293
|
+
};
|
|
10294
|
+
}
|
|
10295
|
+
if (recipes.length === 0) {
|
|
10296
|
+
return {
|
|
10297
|
+
content: [
|
|
10298
|
+
{
|
|
10299
|
+
type: "text",
|
|
10300
|
+
text: "No recipes found. Recipes are pre-built automation templates \u2014 check back after setup."
|
|
10301
|
+
}
|
|
10302
|
+
]
|
|
10303
|
+
};
|
|
10304
|
+
}
|
|
10305
|
+
const lines = recipes.map(
|
|
10306
|
+
(r) => `**${r.name}** (${r.slug})
|
|
10307
|
+
${r.description}
|
|
10308
|
+
Category: ${r.category} | Credits: ~${r.estimated_credits} | Steps: ${r.steps.length}${r.is_featured ? " | \u2B50 Featured" : ""}
|
|
10309
|
+
Inputs: ${r.inputs_schema.map((i) => `${i.label}${i.required ? "*" : ""}`).join(", ")}`
|
|
10310
|
+
);
|
|
10311
|
+
return {
|
|
10312
|
+
content: [
|
|
10313
|
+
{
|
|
10314
|
+
type: "text",
|
|
10315
|
+
text: `## Available Recipes (${recipes.length})
|
|
10316
|
+
|
|
10317
|
+
${lines.join("\n\n")}`
|
|
10318
|
+
}
|
|
10319
|
+
]
|
|
10320
|
+
};
|
|
10321
|
+
}
|
|
10322
|
+
);
|
|
10323
|
+
server2.tool(
|
|
10324
|
+
"get_recipe_details",
|
|
10325
|
+
"Get full details of a recipe template including all steps, input schema, and estimated costs. Use this before execute_recipe to understand what inputs are required.",
|
|
10326
|
+
{
|
|
10327
|
+
slug: z17.string().describe('Recipe slug (e.g., "weekly-instagram-calendar")'),
|
|
10328
|
+
response_format: z17.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
10329
|
+
},
|
|
10330
|
+
async ({ slug, response_format }) => {
|
|
10331
|
+
const format = response_format ?? "text";
|
|
10332
|
+
const { data: result, error: efError } = await callEdgeFunction("mcp-data", {
|
|
10333
|
+
action: "get-recipe-details",
|
|
10334
|
+
slug
|
|
10335
|
+
});
|
|
10336
|
+
if (efError) {
|
|
10337
|
+
return {
|
|
10338
|
+
content: [{ type: "text", text: `Error: ${efError}` }],
|
|
10339
|
+
isError: true
|
|
10340
|
+
};
|
|
10341
|
+
}
|
|
10342
|
+
const recipe = result?.recipe;
|
|
10343
|
+
if (!recipe) {
|
|
10344
|
+
return {
|
|
10345
|
+
content: [
|
|
10346
|
+
{
|
|
10347
|
+
type: "text",
|
|
10348
|
+
text: `Recipe "${slug}" not found. Use list_recipes to see available recipes.`
|
|
10349
|
+
}
|
|
10350
|
+
],
|
|
10351
|
+
isError: true
|
|
10352
|
+
};
|
|
10353
|
+
}
|
|
10354
|
+
if (format === "json") {
|
|
10355
|
+
return {
|
|
10356
|
+
content: [
|
|
10357
|
+
{
|
|
10358
|
+
type: "text",
|
|
10359
|
+
text: JSON.stringify(asEnvelope13(recipe))
|
|
10360
|
+
}
|
|
10361
|
+
]
|
|
10362
|
+
};
|
|
10363
|
+
}
|
|
10364
|
+
const stepsText = recipe.steps.map((s, i) => ` ${i + 1}. **${s.name}** (${s.type})`).join("\n");
|
|
10365
|
+
const inputsText = recipe.inputs_schema.map(
|
|
10366
|
+
(i) => ` - **${i.label}**${i.required ? " (required)" : ""}: ${i.type}${i.placeholder ? ` \u2014 e.g., "${i.placeholder}"` : ""}`
|
|
10367
|
+
).join("\n");
|
|
10368
|
+
return {
|
|
10369
|
+
content: [
|
|
10370
|
+
{
|
|
10371
|
+
type: "text",
|
|
10372
|
+
text: [
|
|
10373
|
+
`## ${recipe.name}`,
|
|
10374
|
+
recipe.description,
|
|
10375
|
+
"",
|
|
10376
|
+
`**Category:** ${recipe.category}`,
|
|
10377
|
+
`**Estimated credits:** ~${recipe.estimated_credits}`,
|
|
10378
|
+
`**Estimated time:** ~${Math.round(recipe.estimated_duration_seconds / 60)} minutes`,
|
|
10379
|
+
"",
|
|
10380
|
+
"### Steps",
|
|
10381
|
+
stepsText,
|
|
10382
|
+
"",
|
|
10383
|
+
"### Required Inputs",
|
|
10384
|
+
inputsText
|
|
10385
|
+
].join("\n")
|
|
10386
|
+
}
|
|
10387
|
+
]
|
|
10388
|
+
};
|
|
10389
|
+
}
|
|
10390
|
+
);
|
|
10391
|
+
server2.tool(
|
|
10392
|
+
"execute_recipe",
|
|
10393
|
+
"Execute a recipe template with the provided inputs. This creates a recipe run that processes each step sequentially. Long-running recipes will return a run_id you can check with get_recipe_run_status.",
|
|
10394
|
+
{
|
|
10395
|
+
slug: z17.string().describe('Recipe slug (e.g., "weekly-instagram-calendar")'),
|
|
10396
|
+
inputs: z17.record(z17.unknown()).describe(
|
|
10397
|
+
"Input values matching the recipe input schema. Use get_recipe_details to see required inputs."
|
|
10398
|
+
),
|
|
10399
|
+
response_format: z17.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
10400
|
+
},
|
|
10401
|
+
async ({ slug, inputs, response_format }) => {
|
|
10402
|
+
const format = response_format ?? "text";
|
|
10403
|
+
const { data: result, error: efError } = await callEdgeFunction("mcp-data", {
|
|
10404
|
+
action: "execute-recipe",
|
|
10405
|
+
slug,
|
|
10406
|
+
inputs
|
|
10407
|
+
});
|
|
10408
|
+
if (efError) {
|
|
10409
|
+
return {
|
|
10410
|
+
content: [{ type: "text", text: `Error: ${efError}` }],
|
|
10411
|
+
isError: true
|
|
10412
|
+
};
|
|
10413
|
+
}
|
|
10414
|
+
if (format === "json") {
|
|
10415
|
+
return {
|
|
10416
|
+
content: [
|
|
10417
|
+
{
|
|
10418
|
+
type: "text",
|
|
10419
|
+
text: JSON.stringify(asEnvelope13(result))
|
|
10420
|
+
}
|
|
10421
|
+
]
|
|
10422
|
+
};
|
|
10423
|
+
}
|
|
10424
|
+
return {
|
|
10425
|
+
content: [
|
|
10426
|
+
{
|
|
10427
|
+
type: "text",
|
|
10428
|
+
text: `Recipe "${slug}" started.
|
|
10429
|
+
|
|
10430
|
+
**Run ID:** ${result?.run_id}
|
|
10431
|
+
**Status:** ${result?.status}
|
|
10432
|
+
|
|
10433
|
+
${result?.message || "Use get_recipe_run_status to check progress."}`
|
|
10434
|
+
}
|
|
10435
|
+
]
|
|
10436
|
+
};
|
|
10437
|
+
}
|
|
10438
|
+
);
|
|
10439
|
+
server2.tool(
|
|
10440
|
+
"get_recipe_run_status",
|
|
10441
|
+
"Check the status of a running recipe execution. Shows progress, current step, credits used, and outputs when complete.",
|
|
10442
|
+
{
|
|
10443
|
+
run_id: z17.string().describe("The recipe run ID returned by execute_recipe"),
|
|
10444
|
+
response_format: z17.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
10445
|
+
},
|
|
10446
|
+
async ({ run_id, response_format }) => {
|
|
10447
|
+
const format = response_format ?? "text";
|
|
10448
|
+
const { data: result, error: efError } = await callEdgeFunction("mcp-data", {
|
|
10449
|
+
action: "get-recipe-run-status",
|
|
10450
|
+
run_id
|
|
10451
|
+
});
|
|
10452
|
+
if (efError) {
|
|
10453
|
+
return {
|
|
10454
|
+
content: [{ type: "text", text: `Error: ${efError}` }],
|
|
10455
|
+
isError: true
|
|
10456
|
+
};
|
|
10457
|
+
}
|
|
10458
|
+
const run = result?.run;
|
|
10459
|
+
if (!run) {
|
|
10460
|
+
return {
|
|
10461
|
+
content: [
|
|
10462
|
+
{
|
|
10463
|
+
type: "text",
|
|
10464
|
+
text: `Run "${run_id}" not found.`
|
|
10465
|
+
}
|
|
10466
|
+
],
|
|
10467
|
+
isError: true
|
|
10468
|
+
};
|
|
10469
|
+
}
|
|
10470
|
+
if (format === "json") {
|
|
10471
|
+
return {
|
|
10472
|
+
content: [
|
|
10473
|
+
{
|
|
10474
|
+
type: "text",
|
|
10475
|
+
text: JSON.stringify(asEnvelope13(run))
|
|
10476
|
+
}
|
|
10477
|
+
]
|
|
10478
|
+
};
|
|
10479
|
+
}
|
|
10480
|
+
const statusEmoji = run.status === "completed" ? "Done" : run.status === "failed" ? "Failed" : run.status === "running" ? "Running" : run.status;
|
|
10481
|
+
return {
|
|
10482
|
+
content: [
|
|
10483
|
+
{
|
|
10484
|
+
type: "text",
|
|
10485
|
+
text: [
|
|
10486
|
+
`**Recipe Run:** ${run.id}`,
|
|
10487
|
+
`**Status:** ${statusEmoji}`,
|
|
10488
|
+
`**Progress:** ${run.progress}%`,
|
|
10489
|
+
run.current_step ? `**Current step:** ${run.current_step}` : "",
|
|
10490
|
+
`**Credits used:** ${run.credits_used}`,
|
|
10491
|
+
run.completed_at ? `**Completed:** ${run.completed_at}` : "",
|
|
10492
|
+
run.outputs ? `
|
|
10493
|
+
**Outputs:**
|
|
10494
|
+
\`\`\`json
|
|
10495
|
+
${JSON.stringify(run.outputs, null, 2)}
|
|
10496
|
+
\`\`\`` : ""
|
|
10497
|
+
].filter(Boolean).join("\n")
|
|
10498
|
+
}
|
|
10499
|
+
]
|
|
10500
|
+
};
|
|
10501
|
+
}
|
|
10502
|
+
);
|
|
10503
|
+
}
|
|
10504
|
+
|
|
10505
|
+
// src/tools/extraction.ts
|
|
10506
|
+
init_edge_function();
|
|
10507
|
+
import { z as z18 } from "zod";
|
|
10187
10508
|
init_supabase();
|
|
10188
10509
|
init_version();
|
|
10189
|
-
function
|
|
10510
|
+
function asEnvelope14(data) {
|
|
10190
10511
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
10191
10512
|
}
|
|
10192
10513
|
function isYouTubeUrl(url) {
|
|
@@ -10240,11 +10561,11 @@ function registerExtractionTools(server2) {
|
|
|
10240
10561
|
"extract_url_content",
|
|
10241
10562
|
"Extract text content from any URL \u2014 YouTube video transcripts, article text, or product page features/benefits/USP. YouTube URLs auto-route to transcript extraction with optional comments. Use before generate_content to repurpose existing content, or before plan_content_week to base a content plan on a source URL.",
|
|
10242
10563
|
{
|
|
10243
|
-
url:
|
|
10244
|
-
extract_type:
|
|
10245
|
-
include_comments:
|
|
10246
|
-
max_results:
|
|
10247
|
-
response_format:
|
|
10564
|
+
url: z18.string().url().describe("URL to extract content from"),
|
|
10565
|
+
extract_type: z18.enum(["auto", "transcript", "article", "product"]).default("auto").describe("Type of extraction"),
|
|
10566
|
+
include_comments: z18.boolean().default(false).describe("Include top comments (YouTube only)"),
|
|
10567
|
+
max_results: z18.number().min(1).max(100).default(10).describe("Max comments to include"),
|
|
10568
|
+
response_format: z18.enum(["text", "json"]).default("text")
|
|
10248
10569
|
},
|
|
10249
10570
|
async ({ url, extract_type, include_comments, max_results, response_format }) => {
|
|
10250
10571
|
const startedAt = Date.now();
|
|
@@ -10379,7 +10700,7 @@ function registerExtractionTools(server2) {
|
|
|
10379
10700
|
if (response_format === "json") {
|
|
10380
10701
|
return {
|
|
10381
10702
|
content: [
|
|
10382
|
-
{ type: "text", text: JSON.stringify(
|
|
10703
|
+
{ type: "text", text: JSON.stringify(asEnvelope14(extracted), null, 2) }
|
|
10383
10704
|
],
|
|
10384
10705
|
isError: false
|
|
10385
10706
|
};
|
|
@@ -10410,8 +10731,8 @@ function registerExtractionTools(server2) {
|
|
|
10410
10731
|
init_quality();
|
|
10411
10732
|
init_supabase();
|
|
10412
10733
|
init_version();
|
|
10413
|
-
import { z as
|
|
10414
|
-
function
|
|
10734
|
+
import { z as z19 } from "zod";
|
|
10735
|
+
function asEnvelope15(data) {
|
|
10415
10736
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
10416
10737
|
}
|
|
10417
10738
|
function registerQualityTools(server2) {
|
|
@@ -10419,12 +10740,12 @@ function registerQualityTools(server2) {
|
|
|
10419
10740
|
"quality_check",
|
|
10420
10741
|
"Score post quality across 7 categories: Hook Strength, Message Clarity, Platform Fit, Brand Alignment, Novelty, CTA Strength, and Safety/Claims. Each scored 0-5, total 35. Default pass threshold is 26 (~75%). Run after generate_content and before schedule_post. Include hashtags in caption if they will be published \u2014 they affect Platform Fit and Safety scores.",
|
|
10421
10742
|
{
|
|
10422
|
-
caption:
|
|
10743
|
+
caption: z19.string().describe(
|
|
10423
10744
|
"The post text to score. Include hashtags if they will be published \u2014 they affect Platform Fit and Safety/Claims scores."
|
|
10424
10745
|
),
|
|
10425
|
-
title:
|
|
10426
|
-
platforms:
|
|
10427
|
-
|
|
10746
|
+
title: z19.string().optional().describe("Post title (important for YouTube)"),
|
|
10747
|
+
platforms: z19.array(
|
|
10748
|
+
z19.enum([
|
|
10428
10749
|
"youtube",
|
|
10429
10750
|
"tiktok",
|
|
10430
10751
|
"instagram",
|
|
@@ -10435,13 +10756,13 @@ function registerQualityTools(server2) {
|
|
|
10435
10756
|
"bluesky"
|
|
10436
10757
|
])
|
|
10437
10758
|
).min(1).describe("Target platforms"),
|
|
10438
|
-
threshold:
|
|
10759
|
+
threshold: z19.number().min(0).max(35).default(26).describe(
|
|
10439
10760
|
"Minimum total score to pass (max 35, scored across 7 categories at 0-5 each). Default 26 (~75%). Use 20 for rough drafts, 28+ for final posts going to large audiences."
|
|
10440
10761
|
),
|
|
10441
|
-
brand_keyword:
|
|
10442
|
-
brand_avoid_patterns:
|
|
10443
|
-
custom_banned_terms:
|
|
10444
|
-
response_format:
|
|
10762
|
+
brand_keyword: z19.string().optional().describe("Brand keyword for alignment check"),
|
|
10763
|
+
brand_avoid_patterns: z19.array(z19.string()).optional(),
|
|
10764
|
+
custom_banned_terms: z19.array(z19.string()).optional(),
|
|
10765
|
+
response_format: z19.enum(["text", "json"]).default("text")
|
|
10445
10766
|
},
|
|
10446
10767
|
async ({
|
|
10447
10768
|
caption,
|
|
@@ -10472,7 +10793,7 @@ function registerQualityTools(server2) {
|
|
|
10472
10793
|
});
|
|
10473
10794
|
if (response_format === "json") {
|
|
10474
10795
|
return {
|
|
10475
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
10796
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope15(result), null, 2) }],
|
|
10476
10797
|
isError: false
|
|
10477
10798
|
};
|
|
10478
10799
|
}
|
|
@@ -10500,20 +10821,20 @@ function registerQualityTools(server2) {
|
|
|
10500
10821
|
"quality_check_plan",
|
|
10501
10822
|
"Batch quality check all posts in a content plan. Returns per-post scores and aggregate pass/fail summary. Use after plan_content_week and before schedule_content_plan to catch low-quality posts before publishing.",
|
|
10502
10823
|
{
|
|
10503
|
-
plan:
|
|
10504
|
-
posts:
|
|
10505
|
-
|
|
10506
|
-
id:
|
|
10507
|
-
caption:
|
|
10508
|
-
title:
|
|
10509
|
-
platform:
|
|
10824
|
+
plan: z19.object({
|
|
10825
|
+
posts: z19.array(
|
|
10826
|
+
z19.object({
|
|
10827
|
+
id: z19.string(),
|
|
10828
|
+
caption: z19.string(),
|
|
10829
|
+
title: z19.string().optional(),
|
|
10830
|
+
platform: z19.string()
|
|
10510
10831
|
})
|
|
10511
10832
|
)
|
|
10512
10833
|
}).passthrough().describe("Content plan with posts array"),
|
|
10513
|
-
threshold:
|
|
10834
|
+
threshold: z19.number().min(0).max(35).default(26).describe(
|
|
10514
10835
|
"Minimum total score to pass (max 35, scored across 7 categories at 0-5 each). Default 26 (~75%). Use 20 for rough drafts, 28+ for final posts going to large audiences."
|
|
10515
10836
|
),
|
|
10516
|
-
response_format:
|
|
10837
|
+
response_format: z19.enum(["text", "json"]).default("text")
|
|
10517
10838
|
},
|
|
10518
10839
|
async ({ plan, threshold, response_format }) => {
|
|
10519
10840
|
const startedAt = Date.now();
|
|
@@ -10555,7 +10876,7 @@ function registerQualityTools(server2) {
|
|
|
10555
10876
|
content: [
|
|
10556
10877
|
{
|
|
10557
10878
|
type: "text",
|
|
10558
|
-
text: JSON.stringify(
|
|
10879
|
+
text: JSON.stringify(asEnvelope15({ posts: postsWithQuality, summary }), null, 2)
|
|
10559
10880
|
}
|
|
10560
10881
|
],
|
|
10561
10882
|
isError: false
|
|
@@ -10580,7 +10901,7 @@ function registerQualityTools(server2) {
|
|
|
10580
10901
|
|
|
10581
10902
|
// src/tools/planning.ts
|
|
10582
10903
|
init_edge_function();
|
|
10583
|
-
import { z as
|
|
10904
|
+
import { z as z20 } from "zod";
|
|
10584
10905
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
10585
10906
|
init_supabase();
|
|
10586
10907
|
init_version();
|
|
@@ -10609,7 +10930,7 @@ function extractJsonArray(text) {
|
|
|
10609
10930
|
function toRecord(value) {
|
|
10610
10931
|
return value && typeof value === "object" ? value : void 0;
|
|
10611
10932
|
}
|
|
10612
|
-
function
|
|
10933
|
+
function asEnvelope16(data) {
|
|
10613
10934
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
10614
10935
|
}
|
|
10615
10936
|
function tomorrowIsoDate() {
|
|
@@ -10679,10 +11000,10 @@ function registerPlanningTools(server2) {
|
|
|
10679
11000
|
"plan_content_week",
|
|
10680
11001
|
"Generate a full content plan with platform-specific drafts, hooks, angles, and optimal schedule times. Pass a topic or source_url \u2014 brand context and performance insights auto-load via project_id. Output feeds directly into quality_check_plan then schedule_content_plan. Costs ~5-15 credits depending on post count.",
|
|
10681
11002
|
{
|
|
10682
|
-
topic:
|
|
10683
|
-
source_url:
|
|
10684
|
-
platforms:
|
|
10685
|
-
|
|
11003
|
+
topic: z20.string().describe("Main topic or content theme"),
|
|
11004
|
+
source_url: z20.string().optional().describe("URL to extract content from (YouTube, article)"),
|
|
11005
|
+
platforms: z20.array(
|
|
11006
|
+
z20.enum([
|
|
10686
11007
|
"youtube",
|
|
10687
11008
|
"tiktok",
|
|
10688
11009
|
"instagram",
|
|
@@ -10693,12 +11014,12 @@ function registerPlanningTools(server2) {
|
|
|
10693
11014
|
"bluesky"
|
|
10694
11015
|
])
|
|
10695
11016
|
).min(1).describe("Target platforms"),
|
|
10696
|
-
posts_per_day:
|
|
10697
|
-
days:
|
|
10698
|
-
start_date:
|
|
10699
|
-
brand_voice:
|
|
10700
|
-
project_id:
|
|
10701
|
-
response_format:
|
|
11017
|
+
posts_per_day: z20.number().min(1).max(5).default(1).describe("Posts per platform per day"),
|
|
11018
|
+
days: z20.number().min(1).max(7).default(5).describe("Number of days to plan"),
|
|
11019
|
+
start_date: z20.string().optional().describe("ISO date, defaults to tomorrow"),
|
|
11020
|
+
brand_voice: z20.string().optional().describe("Override brand voice description"),
|
|
11021
|
+
project_id: z20.string().optional().describe("Project ID for brand/insights context"),
|
|
11022
|
+
response_format: z20.enum(["text", "json"]).default("json")
|
|
10702
11023
|
},
|
|
10703
11024
|
async ({
|
|
10704
11025
|
topic,
|
|
@@ -10972,7 +11293,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
10972
11293
|
});
|
|
10973
11294
|
if (response_format === "json") {
|
|
10974
11295
|
return {
|
|
10975
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
11296
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope16(plan), null, 2) }],
|
|
10976
11297
|
isError: false
|
|
10977
11298
|
};
|
|
10978
11299
|
}
|
|
@@ -11000,13 +11321,13 @@ ${rawText.slice(0, 1e3)}`
|
|
|
11000
11321
|
"save_content_plan",
|
|
11001
11322
|
"Save a content plan to the database for team review, approval workflows, and scheduled publishing. Creates a plan_id you can reference in get_content_plan, update_content_plan, and schedule_content_plan.",
|
|
11002
11323
|
{
|
|
11003
|
-
plan:
|
|
11004
|
-
topic:
|
|
11005
|
-
posts:
|
|
11324
|
+
plan: z20.object({
|
|
11325
|
+
topic: z20.string(),
|
|
11326
|
+
posts: z20.array(z20.record(z20.string(), z20.unknown()))
|
|
11006
11327
|
}).passthrough(),
|
|
11007
|
-
project_id:
|
|
11008
|
-
status:
|
|
11009
|
-
response_format:
|
|
11328
|
+
project_id: z20.string().uuid().optional(),
|
|
11329
|
+
status: z20.enum(["draft", "in_review", "approved", "scheduled", "completed"]).default("draft"),
|
|
11330
|
+
response_format: z20.enum(["text", "json"]).default("json")
|
|
11010
11331
|
},
|
|
11011
11332
|
async ({ plan, project_id, status, response_format }) => {
|
|
11012
11333
|
const startedAt = Date.now();
|
|
@@ -11062,7 +11383,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
11062
11383
|
};
|
|
11063
11384
|
if (response_format === "json") {
|
|
11064
11385
|
return {
|
|
11065
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
11386
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope16(result), null, 2) }],
|
|
11066
11387
|
isError: false
|
|
11067
11388
|
};
|
|
11068
11389
|
}
|
|
@@ -11092,8 +11413,8 @@ ${rawText.slice(0, 1e3)}`
|
|
|
11092
11413
|
"get_content_plan",
|
|
11093
11414
|
"Retrieve a persisted content plan by ID.",
|
|
11094
11415
|
{
|
|
11095
|
-
plan_id:
|
|
11096
|
-
response_format:
|
|
11416
|
+
plan_id: z20.string().uuid().describe("Persisted content plan ID"),
|
|
11417
|
+
response_format: z20.enum(["text", "json"]).default("json")
|
|
11097
11418
|
},
|
|
11098
11419
|
async ({ plan_id, response_format }) => {
|
|
11099
11420
|
const { data: result, error } = await callEdgeFunction("mcp-data", { action: "get-content-plan", plan_id }, { timeoutMs: 1e4 });
|
|
@@ -11128,7 +11449,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
11128
11449
|
};
|
|
11129
11450
|
if (response_format === "json") {
|
|
11130
11451
|
return {
|
|
11131
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
11452
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope16(payload), null, 2) }],
|
|
11132
11453
|
isError: false
|
|
11133
11454
|
};
|
|
11134
11455
|
}
|
|
@@ -11146,23 +11467,23 @@ ${rawText.slice(0, 1e3)}`
|
|
|
11146
11467
|
"update_content_plan",
|
|
11147
11468
|
"Update individual posts in a persisted content plan.",
|
|
11148
11469
|
{
|
|
11149
|
-
plan_id:
|
|
11150
|
-
post_updates:
|
|
11151
|
-
|
|
11152
|
-
post_id:
|
|
11153
|
-
caption:
|
|
11154
|
-
title:
|
|
11155
|
-
hashtags:
|
|
11156
|
-
hook:
|
|
11157
|
-
angle:
|
|
11158
|
-
visual_direction:
|
|
11159
|
-
media_url:
|
|
11160
|
-
schedule_at:
|
|
11161
|
-
platform:
|
|
11162
|
-
status:
|
|
11470
|
+
plan_id: z20.string().uuid(),
|
|
11471
|
+
post_updates: z20.array(
|
|
11472
|
+
z20.object({
|
|
11473
|
+
post_id: z20.string(),
|
|
11474
|
+
caption: z20.string().optional(),
|
|
11475
|
+
title: z20.string().optional(),
|
|
11476
|
+
hashtags: z20.array(z20.string()).optional(),
|
|
11477
|
+
hook: z20.string().optional(),
|
|
11478
|
+
angle: z20.string().optional(),
|
|
11479
|
+
visual_direction: z20.string().optional(),
|
|
11480
|
+
media_url: z20.string().optional(),
|
|
11481
|
+
schedule_at: z20.string().optional(),
|
|
11482
|
+
platform: z20.string().optional(),
|
|
11483
|
+
status: z20.enum(["approved", "rejected", "needs_edit"]).optional()
|
|
11163
11484
|
})
|
|
11164
11485
|
).min(1),
|
|
11165
|
-
response_format:
|
|
11486
|
+
response_format: z20.enum(["text", "json"]).default("json")
|
|
11166
11487
|
},
|
|
11167
11488
|
async ({ plan_id, post_updates, response_format }) => {
|
|
11168
11489
|
const { data: result, error } = await callEdgeFunction(
|
|
@@ -11200,7 +11521,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
11200
11521
|
};
|
|
11201
11522
|
if (response_format === "json") {
|
|
11202
11523
|
return {
|
|
11203
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
11524
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope16(payload), null, 2) }],
|
|
11204
11525
|
isError: false
|
|
11205
11526
|
};
|
|
11206
11527
|
}
|
|
@@ -11219,8 +11540,8 @@ ${rawText.slice(0, 1e3)}`
|
|
|
11219
11540
|
"submit_content_plan_for_approval",
|
|
11220
11541
|
"Create pending approval items for each post in a plan and mark plan status as in_review.",
|
|
11221
11542
|
{
|
|
11222
|
-
plan_id:
|
|
11223
|
-
response_format:
|
|
11543
|
+
plan_id: z20.string().uuid(),
|
|
11544
|
+
response_format: z20.enum(["text", "json"]).default("json")
|
|
11224
11545
|
},
|
|
11225
11546
|
async ({ plan_id, response_format }) => {
|
|
11226
11547
|
const { data: result, error } = await callEdgeFunction("mcp-data", { action: "submit-plan-approval", plan_id }, { timeoutMs: 15e3 });
|
|
@@ -11253,7 +11574,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
11253
11574
|
};
|
|
11254
11575
|
if (response_format === "json") {
|
|
11255
11576
|
return {
|
|
11256
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
11577
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope16(payload), null, 2) }],
|
|
11257
11578
|
isError: false
|
|
11258
11579
|
};
|
|
11259
11580
|
}
|
|
@@ -11274,8 +11595,8 @@ ${rawText.slice(0, 1e3)}`
|
|
|
11274
11595
|
init_edge_function();
|
|
11275
11596
|
init_supabase();
|
|
11276
11597
|
init_version();
|
|
11277
|
-
import { z as
|
|
11278
|
-
function
|
|
11598
|
+
import { z as z21 } from "zod";
|
|
11599
|
+
function asEnvelope17(data) {
|
|
11279
11600
|
return {
|
|
11280
11601
|
_meta: {
|
|
11281
11602
|
version: MCP_VERSION,
|
|
@@ -11289,19 +11610,19 @@ function registerPlanApprovalTools(server2) {
|
|
|
11289
11610
|
"create_plan_approvals",
|
|
11290
11611
|
"Create pending approval rows for each post in a content plan.",
|
|
11291
11612
|
{
|
|
11292
|
-
plan_id:
|
|
11293
|
-
posts:
|
|
11294
|
-
|
|
11295
|
-
id:
|
|
11296
|
-
platform:
|
|
11297
|
-
caption:
|
|
11298
|
-
title:
|
|
11299
|
-
media_url:
|
|
11300
|
-
schedule_at:
|
|
11613
|
+
plan_id: z21.string().uuid().describe("Content plan ID"),
|
|
11614
|
+
posts: z21.array(
|
|
11615
|
+
z21.object({
|
|
11616
|
+
id: z21.string(),
|
|
11617
|
+
platform: z21.string().optional(),
|
|
11618
|
+
caption: z21.string().optional(),
|
|
11619
|
+
title: z21.string().optional(),
|
|
11620
|
+
media_url: z21.string().optional(),
|
|
11621
|
+
schedule_at: z21.string().optional()
|
|
11301
11622
|
}).passthrough()
|
|
11302
11623
|
).min(1).describe("Posts to create approval entries for."),
|
|
11303
|
-
project_id:
|
|
11304
|
-
response_format:
|
|
11624
|
+
project_id: z21.string().uuid().optional().describe("Project ID. Defaults to active project context."),
|
|
11625
|
+
response_format: z21.enum(["text", "json"]).optional()
|
|
11305
11626
|
},
|
|
11306
11627
|
async ({ plan_id, posts, project_id, response_format }) => {
|
|
11307
11628
|
const projectId = project_id || await getDefaultProjectId();
|
|
@@ -11350,7 +11671,7 @@ function registerPlanApprovalTools(server2) {
|
|
|
11350
11671
|
};
|
|
11351
11672
|
if ((response_format || "text") === "json") {
|
|
11352
11673
|
return {
|
|
11353
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
11674
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope17(payload), null, 2) }],
|
|
11354
11675
|
isError: false
|
|
11355
11676
|
};
|
|
11356
11677
|
}
|
|
@@ -11369,9 +11690,9 @@ function registerPlanApprovalTools(server2) {
|
|
|
11369
11690
|
"list_plan_approvals",
|
|
11370
11691
|
"List MCP-native approval items for a specific content plan.",
|
|
11371
11692
|
{
|
|
11372
|
-
plan_id:
|
|
11373
|
-
status:
|
|
11374
|
-
response_format:
|
|
11693
|
+
plan_id: z21.string().uuid().describe("Content plan ID"),
|
|
11694
|
+
status: z21.enum(["pending", "approved", "rejected", "edited"]).optional(),
|
|
11695
|
+
response_format: z21.enum(["text", "json"]).optional()
|
|
11375
11696
|
},
|
|
11376
11697
|
async ({ plan_id, status, response_format }) => {
|
|
11377
11698
|
const { data: result, error } = await callEdgeFunction(
|
|
@@ -11402,7 +11723,7 @@ function registerPlanApprovalTools(server2) {
|
|
|
11402
11723
|
};
|
|
11403
11724
|
if ((response_format || "text") === "json") {
|
|
11404
11725
|
return {
|
|
11405
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
11726
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope17(payload), null, 2) }],
|
|
11406
11727
|
isError: false
|
|
11407
11728
|
};
|
|
11408
11729
|
}
|
|
@@ -11429,11 +11750,11 @@ function registerPlanApprovalTools(server2) {
|
|
|
11429
11750
|
"respond_plan_approval",
|
|
11430
11751
|
"Approve, reject, or edit a pending plan approval item.",
|
|
11431
11752
|
{
|
|
11432
|
-
approval_id:
|
|
11433
|
-
decision:
|
|
11434
|
-
edited_post:
|
|
11435
|
-
reason:
|
|
11436
|
-
response_format:
|
|
11753
|
+
approval_id: z21.string().uuid().describe("Approval item ID"),
|
|
11754
|
+
decision: z21.enum(["approved", "rejected", "edited"]),
|
|
11755
|
+
edited_post: z21.record(z21.string(), z21.unknown()).optional(),
|
|
11756
|
+
reason: z21.string().max(1e3).optional(),
|
|
11757
|
+
response_format: z21.enum(["text", "json"]).optional()
|
|
11437
11758
|
},
|
|
11438
11759
|
async ({ approval_id, decision, edited_post, reason, response_format }) => {
|
|
11439
11760
|
if (decision === "edited" && !edited_post) {
|
|
@@ -11480,7 +11801,7 @@ function registerPlanApprovalTools(server2) {
|
|
|
11480
11801
|
}
|
|
11481
11802
|
if ((response_format || "text") === "json") {
|
|
11482
11803
|
return {
|
|
11483
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
11804
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope17(data), null, 2) }],
|
|
11484
11805
|
isError: false
|
|
11485
11806
|
};
|
|
11486
11807
|
}
|
|
@@ -11499,16 +11820,16 @@ function registerPlanApprovalTools(server2) {
|
|
|
11499
11820
|
|
|
11500
11821
|
// src/tools/discovery.ts
|
|
11501
11822
|
init_tool_catalog();
|
|
11502
|
-
import { z as
|
|
11823
|
+
import { z as z22 } from "zod";
|
|
11503
11824
|
function registerDiscoveryTools(server2) {
|
|
11504
11825
|
server2.tool(
|
|
11505
11826
|
"search_tools",
|
|
11506
11827
|
'Search available tools by name, description, module, or scope. Use "name" detail (~50 tokens) for quick lookup, "summary" (~500 tokens) for descriptions, "full" for complete input schemas. Start here if unsure which tool to call \u2014 filter by module (e.g. "planning", "content", "analytics") to narrow results.',
|
|
11507
11828
|
{
|
|
11508
|
-
query:
|
|
11509
|
-
module:
|
|
11510
|
-
scope:
|
|
11511
|
-
detail:
|
|
11829
|
+
query: z22.string().optional().describe("Search query to filter tools by name or description"),
|
|
11830
|
+
module: z22.string().optional().describe('Filter by module name (e.g. "planning", "content", "analytics")'),
|
|
11831
|
+
scope: z22.string().optional().describe('Filter by required scope (e.g. "mcp:read", "mcp:write")'),
|
|
11832
|
+
detail: z22.enum(["name", "summary", "full"]).default("summary").describe(
|
|
11512
11833
|
'Detail level: "name" for just tool names, "summary" for names + descriptions, "full" for complete info including scope and module'
|
|
11513
11834
|
)
|
|
11514
11835
|
},
|
|
@@ -11552,15 +11873,15 @@ function registerDiscoveryTools(server2) {
|
|
|
11552
11873
|
|
|
11553
11874
|
// src/tools/pipeline.ts
|
|
11554
11875
|
init_edge_function();
|
|
11555
|
-
import { z as
|
|
11876
|
+
import { z as z23 } from "zod";
|
|
11556
11877
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
11557
11878
|
init_supabase();
|
|
11558
11879
|
init_quality();
|
|
11559
11880
|
init_version();
|
|
11560
|
-
function
|
|
11881
|
+
function asEnvelope18(data) {
|
|
11561
11882
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
11562
11883
|
}
|
|
11563
|
-
var PLATFORM_ENUM2 =
|
|
11884
|
+
var PLATFORM_ENUM2 = z23.enum([
|
|
11564
11885
|
"youtube",
|
|
11565
11886
|
"tiktok",
|
|
11566
11887
|
"instagram",
|
|
@@ -11577,10 +11898,10 @@ function registerPipelineTools(server2) {
|
|
|
11577
11898
|
"check_pipeline_readiness",
|
|
11578
11899
|
"Pre-flight check before run_content_pipeline. Verifies: sufficient credits for estimated_posts, active OAuth on target platforms, brand profile exists, no stale insights. Returns pass/fail with specific issues to fix before running the pipeline.",
|
|
11579
11900
|
{
|
|
11580
|
-
project_id:
|
|
11581
|
-
platforms:
|
|
11582
|
-
estimated_posts:
|
|
11583
|
-
response_format:
|
|
11901
|
+
project_id: z23.string().uuid().optional().describe("Project ID (auto-detected if omitted)"),
|
|
11902
|
+
platforms: z23.array(PLATFORM_ENUM2).min(1).describe("Target platforms to check"),
|
|
11903
|
+
estimated_posts: z23.number().min(1).max(50).default(5).describe("Estimated posts to generate"),
|
|
11904
|
+
response_format: z23.enum(["text", "json"]).optional().describe("Response format")
|
|
11584
11905
|
},
|
|
11585
11906
|
async ({ project_id, platforms, estimated_posts, response_format }) => {
|
|
11586
11907
|
const format = response_format ?? "text";
|
|
@@ -11656,7 +11977,7 @@ function registerPipelineTools(server2) {
|
|
|
11656
11977
|
});
|
|
11657
11978
|
if (format === "json") {
|
|
11658
11979
|
return {
|
|
11659
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
11980
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope18(result), null, 2) }]
|
|
11660
11981
|
};
|
|
11661
11982
|
}
|
|
11662
11983
|
const lines = [];
|
|
@@ -11704,22 +12025,22 @@ function registerPipelineTools(server2) {
|
|
|
11704
12025
|
"run_content_pipeline",
|
|
11705
12026
|
"Run the full content pipeline: research trends \u2192 generate plan \u2192 quality check \u2192 auto-approve \u2192 schedule posts. Chains all stages in one call for maximum efficiency. Set dry_run=true to preview the plan without publishing. Check check_pipeline_readiness first to verify credits, OAuth, and brand profile are ready.",
|
|
11706
12027
|
{
|
|
11707
|
-
project_id:
|
|
11708
|
-
topic:
|
|
11709
|
-
source_url:
|
|
11710
|
-
platforms:
|
|
11711
|
-
days:
|
|
11712
|
-
posts_per_day:
|
|
11713
|
-
approval_mode:
|
|
12028
|
+
project_id: z23.string().uuid().optional().describe("Project ID (auto-detected if omitted)"),
|
|
12029
|
+
topic: z23.string().optional().describe("Content topic (required if no source_url)"),
|
|
12030
|
+
source_url: z23.string().optional().describe("URL to extract content from"),
|
|
12031
|
+
platforms: z23.array(PLATFORM_ENUM2).min(1).describe("Target platforms"),
|
|
12032
|
+
days: z23.number().min(1).max(7).default(5).describe("Days to plan"),
|
|
12033
|
+
posts_per_day: z23.number().min(1).max(3).default(1).describe("Posts per platform per day"),
|
|
12034
|
+
approval_mode: z23.enum(["auto", "review_all", "review_low_confidence"]).default("review_low_confidence").describe(
|
|
11714
12035
|
"auto: approve all passing quality. review_all: flag everything. review_low_confidence: auto-approve high scorers."
|
|
11715
12036
|
),
|
|
11716
|
-
auto_approve_threshold:
|
|
12037
|
+
auto_approve_threshold: z23.number().min(0).max(35).default(28).describe(
|
|
11717
12038
|
"Quality score threshold for auto-approval (used in auto/review_low_confidence modes)"
|
|
11718
12039
|
),
|
|
11719
|
-
max_credits:
|
|
11720
|
-
dry_run:
|
|
11721
|
-
skip_stages:
|
|
11722
|
-
response_format:
|
|
12040
|
+
max_credits: z23.number().optional().describe("Credit budget cap"),
|
|
12041
|
+
dry_run: z23.boolean().default(false).describe("If true, skip scheduling and return plan only"),
|
|
12042
|
+
skip_stages: z23.array(z23.enum(["research", "quality", "schedule"])).optional().describe("Stages to skip"),
|
|
12043
|
+
response_format: z23.enum(["text", "json"]).default("json")
|
|
11723
12044
|
},
|
|
11724
12045
|
async ({
|
|
11725
12046
|
project_id,
|
|
@@ -12081,7 +12402,7 @@ function registerPipelineTools(server2) {
|
|
|
12081
12402
|
if (response_format === "json") {
|
|
12082
12403
|
return {
|
|
12083
12404
|
content: [
|
|
12084
|
-
{ type: "text", text: JSON.stringify(
|
|
12405
|
+
{ type: "text", text: JSON.stringify(asEnvelope18(resultPayload), null, 2) }
|
|
12085
12406
|
]
|
|
12086
12407
|
};
|
|
12087
12408
|
}
|
|
@@ -12142,8 +12463,8 @@ function registerPipelineTools(server2) {
|
|
|
12142
12463
|
"get_pipeline_status",
|
|
12143
12464
|
"Check status of a pipeline run, including stages completed, pending approvals, and scheduled posts.",
|
|
12144
12465
|
{
|
|
12145
|
-
pipeline_id:
|
|
12146
|
-
response_format:
|
|
12466
|
+
pipeline_id: z23.string().uuid().optional().describe("Pipeline run ID (omit for latest)"),
|
|
12467
|
+
response_format: z23.enum(["text", "json"]).optional()
|
|
12147
12468
|
},
|
|
12148
12469
|
async ({ pipeline_id, response_format }) => {
|
|
12149
12470
|
const format = response_format ?? "text";
|
|
@@ -12169,7 +12490,7 @@ function registerPipelineTools(server2) {
|
|
|
12169
12490
|
}
|
|
12170
12491
|
if (format === "json") {
|
|
12171
12492
|
return {
|
|
12172
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
12493
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope18(data), null, 2) }]
|
|
12173
12494
|
};
|
|
12174
12495
|
}
|
|
12175
12496
|
const lines = [];
|
|
@@ -12203,9 +12524,9 @@ function registerPipelineTools(server2) {
|
|
|
12203
12524
|
"auto_approve_plan",
|
|
12204
12525
|
"Batch auto-approve posts in a content plan that meet quality thresholds. Posts below the threshold are flagged for manual review.",
|
|
12205
12526
|
{
|
|
12206
|
-
plan_id:
|
|
12207
|
-
quality_threshold:
|
|
12208
|
-
response_format:
|
|
12527
|
+
plan_id: z23.string().uuid().describe("Content plan ID"),
|
|
12528
|
+
quality_threshold: z23.number().min(0).max(35).default(26).describe("Minimum quality score to auto-approve"),
|
|
12529
|
+
response_format: z23.enum(["text", "json"]).default("json")
|
|
12209
12530
|
},
|
|
12210
12531
|
async ({ plan_id, quality_threshold, response_format }) => {
|
|
12211
12532
|
const startedAt = Date.now();
|
|
@@ -12311,7 +12632,7 @@ function registerPipelineTools(server2) {
|
|
|
12311
12632
|
if (response_format === "json") {
|
|
12312
12633
|
return {
|
|
12313
12634
|
content: [
|
|
12314
|
-
{ type: "text", text: JSON.stringify(
|
|
12635
|
+
{ type: "text", text: JSON.stringify(asEnvelope18(resultPayload), null, 2) }
|
|
12315
12636
|
]
|
|
12316
12637
|
};
|
|
12317
12638
|
}
|
|
@@ -12366,10 +12687,10 @@ function buildPlanPrompt(topic, platforms, days, postsPerDay, sourceUrl) {
|
|
|
12366
12687
|
|
|
12367
12688
|
// src/tools/suggest.ts
|
|
12368
12689
|
init_edge_function();
|
|
12369
|
-
import { z as
|
|
12690
|
+
import { z as z24 } from "zod";
|
|
12370
12691
|
init_supabase();
|
|
12371
12692
|
init_version();
|
|
12372
|
-
function
|
|
12693
|
+
function asEnvelope19(data) {
|
|
12373
12694
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
12374
12695
|
}
|
|
12375
12696
|
function registerSuggestTools(server2) {
|
|
@@ -12377,10 +12698,10 @@ function registerSuggestTools(server2) {
|
|
|
12377
12698
|
"suggest_next_content",
|
|
12378
12699
|
"Suggest next content topics based on performance insights, past content, and competitor patterns. No AI call, no credit cost \u2014 purely data-driven recommendations.",
|
|
12379
12700
|
{
|
|
12380
|
-
project_id:
|
|
12381
|
-
count:
|
|
12382
|
-
response_format:
|
|
12383
|
-
},
|
|
12701
|
+
project_id: z24.string().uuid().optional().describe("Project ID (auto-detected if omitted)"),
|
|
12702
|
+
count: z24.number().min(1).max(10).default(3).describe("Number of suggestions to return"),
|
|
12703
|
+
response_format: z24.enum(["text", "json"]).optional()
|
|
12704
|
+
},
|
|
12384
12705
|
async ({ project_id, count, response_format }) => {
|
|
12385
12706
|
const format = response_format ?? "text";
|
|
12386
12707
|
const startedAt = Date.now();
|
|
@@ -12489,7 +12810,7 @@ function registerSuggestTools(server2) {
|
|
|
12489
12810
|
if (format === "json") {
|
|
12490
12811
|
return {
|
|
12491
12812
|
content: [
|
|
12492
|
-
{ type: "text", text: JSON.stringify(
|
|
12813
|
+
{ type: "text", text: JSON.stringify(asEnvelope19(resultPayload), null, 2) }
|
|
12493
12814
|
]
|
|
12494
12815
|
};
|
|
12495
12816
|
}
|
|
@@ -12529,7 +12850,7 @@ ${i + 1}. ${s.topic}`);
|
|
|
12529
12850
|
|
|
12530
12851
|
// src/tools/digest.ts
|
|
12531
12852
|
init_edge_function();
|
|
12532
|
-
import { z as
|
|
12853
|
+
import { z as z25 } from "zod";
|
|
12533
12854
|
init_supabase();
|
|
12534
12855
|
|
|
12535
12856
|
// src/lib/anomaly-detector.ts
|
|
@@ -12634,10 +12955,10 @@ function detectAnomalies(currentData, previousData, sensitivity = "medium", aver
|
|
|
12634
12955
|
|
|
12635
12956
|
// src/tools/digest.ts
|
|
12636
12957
|
init_version();
|
|
12637
|
-
function
|
|
12958
|
+
function asEnvelope20(data) {
|
|
12638
12959
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
12639
12960
|
}
|
|
12640
|
-
var PLATFORM_ENUM3 =
|
|
12961
|
+
var PLATFORM_ENUM3 = z25.enum([
|
|
12641
12962
|
"youtube",
|
|
12642
12963
|
"tiktok",
|
|
12643
12964
|
"instagram",
|
|
@@ -12652,10 +12973,10 @@ function registerDigestTools(server2) {
|
|
|
12652
12973
|
"generate_performance_digest",
|
|
12653
12974
|
"Generate a performance summary for a time period. Includes metrics, trends vs previous period, top/bottom performers, platform breakdown, and actionable recommendations. No AI call, no credit cost.",
|
|
12654
12975
|
{
|
|
12655
|
-
project_id:
|
|
12656
|
-
period:
|
|
12657
|
-
include_recommendations:
|
|
12658
|
-
response_format:
|
|
12976
|
+
project_id: z25.string().uuid().optional().describe("Project ID (auto-detected if omitted)"),
|
|
12977
|
+
period: z25.enum(["7d", "14d", "30d"]).default("7d").describe("Time period to analyze"),
|
|
12978
|
+
include_recommendations: z25.boolean().default(true),
|
|
12979
|
+
response_format: z25.enum(["text", "json"]).optional()
|
|
12659
12980
|
},
|
|
12660
12981
|
async ({ project_id, period, include_recommendations, response_format }) => {
|
|
12661
12982
|
const format = response_format ?? "text";
|
|
@@ -12811,7 +13132,7 @@ function registerDigestTools(server2) {
|
|
|
12811
13132
|
});
|
|
12812
13133
|
if (format === "json") {
|
|
12813
13134
|
return {
|
|
12814
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
13135
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope20(digest), null, 2) }]
|
|
12815
13136
|
};
|
|
12816
13137
|
}
|
|
12817
13138
|
const lines = [];
|
|
@@ -12870,11 +13191,11 @@ Best: ${best.id.slice(0, 8)}... (${best.platform}) \u2014 ${best.views.toLocaleS
|
|
|
12870
13191
|
"detect_anomalies",
|
|
12871
13192
|
"Detect significant performance changes: spikes, drops, viral content, trend shifts. Compares current period against previous equal-length period. No AI call, no credit cost.",
|
|
12872
13193
|
{
|
|
12873
|
-
project_id:
|
|
12874
|
-
days:
|
|
12875
|
-
sensitivity:
|
|
12876
|
-
platforms:
|
|
12877
|
-
response_format:
|
|
13194
|
+
project_id: z25.string().uuid().optional().describe("Project ID (auto-detected if omitted)"),
|
|
13195
|
+
days: z25.number().min(7).max(90).default(14).describe("Days to analyze"),
|
|
13196
|
+
sensitivity: z25.enum(["low", "medium", "high"]).default("medium").describe("Detection sensitivity: low=50%+, medium=30%+, high=15%+ changes"),
|
|
13197
|
+
platforms: z25.array(PLATFORM_ENUM3).optional().describe("Filter to specific platforms"),
|
|
13198
|
+
response_format: z25.enum(["text", "json"]).optional()
|
|
12878
13199
|
},
|
|
12879
13200
|
async ({ project_id, days, sensitivity, platforms, response_format }) => {
|
|
12880
13201
|
const format = response_format ?? "text";
|
|
@@ -12925,7 +13246,7 @@ Best: ${best.id.slice(0, 8)}... (${best.platform}) \u2014 ${best.views.toLocaleS
|
|
|
12925
13246
|
if (format === "json") {
|
|
12926
13247
|
return {
|
|
12927
13248
|
content: [
|
|
12928
|
-
{ type: "text", text: JSON.stringify(
|
|
13249
|
+
{ type: "text", text: JSON.stringify(asEnvelope20(resultPayload), null, 2) }
|
|
12929
13250
|
]
|
|
12930
13251
|
};
|
|
12931
13252
|
}
|
|
@@ -12969,7 +13290,7 @@ Best: ${best.id.slice(0, 8)}... (${best.platform}) \u2014 ${best.views.toLocaleS
|
|
|
12969
13290
|
init_edge_function();
|
|
12970
13291
|
init_supabase();
|
|
12971
13292
|
init_version();
|
|
12972
|
-
import { z as
|
|
13293
|
+
import { z as z26 } from "zod";
|
|
12973
13294
|
|
|
12974
13295
|
// src/lib/brandScoring.ts
|
|
12975
13296
|
var WEIGHTS = {
|
|
@@ -13238,8 +13559,181 @@ function computeBrandConsistency(content, profile, threshold = 60) {
|
|
|
13238
13559
|
};
|
|
13239
13560
|
}
|
|
13240
13561
|
|
|
13562
|
+
// src/lib/colorAudit.ts
|
|
13563
|
+
function hexToRgb(hex) {
|
|
13564
|
+
const h = hex.replace("#", "");
|
|
13565
|
+
const full = h.length === 3 ? h[0] + h[0] + h[1] + h[1] + h[2] + h[2] : h;
|
|
13566
|
+
const n = parseInt(full, 16);
|
|
13567
|
+
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
13568
|
+
}
|
|
13569
|
+
function srgbToLinear(c) {
|
|
13570
|
+
const s = c / 255;
|
|
13571
|
+
return s <= 0.04045 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
|
|
13572
|
+
}
|
|
13573
|
+
function hexToLab(hex) {
|
|
13574
|
+
const [r, g, b] = hexToRgb(hex);
|
|
13575
|
+
const lr = srgbToLinear(r);
|
|
13576
|
+
const lg = srgbToLinear(g);
|
|
13577
|
+
const lb = srgbToLinear(b);
|
|
13578
|
+
const x = lr * 0.4124564 + lg * 0.3575761 + lb * 0.1804375;
|
|
13579
|
+
const y = lr * 0.2126729 + lg * 0.7151522 + lb * 0.072175;
|
|
13580
|
+
const z29 = lr * 0.0193339 + lg * 0.119192 + lb * 0.9503041;
|
|
13581
|
+
const f = (t) => t > 8856e-6 ? Math.cbrt(t) : 7.787 * t + 16 / 116;
|
|
13582
|
+
const fx = f(x / 0.95047);
|
|
13583
|
+
const fy = f(y / 1);
|
|
13584
|
+
const fz = f(z29 / 1.08883);
|
|
13585
|
+
return { L: 116 * fy - 16, a: 500 * (fx - fy), b: 200 * (fy - fz) };
|
|
13586
|
+
}
|
|
13587
|
+
function deltaE2000(lab1, lab2) {
|
|
13588
|
+
const { L: L1, a: a1, b: b1 } = lab1;
|
|
13589
|
+
const { L: L2, a: a2, b: b2 } = lab2;
|
|
13590
|
+
const C1 = Math.sqrt(a1 * a1 + b1 * b1);
|
|
13591
|
+
const C2 = Math.sqrt(a2 * a2 + b2 * b2);
|
|
13592
|
+
const Cab = (C1 + C2) / 2;
|
|
13593
|
+
const Cab7 = Math.pow(Cab, 7);
|
|
13594
|
+
const G = 0.5 * (1 - Math.sqrt(Cab7 / (Cab7 + Math.pow(25, 7))));
|
|
13595
|
+
const a1p = a1 * (1 + G);
|
|
13596
|
+
const a2p = a2 * (1 + G);
|
|
13597
|
+
const C1p = Math.sqrt(a1p * a1p + b1 * b1);
|
|
13598
|
+
const C2p = Math.sqrt(a2p * a2p + b2 * b2);
|
|
13599
|
+
let h1p = Math.atan2(b1, a1p) * (180 / Math.PI);
|
|
13600
|
+
if (h1p < 0) h1p += 360;
|
|
13601
|
+
let h2p = Math.atan2(b2, a2p) * (180 / Math.PI);
|
|
13602
|
+
if (h2p < 0) h2p += 360;
|
|
13603
|
+
const dLp = L2 - L1;
|
|
13604
|
+
const dCp = C2p - C1p;
|
|
13605
|
+
let dhp;
|
|
13606
|
+
if (C1p * C2p === 0) dhp = 0;
|
|
13607
|
+
else if (Math.abs(h2p - h1p) <= 180) dhp = h2p - h1p;
|
|
13608
|
+
else if (h2p - h1p > 180) dhp = h2p - h1p - 360;
|
|
13609
|
+
else dhp = h2p - h1p + 360;
|
|
13610
|
+
const dHp = 2 * Math.sqrt(C1p * C2p) * Math.sin(dhp * Math.PI / 360);
|
|
13611
|
+
const Lp = (L1 + L2) / 2;
|
|
13612
|
+
const Cp = (C1p + C2p) / 2;
|
|
13613
|
+
let hp;
|
|
13614
|
+
if (C1p * C2p === 0) hp = h1p + h2p;
|
|
13615
|
+
else if (Math.abs(h1p - h2p) <= 180) hp = (h1p + h2p) / 2;
|
|
13616
|
+
else if (h1p + h2p < 360) hp = (h1p + h2p + 360) / 2;
|
|
13617
|
+
else hp = (h1p + h2p - 360) / 2;
|
|
13618
|
+
const T = 1 - 0.17 * Math.cos((hp - 30) * Math.PI / 180) + 0.24 * Math.cos(2 * hp * Math.PI / 180) + 0.32 * Math.cos((3 * hp + 6) * Math.PI / 180) - 0.2 * Math.cos((4 * hp - 63) * Math.PI / 180);
|
|
13619
|
+
const SL = 1 + 0.015 * (Lp - 50) * (Lp - 50) / Math.sqrt(20 + (Lp - 50) * (Lp - 50));
|
|
13620
|
+
const SC = 1 + 0.045 * Cp;
|
|
13621
|
+
const SH = 1 + 0.015 * Cp * T;
|
|
13622
|
+
const Cp7 = Math.pow(Cp, 7);
|
|
13623
|
+
const RT = -2 * Math.sqrt(Cp7 / (Cp7 + Math.pow(25, 7))) * Math.sin(60 * Math.exp(-Math.pow((hp - 275) / 25, 2)) * Math.PI / 180);
|
|
13624
|
+
return Math.sqrt(
|
|
13625
|
+
Math.pow(dLp / SL, 2) + Math.pow(dCp / SC, 2) + Math.pow(dHp / SH, 2) + RT * (dCp / SC) * (dHp / SH)
|
|
13626
|
+
);
|
|
13627
|
+
}
|
|
13628
|
+
var COLOR_SLOTS = [
|
|
13629
|
+
"primary",
|
|
13630
|
+
"secondary",
|
|
13631
|
+
"accent",
|
|
13632
|
+
"background",
|
|
13633
|
+
"success",
|
|
13634
|
+
"warning",
|
|
13635
|
+
"error",
|
|
13636
|
+
"text",
|
|
13637
|
+
"textSecondary"
|
|
13638
|
+
];
|
|
13639
|
+
function auditBrandColors(palette, contentColors, threshold = 10) {
|
|
13640
|
+
if (!contentColors.length) return { entries: [], overallScore: 100, passed: true };
|
|
13641
|
+
const brandColors = [];
|
|
13642
|
+
for (const slot of COLOR_SLOTS) {
|
|
13643
|
+
const hex = palette[slot];
|
|
13644
|
+
if (typeof hex === "string" && hex.startsWith("#")) {
|
|
13645
|
+
brandColors.push({ slot, hex, lab: hexToLab(hex) });
|
|
13646
|
+
}
|
|
13647
|
+
}
|
|
13648
|
+
if (!brandColors.length) return { entries: [], overallScore: 50, passed: false };
|
|
13649
|
+
const entries = contentColors.map((color) => {
|
|
13650
|
+
const colorLab = hexToLab(color);
|
|
13651
|
+
let minDE = Infinity;
|
|
13652
|
+
let closest = brandColors[0];
|
|
13653
|
+
for (const bc of brandColors) {
|
|
13654
|
+
const de = deltaE2000(colorLab, bc.lab);
|
|
13655
|
+
if (de < minDE) {
|
|
13656
|
+
minDE = de;
|
|
13657
|
+
closest = bc;
|
|
13658
|
+
}
|
|
13659
|
+
}
|
|
13660
|
+
return {
|
|
13661
|
+
color,
|
|
13662
|
+
closestBrandColor: closest.hex,
|
|
13663
|
+
closestBrandSlot: closest.slot,
|
|
13664
|
+
deltaE: Math.round(minDE * 100) / 100,
|
|
13665
|
+
passed: minDE <= threshold
|
|
13666
|
+
};
|
|
13667
|
+
});
|
|
13668
|
+
const passedCount = entries.filter((e) => e.passed).length;
|
|
13669
|
+
const overallScore = Math.round(passedCount / entries.length * 100);
|
|
13670
|
+
return { entries, overallScore, passed: overallScore >= 80 };
|
|
13671
|
+
}
|
|
13672
|
+
function exportDesignTokens(palette, typography, format) {
|
|
13673
|
+
if (format === "css") return exportCSS(palette, typography);
|
|
13674
|
+
if (format === "tailwind") return JSON.stringify(exportTailwind(palette), null, 2);
|
|
13675
|
+
return JSON.stringify(exportFigma(palette, typography), null, 2);
|
|
13676
|
+
}
|
|
13677
|
+
function exportCSS(palette, typography) {
|
|
13678
|
+
const lines = [":root {"];
|
|
13679
|
+
const slots = [
|
|
13680
|
+
["--brand-primary", "primary"],
|
|
13681
|
+
["--brand-secondary", "secondary"],
|
|
13682
|
+
["--brand-accent", "accent"],
|
|
13683
|
+
["--brand-background", "background"],
|
|
13684
|
+
["--brand-success", "success"],
|
|
13685
|
+
["--brand-warning", "warning"],
|
|
13686
|
+
["--brand-error", "error"],
|
|
13687
|
+
["--brand-text", "text"],
|
|
13688
|
+
["--brand-text-secondary", "textSecondary"]
|
|
13689
|
+
];
|
|
13690
|
+
for (const [varName, key] of slots) {
|
|
13691
|
+
const v = palette[key];
|
|
13692
|
+
if (typeof v === "string" && v) lines.push(` ${varName}: ${v};`);
|
|
13693
|
+
}
|
|
13694
|
+
if (typography) {
|
|
13695
|
+
const hf = typography.headingFont;
|
|
13696
|
+
const bf = typography.bodyFont;
|
|
13697
|
+
if (hf) lines.push(` --brand-font-heading: ${hf};`);
|
|
13698
|
+
if (bf) lines.push(` --brand-font-body: ${bf};`);
|
|
13699
|
+
}
|
|
13700
|
+
lines.push("}");
|
|
13701
|
+
return lines.join("\n");
|
|
13702
|
+
}
|
|
13703
|
+
function exportTailwind(palette) {
|
|
13704
|
+
const colors = {};
|
|
13705
|
+
const map = [
|
|
13706
|
+
["brand-primary", "primary"],
|
|
13707
|
+
["brand-secondary", "secondary"],
|
|
13708
|
+
["brand-accent", "accent"],
|
|
13709
|
+
["brand-bg", "background"]
|
|
13710
|
+
];
|
|
13711
|
+
for (const [tw, key] of map) {
|
|
13712
|
+
const v = palette[key];
|
|
13713
|
+
if (typeof v === "string" && v) colors[tw] = v;
|
|
13714
|
+
}
|
|
13715
|
+
return colors;
|
|
13716
|
+
}
|
|
13717
|
+
function exportFigma(palette, typography) {
|
|
13718
|
+
const tokens = { color: {} };
|
|
13719
|
+
for (const slot of ["primary", "secondary", "accent", "background"]) {
|
|
13720
|
+
const v = palette[slot];
|
|
13721
|
+
if (typeof v === "string") tokens.color[slot] = { value: v, type: "color" };
|
|
13722
|
+
}
|
|
13723
|
+
if (typography) {
|
|
13724
|
+
const hf = typography.headingFont;
|
|
13725
|
+
const bf = typography.bodyFont;
|
|
13726
|
+
if (hf || bf) {
|
|
13727
|
+
tokens.fontFamily = {};
|
|
13728
|
+
if (hf) tokens.fontFamily.heading = { value: String(hf), type: "fontFamilies" };
|
|
13729
|
+
if (bf) tokens.fontFamily.body = { value: String(bf), type: "fontFamilies" };
|
|
13730
|
+
}
|
|
13731
|
+
}
|
|
13732
|
+
return tokens;
|
|
13733
|
+
}
|
|
13734
|
+
|
|
13241
13735
|
// src/tools/brandRuntime.ts
|
|
13242
|
-
function
|
|
13736
|
+
function asEnvelope21(data) {
|
|
13243
13737
|
return {
|
|
13244
13738
|
_meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() },
|
|
13245
13739
|
data
|
|
@@ -13250,7 +13744,7 @@ function registerBrandRuntimeTools(server2) {
|
|
|
13250
13744
|
"get_brand_runtime",
|
|
13251
13745
|
"Get the full brand runtime for a project. Returns the 4-layer brand system: messaging (value props, pillars, proof points), voice (tone, vocabulary, avoid patterns), visual (palette, typography, composition), and operating constraints (audience, archetype). Also returns extraction confidence metadata.",
|
|
13252
13746
|
{
|
|
13253
|
-
project_id:
|
|
13747
|
+
project_id: z26.string().optional().describe("Project ID. Defaults to current project.")
|
|
13254
13748
|
},
|
|
13255
13749
|
async ({ project_id }) => {
|
|
13256
13750
|
const projectId = project_id || await getDefaultProjectId();
|
|
@@ -13310,7 +13804,7 @@ function registerBrandRuntimeTools(server2) {
|
|
|
13310
13804
|
pagesScraped: meta.pagesScraped || 0
|
|
13311
13805
|
}
|
|
13312
13806
|
};
|
|
13313
|
-
const envelope =
|
|
13807
|
+
const envelope = asEnvelope21(runtime);
|
|
13314
13808
|
return {
|
|
13315
13809
|
content: [{ type: "text", text: JSON.stringify(envelope, null, 2) }]
|
|
13316
13810
|
};
|
|
@@ -13320,7 +13814,7 @@ function registerBrandRuntimeTools(server2) {
|
|
|
13320
13814
|
"explain_brand_system",
|
|
13321
13815
|
"Explains what brand data is available vs missing for a project. Returns a human-readable summary of completeness, confidence levels, and recommendations for improving the brand profile.",
|
|
13322
13816
|
{
|
|
13323
|
-
project_id:
|
|
13817
|
+
project_id: z26.string().optional().describe("Project ID. Defaults to current project.")
|
|
13324
13818
|
},
|
|
13325
13819
|
async ({ project_id }) => {
|
|
13326
13820
|
const projectId = project_id || await getDefaultProjectId();
|
|
@@ -13432,8 +13926,8 @@ function registerBrandRuntimeTools(server2) {
|
|
|
13432
13926
|
"check_brand_consistency",
|
|
13433
13927
|
"Check if content text is consistent with the brand voice, vocabulary, messaging, and factual claims. Returns per-dimension scores (0-100) and specific issues found. Use this to validate scripts, captions, or post copy before publishing.",
|
|
13434
13928
|
{
|
|
13435
|
-
content:
|
|
13436
|
-
project_id:
|
|
13929
|
+
content: z26.string().describe("The content text to check for brand consistency."),
|
|
13930
|
+
project_id: z26.string().optional().describe("Project ID. Defaults to current project.")
|
|
13437
13931
|
},
|
|
13438
13932
|
async ({ content, project_id }) => {
|
|
13439
13933
|
const projectId = project_id || await getDefaultProjectId();
|
|
@@ -13452,7 +13946,76 @@ function registerBrandRuntimeTools(server2) {
|
|
|
13452
13946
|
}
|
|
13453
13947
|
const profile = row.profile_data;
|
|
13454
13948
|
const checkResult = computeBrandConsistency(content, profile);
|
|
13455
|
-
const envelope =
|
|
13949
|
+
const envelope = asEnvelope21(checkResult);
|
|
13950
|
+
return {
|
|
13951
|
+
content: [{ type: "text", text: JSON.stringify(envelope, null, 2) }]
|
|
13952
|
+
};
|
|
13953
|
+
}
|
|
13954
|
+
);
|
|
13955
|
+
server2.tool(
|
|
13956
|
+
"audit_brand_colors",
|
|
13957
|
+
"Audit content colors against the brand palette using perceptual color distance (Delta E 2000). Returns per-color compliance scores and identifies the closest brand color for each input.",
|
|
13958
|
+
{
|
|
13959
|
+
content_colors: z26.array(z26.string()).describe('Hex color strings used in the content (e.g., ["#FF0000", "#00FF00"])'),
|
|
13960
|
+
project_id: z26.string().optional().describe("Project ID. Defaults to current project."),
|
|
13961
|
+
threshold: z26.number().optional().describe("Max Delta E for on-brand (default 10). Lower = stricter.")
|
|
13962
|
+
},
|
|
13963
|
+
async ({ content_colors, project_id, threshold }) => {
|
|
13964
|
+
const projectId = project_id || await getDefaultProjectId();
|
|
13965
|
+
const { data: result, error: efError } = await callEdgeFunction("mcp-data", { action: "brand-profile", projectId });
|
|
13966
|
+
const row = !efError && result?.success ? result.profile : null;
|
|
13967
|
+
if (!row?.profile_data?.colorPalette) {
|
|
13968
|
+
return {
|
|
13969
|
+
content: [
|
|
13970
|
+
{
|
|
13971
|
+
type: "text",
|
|
13972
|
+
text: "No brand color palette found. Extract a brand profile first."
|
|
13973
|
+
}
|
|
13974
|
+
],
|
|
13975
|
+
isError: true
|
|
13976
|
+
};
|
|
13977
|
+
}
|
|
13978
|
+
const auditResult = auditBrandColors(
|
|
13979
|
+
row.profile_data.colorPalette,
|
|
13980
|
+
content_colors,
|
|
13981
|
+
threshold ?? 10
|
|
13982
|
+
);
|
|
13983
|
+
const envelope = asEnvelope21(auditResult);
|
|
13984
|
+
return {
|
|
13985
|
+
content: [{ type: "text", text: JSON.stringify(envelope, null, 2) }]
|
|
13986
|
+
};
|
|
13987
|
+
}
|
|
13988
|
+
);
|
|
13989
|
+
server2.tool(
|
|
13990
|
+
"export_design_tokens",
|
|
13991
|
+
"Export brand palette and typography as design tokens. Supports CSS custom properties, Tailwind config, and Figma Tokens JSON formats.",
|
|
13992
|
+
{
|
|
13993
|
+
format: z26.enum(["css", "tailwind", "figma"]).describe(
|
|
13994
|
+
"Output format: css (CSS variables), tailwind (theme.extend.colors), figma (Figma Tokens JSON)"
|
|
13995
|
+
),
|
|
13996
|
+
project_id: z26.string().optional().describe("Project ID. Defaults to current project.")
|
|
13997
|
+
},
|
|
13998
|
+
async ({ format, project_id }) => {
|
|
13999
|
+
const projectId = project_id || await getDefaultProjectId();
|
|
14000
|
+
const { data: result, error: efError } = await callEdgeFunction("mcp-data", { action: "brand-profile", projectId });
|
|
14001
|
+
const row = !efError && result?.success ? result.profile : null;
|
|
14002
|
+
if (!row?.profile_data?.colorPalette) {
|
|
14003
|
+
return {
|
|
14004
|
+
content: [
|
|
14005
|
+
{
|
|
14006
|
+
type: "text",
|
|
14007
|
+
text: "No brand color palette found. Extract a brand profile first."
|
|
14008
|
+
}
|
|
14009
|
+
],
|
|
14010
|
+
isError: true
|
|
14011
|
+
};
|
|
14012
|
+
}
|
|
14013
|
+
const output = exportDesignTokens(
|
|
14014
|
+
row.profile_data.colorPalette,
|
|
14015
|
+
row.profile_data.typography,
|
|
14016
|
+
format
|
|
14017
|
+
);
|
|
14018
|
+
const envelope = asEnvelope21({ format, tokens: output });
|
|
13456
14019
|
return {
|
|
13457
14020
|
content: [{ type: "text", text: JSON.stringify(envelope, null, 2) }]
|
|
13458
14021
|
};
|
|
@@ -13460,6 +14023,391 @@ function registerBrandRuntimeTools(server2) {
|
|
|
13460
14023
|
);
|
|
13461
14024
|
}
|
|
13462
14025
|
|
|
14026
|
+
// src/tools/carousel.ts
|
|
14027
|
+
init_edge_function();
|
|
14028
|
+
import { z as z27 } from "zod";
|
|
14029
|
+
init_supabase();
|
|
14030
|
+
init_request_context();
|
|
14031
|
+
init_version();
|
|
14032
|
+
var MAX_CREDITS_PER_RUN2 = Math.max(0, Number(process.env.SOCIALNEURON_MAX_CREDITS_PER_RUN || 0));
|
|
14033
|
+
var MAX_ASSETS_PER_RUN2 = Math.max(0, Number(process.env.SOCIALNEURON_MAX_ASSETS_PER_RUN || 0));
|
|
14034
|
+
var _globalCreditsUsed2 = 0;
|
|
14035
|
+
var _globalAssetsGenerated2 = 0;
|
|
14036
|
+
function getCreditsUsed2() {
|
|
14037
|
+
const ctx = requestContext.getStore();
|
|
14038
|
+
return ctx ? ctx.creditsUsed : _globalCreditsUsed2;
|
|
14039
|
+
}
|
|
14040
|
+
function addCreditsUsed2(amount) {
|
|
14041
|
+
const ctx = requestContext.getStore();
|
|
14042
|
+
if (ctx) {
|
|
14043
|
+
ctx.creditsUsed += amount;
|
|
14044
|
+
} else {
|
|
14045
|
+
_globalCreditsUsed2 += amount;
|
|
14046
|
+
}
|
|
14047
|
+
}
|
|
14048
|
+
function getAssetsGenerated2() {
|
|
14049
|
+
const ctx = requestContext.getStore();
|
|
14050
|
+
return ctx ? ctx.assetsGenerated : _globalAssetsGenerated2;
|
|
14051
|
+
}
|
|
14052
|
+
function addAssetsGenerated2(count) {
|
|
14053
|
+
const ctx = requestContext.getStore();
|
|
14054
|
+
if (ctx) {
|
|
14055
|
+
ctx.assetsGenerated += count;
|
|
14056
|
+
} else {
|
|
14057
|
+
_globalAssetsGenerated2 += count;
|
|
14058
|
+
}
|
|
14059
|
+
}
|
|
14060
|
+
function checkCreditBudget2(estimatedCost) {
|
|
14061
|
+
if (MAX_CREDITS_PER_RUN2 <= 0) return { ok: true };
|
|
14062
|
+
const used = getCreditsUsed2();
|
|
14063
|
+
if (used + estimatedCost > MAX_CREDITS_PER_RUN2) {
|
|
14064
|
+
return {
|
|
14065
|
+
ok: false,
|
|
14066
|
+
message: `Credit budget exceeded: ${used} used + ${estimatedCost} estimated > ${MAX_CREDITS_PER_RUN2} limit. Use a smaller slide count or cheaper image model.`
|
|
14067
|
+
};
|
|
14068
|
+
}
|
|
14069
|
+
return { ok: true };
|
|
14070
|
+
}
|
|
14071
|
+
function checkAssetBudget2() {
|
|
14072
|
+
if (MAX_ASSETS_PER_RUN2 <= 0) return { ok: true };
|
|
14073
|
+
const gen = getAssetsGenerated2();
|
|
14074
|
+
if (gen >= MAX_ASSETS_PER_RUN2) {
|
|
14075
|
+
return {
|
|
14076
|
+
ok: false,
|
|
14077
|
+
message: `Asset limit reached: ${gen}/${MAX_ASSETS_PER_RUN2} assets generated this run.`
|
|
14078
|
+
};
|
|
14079
|
+
}
|
|
14080
|
+
return { ok: true };
|
|
14081
|
+
}
|
|
14082
|
+
var IMAGE_CREDIT_ESTIMATES2 = {
|
|
14083
|
+
midjourney: 20,
|
|
14084
|
+
"nano-banana": 15,
|
|
14085
|
+
"nano-banana-pro": 25,
|
|
14086
|
+
"flux-pro": 30,
|
|
14087
|
+
"flux-max": 50,
|
|
14088
|
+
"gpt4o-image": 40,
|
|
14089
|
+
imagen4: 35,
|
|
14090
|
+
"imagen4-fast": 25,
|
|
14091
|
+
seedream: 20
|
|
14092
|
+
};
|
|
14093
|
+
async function fetchBrandVisualContext(projectId) {
|
|
14094
|
+
const { data, error } = await callEdgeFunction("mcp-data", { action: "brand-profile", projectId });
|
|
14095
|
+
if (error || !data?.success || !data.profile?.profile_data) return null;
|
|
14096
|
+
const profile = data.profile.profile_data;
|
|
14097
|
+
const parts = [];
|
|
14098
|
+
const palette = profile.colorPalette;
|
|
14099
|
+
if (palette) {
|
|
14100
|
+
const colors = Object.entries(palette).filter(([, v]) => typeof v === "string" && v.startsWith("#")).map(([k, v]) => `${k}: ${v}`).slice(0, 5);
|
|
14101
|
+
if (colors.length > 0) {
|
|
14102
|
+
parts.push(`Brand color palette: ${colors.join(", ")}`);
|
|
14103
|
+
}
|
|
14104
|
+
}
|
|
14105
|
+
const logoUrl = profile.logoUrl;
|
|
14106
|
+
let logoDesc = null;
|
|
14107
|
+
if (logoUrl) {
|
|
14108
|
+
const brandName = profile.name || "brand";
|
|
14109
|
+
logoDesc = `Include a small "${brandName}" logo watermark in the bottom-right corner`;
|
|
14110
|
+
parts.push(logoDesc);
|
|
14111
|
+
}
|
|
14112
|
+
const voice = profile.voiceProfile;
|
|
14113
|
+
if (voice?.tone && Array.isArray(voice.tone) && voice.tone.length > 0) {
|
|
14114
|
+
parts.push(`Visual mood: ${voice.tone.slice(0, 3).join(", ")}`);
|
|
14115
|
+
}
|
|
14116
|
+
if (parts.length === 0) return null;
|
|
14117
|
+
return {
|
|
14118
|
+
stylePrefix: parts.join(". "),
|
|
14119
|
+
brandName: profile.name || null,
|
|
14120
|
+
logoDescription: logoDesc
|
|
14121
|
+
};
|
|
14122
|
+
}
|
|
14123
|
+
function registerCarouselTools(server2) {
|
|
14124
|
+
server2.tool(
|
|
14125
|
+
"create_carousel",
|
|
14126
|
+
"End-to-end carousel creation: generates slide text + kicks off image generation for each slide in parallel. When brand_id is provided, auto-injects brand colors, logo watermark, and visual mood into every image prompt. Returns carousel data + image job_ids. Poll each job_id with check_status until complete, then call schedule_post with job_ids to publish as Instagram carousel (media_type=CAROUSEL_ALBUM).",
|
|
14127
|
+
{
|
|
14128
|
+
topic: z27.string().max(200).describe(
|
|
14129
|
+
'Carousel topic/hook \u2014 be specific. Example: "5 pricing mistakes that kill SaaS startups" beats "SaaS tips".'
|
|
14130
|
+
),
|
|
14131
|
+
image_model: z27.enum([
|
|
14132
|
+
"midjourney",
|
|
14133
|
+
"nano-banana",
|
|
14134
|
+
"nano-banana-pro",
|
|
14135
|
+
"flux-pro",
|
|
14136
|
+
"flux-max",
|
|
14137
|
+
"gpt4o-image",
|
|
14138
|
+
"imagen4",
|
|
14139
|
+
"imagen4-fast",
|
|
14140
|
+
"seedream"
|
|
14141
|
+
]).describe(
|
|
14142
|
+
"Image model for slide visuals. flux-pro for general purpose, imagen4 for photorealistic, midjourney for artistic."
|
|
14143
|
+
),
|
|
14144
|
+
template_id: z27.enum([
|
|
14145
|
+
"educational-series",
|
|
14146
|
+
"product-showcase",
|
|
14147
|
+
"story-arc",
|
|
14148
|
+
"before-after",
|
|
14149
|
+
"step-by-step",
|
|
14150
|
+
"quote-collection",
|
|
14151
|
+
"data-stats",
|
|
14152
|
+
"myth-vs-reality",
|
|
14153
|
+
"hormozi-authority"
|
|
14154
|
+
]).optional().describe("Carousel template. Default: hormozi-authority."),
|
|
14155
|
+
slide_count: z27.number().min(3).max(10).optional().describe("Number of slides (3-10). Default: 7."),
|
|
14156
|
+
aspect_ratio: z27.enum(["1:1", "4:5", "9:16"]).optional().describe("Aspect ratio for both carousel and images. Default: 1:1."),
|
|
14157
|
+
style: z27.enum(["minimal", "bold", "professional", "playful", "hormozi"]).optional().describe("Visual style. Default: hormozi for hormozi-authority template."),
|
|
14158
|
+
image_style_suffix: z27.string().max(500).optional().describe(
|
|
14159
|
+
'Style suffix appended to every image prompt for visual consistency across slides. Example: "dark moody lighting, cinematic, 35mm film grain".'
|
|
14160
|
+
),
|
|
14161
|
+
project_id: z27.string().optional().describe("Project ID to associate the carousel with."),
|
|
14162
|
+
response_format: z27.enum(["text", "json"]).optional().describe("Response format. Default: text.")
|
|
14163
|
+
},
|
|
14164
|
+
async ({
|
|
14165
|
+
topic,
|
|
14166
|
+
image_model,
|
|
14167
|
+
template_id,
|
|
14168
|
+
slide_count,
|
|
14169
|
+
aspect_ratio,
|
|
14170
|
+
style,
|
|
14171
|
+
image_style_suffix,
|
|
14172
|
+
brand_id,
|
|
14173
|
+
project_id,
|
|
14174
|
+
response_format
|
|
14175
|
+
}) => {
|
|
14176
|
+
const format = response_format ?? "text";
|
|
14177
|
+
const startedAt = Date.now();
|
|
14178
|
+
const templateId = template_id ?? "hormozi-authority";
|
|
14179
|
+
const resolvedStyle = style ?? (templateId === "hormozi-authority" ? "hormozi" : "professional");
|
|
14180
|
+
const slideCount = slide_count ?? 7;
|
|
14181
|
+
const ratio = aspect_ratio ?? "1:1";
|
|
14182
|
+
let brandContext = null;
|
|
14183
|
+
const brandProjectId = brand_id || project_id || await getDefaultProjectId();
|
|
14184
|
+
if (brandProjectId) {
|
|
14185
|
+
brandContext = await fetchBrandVisualContext(brandProjectId);
|
|
14186
|
+
}
|
|
14187
|
+
const carouselTextCost = 10 + slideCount * 2;
|
|
14188
|
+
const perImageCost = IMAGE_CREDIT_ESTIMATES2[image_model] ?? 30;
|
|
14189
|
+
const totalEstimatedCost = carouselTextCost + slideCount * perImageCost;
|
|
14190
|
+
const budgetCheck = checkCreditBudget2(totalEstimatedCost);
|
|
14191
|
+
if (!budgetCheck.ok) {
|
|
14192
|
+
await logMcpToolInvocation({
|
|
14193
|
+
toolName: "create_carousel",
|
|
14194
|
+
status: "error",
|
|
14195
|
+
durationMs: Date.now() - startedAt,
|
|
14196
|
+
details: { error: budgetCheck.message, totalEstimatedCost }
|
|
14197
|
+
});
|
|
14198
|
+
return {
|
|
14199
|
+
content: [{ type: "text", text: budgetCheck.message }],
|
|
14200
|
+
isError: true
|
|
14201
|
+
};
|
|
14202
|
+
}
|
|
14203
|
+
const assetBudget = checkAssetBudget2();
|
|
14204
|
+
if (!assetBudget.ok) {
|
|
14205
|
+
await logMcpToolInvocation({
|
|
14206
|
+
toolName: "create_carousel",
|
|
14207
|
+
status: "error",
|
|
14208
|
+
durationMs: Date.now() - startedAt,
|
|
14209
|
+
details: { error: assetBudget.message }
|
|
14210
|
+
});
|
|
14211
|
+
return {
|
|
14212
|
+
content: [{ type: "text", text: assetBudget.message }],
|
|
14213
|
+
isError: true
|
|
14214
|
+
};
|
|
14215
|
+
}
|
|
14216
|
+
const userId = await getDefaultUserId();
|
|
14217
|
+
const rateLimit = checkRateLimit("posting", `create_carousel:${userId}`);
|
|
14218
|
+
if (!rateLimit.allowed) {
|
|
14219
|
+
await logMcpToolInvocation({
|
|
14220
|
+
toolName: "create_carousel",
|
|
14221
|
+
status: "rate_limited",
|
|
14222
|
+
durationMs: Date.now() - startedAt,
|
|
14223
|
+
details: { retryAfter: rateLimit.retryAfter }
|
|
14224
|
+
});
|
|
14225
|
+
return {
|
|
14226
|
+
content: [
|
|
14227
|
+
{
|
|
14228
|
+
type: "text",
|
|
14229
|
+
text: `Rate limit exceeded. Retry in ~${rateLimit.retryAfter}s.`
|
|
14230
|
+
}
|
|
14231
|
+
],
|
|
14232
|
+
isError: true
|
|
14233
|
+
};
|
|
14234
|
+
}
|
|
14235
|
+
const { data: carouselData, error: carouselError } = await callEdgeFunction(
|
|
14236
|
+
"generate-carousel",
|
|
14237
|
+
{
|
|
14238
|
+
topic,
|
|
14239
|
+
templateId,
|
|
14240
|
+
slideCount,
|
|
14241
|
+
aspectRatio: ratio,
|
|
14242
|
+
style: resolvedStyle,
|
|
14243
|
+
projectId: project_id
|
|
14244
|
+
},
|
|
14245
|
+
{ timeoutMs: 6e4 }
|
|
14246
|
+
);
|
|
14247
|
+
if (carouselError || !carouselData?.carousel) {
|
|
14248
|
+
const errMsg = carouselError ?? "No carousel data returned";
|
|
14249
|
+
await logMcpToolInvocation({
|
|
14250
|
+
toolName: "create_carousel",
|
|
14251
|
+
status: "error",
|
|
14252
|
+
durationMs: Date.now() - startedAt,
|
|
14253
|
+
details: { phase: "text_generation", error: errMsg }
|
|
14254
|
+
});
|
|
14255
|
+
return {
|
|
14256
|
+
content: [{ type: "text", text: `Carousel text generation failed: ${errMsg}` }],
|
|
14257
|
+
isError: true
|
|
14258
|
+
};
|
|
14259
|
+
}
|
|
14260
|
+
const carousel = carouselData.carousel;
|
|
14261
|
+
const textCredits = carousel.credits?.used ?? carouselTextCost;
|
|
14262
|
+
addCreditsUsed2(textCredits);
|
|
14263
|
+
const imageJobs = await Promise.all(
|
|
14264
|
+
carousel.slides.map(async (slide) => {
|
|
14265
|
+
const promptParts = [];
|
|
14266
|
+
if (brandContext) promptParts.push(brandContext.stylePrefix);
|
|
14267
|
+
if (slide.headline) promptParts.push(slide.headline);
|
|
14268
|
+
if (slide.body) promptParts.push(slide.body);
|
|
14269
|
+
if (promptParts.length === 0) promptParts.push(topic);
|
|
14270
|
+
if (image_style_suffix) promptParts.push(image_style_suffix);
|
|
14271
|
+
const imagePrompt = promptParts.join(". ");
|
|
14272
|
+
try {
|
|
14273
|
+
const { data, error } = await callEdgeFunction(
|
|
14274
|
+
"kie-image-generate",
|
|
14275
|
+
{
|
|
14276
|
+
prompt: imagePrompt,
|
|
14277
|
+
model: image_model,
|
|
14278
|
+
aspectRatio: ratio
|
|
14279
|
+
},
|
|
14280
|
+
{ timeoutMs: 3e4 }
|
|
14281
|
+
);
|
|
14282
|
+
if (error || !data?.taskId && !data?.asyncJobId) {
|
|
14283
|
+
return {
|
|
14284
|
+
slideNumber: slide.slideNumber,
|
|
14285
|
+
jobId: null,
|
|
14286
|
+
model: image_model,
|
|
14287
|
+
error: error ?? "No job ID returned"
|
|
14288
|
+
};
|
|
14289
|
+
}
|
|
14290
|
+
const jobId = data.asyncJobId ?? data.taskId ?? null;
|
|
14291
|
+
if (jobId) {
|
|
14292
|
+
addCreditsUsed2(perImageCost);
|
|
14293
|
+
addAssetsGenerated2(1);
|
|
14294
|
+
}
|
|
14295
|
+
return {
|
|
14296
|
+
slideNumber: slide.slideNumber,
|
|
14297
|
+
jobId,
|
|
14298
|
+
model: image_model,
|
|
14299
|
+
error: null
|
|
14300
|
+
};
|
|
14301
|
+
} catch (err) {
|
|
14302
|
+
return {
|
|
14303
|
+
slideNumber: slide.slideNumber,
|
|
14304
|
+
jobId: null,
|
|
14305
|
+
model: image_model,
|
|
14306
|
+
error: sanitizeError2(err)
|
|
14307
|
+
};
|
|
14308
|
+
}
|
|
14309
|
+
})
|
|
14310
|
+
);
|
|
14311
|
+
const successfulJobs = imageJobs.filter((j) => j.jobId !== null);
|
|
14312
|
+
const failedJobs = imageJobs.filter((j) => j.jobId === null);
|
|
14313
|
+
await logMcpToolInvocation({
|
|
14314
|
+
toolName: "create_carousel",
|
|
14315
|
+
status: failedJobs.length === imageJobs.length ? "error" : "success",
|
|
14316
|
+
durationMs: Date.now() - startedAt,
|
|
14317
|
+
details: {
|
|
14318
|
+
carouselId: carousel.id,
|
|
14319
|
+
templateId,
|
|
14320
|
+
slideCount: carousel.slides.length,
|
|
14321
|
+
imagesStarted: successfulJobs.length,
|
|
14322
|
+
imagesFailed: failedJobs.length,
|
|
14323
|
+
imageModel: image_model,
|
|
14324
|
+
creditsUsed: getCreditsUsed2()
|
|
14325
|
+
}
|
|
14326
|
+
});
|
|
14327
|
+
if (format === "json") {
|
|
14328
|
+
return {
|
|
14329
|
+
content: [
|
|
14330
|
+
{
|
|
14331
|
+
type: "text",
|
|
14332
|
+
text: JSON.stringify(
|
|
14333
|
+
{
|
|
14334
|
+
_meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() },
|
|
14335
|
+
data: {
|
|
14336
|
+
carouselId: carousel.id,
|
|
14337
|
+
templateId,
|
|
14338
|
+
style: resolvedStyle,
|
|
14339
|
+
slideCount: carousel.slides.length,
|
|
14340
|
+
slides: carousel.slides.map((s) => {
|
|
14341
|
+
const job = imageJobs.find((j) => j.slideNumber === s.slideNumber);
|
|
14342
|
+
return {
|
|
14343
|
+
...s,
|
|
14344
|
+
imageJobId: job?.jobId ?? null,
|
|
14345
|
+
imageError: job?.error ?? null
|
|
14346
|
+
};
|
|
14347
|
+
}),
|
|
14348
|
+
imageModel: image_model,
|
|
14349
|
+
brandApplied: brandContext ? {
|
|
14350
|
+
brandName: brandContext.brandName,
|
|
14351
|
+
hasLogo: !!brandContext.logoDescription,
|
|
14352
|
+
stylePrefix: brandContext.stylePrefix
|
|
14353
|
+
} : null,
|
|
14354
|
+
jobIds: successfulJobs.map((j) => j.jobId),
|
|
14355
|
+
failedSlides: failedJobs.map((j) => ({
|
|
14356
|
+
slideNumber: j.slideNumber,
|
|
14357
|
+
error: j.error
|
|
14358
|
+
})),
|
|
14359
|
+
credits: {
|
|
14360
|
+
textGeneration: textCredits,
|
|
14361
|
+
imagesEstimated: successfulJobs.length * perImageCost,
|
|
14362
|
+
totalEstimated: textCredits + successfulJobs.length * perImageCost
|
|
14363
|
+
}
|
|
14364
|
+
}
|
|
14365
|
+
},
|
|
14366
|
+
null,
|
|
14367
|
+
2
|
|
14368
|
+
)
|
|
14369
|
+
}
|
|
14370
|
+
]
|
|
14371
|
+
};
|
|
14372
|
+
}
|
|
14373
|
+
const lines = [
|
|
14374
|
+
`Carousel created: ${carousel.slides.length} slides + ${successfulJobs.length} image jobs started.`,
|
|
14375
|
+
` Carousel ID: ${carousel.id}`,
|
|
14376
|
+
` Template: ${templateId} | Style: ${resolvedStyle}`,
|
|
14377
|
+
` Image model: ${image_model}`,
|
|
14378
|
+
` Credits: ~${textCredits + successfulJobs.length * perImageCost} (${textCredits} text + ${successfulJobs.length * perImageCost} images)`
|
|
14379
|
+
];
|
|
14380
|
+
if (brandContext) {
|
|
14381
|
+
lines.push(
|
|
14382
|
+
` Brand: ${brandContext.brandName || "unnamed"}${brandContext.logoDescription ? " (logo overlay via prompt)" : ""}`
|
|
14383
|
+
);
|
|
14384
|
+
}
|
|
14385
|
+
lines.push("", "Slides:");
|
|
14386
|
+
for (const slide of carousel.slides) {
|
|
14387
|
+
const job = imageJobs.find((j) => j.slideNumber === slide.slideNumber);
|
|
14388
|
+
const status = job?.jobId ? `image: ${job.jobId}` : `image FAILED: ${job?.error}`;
|
|
14389
|
+
lines.push(` ${slide.slideNumber}. ${slide.headline || "(no headline)"} [${status}]`);
|
|
14390
|
+
}
|
|
14391
|
+
if (failedJobs.length > 0) {
|
|
14392
|
+
lines.push("");
|
|
14393
|
+
lines.push(
|
|
14394
|
+
`WARNING: ${failedJobs.length}/${imageJobs.length} image generations failed. Use generate_image manually for failed slides.`
|
|
14395
|
+
);
|
|
14396
|
+
}
|
|
14397
|
+
const jobIdList = successfulJobs.map((j) => j.jobId).join(", ");
|
|
14398
|
+
lines.push("");
|
|
14399
|
+
lines.push("Next steps:");
|
|
14400
|
+
lines.push(` 1. Poll each job: check_status with job_id for each of: ${jobIdList}`);
|
|
14401
|
+
lines.push(
|
|
14402
|
+
" 2. When all complete: schedule_post with job_ids=[...] and media_type=CAROUSEL_ALBUM"
|
|
14403
|
+
);
|
|
14404
|
+
return {
|
|
14405
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
14406
|
+
};
|
|
14407
|
+
}
|
|
14408
|
+
);
|
|
14409
|
+
}
|
|
14410
|
+
|
|
13463
14411
|
// src/lib/register-tools.ts
|
|
13464
14412
|
function applyScopeEnforcement(server2, scopeResolver) {
|
|
13465
14413
|
const originalTool = server2.tool.bind(server2);
|
|
@@ -13554,6 +14502,7 @@ function registerAllTools(server2, options) {
|
|
|
13554
14502
|
registerLoopSummaryTools(server2);
|
|
13555
14503
|
registerUsageTools(server2);
|
|
13556
14504
|
registerAutopilotTools(server2);
|
|
14505
|
+
registerRecipeTools(server2);
|
|
13557
14506
|
registerExtractionTools(server2);
|
|
13558
14507
|
registerQualityTools(server2);
|
|
13559
14508
|
registerPlanningTools(server2);
|
|
@@ -13563,21 +14512,22 @@ function registerAllTools(server2, options) {
|
|
|
13563
14512
|
registerSuggestTools(server2);
|
|
13564
14513
|
registerDigestTools(server2);
|
|
13565
14514
|
registerBrandRuntimeTools(server2);
|
|
14515
|
+
registerCarouselTools(server2);
|
|
13566
14516
|
applyAnnotations(server2);
|
|
13567
14517
|
}
|
|
13568
14518
|
|
|
13569
14519
|
// src/prompts.ts
|
|
13570
|
-
import { z as
|
|
14520
|
+
import { z as z28 } from "zod";
|
|
13571
14521
|
function registerPrompts(server2) {
|
|
13572
14522
|
server2.prompt(
|
|
13573
14523
|
"create_weekly_content_plan",
|
|
13574
14524
|
"Generate a full week of social media content (7 days, multiple platforms). Returns a structured plan with topics, formats, and posting times.",
|
|
13575
14525
|
{
|
|
13576
|
-
niche:
|
|
13577
|
-
platforms:
|
|
14526
|
+
niche: z28.string().describe('Your content niche or industry (e.g., "fitness coaching", "SaaS marketing")'),
|
|
14527
|
+
platforms: z28.string().optional().describe(
|
|
13578
14528
|
'Comma-separated platforms to target (default: "YouTube, Instagram, TikTok, LinkedIn")'
|
|
13579
14529
|
),
|
|
13580
|
-
tone:
|
|
14530
|
+
tone: z28.string().optional().describe('Brand tone of voice (e.g., "professional", "casual", "bold and edgy")')
|
|
13581
14531
|
},
|
|
13582
14532
|
({ niche, platforms, tone }) => {
|
|
13583
14533
|
const targetPlatforms = platforms || "YouTube, Instagram, TikTok, LinkedIn";
|
|
@@ -13619,8 +14569,8 @@ After building the plan, use \`save_content_plan\` to save it.`
|
|
|
13619
14569
|
"analyze_top_content",
|
|
13620
14570
|
"Analyze your best-performing posts to identify patterns and replicate success. Returns insights on hooks, formats, timing, and topics that resonate.",
|
|
13621
14571
|
{
|
|
13622
|
-
timeframe:
|
|
13623
|
-
platform:
|
|
14572
|
+
timeframe: z28.string().optional().describe('Analysis period (default: "30 days"). E.g., "7 days", "90 days"'),
|
|
14573
|
+
platform: z28.string().optional().describe('Filter to a specific platform (e.g., "youtube", "instagram")')
|
|
13624
14574
|
},
|
|
13625
14575
|
({ timeframe, platform: platform3 }) => {
|
|
13626
14576
|
const period = timeframe || "30 days";
|
|
@@ -13657,10 +14607,10 @@ Format as a clear, actionable performance report.`
|
|
|
13657
14607
|
"repurpose_content",
|
|
13658
14608
|
"Take one piece of content and transform it into 8-10 pieces across multiple platforms and formats.",
|
|
13659
14609
|
{
|
|
13660
|
-
source:
|
|
14610
|
+
source: z28.string().describe(
|
|
13661
14611
|
"The source content to repurpose \u2014 a URL, transcript, or the content text itself"
|
|
13662
14612
|
),
|
|
13663
|
-
target_platforms:
|
|
14613
|
+
target_platforms: z28.string().optional().describe(
|
|
13664
14614
|
'Comma-separated target platforms (default: "Twitter, LinkedIn, Instagram, TikTok, YouTube")'
|
|
13665
14615
|
)
|
|
13666
14616
|
},
|
|
@@ -13702,9 +14652,9 @@ For each piece, include the platform, format, character count, and suggested pos
|
|
|
13702
14652
|
"setup_brand_voice",
|
|
13703
14653
|
"Define or refine your brand voice profile so all generated content stays on-brand. Walks through tone, audience, values, and style.",
|
|
13704
14654
|
{
|
|
13705
|
-
brand_name:
|
|
13706
|
-
industry:
|
|
13707
|
-
website:
|
|
14655
|
+
brand_name: z28.string().describe("Your brand or business name"),
|
|
14656
|
+
industry: z28.string().optional().describe('Your industry or niche (e.g., "B2B SaaS", "fitness coaching")'),
|
|
14657
|
+
website: z28.string().optional().describe("Your website URL for context")
|
|
13708
14658
|
},
|
|
13709
14659
|
({ brand_name, industry, website }) => {
|
|
13710
14660
|
const industryContext = industry ? ` in the ${industry} space` : "";
|