@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/http.js
CHANGED
|
@@ -570,6 +570,8 @@ var TOOL_SCOPES = {
|
|
|
570
570
|
get_brand_runtime: "mcp:read",
|
|
571
571
|
explain_brand_system: "mcp:read",
|
|
572
572
|
check_brand_consistency: "mcp:read",
|
|
573
|
+
audit_brand_colors: "mcp:read",
|
|
574
|
+
export_design_tokens: "mcp:read",
|
|
573
575
|
get_ideation_context: "mcp:read",
|
|
574
576
|
get_credit_balance: "mcp:read",
|
|
575
577
|
get_budget_status: "mcp:read",
|
|
@@ -591,6 +593,7 @@ var TOOL_SCOPES = {
|
|
|
591
593
|
create_storyboard: "mcp:write",
|
|
592
594
|
generate_voiceover: "mcp:write",
|
|
593
595
|
generate_carousel: "mcp:write",
|
|
596
|
+
create_carousel: "mcp:write",
|
|
594
597
|
upload_media: "mcp:write",
|
|
595
598
|
// mcp:read (media)
|
|
596
599
|
get_media_url: "mcp:read",
|
|
@@ -609,6 +612,11 @@ var TOOL_SCOPES = {
|
|
|
609
612
|
list_autopilot_configs: "mcp:autopilot",
|
|
610
613
|
update_autopilot_config: "mcp:autopilot",
|
|
611
614
|
get_autopilot_status: "mcp:autopilot",
|
|
615
|
+
// Recipes
|
|
616
|
+
list_recipes: "mcp:read",
|
|
617
|
+
get_recipe_details: "mcp:read",
|
|
618
|
+
execute_recipe: "mcp:write",
|
|
619
|
+
get_recipe_run_status: "mcp:read",
|
|
612
620
|
// mcp:read (content lifecycle — read-only tools)
|
|
613
621
|
extract_url_content: "mcp:read",
|
|
614
622
|
quality_check: "mcp:read",
|
|
@@ -1375,7 +1383,7 @@ init_supabase();
|
|
|
1375
1383
|
init_request_context();
|
|
1376
1384
|
|
|
1377
1385
|
// src/lib/version.ts
|
|
1378
|
-
var MCP_VERSION = "1.7.
|
|
1386
|
+
var MCP_VERSION = "1.7.4";
|
|
1379
1387
|
|
|
1380
1388
|
// src/tools/content.ts
|
|
1381
1389
|
var MAX_CREDITS_PER_RUN = Math.max(0, Number(process.env.SOCIALNEURON_MAX_CREDITS_PER_RUN || 0));
|
|
@@ -7130,10 +7138,276 @@ Active: ${is_active}`
|
|
|
7130
7138
|
);
|
|
7131
7139
|
}
|
|
7132
7140
|
|
|
7133
|
-
// src/tools/
|
|
7141
|
+
// src/tools/recipes.ts
|
|
7134
7142
|
import { z as z17 } from "zod";
|
|
7135
|
-
init_supabase();
|
|
7136
7143
|
function asEnvelope13(data) {
|
|
7144
|
+
return {
|
|
7145
|
+
_meta: {
|
|
7146
|
+
version: MCP_VERSION,
|
|
7147
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
7148
|
+
},
|
|
7149
|
+
data
|
|
7150
|
+
};
|
|
7151
|
+
}
|
|
7152
|
+
function registerRecipeTools(server) {
|
|
7153
|
+
server.tool(
|
|
7154
|
+
"list_recipes",
|
|
7155
|
+
'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.',
|
|
7156
|
+
{
|
|
7157
|
+
category: z17.enum([
|
|
7158
|
+
"content_creation",
|
|
7159
|
+
"distribution",
|
|
7160
|
+
"repurposing",
|
|
7161
|
+
"analytics",
|
|
7162
|
+
"engagement",
|
|
7163
|
+
"general"
|
|
7164
|
+
]).optional().describe("Filter by category. Omit to list all."),
|
|
7165
|
+
featured_only: z17.boolean().optional().describe("If true, only return featured recipes. Defaults to false."),
|
|
7166
|
+
response_format: z17.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
7167
|
+
},
|
|
7168
|
+
async ({ category, featured_only, response_format }) => {
|
|
7169
|
+
const format = response_format ?? "text";
|
|
7170
|
+
const { data: result, error: efError } = await callEdgeFunction("mcp-data", {
|
|
7171
|
+
action: "list-recipes",
|
|
7172
|
+
category: category ?? null,
|
|
7173
|
+
featured_only: featured_only ?? false
|
|
7174
|
+
});
|
|
7175
|
+
if (efError) {
|
|
7176
|
+
return {
|
|
7177
|
+
content: [
|
|
7178
|
+
{
|
|
7179
|
+
type: "text",
|
|
7180
|
+
text: `Error fetching recipes: ${efError}`
|
|
7181
|
+
}
|
|
7182
|
+
],
|
|
7183
|
+
isError: true
|
|
7184
|
+
};
|
|
7185
|
+
}
|
|
7186
|
+
const recipes = result?.recipes ?? [];
|
|
7187
|
+
if (format === "json") {
|
|
7188
|
+
return {
|
|
7189
|
+
content: [
|
|
7190
|
+
{
|
|
7191
|
+
type: "text",
|
|
7192
|
+
text: JSON.stringify(asEnvelope13(recipes))
|
|
7193
|
+
}
|
|
7194
|
+
]
|
|
7195
|
+
};
|
|
7196
|
+
}
|
|
7197
|
+
if (recipes.length === 0) {
|
|
7198
|
+
return {
|
|
7199
|
+
content: [
|
|
7200
|
+
{
|
|
7201
|
+
type: "text",
|
|
7202
|
+
text: "No recipes found. Recipes are pre-built automation templates \u2014 check back after setup."
|
|
7203
|
+
}
|
|
7204
|
+
]
|
|
7205
|
+
};
|
|
7206
|
+
}
|
|
7207
|
+
const lines = recipes.map(
|
|
7208
|
+
(r) => `**${r.name}** (${r.slug})
|
|
7209
|
+
${r.description}
|
|
7210
|
+
Category: ${r.category} | Credits: ~${r.estimated_credits} | Steps: ${r.steps.length}${r.is_featured ? " | \u2B50 Featured" : ""}
|
|
7211
|
+
Inputs: ${r.inputs_schema.map((i) => `${i.label}${i.required ? "*" : ""}`).join(", ")}`
|
|
7212
|
+
);
|
|
7213
|
+
return {
|
|
7214
|
+
content: [
|
|
7215
|
+
{
|
|
7216
|
+
type: "text",
|
|
7217
|
+
text: `## Available Recipes (${recipes.length})
|
|
7218
|
+
|
|
7219
|
+
${lines.join("\n\n")}`
|
|
7220
|
+
}
|
|
7221
|
+
]
|
|
7222
|
+
};
|
|
7223
|
+
}
|
|
7224
|
+
);
|
|
7225
|
+
server.tool(
|
|
7226
|
+
"get_recipe_details",
|
|
7227
|
+
"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.",
|
|
7228
|
+
{
|
|
7229
|
+
slug: z17.string().describe('Recipe slug (e.g., "weekly-instagram-calendar")'),
|
|
7230
|
+
response_format: z17.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
7231
|
+
},
|
|
7232
|
+
async ({ slug, response_format }) => {
|
|
7233
|
+
const format = response_format ?? "text";
|
|
7234
|
+
const { data: result, error: efError } = await callEdgeFunction("mcp-data", {
|
|
7235
|
+
action: "get-recipe-details",
|
|
7236
|
+
slug
|
|
7237
|
+
});
|
|
7238
|
+
if (efError) {
|
|
7239
|
+
return {
|
|
7240
|
+
content: [{ type: "text", text: `Error: ${efError}` }],
|
|
7241
|
+
isError: true
|
|
7242
|
+
};
|
|
7243
|
+
}
|
|
7244
|
+
const recipe = result?.recipe;
|
|
7245
|
+
if (!recipe) {
|
|
7246
|
+
return {
|
|
7247
|
+
content: [
|
|
7248
|
+
{
|
|
7249
|
+
type: "text",
|
|
7250
|
+
text: `Recipe "${slug}" not found. Use list_recipes to see available recipes.`
|
|
7251
|
+
}
|
|
7252
|
+
],
|
|
7253
|
+
isError: true
|
|
7254
|
+
};
|
|
7255
|
+
}
|
|
7256
|
+
if (format === "json") {
|
|
7257
|
+
return {
|
|
7258
|
+
content: [
|
|
7259
|
+
{
|
|
7260
|
+
type: "text",
|
|
7261
|
+
text: JSON.stringify(asEnvelope13(recipe))
|
|
7262
|
+
}
|
|
7263
|
+
]
|
|
7264
|
+
};
|
|
7265
|
+
}
|
|
7266
|
+
const stepsText = recipe.steps.map((s, i) => ` ${i + 1}. **${s.name}** (${s.type})`).join("\n");
|
|
7267
|
+
const inputsText = recipe.inputs_schema.map(
|
|
7268
|
+
(i) => ` - **${i.label}**${i.required ? " (required)" : ""}: ${i.type}${i.placeholder ? ` \u2014 e.g., "${i.placeholder}"` : ""}`
|
|
7269
|
+
).join("\n");
|
|
7270
|
+
return {
|
|
7271
|
+
content: [
|
|
7272
|
+
{
|
|
7273
|
+
type: "text",
|
|
7274
|
+
text: [
|
|
7275
|
+
`## ${recipe.name}`,
|
|
7276
|
+
recipe.description,
|
|
7277
|
+
"",
|
|
7278
|
+
`**Category:** ${recipe.category}`,
|
|
7279
|
+
`**Estimated credits:** ~${recipe.estimated_credits}`,
|
|
7280
|
+
`**Estimated time:** ~${Math.round(recipe.estimated_duration_seconds / 60)} minutes`,
|
|
7281
|
+
"",
|
|
7282
|
+
"### Steps",
|
|
7283
|
+
stepsText,
|
|
7284
|
+
"",
|
|
7285
|
+
"### Required Inputs",
|
|
7286
|
+
inputsText
|
|
7287
|
+
].join("\n")
|
|
7288
|
+
}
|
|
7289
|
+
]
|
|
7290
|
+
};
|
|
7291
|
+
}
|
|
7292
|
+
);
|
|
7293
|
+
server.tool(
|
|
7294
|
+
"execute_recipe",
|
|
7295
|
+
"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.",
|
|
7296
|
+
{
|
|
7297
|
+
slug: z17.string().describe('Recipe slug (e.g., "weekly-instagram-calendar")'),
|
|
7298
|
+
inputs: z17.record(z17.unknown()).describe(
|
|
7299
|
+
"Input values matching the recipe input schema. Use get_recipe_details to see required inputs."
|
|
7300
|
+
),
|
|
7301
|
+
response_format: z17.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
7302
|
+
},
|
|
7303
|
+
async ({ slug, inputs, response_format }) => {
|
|
7304
|
+
const format = response_format ?? "text";
|
|
7305
|
+
const { data: result, error: efError } = await callEdgeFunction("mcp-data", {
|
|
7306
|
+
action: "execute-recipe",
|
|
7307
|
+
slug,
|
|
7308
|
+
inputs
|
|
7309
|
+
});
|
|
7310
|
+
if (efError) {
|
|
7311
|
+
return {
|
|
7312
|
+
content: [{ type: "text", text: `Error: ${efError}` }],
|
|
7313
|
+
isError: true
|
|
7314
|
+
};
|
|
7315
|
+
}
|
|
7316
|
+
if (format === "json") {
|
|
7317
|
+
return {
|
|
7318
|
+
content: [
|
|
7319
|
+
{
|
|
7320
|
+
type: "text",
|
|
7321
|
+
text: JSON.stringify(asEnvelope13(result))
|
|
7322
|
+
}
|
|
7323
|
+
]
|
|
7324
|
+
};
|
|
7325
|
+
}
|
|
7326
|
+
return {
|
|
7327
|
+
content: [
|
|
7328
|
+
{
|
|
7329
|
+
type: "text",
|
|
7330
|
+
text: `Recipe "${slug}" started.
|
|
7331
|
+
|
|
7332
|
+
**Run ID:** ${result?.run_id}
|
|
7333
|
+
**Status:** ${result?.status}
|
|
7334
|
+
|
|
7335
|
+
${result?.message || "Use get_recipe_run_status to check progress."}`
|
|
7336
|
+
}
|
|
7337
|
+
]
|
|
7338
|
+
};
|
|
7339
|
+
}
|
|
7340
|
+
);
|
|
7341
|
+
server.tool(
|
|
7342
|
+
"get_recipe_run_status",
|
|
7343
|
+
"Check the status of a running recipe execution. Shows progress, current step, credits used, and outputs when complete.",
|
|
7344
|
+
{
|
|
7345
|
+
run_id: z17.string().describe("The recipe run ID returned by execute_recipe"),
|
|
7346
|
+
response_format: z17.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
7347
|
+
},
|
|
7348
|
+
async ({ run_id, response_format }) => {
|
|
7349
|
+
const format = response_format ?? "text";
|
|
7350
|
+
const { data: result, error: efError } = await callEdgeFunction("mcp-data", {
|
|
7351
|
+
action: "get-recipe-run-status",
|
|
7352
|
+
run_id
|
|
7353
|
+
});
|
|
7354
|
+
if (efError) {
|
|
7355
|
+
return {
|
|
7356
|
+
content: [{ type: "text", text: `Error: ${efError}` }],
|
|
7357
|
+
isError: true
|
|
7358
|
+
};
|
|
7359
|
+
}
|
|
7360
|
+
const run = result?.run;
|
|
7361
|
+
if (!run) {
|
|
7362
|
+
return {
|
|
7363
|
+
content: [
|
|
7364
|
+
{
|
|
7365
|
+
type: "text",
|
|
7366
|
+
text: `Run "${run_id}" not found.`
|
|
7367
|
+
}
|
|
7368
|
+
],
|
|
7369
|
+
isError: true
|
|
7370
|
+
};
|
|
7371
|
+
}
|
|
7372
|
+
if (format === "json") {
|
|
7373
|
+
return {
|
|
7374
|
+
content: [
|
|
7375
|
+
{
|
|
7376
|
+
type: "text",
|
|
7377
|
+
text: JSON.stringify(asEnvelope13(run))
|
|
7378
|
+
}
|
|
7379
|
+
]
|
|
7380
|
+
};
|
|
7381
|
+
}
|
|
7382
|
+
const statusEmoji = run.status === "completed" ? "Done" : run.status === "failed" ? "Failed" : run.status === "running" ? "Running" : run.status;
|
|
7383
|
+
return {
|
|
7384
|
+
content: [
|
|
7385
|
+
{
|
|
7386
|
+
type: "text",
|
|
7387
|
+
text: [
|
|
7388
|
+
`**Recipe Run:** ${run.id}`,
|
|
7389
|
+
`**Status:** ${statusEmoji}`,
|
|
7390
|
+
`**Progress:** ${run.progress}%`,
|
|
7391
|
+
run.current_step ? `**Current step:** ${run.current_step}` : "",
|
|
7392
|
+
`**Credits used:** ${run.credits_used}`,
|
|
7393
|
+
run.completed_at ? `**Completed:** ${run.completed_at}` : "",
|
|
7394
|
+
run.outputs ? `
|
|
7395
|
+
**Outputs:**
|
|
7396
|
+
\`\`\`json
|
|
7397
|
+
${JSON.stringify(run.outputs, null, 2)}
|
|
7398
|
+
\`\`\`` : ""
|
|
7399
|
+
].filter(Boolean).join("\n")
|
|
7400
|
+
}
|
|
7401
|
+
]
|
|
7402
|
+
};
|
|
7403
|
+
}
|
|
7404
|
+
);
|
|
7405
|
+
}
|
|
7406
|
+
|
|
7407
|
+
// src/tools/extraction.ts
|
|
7408
|
+
import { z as z18 } from "zod";
|
|
7409
|
+
init_supabase();
|
|
7410
|
+
function asEnvelope14(data) {
|
|
7137
7411
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
7138
7412
|
}
|
|
7139
7413
|
function isYouTubeUrl(url) {
|
|
@@ -7187,11 +7461,11 @@ function registerExtractionTools(server) {
|
|
|
7187
7461
|
"extract_url_content",
|
|
7188
7462
|
"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.",
|
|
7189
7463
|
{
|
|
7190
|
-
url:
|
|
7191
|
-
extract_type:
|
|
7192
|
-
include_comments:
|
|
7193
|
-
max_results:
|
|
7194
|
-
response_format:
|
|
7464
|
+
url: z18.string().url().describe("URL to extract content from"),
|
|
7465
|
+
extract_type: z18.enum(["auto", "transcript", "article", "product"]).default("auto").describe("Type of extraction"),
|
|
7466
|
+
include_comments: z18.boolean().default(false).describe("Include top comments (YouTube only)"),
|
|
7467
|
+
max_results: z18.number().min(1).max(100).default(10).describe("Max comments to include"),
|
|
7468
|
+
response_format: z18.enum(["text", "json"]).default("text")
|
|
7195
7469
|
},
|
|
7196
7470
|
async ({ url, extract_type, include_comments, max_results, response_format }) => {
|
|
7197
7471
|
const startedAt = Date.now();
|
|
@@ -7326,7 +7600,7 @@ function registerExtractionTools(server) {
|
|
|
7326
7600
|
if (response_format === "json") {
|
|
7327
7601
|
return {
|
|
7328
7602
|
content: [
|
|
7329
|
-
{ type: "text", text: JSON.stringify(
|
|
7603
|
+
{ type: "text", text: JSON.stringify(asEnvelope14(extracted), null, 2) }
|
|
7330
7604
|
],
|
|
7331
7605
|
isError: false
|
|
7332
7606
|
};
|
|
@@ -7354,9 +7628,9 @@ function registerExtractionTools(server) {
|
|
|
7354
7628
|
}
|
|
7355
7629
|
|
|
7356
7630
|
// src/tools/quality.ts
|
|
7357
|
-
import { z as
|
|
7631
|
+
import { z as z19 } from "zod";
|
|
7358
7632
|
init_supabase();
|
|
7359
|
-
function
|
|
7633
|
+
function asEnvelope15(data) {
|
|
7360
7634
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
7361
7635
|
}
|
|
7362
7636
|
function registerQualityTools(server) {
|
|
@@ -7364,12 +7638,12 @@ function registerQualityTools(server) {
|
|
|
7364
7638
|
"quality_check",
|
|
7365
7639
|
"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.",
|
|
7366
7640
|
{
|
|
7367
|
-
caption:
|
|
7641
|
+
caption: z19.string().describe(
|
|
7368
7642
|
"The post text to score. Include hashtags if they will be published \u2014 they affect Platform Fit and Safety/Claims scores."
|
|
7369
7643
|
),
|
|
7370
|
-
title:
|
|
7371
|
-
platforms:
|
|
7372
|
-
|
|
7644
|
+
title: z19.string().optional().describe("Post title (important for YouTube)"),
|
|
7645
|
+
platforms: z19.array(
|
|
7646
|
+
z19.enum([
|
|
7373
7647
|
"youtube",
|
|
7374
7648
|
"tiktok",
|
|
7375
7649
|
"instagram",
|
|
@@ -7380,13 +7654,13 @@ function registerQualityTools(server) {
|
|
|
7380
7654
|
"bluesky"
|
|
7381
7655
|
])
|
|
7382
7656
|
).min(1).describe("Target platforms"),
|
|
7383
|
-
threshold:
|
|
7657
|
+
threshold: z19.number().min(0).max(35).default(26).describe(
|
|
7384
7658
|
"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."
|
|
7385
7659
|
),
|
|
7386
|
-
brand_keyword:
|
|
7387
|
-
brand_avoid_patterns:
|
|
7388
|
-
custom_banned_terms:
|
|
7389
|
-
response_format:
|
|
7660
|
+
brand_keyword: z19.string().optional().describe("Brand keyword for alignment check"),
|
|
7661
|
+
brand_avoid_patterns: z19.array(z19.string()).optional(),
|
|
7662
|
+
custom_banned_terms: z19.array(z19.string()).optional(),
|
|
7663
|
+
response_format: z19.enum(["text", "json"]).default("text")
|
|
7390
7664
|
},
|
|
7391
7665
|
async ({
|
|
7392
7666
|
caption,
|
|
@@ -7417,7 +7691,7 @@ function registerQualityTools(server) {
|
|
|
7417
7691
|
});
|
|
7418
7692
|
if (response_format === "json") {
|
|
7419
7693
|
return {
|
|
7420
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
7694
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope15(result), null, 2) }],
|
|
7421
7695
|
isError: false
|
|
7422
7696
|
};
|
|
7423
7697
|
}
|
|
@@ -7445,20 +7719,20 @@ function registerQualityTools(server) {
|
|
|
7445
7719
|
"quality_check_plan",
|
|
7446
7720
|
"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.",
|
|
7447
7721
|
{
|
|
7448
|
-
plan:
|
|
7449
|
-
posts:
|
|
7450
|
-
|
|
7451
|
-
id:
|
|
7452
|
-
caption:
|
|
7453
|
-
title:
|
|
7454
|
-
platform:
|
|
7722
|
+
plan: z19.object({
|
|
7723
|
+
posts: z19.array(
|
|
7724
|
+
z19.object({
|
|
7725
|
+
id: z19.string(),
|
|
7726
|
+
caption: z19.string(),
|
|
7727
|
+
title: z19.string().optional(),
|
|
7728
|
+
platform: z19.string()
|
|
7455
7729
|
})
|
|
7456
7730
|
)
|
|
7457
7731
|
}).passthrough().describe("Content plan with posts array"),
|
|
7458
|
-
threshold:
|
|
7732
|
+
threshold: z19.number().min(0).max(35).default(26).describe(
|
|
7459
7733
|
"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."
|
|
7460
7734
|
),
|
|
7461
|
-
response_format:
|
|
7735
|
+
response_format: z19.enum(["text", "json"]).default("text")
|
|
7462
7736
|
},
|
|
7463
7737
|
async ({ plan, threshold, response_format }) => {
|
|
7464
7738
|
const startedAt = Date.now();
|
|
@@ -7500,7 +7774,7 @@ function registerQualityTools(server) {
|
|
|
7500
7774
|
content: [
|
|
7501
7775
|
{
|
|
7502
7776
|
type: "text",
|
|
7503
|
-
text: JSON.stringify(
|
|
7777
|
+
text: JSON.stringify(asEnvelope15({ posts: postsWithQuality, summary }), null, 2)
|
|
7504
7778
|
}
|
|
7505
7779
|
],
|
|
7506
7780
|
isError: false
|
|
@@ -7524,7 +7798,7 @@ function registerQualityTools(server) {
|
|
|
7524
7798
|
}
|
|
7525
7799
|
|
|
7526
7800
|
// src/tools/planning.ts
|
|
7527
|
-
import { z as
|
|
7801
|
+
import { z as z20 } from "zod";
|
|
7528
7802
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
7529
7803
|
init_supabase();
|
|
7530
7804
|
|
|
@@ -7552,7 +7826,7 @@ function extractJsonArray(text) {
|
|
|
7552
7826
|
function toRecord(value) {
|
|
7553
7827
|
return value && typeof value === "object" ? value : void 0;
|
|
7554
7828
|
}
|
|
7555
|
-
function
|
|
7829
|
+
function asEnvelope16(data) {
|
|
7556
7830
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
7557
7831
|
}
|
|
7558
7832
|
function tomorrowIsoDate() {
|
|
@@ -7622,10 +7896,10 @@ function registerPlanningTools(server) {
|
|
|
7622
7896
|
"plan_content_week",
|
|
7623
7897
|
"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.",
|
|
7624
7898
|
{
|
|
7625
|
-
topic:
|
|
7626
|
-
source_url:
|
|
7627
|
-
platforms:
|
|
7628
|
-
|
|
7899
|
+
topic: z20.string().describe("Main topic or content theme"),
|
|
7900
|
+
source_url: z20.string().optional().describe("URL to extract content from (YouTube, article)"),
|
|
7901
|
+
platforms: z20.array(
|
|
7902
|
+
z20.enum([
|
|
7629
7903
|
"youtube",
|
|
7630
7904
|
"tiktok",
|
|
7631
7905
|
"instagram",
|
|
@@ -7636,12 +7910,12 @@ function registerPlanningTools(server) {
|
|
|
7636
7910
|
"bluesky"
|
|
7637
7911
|
])
|
|
7638
7912
|
).min(1).describe("Target platforms"),
|
|
7639
|
-
posts_per_day:
|
|
7640
|
-
days:
|
|
7641
|
-
start_date:
|
|
7642
|
-
brand_voice:
|
|
7643
|
-
project_id:
|
|
7644
|
-
response_format:
|
|
7913
|
+
posts_per_day: z20.number().min(1).max(5).default(1).describe("Posts per platform per day"),
|
|
7914
|
+
days: z20.number().min(1).max(7).default(5).describe("Number of days to plan"),
|
|
7915
|
+
start_date: z20.string().optional().describe("ISO date, defaults to tomorrow"),
|
|
7916
|
+
brand_voice: z20.string().optional().describe("Override brand voice description"),
|
|
7917
|
+
project_id: z20.string().optional().describe("Project ID for brand/insights context"),
|
|
7918
|
+
response_format: z20.enum(["text", "json"]).default("json")
|
|
7645
7919
|
},
|
|
7646
7920
|
async ({
|
|
7647
7921
|
topic,
|
|
@@ -7915,7 +8189,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
7915
8189
|
});
|
|
7916
8190
|
if (response_format === "json") {
|
|
7917
8191
|
return {
|
|
7918
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
8192
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope16(plan), null, 2) }],
|
|
7919
8193
|
isError: false
|
|
7920
8194
|
};
|
|
7921
8195
|
}
|
|
@@ -7943,13 +8217,13 @@ ${rawText.slice(0, 1e3)}`
|
|
|
7943
8217
|
"save_content_plan",
|
|
7944
8218
|
"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.",
|
|
7945
8219
|
{
|
|
7946
|
-
plan:
|
|
7947
|
-
topic:
|
|
7948
|
-
posts:
|
|
8220
|
+
plan: z20.object({
|
|
8221
|
+
topic: z20.string(),
|
|
8222
|
+
posts: z20.array(z20.record(z20.string(), z20.unknown()))
|
|
7949
8223
|
}).passthrough(),
|
|
7950
|
-
project_id:
|
|
7951
|
-
status:
|
|
7952
|
-
response_format:
|
|
8224
|
+
project_id: z20.string().uuid().optional(),
|
|
8225
|
+
status: z20.enum(["draft", "in_review", "approved", "scheduled", "completed"]).default("draft"),
|
|
8226
|
+
response_format: z20.enum(["text", "json"]).default("json")
|
|
7953
8227
|
},
|
|
7954
8228
|
async ({ plan, project_id, status, response_format }) => {
|
|
7955
8229
|
const startedAt = Date.now();
|
|
@@ -8005,7 +8279,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
8005
8279
|
};
|
|
8006
8280
|
if (response_format === "json") {
|
|
8007
8281
|
return {
|
|
8008
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
8282
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope16(result), null, 2) }],
|
|
8009
8283
|
isError: false
|
|
8010
8284
|
};
|
|
8011
8285
|
}
|
|
@@ -8035,8 +8309,8 @@ ${rawText.slice(0, 1e3)}`
|
|
|
8035
8309
|
"get_content_plan",
|
|
8036
8310
|
"Retrieve a persisted content plan by ID.",
|
|
8037
8311
|
{
|
|
8038
|
-
plan_id:
|
|
8039
|
-
response_format:
|
|
8312
|
+
plan_id: z20.string().uuid().describe("Persisted content plan ID"),
|
|
8313
|
+
response_format: z20.enum(["text", "json"]).default("json")
|
|
8040
8314
|
},
|
|
8041
8315
|
async ({ plan_id, response_format }) => {
|
|
8042
8316
|
const { data: result, error } = await callEdgeFunction("mcp-data", { action: "get-content-plan", plan_id }, { timeoutMs: 1e4 });
|
|
@@ -8071,7 +8345,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
8071
8345
|
};
|
|
8072
8346
|
if (response_format === "json") {
|
|
8073
8347
|
return {
|
|
8074
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
8348
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope16(payload), null, 2) }],
|
|
8075
8349
|
isError: false
|
|
8076
8350
|
};
|
|
8077
8351
|
}
|
|
@@ -8089,23 +8363,23 @@ ${rawText.slice(0, 1e3)}`
|
|
|
8089
8363
|
"update_content_plan",
|
|
8090
8364
|
"Update individual posts in a persisted content plan.",
|
|
8091
8365
|
{
|
|
8092
|
-
plan_id:
|
|
8093
|
-
post_updates:
|
|
8094
|
-
|
|
8095
|
-
post_id:
|
|
8096
|
-
caption:
|
|
8097
|
-
title:
|
|
8098
|
-
hashtags:
|
|
8099
|
-
hook:
|
|
8100
|
-
angle:
|
|
8101
|
-
visual_direction:
|
|
8102
|
-
media_url:
|
|
8103
|
-
schedule_at:
|
|
8104
|
-
platform:
|
|
8105
|
-
status:
|
|
8366
|
+
plan_id: z20.string().uuid(),
|
|
8367
|
+
post_updates: z20.array(
|
|
8368
|
+
z20.object({
|
|
8369
|
+
post_id: z20.string(),
|
|
8370
|
+
caption: z20.string().optional(),
|
|
8371
|
+
title: z20.string().optional(),
|
|
8372
|
+
hashtags: z20.array(z20.string()).optional(),
|
|
8373
|
+
hook: z20.string().optional(),
|
|
8374
|
+
angle: z20.string().optional(),
|
|
8375
|
+
visual_direction: z20.string().optional(),
|
|
8376
|
+
media_url: z20.string().optional(),
|
|
8377
|
+
schedule_at: z20.string().optional(),
|
|
8378
|
+
platform: z20.string().optional(),
|
|
8379
|
+
status: z20.enum(["approved", "rejected", "needs_edit"]).optional()
|
|
8106
8380
|
})
|
|
8107
8381
|
).min(1),
|
|
8108
|
-
response_format:
|
|
8382
|
+
response_format: z20.enum(["text", "json"]).default("json")
|
|
8109
8383
|
},
|
|
8110
8384
|
async ({ plan_id, post_updates, response_format }) => {
|
|
8111
8385
|
const { data: result, error } = await callEdgeFunction(
|
|
@@ -8143,7 +8417,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
8143
8417
|
};
|
|
8144
8418
|
if (response_format === "json") {
|
|
8145
8419
|
return {
|
|
8146
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
8420
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope16(payload), null, 2) }],
|
|
8147
8421
|
isError: false
|
|
8148
8422
|
};
|
|
8149
8423
|
}
|
|
@@ -8162,8 +8436,8 @@ ${rawText.slice(0, 1e3)}`
|
|
|
8162
8436
|
"submit_content_plan_for_approval",
|
|
8163
8437
|
"Create pending approval items for each post in a plan and mark plan status as in_review.",
|
|
8164
8438
|
{
|
|
8165
|
-
plan_id:
|
|
8166
|
-
response_format:
|
|
8439
|
+
plan_id: z20.string().uuid(),
|
|
8440
|
+
response_format: z20.enum(["text", "json"]).default("json")
|
|
8167
8441
|
},
|
|
8168
8442
|
async ({ plan_id, response_format }) => {
|
|
8169
8443
|
const { data: result, error } = await callEdgeFunction("mcp-data", { action: "submit-plan-approval", plan_id }, { timeoutMs: 15e3 });
|
|
@@ -8196,7 +8470,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
8196
8470
|
};
|
|
8197
8471
|
if (response_format === "json") {
|
|
8198
8472
|
return {
|
|
8199
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
8473
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope16(payload), null, 2) }],
|
|
8200
8474
|
isError: false
|
|
8201
8475
|
};
|
|
8202
8476
|
}
|
|
@@ -8214,9 +8488,9 @@ ${rawText.slice(0, 1e3)}`
|
|
|
8214
8488
|
}
|
|
8215
8489
|
|
|
8216
8490
|
// src/tools/plan-approvals.ts
|
|
8217
|
-
import { z as
|
|
8491
|
+
import { z as z21 } from "zod";
|
|
8218
8492
|
init_supabase();
|
|
8219
|
-
function
|
|
8493
|
+
function asEnvelope17(data) {
|
|
8220
8494
|
return {
|
|
8221
8495
|
_meta: {
|
|
8222
8496
|
version: MCP_VERSION,
|
|
@@ -8230,19 +8504,19 @@ function registerPlanApprovalTools(server) {
|
|
|
8230
8504
|
"create_plan_approvals",
|
|
8231
8505
|
"Create pending approval rows for each post in a content plan.",
|
|
8232
8506
|
{
|
|
8233
|
-
plan_id:
|
|
8234
|
-
posts:
|
|
8235
|
-
|
|
8236
|
-
id:
|
|
8237
|
-
platform:
|
|
8238
|
-
caption:
|
|
8239
|
-
title:
|
|
8240
|
-
media_url:
|
|
8241
|
-
schedule_at:
|
|
8507
|
+
plan_id: z21.string().uuid().describe("Content plan ID"),
|
|
8508
|
+
posts: z21.array(
|
|
8509
|
+
z21.object({
|
|
8510
|
+
id: z21.string(),
|
|
8511
|
+
platform: z21.string().optional(),
|
|
8512
|
+
caption: z21.string().optional(),
|
|
8513
|
+
title: z21.string().optional(),
|
|
8514
|
+
media_url: z21.string().optional(),
|
|
8515
|
+
schedule_at: z21.string().optional()
|
|
8242
8516
|
}).passthrough()
|
|
8243
8517
|
).min(1).describe("Posts to create approval entries for."),
|
|
8244
|
-
project_id:
|
|
8245
|
-
response_format:
|
|
8518
|
+
project_id: z21.string().uuid().optional().describe("Project ID. Defaults to active project context."),
|
|
8519
|
+
response_format: z21.enum(["text", "json"]).optional()
|
|
8246
8520
|
},
|
|
8247
8521
|
async ({ plan_id, posts, project_id, response_format }) => {
|
|
8248
8522
|
const projectId = project_id || await getDefaultProjectId();
|
|
@@ -8291,7 +8565,7 @@ function registerPlanApprovalTools(server) {
|
|
|
8291
8565
|
};
|
|
8292
8566
|
if ((response_format || "text") === "json") {
|
|
8293
8567
|
return {
|
|
8294
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
8568
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope17(payload), null, 2) }],
|
|
8295
8569
|
isError: false
|
|
8296
8570
|
};
|
|
8297
8571
|
}
|
|
@@ -8310,9 +8584,9 @@ function registerPlanApprovalTools(server) {
|
|
|
8310
8584
|
"list_plan_approvals",
|
|
8311
8585
|
"List MCP-native approval items for a specific content plan.",
|
|
8312
8586
|
{
|
|
8313
|
-
plan_id:
|
|
8314
|
-
status:
|
|
8315
|
-
response_format:
|
|
8587
|
+
plan_id: z21.string().uuid().describe("Content plan ID"),
|
|
8588
|
+
status: z21.enum(["pending", "approved", "rejected", "edited"]).optional(),
|
|
8589
|
+
response_format: z21.enum(["text", "json"]).optional()
|
|
8316
8590
|
},
|
|
8317
8591
|
async ({ plan_id, status, response_format }) => {
|
|
8318
8592
|
const { data: result, error } = await callEdgeFunction(
|
|
@@ -8343,7 +8617,7 @@ function registerPlanApprovalTools(server) {
|
|
|
8343
8617
|
};
|
|
8344
8618
|
if ((response_format || "text") === "json") {
|
|
8345
8619
|
return {
|
|
8346
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
8620
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope17(payload), null, 2) }],
|
|
8347
8621
|
isError: false
|
|
8348
8622
|
};
|
|
8349
8623
|
}
|
|
@@ -8370,11 +8644,11 @@ function registerPlanApprovalTools(server) {
|
|
|
8370
8644
|
"respond_plan_approval",
|
|
8371
8645
|
"Approve, reject, or edit a pending plan approval item.",
|
|
8372
8646
|
{
|
|
8373
|
-
approval_id:
|
|
8374
|
-
decision:
|
|
8375
|
-
edited_post:
|
|
8376
|
-
reason:
|
|
8377
|
-
response_format:
|
|
8647
|
+
approval_id: z21.string().uuid().describe("Approval item ID"),
|
|
8648
|
+
decision: z21.enum(["approved", "rejected", "edited"]),
|
|
8649
|
+
edited_post: z21.record(z21.string(), z21.unknown()).optional(),
|
|
8650
|
+
reason: z21.string().max(1e3).optional(),
|
|
8651
|
+
response_format: z21.enum(["text", "json"]).optional()
|
|
8378
8652
|
},
|
|
8379
8653
|
async ({ approval_id, decision, edited_post, reason, response_format }) => {
|
|
8380
8654
|
if (decision === "edited" && !edited_post) {
|
|
@@ -8421,7 +8695,7 @@ function registerPlanApprovalTools(server) {
|
|
|
8421
8695
|
}
|
|
8422
8696
|
if ((response_format || "text") === "json") {
|
|
8423
8697
|
return {
|
|
8424
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
8698
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope17(data), null, 2) }],
|
|
8425
8699
|
isError: false
|
|
8426
8700
|
};
|
|
8427
8701
|
}
|
|
@@ -8439,7 +8713,7 @@ function registerPlanApprovalTools(server) {
|
|
|
8439
8713
|
}
|
|
8440
8714
|
|
|
8441
8715
|
// src/tools/discovery.ts
|
|
8442
|
-
import { z as
|
|
8716
|
+
import { z as z22 } from "zod";
|
|
8443
8717
|
|
|
8444
8718
|
// src/lib/tool-catalog.ts
|
|
8445
8719
|
var TOOL_CATALOG = [
|
|
@@ -8506,6 +8780,12 @@ var TOOL_CATALOG = [
|
|
|
8506
8780
|
module: "content",
|
|
8507
8781
|
scope: "mcp:write"
|
|
8508
8782
|
},
|
|
8783
|
+
{
|
|
8784
|
+
name: "create_carousel",
|
|
8785
|
+
description: "End-to-end carousel: generate text + kick off image jobs for each slide",
|
|
8786
|
+
module: "carousel",
|
|
8787
|
+
scope: "mcp:write"
|
|
8788
|
+
},
|
|
8509
8789
|
// media
|
|
8510
8790
|
{
|
|
8511
8791
|
name: "upload_media",
|
|
@@ -8863,6 +9143,45 @@ var TOOL_CATALOG = [
|
|
|
8863
9143
|
description: "Create a new autopilot configuration",
|
|
8864
9144
|
module: "autopilot",
|
|
8865
9145
|
scope: "mcp:autopilot"
|
|
9146
|
+
},
|
|
9147
|
+
// brand runtime (additions)
|
|
9148
|
+
{
|
|
9149
|
+
name: "audit_brand_colors",
|
|
9150
|
+
description: "Audit brand color palette for accessibility, contrast, and harmony",
|
|
9151
|
+
module: "brandRuntime",
|
|
9152
|
+
scope: "mcp:read"
|
|
9153
|
+
},
|
|
9154
|
+
{
|
|
9155
|
+
name: "export_design_tokens",
|
|
9156
|
+
description: "Export brand design tokens in CSS/Tailwind/JSON formats",
|
|
9157
|
+
module: "brandRuntime",
|
|
9158
|
+
scope: "mcp:read"
|
|
9159
|
+
},
|
|
9160
|
+
// carousel (already listed in content section above)
|
|
9161
|
+
// recipes
|
|
9162
|
+
{
|
|
9163
|
+
name: "list_recipes",
|
|
9164
|
+
description: "List available recipe templates for automated content workflows",
|
|
9165
|
+
module: "recipes",
|
|
9166
|
+
scope: "mcp:read"
|
|
9167
|
+
},
|
|
9168
|
+
{
|
|
9169
|
+
name: "get_recipe_details",
|
|
9170
|
+
description: "Get full details of a recipe template including steps and required inputs",
|
|
9171
|
+
module: "recipes",
|
|
9172
|
+
scope: "mcp:read"
|
|
9173
|
+
},
|
|
9174
|
+
{
|
|
9175
|
+
name: "execute_recipe",
|
|
9176
|
+
description: "Execute a recipe template with provided inputs to run a multi-step workflow",
|
|
9177
|
+
module: "recipes",
|
|
9178
|
+
scope: "mcp:write"
|
|
9179
|
+
},
|
|
9180
|
+
{
|
|
9181
|
+
name: "get_recipe_run_status",
|
|
9182
|
+
description: "Check the status and progress of a running recipe execution",
|
|
9183
|
+
module: "recipes",
|
|
9184
|
+
scope: "mcp:read"
|
|
8866
9185
|
}
|
|
8867
9186
|
];
|
|
8868
9187
|
function getToolsByModule(module) {
|
|
@@ -8884,10 +9203,10 @@ function registerDiscoveryTools(server) {
|
|
|
8884
9203
|
"search_tools",
|
|
8885
9204
|
'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.',
|
|
8886
9205
|
{
|
|
8887
|
-
query:
|
|
8888
|
-
module:
|
|
8889
|
-
scope:
|
|
8890
|
-
detail:
|
|
9206
|
+
query: z22.string().optional().describe("Search query to filter tools by name or description"),
|
|
9207
|
+
module: z22.string().optional().describe('Filter by module name (e.g. "planning", "content", "analytics")'),
|
|
9208
|
+
scope: z22.string().optional().describe('Filter by required scope (e.g. "mcp:read", "mcp:write")'),
|
|
9209
|
+
detail: z22.enum(["name", "summary", "full"]).default("summary").describe(
|
|
8891
9210
|
'Detail level: "name" for just tool names, "summary" for names + descriptions, "full" for complete info including scope and module'
|
|
8892
9211
|
)
|
|
8893
9212
|
},
|
|
@@ -8930,13 +9249,13 @@ function registerDiscoveryTools(server) {
|
|
|
8930
9249
|
}
|
|
8931
9250
|
|
|
8932
9251
|
// src/tools/pipeline.ts
|
|
8933
|
-
import { z as
|
|
9252
|
+
import { z as z23 } from "zod";
|
|
8934
9253
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
8935
9254
|
init_supabase();
|
|
8936
|
-
function
|
|
9255
|
+
function asEnvelope18(data) {
|
|
8937
9256
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
8938
9257
|
}
|
|
8939
|
-
var PLATFORM_ENUM2 =
|
|
9258
|
+
var PLATFORM_ENUM2 = z23.enum([
|
|
8940
9259
|
"youtube",
|
|
8941
9260
|
"tiktok",
|
|
8942
9261
|
"instagram",
|
|
@@ -8953,10 +9272,10 @@ function registerPipelineTools(server) {
|
|
|
8953
9272
|
"check_pipeline_readiness",
|
|
8954
9273
|
"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.",
|
|
8955
9274
|
{
|
|
8956
|
-
project_id:
|
|
8957
|
-
platforms:
|
|
8958
|
-
estimated_posts:
|
|
8959
|
-
response_format:
|
|
9275
|
+
project_id: z23.string().uuid().optional().describe("Project ID (auto-detected if omitted)"),
|
|
9276
|
+
platforms: z23.array(PLATFORM_ENUM2).min(1).describe("Target platforms to check"),
|
|
9277
|
+
estimated_posts: z23.number().min(1).max(50).default(5).describe("Estimated posts to generate"),
|
|
9278
|
+
response_format: z23.enum(["text", "json"]).optional().describe("Response format")
|
|
8960
9279
|
},
|
|
8961
9280
|
async ({ project_id, platforms, estimated_posts, response_format }) => {
|
|
8962
9281
|
const format = response_format ?? "text";
|
|
@@ -9032,7 +9351,7 @@ function registerPipelineTools(server) {
|
|
|
9032
9351
|
});
|
|
9033
9352
|
if (format === "json") {
|
|
9034
9353
|
return {
|
|
9035
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
9354
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope18(result), null, 2) }]
|
|
9036
9355
|
};
|
|
9037
9356
|
}
|
|
9038
9357
|
const lines = [];
|
|
@@ -9080,22 +9399,22 @@ function registerPipelineTools(server) {
|
|
|
9080
9399
|
"run_content_pipeline",
|
|
9081
9400
|
"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.",
|
|
9082
9401
|
{
|
|
9083
|
-
project_id:
|
|
9084
|
-
topic:
|
|
9085
|
-
source_url:
|
|
9086
|
-
platforms:
|
|
9087
|
-
days:
|
|
9088
|
-
posts_per_day:
|
|
9089
|
-
approval_mode:
|
|
9402
|
+
project_id: z23.string().uuid().optional().describe("Project ID (auto-detected if omitted)"),
|
|
9403
|
+
topic: z23.string().optional().describe("Content topic (required if no source_url)"),
|
|
9404
|
+
source_url: z23.string().optional().describe("URL to extract content from"),
|
|
9405
|
+
platforms: z23.array(PLATFORM_ENUM2).min(1).describe("Target platforms"),
|
|
9406
|
+
days: z23.number().min(1).max(7).default(5).describe("Days to plan"),
|
|
9407
|
+
posts_per_day: z23.number().min(1).max(3).default(1).describe("Posts per platform per day"),
|
|
9408
|
+
approval_mode: z23.enum(["auto", "review_all", "review_low_confidence"]).default("review_low_confidence").describe(
|
|
9090
9409
|
"auto: approve all passing quality. review_all: flag everything. review_low_confidence: auto-approve high scorers."
|
|
9091
9410
|
),
|
|
9092
|
-
auto_approve_threshold:
|
|
9411
|
+
auto_approve_threshold: z23.number().min(0).max(35).default(28).describe(
|
|
9093
9412
|
"Quality score threshold for auto-approval (used in auto/review_low_confidence modes)"
|
|
9094
9413
|
),
|
|
9095
|
-
max_credits:
|
|
9096
|
-
dry_run:
|
|
9097
|
-
skip_stages:
|
|
9098
|
-
response_format:
|
|
9414
|
+
max_credits: z23.number().optional().describe("Credit budget cap"),
|
|
9415
|
+
dry_run: z23.boolean().default(false).describe("If true, skip scheduling and return plan only"),
|
|
9416
|
+
skip_stages: z23.array(z23.enum(["research", "quality", "schedule"])).optional().describe("Stages to skip"),
|
|
9417
|
+
response_format: z23.enum(["text", "json"]).default("json")
|
|
9099
9418
|
},
|
|
9100
9419
|
async ({
|
|
9101
9420
|
project_id,
|
|
@@ -9457,7 +9776,7 @@ function registerPipelineTools(server) {
|
|
|
9457
9776
|
if (response_format === "json") {
|
|
9458
9777
|
return {
|
|
9459
9778
|
content: [
|
|
9460
|
-
{ type: "text", text: JSON.stringify(
|
|
9779
|
+
{ type: "text", text: JSON.stringify(asEnvelope18(resultPayload), null, 2) }
|
|
9461
9780
|
]
|
|
9462
9781
|
};
|
|
9463
9782
|
}
|
|
@@ -9518,8 +9837,8 @@ function registerPipelineTools(server) {
|
|
|
9518
9837
|
"get_pipeline_status",
|
|
9519
9838
|
"Check status of a pipeline run, including stages completed, pending approvals, and scheduled posts.",
|
|
9520
9839
|
{
|
|
9521
|
-
pipeline_id:
|
|
9522
|
-
response_format:
|
|
9840
|
+
pipeline_id: z23.string().uuid().optional().describe("Pipeline run ID (omit for latest)"),
|
|
9841
|
+
response_format: z23.enum(["text", "json"]).optional()
|
|
9523
9842
|
},
|
|
9524
9843
|
async ({ pipeline_id, response_format }) => {
|
|
9525
9844
|
const format = response_format ?? "text";
|
|
@@ -9545,7 +9864,7 @@ function registerPipelineTools(server) {
|
|
|
9545
9864
|
}
|
|
9546
9865
|
if (format === "json") {
|
|
9547
9866
|
return {
|
|
9548
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
9867
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope18(data), null, 2) }]
|
|
9549
9868
|
};
|
|
9550
9869
|
}
|
|
9551
9870
|
const lines = [];
|
|
@@ -9579,9 +9898,9 @@ function registerPipelineTools(server) {
|
|
|
9579
9898
|
"auto_approve_plan",
|
|
9580
9899
|
"Batch auto-approve posts in a content plan that meet quality thresholds. Posts below the threshold are flagged for manual review.",
|
|
9581
9900
|
{
|
|
9582
|
-
plan_id:
|
|
9583
|
-
quality_threshold:
|
|
9584
|
-
response_format:
|
|
9901
|
+
plan_id: z23.string().uuid().describe("Content plan ID"),
|
|
9902
|
+
quality_threshold: z23.number().min(0).max(35).default(26).describe("Minimum quality score to auto-approve"),
|
|
9903
|
+
response_format: z23.enum(["text", "json"]).default("json")
|
|
9585
9904
|
},
|
|
9586
9905
|
async ({ plan_id, quality_threshold, response_format }) => {
|
|
9587
9906
|
const startedAt = Date.now();
|
|
@@ -9687,7 +10006,7 @@ function registerPipelineTools(server) {
|
|
|
9687
10006
|
if (response_format === "json") {
|
|
9688
10007
|
return {
|
|
9689
10008
|
content: [
|
|
9690
|
-
{ type: "text", text: JSON.stringify(
|
|
10009
|
+
{ type: "text", text: JSON.stringify(asEnvelope18(resultPayload), null, 2) }
|
|
9691
10010
|
]
|
|
9692
10011
|
};
|
|
9693
10012
|
}
|
|
@@ -9741,9 +10060,9 @@ function buildPlanPrompt(topic, platforms, days, postsPerDay, sourceUrl) {
|
|
|
9741
10060
|
}
|
|
9742
10061
|
|
|
9743
10062
|
// src/tools/suggest.ts
|
|
9744
|
-
import { z as
|
|
10063
|
+
import { z as z24 } from "zod";
|
|
9745
10064
|
init_supabase();
|
|
9746
|
-
function
|
|
10065
|
+
function asEnvelope19(data) {
|
|
9747
10066
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
9748
10067
|
}
|
|
9749
10068
|
function registerSuggestTools(server) {
|
|
@@ -9751,9 +10070,9 @@ function registerSuggestTools(server) {
|
|
|
9751
10070
|
"suggest_next_content",
|
|
9752
10071
|
"Suggest next content topics based on performance insights, past content, and competitor patterns. No AI call, no credit cost \u2014 purely data-driven recommendations.",
|
|
9753
10072
|
{
|
|
9754
|
-
project_id:
|
|
9755
|
-
count:
|
|
9756
|
-
response_format:
|
|
10073
|
+
project_id: z24.string().uuid().optional().describe("Project ID (auto-detected if omitted)"),
|
|
10074
|
+
count: z24.number().min(1).max(10).default(3).describe("Number of suggestions to return"),
|
|
10075
|
+
response_format: z24.enum(["text", "json"]).optional()
|
|
9757
10076
|
},
|
|
9758
10077
|
async ({ project_id, count, response_format }) => {
|
|
9759
10078
|
const format = response_format ?? "text";
|
|
@@ -9863,7 +10182,7 @@ function registerSuggestTools(server) {
|
|
|
9863
10182
|
if (format === "json") {
|
|
9864
10183
|
return {
|
|
9865
10184
|
content: [
|
|
9866
|
-
{ type: "text", text: JSON.stringify(
|
|
10185
|
+
{ type: "text", text: JSON.stringify(asEnvelope19(resultPayload), null, 2) }
|
|
9867
10186
|
]
|
|
9868
10187
|
};
|
|
9869
10188
|
}
|
|
@@ -9902,7 +10221,7 @@ ${i + 1}. ${s.topic}`);
|
|
|
9902
10221
|
}
|
|
9903
10222
|
|
|
9904
10223
|
// src/tools/digest.ts
|
|
9905
|
-
import { z as
|
|
10224
|
+
import { z as z25 } from "zod";
|
|
9906
10225
|
init_supabase();
|
|
9907
10226
|
|
|
9908
10227
|
// src/lib/anomaly-detector.ts
|
|
@@ -10006,10 +10325,10 @@ function detectAnomalies(currentData, previousData, sensitivity = "medium", aver
|
|
|
10006
10325
|
}
|
|
10007
10326
|
|
|
10008
10327
|
// src/tools/digest.ts
|
|
10009
|
-
function
|
|
10328
|
+
function asEnvelope20(data) {
|
|
10010
10329
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
10011
10330
|
}
|
|
10012
|
-
var PLATFORM_ENUM3 =
|
|
10331
|
+
var PLATFORM_ENUM3 = z25.enum([
|
|
10013
10332
|
"youtube",
|
|
10014
10333
|
"tiktok",
|
|
10015
10334
|
"instagram",
|
|
@@ -10024,10 +10343,10 @@ function registerDigestTools(server) {
|
|
|
10024
10343
|
"generate_performance_digest",
|
|
10025
10344
|
"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.",
|
|
10026
10345
|
{
|
|
10027
|
-
project_id:
|
|
10028
|
-
period:
|
|
10029
|
-
include_recommendations:
|
|
10030
|
-
response_format:
|
|
10346
|
+
project_id: z25.string().uuid().optional().describe("Project ID (auto-detected if omitted)"),
|
|
10347
|
+
period: z25.enum(["7d", "14d", "30d"]).default("7d").describe("Time period to analyze"),
|
|
10348
|
+
include_recommendations: z25.boolean().default(true),
|
|
10349
|
+
response_format: z25.enum(["text", "json"]).optional()
|
|
10031
10350
|
},
|
|
10032
10351
|
async ({ project_id, period, include_recommendations, response_format }) => {
|
|
10033
10352
|
const format = response_format ?? "text";
|
|
@@ -10183,7 +10502,7 @@ function registerDigestTools(server) {
|
|
|
10183
10502
|
});
|
|
10184
10503
|
if (format === "json") {
|
|
10185
10504
|
return {
|
|
10186
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
10505
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope20(digest), null, 2) }]
|
|
10187
10506
|
};
|
|
10188
10507
|
}
|
|
10189
10508
|
const lines = [];
|
|
@@ -10242,11 +10561,11 @@ Best: ${best.id.slice(0, 8)}... (${best.platform}) \u2014 ${best.views.toLocaleS
|
|
|
10242
10561
|
"detect_anomalies",
|
|
10243
10562
|
"Detect significant performance changes: spikes, drops, viral content, trend shifts. Compares current period against previous equal-length period. No AI call, no credit cost.",
|
|
10244
10563
|
{
|
|
10245
|
-
project_id:
|
|
10246
|
-
days:
|
|
10247
|
-
sensitivity:
|
|
10248
|
-
platforms:
|
|
10249
|
-
response_format:
|
|
10564
|
+
project_id: z25.string().uuid().optional().describe("Project ID (auto-detected if omitted)"),
|
|
10565
|
+
days: z25.number().min(7).max(90).default(14).describe("Days to analyze"),
|
|
10566
|
+
sensitivity: z25.enum(["low", "medium", "high"]).default("medium").describe("Detection sensitivity: low=50%+, medium=30%+, high=15%+ changes"),
|
|
10567
|
+
platforms: z25.array(PLATFORM_ENUM3).optional().describe("Filter to specific platforms"),
|
|
10568
|
+
response_format: z25.enum(["text", "json"]).optional()
|
|
10250
10569
|
},
|
|
10251
10570
|
async ({ project_id, days, sensitivity, platforms, response_format }) => {
|
|
10252
10571
|
const format = response_format ?? "text";
|
|
@@ -10297,7 +10616,7 @@ Best: ${best.id.slice(0, 8)}... (${best.platform}) \u2014 ${best.views.toLocaleS
|
|
|
10297
10616
|
if (format === "json") {
|
|
10298
10617
|
return {
|
|
10299
10618
|
content: [
|
|
10300
|
-
{ type: "text", text: JSON.stringify(
|
|
10619
|
+
{ type: "text", text: JSON.stringify(asEnvelope20(resultPayload), null, 2) }
|
|
10301
10620
|
]
|
|
10302
10621
|
};
|
|
10303
10622
|
}
|
|
@@ -10338,7 +10657,7 @@ Best: ${best.id.slice(0, 8)}... (${best.platform}) \u2014 ${best.views.toLocaleS
|
|
|
10338
10657
|
}
|
|
10339
10658
|
|
|
10340
10659
|
// src/tools/brandRuntime.ts
|
|
10341
|
-
import { z as
|
|
10660
|
+
import { z as z26 } from "zod";
|
|
10342
10661
|
init_supabase();
|
|
10343
10662
|
|
|
10344
10663
|
// src/lib/brandScoring.ts
|
|
@@ -10608,8 +10927,181 @@ function computeBrandConsistency(content, profile, threshold = 60) {
|
|
|
10608
10927
|
};
|
|
10609
10928
|
}
|
|
10610
10929
|
|
|
10930
|
+
// src/lib/colorAudit.ts
|
|
10931
|
+
function hexToRgb(hex) {
|
|
10932
|
+
const h = hex.replace("#", "");
|
|
10933
|
+
const full = h.length === 3 ? h[0] + h[0] + h[1] + h[1] + h[2] + h[2] : h;
|
|
10934
|
+
const n = parseInt(full, 16);
|
|
10935
|
+
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
10936
|
+
}
|
|
10937
|
+
function srgbToLinear(c) {
|
|
10938
|
+
const s = c / 255;
|
|
10939
|
+
return s <= 0.04045 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
|
|
10940
|
+
}
|
|
10941
|
+
function hexToLab(hex) {
|
|
10942
|
+
const [r, g, b] = hexToRgb(hex);
|
|
10943
|
+
const lr = srgbToLinear(r);
|
|
10944
|
+
const lg = srgbToLinear(g);
|
|
10945
|
+
const lb = srgbToLinear(b);
|
|
10946
|
+
const x = lr * 0.4124564 + lg * 0.3575761 + lb * 0.1804375;
|
|
10947
|
+
const y = lr * 0.2126729 + lg * 0.7151522 + lb * 0.072175;
|
|
10948
|
+
const z29 = lr * 0.0193339 + lg * 0.119192 + lb * 0.9503041;
|
|
10949
|
+
const f = (t) => t > 8856e-6 ? Math.cbrt(t) : 7.787 * t + 16 / 116;
|
|
10950
|
+
const fx = f(x / 0.95047);
|
|
10951
|
+
const fy = f(y / 1);
|
|
10952
|
+
const fz = f(z29 / 1.08883);
|
|
10953
|
+
return { L: 116 * fy - 16, a: 500 * (fx - fy), b: 200 * (fy - fz) };
|
|
10954
|
+
}
|
|
10955
|
+
function deltaE2000(lab1, lab2) {
|
|
10956
|
+
const { L: L1, a: a1, b: b1 } = lab1;
|
|
10957
|
+
const { L: L2, a: a2, b: b2 } = lab2;
|
|
10958
|
+
const C1 = Math.sqrt(a1 * a1 + b1 * b1);
|
|
10959
|
+
const C2 = Math.sqrt(a2 * a2 + b2 * b2);
|
|
10960
|
+
const Cab = (C1 + C2) / 2;
|
|
10961
|
+
const Cab7 = Math.pow(Cab, 7);
|
|
10962
|
+
const G = 0.5 * (1 - Math.sqrt(Cab7 / (Cab7 + Math.pow(25, 7))));
|
|
10963
|
+
const a1p = a1 * (1 + G);
|
|
10964
|
+
const a2p = a2 * (1 + G);
|
|
10965
|
+
const C1p = Math.sqrt(a1p * a1p + b1 * b1);
|
|
10966
|
+
const C2p = Math.sqrt(a2p * a2p + b2 * b2);
|
|
10967
|
+
let h1p = Math.atan2(b1, a1p) * (180 / Math.PI);
|
|
10968
|
+
if (h1p < 0) h1p += 360;
|
|
10969
|
+
let h2p = Math.atan2(b2, a2p) * (180 / Math.PI);
|
|
10970
|
+
if (h2p < 0) h2p += 360;
|
|
10971
|
+
const dLp = L2 - L1;
|
|
10972
|
+
const dCp = C2p - C1p;
|
|
10973
|
+
let dhp;
|
|
10974
|
+
if (C1p * C2p === 0) dhp = 0;
|
|
10975
|
+
else if (Math.abs(h2p - h1p) <= 180) dhp = h2p - h1p;
|
|
10976
|
+
else if (h2p - h1p > 180) dhp = h2p - h1p - 360;
|
|
10977
|
+
else dhp = h2p - h1p + 360;
|
|
10978
|
+
const dHp = 2 * Math.sqrt(C1p * C2p) * Math.sin(dhp * Math.PI / 360);
|
|
10979
|
+
const Lp = (L1 + L2) / 2;
|
|
10980
|
+
const Cp = (C1p + C2p) / 2;
|
|
10981
|
+
let hp;
|
|
10982
|
+
if (C1p * C2p === 0) hp = h1p + h2p;
|
|
10983
|
+
else if (Math.abs(h1p - h2p) <= 180) hp = (h1p + h2p) / 2;
|
|
10984
|
+
else if (h1p + h2p < 360) hp = (h1p + h2p + 360) / 2;
|
|
10985
|
+
else hp = (h1p + h2p - 360) / 2;
|
|
10986
|
+
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);
|
|
10987
|
+
const SL = 1 + 0.015 * (Lp - 50) * (Lp - 50) / Math.sqrt(20 + (Lp - 50) * (Lp - 50));
|
|
10988
|
+
const SC = 1 + 0.045 * Cp;
|
|
10989
|
+
const SH = 1 + 0.015 * Cp * T;
|
|
10990
|
+
const Cp7 = Math.pow(Cp, 7);
|
|
10991
|
+
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);
|
|
10992
|
+
return Math.sqrt(
|
|
10993
|
+
Math.pow(dLp / SL, 2) + Math.pow(dCp / SC, 2) + Math.pow(dHp / SH, 2) + RT * (dCp / SC) * (dHp / SH)
|
|
10994
|
+
);
|
|
10995
|
+
}
|
|
10996
|
+
var COLOR_SLOTS = [
|
|
10997
|
+
"primary",
|
|
10998
|
+
"secondary",
|
|
10999
|
+
"accent",
|
|
11000
|
+
"background",
|
|
11001
|
+
"success",
|
|
11002
|
+
"warning",
|
|
11003
|
+
"error",
|
|
11004
|
+
"text",
|
|
11005
|
+
"textSecondary"
|
|
11006
|
+
];
|
|
11007
|
+
function auditBrandColors(palette, contentColors, threshold = 10) {
|
|
11008
|
+
if (!contentColors.length) return { entries: [], overallScore: 100, passed: true };
|
|
11009
|
+
const brandColors = [];
|
|
11010
|
+
for (const slot of COLOR_SLOTS) {
|
|
11011
|
+
const hex = palette[slot];
|
|
11012
|
+
if (typeof hex === "string" && hex.startsWith("#")) {
|
|
11013
|
+
brandColors.push({ slot, hex, lab: hexToLab(hex) });
|
|
11014
|
+
}
|
|
11015
|
+
}
|
|
11016
|
+
if (!brandColors.length) return { entries: [], overallScore: 50, passed: false };
|
|
11017
|
+
const entries = contentColors.map((color) => {
|
|
11018
|
+
const colorLab = hexToLab(color);
|
|
11019
|
+
let minDE = Infinity;
|
|
11020
|
+
let closest = brandColors[0];
|
|
11021
|
+
for (const bc of brandColors) {
|
|
11022
|
+
const de = deltaE2000(colorLab, bc.lab);
|
|
11023
|
+
if (de < minDE) {
|
|
11024
|
+
minDE = de;
|
|
11025
|
+
closest = bc;
|
|
11026
|
+
}
|
|
11027
|
+
}
|
|
11028
|
+
return {
|
|
11029
|
+
color,
|
|
11030
|
+
closestBrandColor: closest.hex,
|
|
11031
|
+
closestBrandSlot: closest.slot,
|
|
11032
|
+
deltaE: Math.round(minDE * 100) / 100,
|
|
11033
|
+
passed: minDE <= threshold
|
|
11034
|
+
};
|
|
11035
|
+
});
|
|
11036
|
+
const passedCount = entries.filter((e) => e.passed).length;
|
|
11037
|
+
const overallScore = Math.round(passedCount / entries.length * 100);
|
|
11038
|
+
return { entries, overallScore, passed: overallScore >= 80 };
|
|
11039
|
+
}
|
|
11040
|
+
function exportDesignTokens(palette, typography, format) {
|
|
11041
|
+
if (format === "css") return exportCSS(palette, typography);
|
|
11042
|
+
if (format === "tailwind") return JSON.stringify(exportTailwind(palette), null, 2);
|
|
11043
|
+
return JSON.stringify(exportFigma(palette, typography), null, 2);
|
|
11044
|
+
}
|
|
11045
|
+
function exportCSS(palette, typography) {
|
|
11046
|
+
const lines = [":root {"];
|
|
11047
|
+
const slots = [
|
|
11048
|
+
["--brand-primary", "primary"],
|
|
11049
|
+
["--brand-secondary", "secondary"],
|
|
11050
|
+
["--brand-accent", "accent"],
|
|
11051
|
+
["--brand-background", "background"],
|
|
11052
|
+
["--brand-success", "success"],
|
|
11053
|
+
["--brand-warning", "warning"],
|
|
11054
|
+
["--brand-error", "error"],
|
|
11055
|
+
["--brand-text", "text"],
|
|
11056
|
+
["--brand-text-secondary", "textSecondary"]
|
|
11057
|
+
];
|
|
11058
|
+
for (const [varName, key] of slots) {
|
|
11059
|
+
const v = palette[key];
|
|
11060
|
+
if (typeof v === "string" && v) lines.push(` ${varName}: ${v};`);
|
|
11061
|
+
}
|
|
11062
|
+
if (typography) {
|
|
11063
|
+
const hf = typography.headingFont;
|
|
11064
|
+
const bf = typography.bodyFont;
|
|
11065
|
+
if (hf) lines.push(` --brand-font-heading: ${hf};`);
|
|
11066
|
+
if (bf) lines.push(` --brand-font-body: ${bf};`);
|
|
11067
|
+
}
|
|
11068
|
+
lines.push("}");
|
|
11069
|
+
return lines.join("\n");
|
|
11070
|
+
}
|
|
11071
|
+
function exportTailwind(palette) {
|
|
11072
|
+
const colors = {};
|
|
11073
|
+
const map = [
|
|
11074
|
+
["brand-primary", "primary"],
|
|
11075
|
+
["brand-secondary", "secondary"],
|
|
11076
|
+
["brand-accent", "accent"],
|
|
11077
|
+
["brand-bg", "background"]
|
|
11078
|
+
];
|
|
11079
|
+
for (const [tw, key] of map) {
|
|
11080
|
+
const v = palette[key];
|
|
11081
|
+
if (typeof v === "string" && v) colors[tw] = v;
|
|
11082
|
+
}
|
|
11083
|
+
return colors;
|
|
11084
|
+
}
|
|
11085
|
+
function exportFigma(palette, typography) {
|
|
11086
|
+
const tokens = { color: {} };
|
|
11087
|
+
for (const slot of ["primary", "secondary", "accent", "background"]) {
|
|
11088
|
+
const v = palette[slot];
|
|
11089
|
+
if (typeof v === "string") tokens.color[slot] = { value: v, type: "color" };
|
|
11090
|
+
}
|
|
11091
|
+
if (typography) {
|
|
11092
|
+
const hf = typography.headingFont;
|
|
11093
|
+
const bf = typography.bodyFont;
|
|
11094
|
+
if (hf || bf) {
|
|
11095
|
+
tokens.fontFamily = {};
|
|
11096
|
+
if (hf) tokens.fontFamily.heading = { value: String(hf), type: "fontFamilies" };
|
|
11097
|
+
if (bf) tokens.fontFamily.body = { value: String(bf), type: "fontFamilies" };
|
|
11098
|
+
}
|
|
11099
|
+
}
|
|
11100
|
+
return tokens;
|
|
11101
|
+
}
|
|
11102
|
+
|
|
10611
11103
|
// src/tools/brandRuntime.ts
|
|
10612
|
-
function
|
|
11104
|
+
function asEnvelope21(data) {
|
|
10613
11105
|
return {
|
|
10614
11106
|
_meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() },
|
|
10615
11107
|
data
|
|
@@ -10620,7 +11112,7 @@ function registerBrandRuntimeTools(server) {
|
|
|
10620
11112
|
"get_brand_runtime",
|
|
10621
11113
|
"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.",
|
|
10622
11114
|
{
|
|
10623
|
-
project_id:
|
|
11115
|
+
project_id: z26.string().optional().describe("Project ID. Defaults to current project.")
|
|
10624
11116
|
},
|
|
10625
11117
|
async ({ project_id }) => {
|
|
10626
11118
|
const projectId = project_id || await getDefaultProjectId();
|
|
@@ -10680,7 +11172,7 @@ function registerBrandRuntimeTools(server) {
|
|
|
10680
11172
|
pagesScraped: meta.pagesScraped || 0
|
|
10681
11173
|
}
|
|
10682
11174
|
};
|
|
10683
|
-
const envelope =
|
|
11175
|
+
const envelope = asEnvelope21(runtime);
|
|
10684
11176
|
return {
|
|
10685
11177
|
content: [{ type: "text", text: JSON.stringify(envelope, null, 2) }]
|
|
10686
11178
|
};
|
|
@@ -10690,7 +11182,7 @@ function registerBrandRuntimeTools(server) {
|
|
|
10690
11182
|
"explain_brand_system",
|
|
10691
11183
|
"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.",
|
|
10692
11184
|
{
|
|
10693
|
-
project_id:
|
|
11185
|
+
project_id: z26.string().optional().describe("Project ID. Defaults to current project.")
|
|
10694
11186
|
},
|
|
10695
11187
|
async ({ project_id }) => {
|
|
10696
11188
|
const projectId = project_id || await getDefaultProjectId();
|
|
@@ -10802,8 +11294,8 @@ function registerBrandRuntimeTools(server) {
|
|
|
10802
11294
|
"check_brand_consistency",
|
|
10803
11295
|
"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.",
|
|
10804
11296
|
{
|
|
10805
|
-
content:
|
|
10806
|
-
project_id:
|
|
11297
|
+
content: z26.string().describe("The content text to check for brand consistency."),
|
|
11298
|
+
project_id: z26.string().optional().describe("Project ID. Defaults to current project.")
|
|
10807
11299
|
},
|
|
10808
11300
|
async ({ content, project_id }) => {
|
|
10809
11301
|
const projectId = project_id || await getDefaultProjectId();
|
|
@@ -10822,7 +11314,76 @@ function registerBrandRuntimeTools(server) {
|
|
|
10822
11314
|
}
|
|
10823
11315
|
const profile = row.profile_data;
|
|
10824
11316
|
const checkResult = computeBrandConsistency(content, profile);
|
|
10825
|
-
const envelope =
|
|
11317
|
+
const envelope = asEnvelope21(checkResult);
|
|
11318
|
+
return {
|
|
11319
|
+
content: [{ type: "text", text: JSON.stringify(envelope, null, 2) }]
|
|
11320
|
+
};
|
|
11321
|
+
}
|
|
11322
|
+
);
|
|
11323
|
+
server.tool(
|
|
11324
|
+
"audit_brand_colors",
|
|
11325
|
+
"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.",
|
|
11326
|
+
{
|
|
11327
|
+
content_colors: z26.array(z26.string()).describe('Hex color strings used in the content (e.g., ["#FF0000", "#00FF00"])'),
|
|
11328
|
+
project_id: z26.string().optional().describe("Project ID. Defaults to current project."),
|
|
11329
|
+
threshold: z26.number().optional().describe("Max Delta E for on-brand (default 10). Lower = stricter.")
|
|
11330
|
+
},
|
|
11331
|
+
async ({ content_colors, project_id, threshold }) => {
|
|
11332
|
+
const projectId = project_id || await getDefaultProjectId();
|
|
11333
|
+
const { data: result, error: efError } = await callEdgeFunction("mcp-data", { action: "brand-profile", projectId });
|
|
11334
|
+
const row = !efError && result?.success ? result.profile : null;
|
|
11335
|
+
if (!row?.profile_data?.colorPalette) {
|
|
11336
|
+
return {
|
|
11337
|
+
content: [
|
|
11338
|
+
{
|
|
11339
|
+
type: "text",
|
|
11340
|
+
text: "No brand color palette found. Extract a brand profile first."
|
|
11341
|
+
}
|
|
11342
|
+
],
|
|
11343
|
+
isError: true
|
|
11344
|
+
};
|
|
11345
|
+
}
|
|
11346
|
+
const auditResult = auditBrandColors(
|
|
11347
|
+
row.profile_data.colorPalette,
|
|
11348
|
+
content_colors,
|
|
11349
|
+
threshold ?? 10
|
|
11350
|
+
);
|
|
11351
|
+
const envelope = asEnvelope21(auditResult);
|
|
11352
|
+
return {
|
|
11353
|
+
content: [{ type: "text", text: JSON.stringify(envelope, null, 2) }]
|
|
11354
|
+
};
|
|
11355
|
+
}
|
|
11356
|
+
);
|
|
11357
|
+
server.tool(
|
|
11358
|
+
"export_design_tokens",
|
|
11359
|
+
"Export brand palette and typography as design tokens. Supports CSS custom properties, Tailwind config, and Figma Tokens JSON formats.",
|
|
11360
|
+
{
|
|
11361
|
+
format: z26.enum(["css", "tailwind", "figma"]).describe(
|
|
11362
|
+
"Output format: css (CSS variables), tailwind (theme.extend.colors), figma (Figma Tokens JSON)"
|
|
11363
|
+
),
|
|
11364
|
+
project_id: z26.string().optional().describe("Project ID. Defaults to current project.")
|
|
11365
|
+
},
|
|
11366
|
+
async ({ format, project_id }) => {
|
|
11367
|
+
const projectId = project_id || await getDefaultProjectId();
|
|
11368
|
+
const { data: result, error: efError } = await callEdgeFunction("mcp-data", { action: "brand-profile", projectId });
|
|
11369
|
+
const row = !efError && result?.success ? result.profile : null;
|
|
11370
|
+
if (!row?.profile_data?.colorPalette) {
|
|
11371
|
+
return {
|
|
11372
|
+
content: [
|
|
11373
|
+
{
|
|
11374
|
+
type: "text",
|
|
11375
|
+
text: "No brand color palette found. Extract a brand profile first."
|
|
11376
|
+
}
|
|
11377
|
+
],
|
|
11378
|
+
isError: true
|
|
11379
|
+
};
|
|
11380
|
+
}
|
|
11381
|
+
const output = exportDesignTokens(
|
|
11382
|
+
row.profile_data.colorPalette,
|
|
11383
|
+
row.profile_data.typography,
|
|
11384
|
+
format
|
|
11385
|
+
);
|
|
11386
|
+
const envelope = asEnvelope21({ format, tokens: output });
|
|
10826
11387
|
return {
|
|
10827
11388
|
content: [{ type: "text", text: JSON.stringify(envelope, null, 2) }]
|
|
10828
11389
|
};
|
|
@@ -10830,6 +11391,389 @@ function registerBrandRuntimeTools(server) {
|
|
|
10830
11391
|
);
|
|
10831
11392
|
}
|
|
10832
11393
|
|
|
11394
|
+
// src/tools/carousel.ts
|
|
11395
|
+
import { z as z27 } from "zod";
|
|
11396
|
+
init_supabase();
|
|
11397
|
+
init_request_context();
|
|
11398
|
+
var MAX_CREDITS_PER_RUN2 = Math.max(0, Number(process.env.SOCIALNEURON_MAX_CREDITS_PER_RUN || 0));
|
|
11399
|
+
var MAX_ASSETS_PER_RUN2 = Math.max(0, Number(process.env.SOCIALNEURON_MAX_ASSETS_PER_RUN || 0));
|
|
11400
|
+
var _globalCreditsUsed2 = 0;
|
|
11401
|
+
var _globalAssetsGenerated2 = 0;
|
|
11402
|
+
function getCreditsUsed2() {
|
|
11403
|
+
const ctx = requestContext.getStore();
|
|
11404
|
+
return ctx ? ctx.creditsUsed : _globalCreditsUsed2;
|
|
11405
|
+
}
|
|
11406
|
+
function addCreditsUsed2(amount) {
|
|
11407
|
+
const ctx = requestContext.getStore();
|
|
11408
|
+
if (ctx) {
|
|
11409
|
+
ctx.creditsUsed += amount;
|
|
11410
|
+
} else {
|
|
11411
|
+
_globalCreditsUsed2 += amount;
|
|
11412
|
+
}
|
|
11413
|
+
}
|
|
11414
|
+
function getAssetsGenerated2() {
|
|
11415
|
+
const ctx = requestContext.getStore();
|
|
11416
|
+
return ctx ? ctx.assetsGenerated : _globalAssetsGenerated2;
|
|
11417
|
+
}
|
|
11418
|
+
function addAssetsGenerated2(count) {
|
|
11419
|
+
const ctx = requestContext.getStore();
|
|
11420
|
+
if (ctx) {
|
|
11421
|
+
ctx.assetsGenerated += count;
|
|
11422
|
+
} else {
|
|
11423
|
+
_globalAssetsGenerated2 += count;
|
|
11424
|
+
}
|
|
11425
|
+
}
|
|
11426
|
+
function checkCreditBudget2(estimatedCost) {
|
|
11427
|
+
if (MAX_CREDITS_PER_RUN2 <= 0) return { ok: true };
|
|
11428
|
+
const used = getCreditsUsed2();
|
|
11429
|
+
if (used + estimatedCost > MAX_CREDITS_PER_RUN2) {
|
|
11430
|
+
return {
|
|
11431
|
+
ok: false,
|
|
11432
|
+
message: `Credit budget exceeded: ${used} used + ${estimatedCost} estimated > ${MAX_CREDITS_PER_RUN2} limit. Use a smaller slide count or cheaper image model.`
|
|
11433
|
+
};
|
|
11434
|
+
}
|
|
11435
|
+
return { ok: true };
|
|
11436
|
+
}
|
|
11437
|
+
function checkAssetBudget2() {
|
|
11438
|
+
if (MAX_ASSETS_PER_RUN2 <= 0) return { ok: true };
|
|
11439
|
+
const gen = getAssetsGenerated2();
|
|
11440
|
+
if (gen >= MAX_ASSETS_PER_RUN2) {
|
|
11441
|
+
return {
|
|
11442
|
+
ok: false,
|
|
11443
|
+
message: `Asset limit reached: ${gen}/${MAX_ASSETS_PER_RUN2} assets generated this run.`
|
|
11444
|
+
};
|
|
11445
|
+
}
|
|
11446
|
+
return { ok: true };
|
|
11447
|
+
}
|
|
11448
|
+
var IMAGE_CREDIT_ESTIMATES2 = {
|
|
11449
|
+
midjourney: 20,
|
|
11450
|
+
"nano-banana": 15,
|
|
11451
|
+
"nano-banana-pro": 25,
|
|
11452
|
+
"flux-pro": 30,
|
|
11453
|
+
"flux-max": 50,
|
|
11454
|
+
"gpt4o-image": 40,
|
|
11455
|
+
imagen4: 35,
|
|
11456
|
+
"imagen4-fast": 25,
|
|
11457
|
+
seedream: 20
|
|
11458
|
+
};
|
|
11459
|
+
async function fetchBrandVisualContext(projectId) {
|
|
11460
|
+
const { data, error } = await callEdgeFunction("mcp-data", { action: "brand-profile", projectId });
|
|
11461
|
+
if (error || !data?.success || !data.profile?.profile_data) return null;
|
|
11462
|
+
const profile = data.profile.profile_data;
|
|
11463
|
+
const parts = [];
|
|
11464
|
+
const palette = profile.colorPalette;
|
|
11465
|
+
if (palette) {
|
|
11466
|
+
const colors = Object.entries(palette).filter(([, v]) => typeof v === "string" && v.startsWith("#")).map(([k, v]) => `${k}: ${v}`).slice(0, 5);
|
|
11467
|
+
if (colors.length > 0) {
|
|
11468
|
+
parts.push(`Brand color palette: ${colors.join(", ")}`);
|
|
11469
|
+
}
|
|
11470
|
+
}
|
|
11471
|
+
const logoUrl = profile.logoUrl;
|
|
11472
|
+
let logoDesc = null;
|
|
11473
|
+
if (logoUrl) {
|
|
11474
|
+
const brandName = profile.name || "brand";
|
|
11475
|
+
logoDesc = `Include a small "${brandName}" logo watermark in the bottom-right corner`;
|
|
11476
|
+
parts.push(logoDesc);
|
|
11477
|
+
}
|
|
11478
|
+
const voice = profile.voiceProfile;
|
|
11479
|
+
if (voice?.tone && Array.isArray(voice.tone) && voice.tone.length > 0) {
|
|
11480
|
+
parts.push(`Visual mood: ${voice.tone.slice(0, 3).join(", ")}`);
|
|
11481
|
+
}
|
|
11482
|
+
if (parts.length === 0) return null;
|
|
11483
|
+
return {
|
|
11484
|
+
stylePrefix: parts.join(". "),
|
|
11485
|
+
brandName: profile.name || null,
|
|
11486
|
+
logoDescription: logoDesc
|
|
11487
|
+
};
|
|
11488
|
+
}
|
|
11489
|
+
function registerCarouselTools(server) {
|
|
11490
|
+
server.tool(
|
|
11491
|
+
"create_carousel",
|
|
11492
|
+
"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).",
|
|
11493
|
+
{
|
|
11494
|
+
topic: z27.string().max(200).describe(
|
|
11495
|
+
'Carousel topic/hook \u2014 be specific. Example: "5 pricing mistakes that kill SaaS startups" beats "SaaS tips".'
|
|
11496
|
+
),
|
|
11497
|
+
image_model: z27.enum([
|
|
11498
|
+
"midjourney",
|
|
11499
|
+
"nano-banana",
|
|
11500
|
+
"nano-banana-pro",
|
|
11501
|
+
"flux-pro",
|
|
11502
|
+
"flux-max",
|
|
11503
|
+
"gpt4o-image",
|
|
11504
|
+
"imagen4",
|
|
11505
|
+
"imagen4-fast",
|
|
11506
|
+
"seedream"
|
|
11507
|
+
]).describe(
|
|
11508
|
+
"Image model for slide visuals. flux-pro for general purpose, imagen4 for photorealistic, midjourney for artistic."
|
|
11509
|
+
),
|
|
11510
|
+
template_id: z27.enum([
|
|
11511
|
+
"educational-series",
|
|
11512
|
+
"product-showcase",
|
|
11513
|
+
"story-arc",
|
|
11514
|
+
"before-after",
|
|
11515
|
+
"step-by-step",
|
|
11516
|
+
"quote-collection",
|
|
11517
|
+
"data-stats",
|
|
11518
|
+
"myth-vs-reality",
|
|
11519
|
+
"hormozi-authority"
|
|
11520
|
+
]).optional().describe("Carousel template. Default: hormozi-authority."),
|
|
11521
|
+
slide_count: z27.number().min(3).max(10).optional().describe("Number of slides (3-10). Default: 7."),
|
|
11522
|
+
aspect_ratio: z27.enum(["1:1", "4:5", "9:16"]).optional().describe("Aspect ratio for both carousel and images. Default: 1:1."),
|
|
11523
|
+
style: z27.enum(["minimal", "bold", "professional", "playful", "hormozi"]).optional().describe("Visual style. Default: hormozi for hormozi-authority template."),
|
|
11524
|
+
image_style_suffix: z27.string().max(500).optional().describe(
|
|
11525
|
+
'Style suffix appended to every image prompt for visual consistency across slides. Example: "dark moody lighting, cinematic, 35mm film grain".'
|
|
11526
|
+
),
|
|
11527
|
+
project_id: z27.string().optional().describe("Project ID to associate the carousel with."),
|
|
11528
|
+
response_format: z27.enum(["text", "json"]).optional().describe("Response format. Default: text.")
|
|
11529
|
+
},
|
|
11530
|
+
async ({
|
|
11531
|
+
topic,
|
|
11532
|
+
image_model,
|
|
11533
|
+
template_id,
|
|
11534
|
+
slide_count,
|
|
11535
|
+
aspect_ratio,
|
|
11536
|
+
style,
|
|
11537
|
+
image_style_suffix,
|
|
11538
|
+
brand_id,
|
|
11539
|
+
project_id,
|
|
11540
|
+
response_format
|
|
11541
|
+
}) => {
|
|
11542
|
+
const format = response_format ?? "text";
|
|
11543
|
+
const startedAt = Date.now();
|
|
11544
|
+
const templateId = template_id ?? "hormozi-authority";
|
|
11545
|
+
const resolvedStyle = style ?? (templateId === "hormozi-authority" ? "hormozi" : "professional");
|
|
11546
|
+
const slideCount = slide_count ?? 7;
|
|
11547
|
+
const ratio = aspect_ratio ?? "1:1";
|
|
11548
|
+
let brandContext = null;
|
|
11549
|
+
const brandProjectId = brand_id || project_id || await getDefaultProjectId();
|
|
11550
|
+
if (brandProjectId) {
|
|
11551
|
+
brandContext = await fetchBrandVisualContext(brandProjectId);
|
|
11552
|
+
}
|
|
11553
|
+
const carouselTextCost = 10 + slideCount * 2;
|
|
11554
|
+
const perImageCost = IMAGE_CREDIT_ESTIMATES2[image_model] ?? 30;
|
|
11555
|
+
const totalEstimatedCost = carouselTextCost + slideCount * perImageCost;
|
|
11556
|
+
const budgetCheck = checkCreditBudget2(totalEstimatedCost);
|
|
11557
|
+
if (!budgetCheck.ok) {
|
|
11558
|
+
await logMcpToolInvocation({
|
|
11559
|
+
toolName: "create_carousel",
|
|
11560
|
+
status: "error",
|
|
11561
|
+
durationMs: Date.now() - startedAt,
|
|
11562
|
+
details: { error: budgetCheck.message, totalEstimatedCost }
|
|
11563
|
+
});
|
|
11564
|
+
return {
|
|
11565
|
+
content: [{ type: "text", text: budgetCheck.message }],
|
|
11566
|
+
isError: true
|
|
11567
|
+
};
|
|
11568
|
+
}
|
|
11569
|
+
const assetBudget = checkAssetBudget2();
|
|
11570
|
+
if (!assetBudget.ok) {
|
|
11571
|
+
await logMcpToolInvocation({
|
|
11572
|
+
toolName: "create_carousel",
|
|
11573
|
+
status: "error",
|
|
11574
|
+
durationMs: Date.now() - startedAt,
|
|
11575
|
+
details: { error: assetBudget.message }
|
|
11576
|
+
});
|
|
11577
|
+
return {
|
|
11578
|
+
content: [{ type: "text", text: assetBudget.message }],
|
|
11579
|
+
isError: true
|
|
11580
|
+
};
|
|
11581
|
+
}
|
|
11582
|
+
const userId = await getDefaultUserId();
|
|
11583
|
+
const rateLimit = checkRateLimit("posting", `create_carousel:${userId}`);
|
|
11584
|
+
if (!rateLimit.allowed) {
|
|
11585
|
+
await logMcpToolInvocation({
|
|
11586
|
+
toolName: "create_carousel",
|
|
11587
|
+
status: "rate_limited",
|
|
11588
|
+
durationMs: Date.now() - startedAt,
|
|
11589
|
+
details: { retryAfter: rateLimit.retryAfter }
|
|
11590
|
+
});
|
|
11591
|
+
return {
|
|
11592
|
+
content: [
|
|
11593
|
+
{
|
|
11594
|
+
type: "text",
|
|
11595
|
+
text: `Rate limit exceeded. Retry in ~${rateLimit.retryAfter}s.`
|
|
11596
|
+
}
|
|
11597
|
+
],
|
|
11598
|
+
isError: true
|
|
11599
|
+
};
|
|
11600
|
+
}
|
|
11601
|
+
const { data: carouselData, error: carouselError } = await callEdgeFunction(
|
|
11602
|
+
"generate-carousel",
|
|
11603
|
+
{
|
|
11604
|
+
topic,
|
|
11605
|
+
templateId,
|
|
11606
|
+
slideCount,
|
|
11607
|
+
aspectRatio: ratio,
|
|
11608
|
+
style: resolvedStyle,
|
|
11609
|
+
projectId: project_id
|
|
11610
|
+
},
|
|
11611
|
+
{ timeoutMs: 6e4 }
|
|
11612
|
+
);
|
|
11613
|
+
if (carouselError || !carouselData?.carousel) {
|
|
11614
|
+
const errMsg = carouselError ?? "No carousel data returned";
|
|
11615
|
+
await logMcpToolInvocation({
|
|
11616
|
+
toolName: "create_carousel",
|
|
11617
|
+
status: "error",
|
|
11618
|
+
durationMs: Date.now() - startedAt,
|
|
11619
|
+
details: { phase: "text_generation", error: errMsg }
|
|
11620
|
+
});
|
|
11621
|
+
return {
|
|
11622
|
+
content: [{ type: "text", text: `Carousel text generation failed: ${errMsg}` }],
|
|
11623
|
+
isError: true
|
|
11624
|
+
};
|
|
11625
|
+
}
|
|
11626
|
+
const carousel = carouselData.carousel;
|
|
11627
|
+
const textCredits = carousel.credits?.used ?? carouselTextCost;
|
|
11628
|
+
addCreditsUsed2(textCredits);
|
|
11629
|
+
const imageJobs = await Promise.all(
|
|
11630
|
+
carousel.slides.map(async (slide) => {
|
|
11631
|
+
const promptParts = [];
|
|
11632
|
+
if (brandContext) promptParts.push(brandContext.stylePrefix);
|
|
11633
|
+
if (slide.headline) promptParts.push(slide.headline);
|
|
11634
|
+
if (slide.body) promptParts.push(slide.body);
|
|
11635
|
+
if (promptParts.length === 0) promptParts.push(topic);
|
|
11636
|
+
if (image_style_suffix) promptParts.push(image_style_suffix);
|
|
11637
|
+
const imagePrompt = promptParts.join(". ");
|
|
11638
|
+
try {
|
|
11639
|
+
const { data, error } = await callEdgeFunction(
|
|
11640
|
+
"kie-image-generate",
|
|
11641
|
+
{
|
|
11642
|
+
prompt: imagePrompt,
|
|
11643
|
+
model: image_model,
|
|
11644
|
+
aspectRatio: ratio
|
|
11645
|
+
},
|
|
11646
|
+
{ timeoutMs: 3e4 }
|
|
11647
|
+
);
|
|
11648
|
+
if (error || !data?.taskId && !data?.asyncJobId) {
|
|
11649
|
+
return {
|
|
11650
|
+
slideNumber: slide.slideNumber,
|
|
11651
|
+
jobId: null,
|
|
11652
|
+
model: image_model,
|
|
11653
|
+
error: error ?? "No job ID returned"
|
|
11654
|
+
};
|
|
11655
|
+
}
|
|
11656
|
+
const jobId = data.asyncJobId ?? data.taskId ?? null;
|
|
11657
|
+
if (jobId) {
|
|
11658
|
+
addCreditsUsed2(perImageCost);
|
|
11659
|
+
addAssetsGenerated2(1);
|
|
11660
|
+
}
|
|
11661
|
+
return {
|
|
11662
|
+
slideNumber: slide.slideNumber,
|
|
11663
|
+
jobId,
|
|
11664
|
+
model: image_model,
|
|
11665
|
+
error: null
|
|
11666
|
+
};
|
|
11667
|
+
} catch (err) {
|
|
11668
|
+
return {
|
|
11669
|
+
slideNumber: slide.slideNumber,
|
|
11670
|
+
jobId: null,
|
|
11671
|
+
model: image_model,
|
|
11672
|
+
error: sanitizeError2(err)
|
|
11673
|
+
};
|
|
11674
|
+
}
|
|
11675
|
+
})
|
|
11676
|
+
);
|
|
11677
|
+
const successfulJobs = imageJobs.filter((j) => j.jobId !== null);
|
|
11678
|
+
const failedJobs = imageJobs.filter((j) => j.jobId === null);
|
|
11679
|
+
await logMcpToolInvocation({
|
|
11680
|
+
toolName: "create_carousel",
|
|
11681
|
+
status: failedJobs.length === imageJobs.length ? "error" : "success",
|
|
11682
|
+
durationMs: Date.now() - startedAt,
|
|
11683
|
+
details: {
|
|
11684
|
+
carouselId: carousel.id,
|
|
11685
|
+
templateId,
|
|
11686
|
+
slideCount: carousel.slides.length,
|
|
11687
|
+
imagesStarted: successfulJobs.length,
|
|
11688
|
+
imagesFailed: failedJobs.length,
|
|
11689
|
+
imageModel: image_model,
|
|
11690
|
+
creditsUsed: getCreditsUsed2()
|
|
11691
|
+
}
|
|
11692
|
+
});
|
|
11693
|
+
if (format === "json") {
|
|
11694
|
+
return {
|
|
11695
|
+
content: [
|
|
11696
|
+
{
|
|
11697
|
+
type: "text",
|
|
11698
|
+
text: JSON.stringify(
|
|
11699
|
+
{
|
|
11700
|
+
_meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() },
|
|
11701
|
+
data: {
|
|
11702
|
+
carouselId: carousel.id,
|
|
11703
|
+
templateId,
|
|
11704
|
+
style: resolvedStyle,
|
|
11705
|
+
slideCount: carousel.slides.length,
|
|
11706
|
+
slides: carousel.slides.map((s) => {
|
|
11707
|
+
const job = imageJobs.find((j) => j.slideNumber === s.slideNumber);
|
|
11708
|
+
return {
|
|
11709
|
+
...s,
|
|
11710
|
+
imageJobId: job?.jobId ?? null,
|
|
11711
|
+
imageError: job?.error ?? null
|
|
11712
|
+
};
|
|
11713
|
+
}),
|
|
11714
|
+
imageModel: image_model,
|
|
11715
|
+
brandApplied: brandContext ? {
|
|
11716
|
+
brandName: brandContext.brandName,
|
|
11717
|
+
hasLogo: !!brandContext.logoDescription,
|
|
11718
|
+
stylePrefix: brandContext.stylePrefix
|
|
11719
|
+
} : null,
|
|
11720
|
+
jobIds: successfulJobs.map((j) => j.jobId),
|
|
11721
|
+
failedSlides: failedJobs.map((j) => ({
|
|
11722
|
+
slideNumber: j.slideNumber,
|
|
11723
|
+
error: j.error
|
|
11724
|
+
})),
|
|
11725
|
+
credits: {
|
|
11726
|
+
textGeneration: textCredits,
|
|
11727
|
+
imagesEstimated: successfulJobs.length * perImageCost,
|
|
11728
|
+
totalEstimated: textCredits + successfulJobs.length * perImageCost
|
|
11729
|
+
}
|
|
11730
|
+
}
|
|
11731
|
+
},
|
|
11732
|
+
null,
|
|
11733
|
+
2
|
|
11734
|
+
)
|
|
11735
|
+
}
|
|
11736
|
+
]
|
|
11737
|
+
};
|
|
11738
|
+
}
|
|
11739
|
+
const lines = [
|
|
11740
|
+
`Carousel created: ${carousel.slides.length} slides + ${successfulJobs.length} image jobs started.`,
|
|
11741
|
+
` Carousel ID: ${carousel.id}`,
|
|
11742
|
+
` Template: ${templateId} | Style: ${resolvedStyle}`,
|
|
11743
|
+
` Image model: ${image_model}`,
|
|
11744
|
+
` Credits: ~${textCredits + successfulJobs.length * perImageCost} (${textCredits} text + ${successfulJobs.length * perImageCost} images)`
|
|
11745
|
+
];
|
|
11746
|
+
if (brandContext) {
|
|
11747
|
+
lines.push(
|
|
11748
|
+
` Brand: ${brandContext.brandName || "unnamed"}${brandContext.logoDescription ? " (logo overlay via prompt)" : ""}`
|
|
11749
|
+
);
|
|
11750
|
+
}
|
|
11751
|
+
lines.push("", "Slides:");
|
|
11752
|
+
for (const slide of carousel.slides) {
|
|
11753
|
+
const job = imageJobs.find((j) => j.slideNumber === slide.slideNumber);
|
|
11754
|
+
const status = job?.jobId ? `image: ${job.jobId}` : `image FAILED: ${job?.error}`;
|
|
11755
|
+
lines.push(` ${slide.slideNumber}. ${slide.headline || "(no headline)"} [${status}]`);
|
|
11756
|
+
}
|
|
11757
|
+
if (failedJobs.length > 0) {
|
|
11758
|
+
lines.push("");
|
|
11759
|
+
lines.push(
|
|
11760
|
+
`WARNING: ${failedJobs.length}/${imageJobs.length} image generations failed. Use generate_image manually for failed slides.`
|
|
11761
|
+
);
|
|
11762
|
+
}
|
|
11763
|
+
const jobIdList = successfulJobs.map((j) => j.jobId).join(", ");
|
|
11764
|
+
lines.push("");
|
|
11765
|
+
lines.push("Next steps:");
|
|
11766
|
+
lines.push(` 1. Poll each job: check_status with job_id for each of: ${jobIdList}`);
|
|
11767
|
+
lines.push(
|
|
11768
|
+
" 2. When all complete: schedule_post with job_ids=[...] and media_type=CAROUSEL_ALBUM"
|
|
11769
|
+
);
|
|
11770
|
+
return {
|
|
11771
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
11772
|
+
};
|
|
11773
|
+
}
|
|
11774
|
+
);
|
|
11775
|
+
}
|
|
11776
|
+
|
|
10833
11777
|
// src/lib/register-tools.ts
|
|
10834
11778
|
function applyScopeEnforcement(server, scopeResolver) {
|
|
10835
11779
|
const originalTool = server.tool.bind(server);
|
|
@@ -10924,6 +11868,7 @@ function registerAllTools(server, options) {
|
|
|
10924
11868
|
registerLoopSummaryTools(server);
|
|
10925
11869
|
registerUsageTools(server);
|
|
10926
11870
|
registerAutopilotTools(server);
|
|
11871
|
+
registerRecipeTools(server);
|
|
10927
11872
|
registerExtractionTools(server);
|
|
10928
11873
|
registerQualityTools(server);
|
|
10929
11874
|
registerPlanningTools(server);
|
|
@@ -10933,21 +11878,22 @@ function registerAllTools(server, options) {
|
|
|
10933
11878
|
registerSuggestTools(server);
|
|
10934
11879
|
registerDigestTools(server);
|
|
10935
11880
|
registerBrandRuntimeTools(server);
|
|
11881
|
+
registerCarouselTools(server);
|
|
10936
11882
|
applyAnnotations(server);
|
|
10937
11883
|
}
|
|
10938
11884
|
|
|
10939
11885
|
// src/prompts.ts
|
|
10940
|
-
import { z as
|
|
11886
|
+
import { z as z28 } from "zod";
|
|
10941
11887
|
function registerPrompts(server) {
|
|
10942
11888
|
server.prompt(
|
|
10943
11889
|
"create_weekly_content_plan",
|
|
10944
11890
|
"Generate a full week of social media content (7 days, multiple platforms). Returns a structured plan with topics, formats, and posting times.",
|
|
10945
11891
|
{
|
|
10946
|
-
niche:
|
|
10947
|
-
platforms:
|
|
11892
|
+
niche: z28.string().describe('Your content niche or industry (e.g., "fitness coaching", "SaaS marketing")'),
|
|
11893
|
+
platforms: z28.string().optional().describe(
|
|
10948
11894
|
'Comma-separated platforms to target (default: "YouTube, Instagram, TikTok, LinkedIn")'
|
|
10949
11895
|
),
|
|
10950
|
-
tone:
|
|
11896
|
+
tone: z28.string().optional().describe('Brand tone of voice (e.g., "professional", "casual", "bold and edgy")')
|
|
10951
11897
|
},
|
|
10952
11898
|
({ niche, platforms, tone }) => {
|
|
10953
11899
|
const targetPlatforms = platforms || "YouTube, Instagram, TikTok, LinkedIn";
|
|
@@ -10989,8 +11935,8 @@ After building the plan, use \`save_content_plan\` to save it.`
|
|
|
10989
11935
|
"analyze_top_content",
|
|
10990
11936
|
"Analyze your best-performing posts to identify patterns and replicate success. Returns insights on hooks, formats, timing, and topics that resonate.",
|
|
10991
11937
|
{
|
|
10992
|
-
timeframe:
|
|
10993
|
-
platform:
|
|
11938
|
+
timeframe: z28.string().optional().describe('Analysis period (default: "30 days"). E.g., "7 days", "90 days"'),
|
|
11939
|
+
platform: z28.string().optional().describe('Filter to a specific platform (e.g., "youtube", "instagram")')
|
|
10994
11940
|
},
|
|
10995
11941
|
({ timeframe, platform: platform2 }) => {
|
|
10996
11942
|
const period = timeframe || "30 days";
|
|
@@ -11027,10 +11973,10 @@ Format as a clear, actionable performance report.`
|
|
|
11027
11973
|
"repurpose_content",
|
|
11028
11974
|
"Take one piece of content and transform it into 8-10 pieces across multiple platforms and formats.",
|
|
11029
11975
|
{
|
|
11030
|
-
source:
|
|
11976
|
+
source: z28.string().describe(
|
|
11031
11977
|
"The source content to repurpose \u2014 a URL, transcript, or the content text itself"
|
|
11032
11978
|
),
|
|
11033
|
-
target_platforms:
|
|
11979
|
+
target_platforms: z28.string().optional().describe(
|
|
11034
11980
|
'Comma-separated target platforms (default: "Twitter, LinkedIn, Instagram, TikTok, YouTube")'
|
|
11035
11981
|
)
|
|
11036
11982
|
},
|
|
@@ -11072,9 +12018,9 @@ For each piece, include the platform, format, character count, and suggested pos
|
|
|
11072
12018
|
"setup_brand_voice",
|
|
11073
12019
|
"Define or refine your brand voice profile so all generated content stays on-brand. Walks through tone, audience, values, and style.",
|
|
11074
12020
|
{
|
|
11075
|
-
brand_name:
|
|
11076
|
-
industry:
|
|
11077
|
-
website:
|
|
12021
|
+
brand_name: z28.string().describe("Your brand or business name"),
|
|
12022
|
+
industry: z28.string().optional().describe('Your industry or niche (e.g., "B2B SaaS", "fitness coaching")'),
|
|
12023
|
+
website: z28.string().optional().describe("Your website URL for context")
|
|
11078
12024
|
},
|
|
11079
12025
|
({ brand_name, industry, website }) => {
|
|
11080
12026
|
const industryContext = industry ? ` in the ${industry} space` : "";
|