@socialneuron/mcp-server 1.7.2 → 1.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/http.js +350 -113
- package/dist/index.js +355 -68
- package/package.json +1 -1
package/dist/http.js
CHANGED
|
@@ -1375,7 +1375,7 @@ init_supabase();
|
|
|
1375
1375
|
init_request_context();
|
|
1376
1376
|
|
|
1377
1377
|
// src/lib/version.ts
|
|
1378
|
-
var MCP_VERSION = "1.7.
|
|
1378
|
+
var MCP_VERSION = "1.7.2";
|
|
1379
1379
|
|
|
1380
1380
|
// src/tools/content.ts
|
|
1381
1381
|
var MAX_CREDITS_PER_RUN = Math.max(0, Number(process.env.SOCIALNEURON_MAX_CREDITS_PER_RUN || 0));
|
|
@@ -2507,6 +2507,56 @@ Return ONLY valid JSON in this exact format:
|
|
|
2507
2507
|
// src/tools/distribution.ts
|
|
2508
2508
|
import { z as z3 } from "zod";
|
|
2509
2509
|
import { createHash as createHash2 } from "node:crypto";
|
|
2510
|
+
|
|
2511
|
+
// src/lib/sanitize-error.ts
|
|
2512
|
+
var ERROR_PATTERNS = [
|
|
2513
|
+
// Postgres / PostgREST
|
|
2514
|
+
[/PGRST301|permission denied/i, "Access denied. Check your account permissions."],
|
|
2515
|
+
[/42P01|does not exist/i, "Service temporarily unavailable. Please try again."],
|
|
2516
|
+
[/23505|unique.*constraint|duplicate key/i, "A duplicate record already exists."],
|
|
2517
|
+
[/23503|foreign key/i, "Referenced record not found."],
|
|
2518
|
+
// Gemini / Google AI
|
|
2519
|
+
[/google.*api.*key|googleapis\.com.*40[13]/i, "Content generation failed. Please try again."],
|
|
2520
|
+
[
|
|
2521
|
+
/RESOURCE_EXHAUSTED|quota.*exceeded|429.*google/i,
|
|
2522
|
+
"AI service rate limit reached. Please wait and retry."
|
|
2523
|
+
],
|
|
2524
|
+
[
|
|
2525
|
+
/SAFETY|prompt.*blocked|content.*filter/i,
|
|
2526
|
+
"Content was blocked by the AI safety filter. Try rephrasing."
|
|
2527
|
+
],
|
|
2528
|
+
[/gemini.*error|generativelanguage/i, "Content generation failed. Please try again."],
|
|
2529
|
+
// Kie.ai
|
|
2530
|
+
[/kie\.ai|kieai|kie_api/i, "Media generation failed. Please try again."],
|
|
2531
|
+
// Stripe
|
|
2532
|
+
[/stripe.*api|sk_live_|sk_test_/i, "Payment processing error. Please try again."],
|
|
2533
|
+
// Network / fetch
|
|
2534
|
+
[
|
|
2535
|
+
/ECONNREFUSED|ENOTFOUND|ETIMEDOUT|ECONNRESET/i,
|
|
2536
|
+
"External service unavailable. Please try again."
|
|
2537
|
+
],
|
|
2538
|
+
[/fetch failed|network error|abort.*timeout/i, "Network request failed. Please try again."],
|
|
2539
|
+
[/CERT_|certificate|SSL|TLS/i, "Secure connection failed. Please try again."],
|
|
2540
|
+
// Supabase Edge Function internals
|
|
2541
|
+
[/FunctionsHttpError|non-2xx status/i, "Backend service error. Please try again."],
|
|
2542
|
+
[/JWT|token.*expired|token.*invalid/i, "Authentication expired. Please re-authenticate."],
|
|
2543
|
+
// Generic sensitive patterns (API keys, URLs with secrets)
|
|
2544
|
+
[/[a-z0-9]{32,}.*key|Bearer [a-zA-Z0-9._-]+/i, "An internal error occurred. Please try again."]
|
|
2545
|
+
];
|
|
2546
|
+
function sanitizeError2(error) {
|
|
2547
|
+
const msg = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
|
|
2548
|
+
if (process.env.NODE_ENV !== "production") {
|
|
2549
|
+
console.error("[Error]", msg);
|
|
2550
|
+
}
|
|
2551
|
+
for (const [pattern, userMessage] of ERROR_PATTERNS) {
|
|
2552
|
+
if (pattern.test(msg)) {
|
|
2553
|
+
return userMessage;
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
return "An unexpected error occurred. Please try again.";
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
// src/tools/distribution.ts
|
|
2510
2560
|
init_supabase();
|
|
2511
2561
|
|
|
2512
2562
|
// src/lib/quality.ts
|
|
@@ -2834,7 +2884,12 @@ function registerDistributionTools(server) {
|
|
|
2834
2884
|
const signed = await signR2Key(r2_key);
|
|
2835
2885
|
if (!signed) {
|
|
2836
2886
|
return {
|
|
2837
|
-
content: [
|
|
2887
|
+
content: [
|
|
2888
|
+
{
|
|
2889
|
+
type: "text",
|
|
2890
|
+
text: `Failed to sign media key. Verify the key exists and you have access.`
|
|
2891
|
+
}
|
|
2892
|
+
],
|
|
2838
2893
|
isError: true
|
|
2839
2894
|
};
|
|
2840
2895
|
}
|
|
@@ -2862,7 +2917,7 @@ function registerDistributionTools(server) {
|
|
|
2862
2917
|
content: [
|
|
2863
2918
|
{
|
|
2864
2919
|
type: "text",
|
|
2865
|
-
text: `Failed to sign
|
|
2920
|
+
text: `Failed to sign media key at index ${failIdx}. Verify the key exists and you have access.`
|
|
2866
2921
|
}
|
|
2867
2922
|
],
|
|
2868
2923
|
isError: true
|
|
@@ -2890,7 +2945,7 @@ function registerDistributionTools(server) {
|
|
|
2890
2945
|
content: [
|
|
2891
2946
|
{
|
|
2892
2947
|
type: "text",
|
|
2893
|
-
text: `Failed to resolve media: ${
|
|
2948
|
+
text: `Failed to resolve media: ${sanitizeError2(resolveErr)}`
|
|
2894
2949
|
}
|
|
2895
2950
|
],
|
|
2896
2951
|
isError: true
|
|
@@ -3255,7 +3310,7 @@ Created with Social Neuron`;
|
|
|
3255
3310
|
return { content: [{ type: "text", text: lines.join("\n") }], isError: false };
|
|
3256
3311
|
} catch (err) {
|
|
3257
3312
|
const durationMs = Date.now() - startedAt;
|
|
3258
|
-
const message =
|
|
3313
|
+
const message = sanitizeError2(err);
|
|
3259
3314
|
logMcpToolInvocation({
|
|
3260
3315
|
toolName: "find_next_slots",
|
|
3261
3316
|
status: "error",
|
|
@@ -3775,7 +3830,7 @@ Created with Social Neuron`;
|
|
|
3775
3830
|
};
|
|
3776
3831
|
} catch (err) {
|
|
3777
3832
|
const durationMs = Date.now() - startedAt;
|
|
3778
|
-
const message =
|
|
3833
|
+
const message = sanitizeError2(err);
|
|
3779
3834
|
logMcpToolInvocation({
|
|
3780
3835
|
toolName: "schedule_content_plan",
|
|
3781
3836
|
status: "error",
|
|
@@ -3930,7 +3985,7 @@ function registerMediaTools(server) {
|
|
|
3930
3985
|
content: [
|
|
3931
3986
|
{
|
|
3932
3987
|
type: "text",
|
|
3933
|
-
text: `R2 upload failed: ${
|
|
3988
|
+
text: `R2 upload failed: ${sanitizeError(uploadErr)}`
|
|
3934
3989
|
}
|
|
3935
3990
|
],
|
|
3936
3991
|
isError: true
|
|
@@ -5054,7 +5109,7 @@ function registerScreenshotTools(server) {
|
|
|
5054
5109
|
};
|
|
5055
5110
|
} catch (err) {
|
|
5056
5111
|
await closeBrowser();
|
|
5057
|
-
const message =
|
|
5112
|
+
const message = sanitizeError2(err);
|
|
5058
5113
|
await logMcpToolInvocation({
|
|
5059
5114
|
toolName: "capture_app_page",
|
|
5060
5115
|
status: "error",
|
|
@@ -5210,7 +5265,7 @@ function registerScreenshotTools(server) {
|
|
|
5210
5265
|
};
|
|
5211
5266
|
} catch (err) {
|
|
5212
5267
|
await closeBrowser();
|
|
5213
|
-
const message =
|
|
5268
|
+
const message = sanitizeError2(err);
|
|
5214
5269
|
await logMcpToolInvocation({
|
|
5215
5270
|
toolName: "capture_screenshot",
|
|
5216
5271
|
status: "error",
|
|
@@ -5503,7 +5558,7 @@ function registerRemotionTools(server) {
|
|
|
5503
5558
|
]
|
|
5504
5559
|
};
|
|
5505
5560
|
} catch (err) {
|
|
5506
|
-
const message =
|
|
5561
|
+
const message = sanitizeError2(err);
|
|
5507
5562
|
await logMcpToolInvocation({
|
|
5508
5563
|
toolName: "render_demo_video",
|
|
5509
5564
|
status: "error",
|
|
@@ -5631,7 +5686,7 @@ function registerRemotionTools(server) {
|
|
|
5631
5686
|
]
|
|
5632
5687
|
};
|
|
5633
5688
|
} catch (err) {
|
|
5634
|
-
const message =
|
|
5689
|
+
const message = sanitizeError2(err);
|
|
5635
5690
|
await logMcpToolInvocation({
|
|
5636
5691
|
toolName: "render_template_video",
|
|
5637
5692
|
status: "error",
|
|
@@ -7282,7 +7337,7 @@ function registerExtractionTools(server) {
|
|
|
7282
7337
|
};
|
|
7283
7338
|
} catch (err) {
|
|
7284
7339
|
const durationMs = Date.now() - startedAt;
|
|
7285
|
-
const message =
|
|
7340
|
+
const message = sanitizeError2(err);
|
|
7286
7341
|
logMcpToolInvocation({
|
|
7287
7342
|
toolName: "extract_url_content",
|
|
7288
7343
|
status: "error",
|
|
@@ -7838,7 +7893,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
7838
7893
|
}
|
|
7839
7894
|
} catch (persistErr) {
|
|
7840
7895
|
const durationMs2 = Date.now() - startedAt;
|
|
7841
|
-
const message =
|
|
7896
|
+
const message = sanitizeError2(persistErr);
|
|
7842
7897
|
logMcpToolInvocation({
|
|
7843
7898
|
toolName: "plan_content_week",
|
|
7844
7899
|
status: "error",
|
|
@@ -7870,7 +7925,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
7870
7925
|
};
|
|
7871
7926
|
} catch (err) {
|
|
7872
7927
|
const durationMs = Date.now() - startedAt;
|
|
7873
|
-
const message =
|
|
7928
|
+
const message = sanitizeError2(err);
|
|
7874
7929
|
logMcpToolInvocation({
|
|
7875
7930
|
toolName: "plan_content_week",
|
|
7876
7931
|
status: "error",
|
|
@@ -7962,7 +8017,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
7962
8017
|
};
|
|
7963
8018
|
} catch (err) {
|
|
7964
8019
|
const durationMs = Date.now() - startedAt;
|
|
7965
|
-
const message =
|
|
8020
|
+
const message = sanitizeError2(err);
|
|
7966
8021
|
logMcpToolInvocation({
|
|
7967
8022
|
toolName: "save_content_plan",
|
|
7968
8023
|
status: "error",
|
|
@@ -9007,7 +9062,7 @@ function registerPipelineTools(server) {
|
|
|
9007
9062
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
9008
9063
|
} catch (err) {
|
|
9009
9064
|
const durationMs = Date.now() - startedAt;
|
|
9010
|
-
const message =
|
|
9065
|
+
const message = sanitizeError2(err);
|
|
9011
9066
|
logMcpToolInvocation({
|
|
9012
9067
|
toolName: "check_pipeline_readiness",
|
|
9013
9068
|
status: "error",
|
|
@@ -9168,7 +9223,7 @@ function registerPipelineTools(server) {
|
|
|
9168
9223
|
} catch (deductErr) {
|
|
9169
9224
|
errors.push({
|
|
9170
9225
|
stage: "planning",
|
|
9171
|
-
message: `Credit deduction failed: ${
|
|
9226
|
+
message: `Credit deduction failed: ${sanitizeError2(deductErr)}`
|
|
9172
9227
|
});
|
|
9173
9228
|
}
|
|
9174
9229
|
}
|
|
@@ -9339,7 +9394,7 @@ function registerPipelineTools(server) {
|
|
|
9339
9394
|
} catch (schedErr) {
|
|
9340
9395
|
errors.push({
|
|
9341
9396
|
stage: "schedule",
|
|
9342
|
-
message: `Failed to schedule ${post.id}: ${
|
|
9397
|
+
message: `Failed to schedule ${post.id}: ${sanitizeError2(schedErr)}`
|
|
9343
9398
|
});
|
|
9344
9399
|
}
|
|
9345
9400
|
}
|
|
@@ -9428,7 +9483,7 @@ function registerPipelineTools(server) {
|
|
|
9428
9483
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
9429
9484
|
} catch (err) {
|
|
9430
9485
|
const durationMs = Date.now() - startedAt;
|
|
9431
|
-
const message =
|
|
9486
|
+
const message = sanitizeError2(err);
|
|
9432
9487
|
logMcpToolInvocation({
|
|
9433
9488
|
toolName: "run_content_pipeline",
|
|
9434
9489
|
status: "error",
|
|
@@ -9652,7 +9707,7 @@ function registerPipelineTools(server) {
|
|
|
9652
9707
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
9653
9708
|
} catch (err) {
|
|
9654
9709
|
const durationMs = Date.now() - startedAt;
|
|
9655
|
-
const message =
|
|
9710
|
+
const message = sanitizeError2(err);
|
|
9656
9711
|
logMcpToolInvocation({
|
|
9657
9712
|
toolName: "auto_approve_plan",
|
|
9658
9713
|
status: "error",
|
|
@@ -9830,7 +9885,7 @@ ${i + 1}. ${s.topic}`);
|
|
|
9830
9885
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
9831
9886
|
} catch (err) {
|
|
9832
9887
|
const durationMs = Date.now() - startedAt;
|
|
9833
|
-
const message =
|
|
9888
|
+
const message = sanitizeError2(err);
|
|
9834
9889
|
logMcpToolInvocation({
|
|
9835
9890
|
toolName: "suggest_next_content",
|
|
9836
9891
|
status: "error",
|
|
@@ -10169,7 +10224,7 @@ Best: ${best.id.slice(0, 8)}... (${best.platform}) \u2014 ${best.views.toLocaleS
|
|
|
10169
10224
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
10170
10225
|
} catch (err) {
|
|
10171
10226
|
const durationMs = Date.now() - startedAt;
|
|
10172
|
-
const message =
|
|
10227
|
+
const message = sanitizeError2(err);
|
|
10173
10228
|
logMcpToolInvocation({
|
|
10174
10229
|
toolName: "generate_performance_digest",
|
|
10175
10230
|
status: "error",
|
|
@@ -10266,7 +10321,7 @@ Best: ${best.id.slice(0, 8)}... (${best.platform}) \u2014 ${best.views.toLocaleS
|
|
|
10266
10321
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
10267
10322
|
} catch (err) {
|
|
10268
10323
|
const durationMs = Date.now() - startedAt;
|
|
10269
|
-
const message =
|
|
10324
|
+
const message = sanitizeError2(err);
|
|
10270
10325
|
logMcpToolInvocation({
|
|
10271
10326
|
toolName: "detect_anomalies",
|
|
10272
10327
|
status: "error",
|
|
@@ -10285,6 +10340,275 @@ Best: ${best.id.slice(0, 8)}... (${best.platform}) \u2014 ${best.views.toLocaleS
|
|
|
10285
10340
|
// src/tools/brandRuntime.ts
|
|
10286
10341
|
import { z as z25 } from "zod";
|
|
10287
10342
|
init_supabase();
|
|
10343
|
+
|
|
10344
|
+
// src/lib/brandScoring.ts
|
|
10345
|
+
var WEIGHTS = {
|
|
10346
|
+
toneAlignment: 0.3,
|
|
10347
|
+
vocabularyAdherence: 0.25,
|
|
10348
|
+
avoidCompliance: 0.2,
|
|
10349
|
+
audienceRelevance: 0.15,
|
|
10350
|
+
brandMentions: 0.05,
|
|
10351
|
+
structuralPatterns: 0.05
|
|
10352
|
+
};
|
|
10353
|
+
function norm(content) {
|
|
10354
|
+
return content.toLowerCase().replace(/[^a-z0-9\s]/g, " ");
|
|
10355
|
+
}
|
|
10356
|
+
function findMatches(content, terms) {
|
|
10357
|
+
const n = norm(content);
|
|
10358
|
+
return terms.filter((t) => n.includes(t.toLowerCase()));
|
|
10359
|
+
}
|
|
10360
|
+
function findMissing(content, terms) {
|
|
10361
|
+
const n = norm(content);
|
|
10362
|
+
return terms.filter((t) => !n.includes(t.toLowerCase()));
|
|
10363
|
+
}
|
|
10364
|
+
var FABRICATION_PATTERNS = [
|
|
10365
|
+
{ regex: /\b\d+[,.]?\d*\s*(%|percent)/gi, label: "unverified percentage" },
|
|
10366
|
+
{ regex: /\b(award[- ]?winning|best[- ]selling|#\s*1)\b/gi, label: "unverified ranking" },
|
|
10367
|
+
{
|
|
10368
|
+
regex: /\b(guaranteed|proven to|studies show|scientifically proven)\b/gi,
|
|
10369
|
+
label: "unverified claim"
|
|
10370
|
+
},
|
|
10371
|
+
{
|
|
10372
|
+
regex: /\b(always works|100% effective|risk[- ]?free|no risk)\b/gi,
|
|
10373
|
+
label: "absolute claim"
|
|
10374
|
+
}
|
|
10375
|
+
];
|
|
10376
|
+
function detectFabricationPatterns(content) {
|
|
10377
|
+
const matches = [];
|
|
10378
|
+
for (const { regex, label } of FABRICATION_PATTERNS) {
|
|
10379
|
+
const re = new RegExp(regex.source, regex.flags);
|
|
10380
|
+
let m;
|
|
10381
|
+
while ((m = re.exec(content)) !== null) {
|
|
10382
|
+
matches.push({ label, match: m[0] });
|
|
10383
|
+
}
|
|
10384
|
+
}
|
|
10385
|
+
return matches;
|
|
10386
|
+
}
|
|
10387
|
+
function scoreTone(content, profile) {
|
|
10388
|
+
const terms = profile.voiceProfile?.tone || [];
|
|
10389
|
+
if (!terms.length)
|
|
10390
|
+
return {
|
|
10391
|
+
score: 50,
|
|
10392
|
+
weight: WEIGHTS.toneAlignment,
|
|
10393
|
+
issues: [],
|
|
10394
|
+
suggestions: ["Define brand tone words for better consistency measurement"]
|
|
10395
|
+
};
|
|
10396
|
+
const matched = findMatches(content, terms);
|
|
10397
|
+
const missing = findMissing(content, terms);
|
|
10398
|
+
const score = Math.min(100, Math.round(matched.length / terms.length * 100));
|
|
10399
|
+
const issues = [];
|
|
10400
|
+
const suggestions = [];
|
|
10401
|
+
if (missing.length > 0) {
|
|
10402
|
+
issues.push(`Missing tone signals: ${missing.join(", ")}`);
|
|
10403
|
+
suggestions.push(`Try incorporating tone words: ${missing.slice(0, 3).join(", ")}`);
|
|
10404
|
+
}
|
|
10405
|
+
return { score, weight: WEIGHTS.toneAlignment, issues, suggestions };
|
|
10406
|
+
}
|
|
10407
|
+
function scoreVocab(content, profile) {
|
|
10408
|
+
const preferred = [
|
|
10409
|
+
...profile.voiceProfile?.languagePatterns || [],
|
|
10410
|
+
...profile.vocabularyRules?.preferredTerms || []
|
|
10411
|
+
];
|
|
10412
|
+
if (!preferred.length)
|
|
10413
|
+
return {
|
|
10414
|
+
score: 50,
|
|
10415
|
+
weight: WEIGHTS.vocabularyAdherence,
|
|
10416
|
+
issues: [],
|
|
10417
|
+
suggestions: ["Add preferred terms to improve vocabulary scoring"]
|
|
10418
|
+
};
|
|
10419
|
+
const matched = findMatches(content, preferred);
|
|
10420
|
+
const missing = findMissing(content, preferred);
|
|
10421
|
+
const score = Math.min(100, Math.round(matched.length / preferred.length * 100));
|
|
10422
|
+
const issues = [];
|
|
10423
|
+
const suggestions = [];
|
|
10424
|
+
if (missing.length > 0 && score < 60) {
|
|
10425
|
+
issues.push(`Low preferred term usage (${matched.length}/${preferred.length})`);
|
|
10426
|
+
suggestions.push(`Consider using: ${missing.slice(0, 3).join(", ")}`);
|
|
10427
|
+
}
|
|
10428
|
+
return { score, weight: WEIGHTS.vocabularyAdherence, issues, suggestions };
|
|
10429
|
+
}
|
|
10430
|
+
function scoreAvoid(content, profile) {
|
|
10431
|
+
const banned = [
|
|
10432
|
+
...profile.voiceProfile?.avoidPatterns || [],
|
|
10433
|
+
...profile.vocabularyRules?.bannedTerms || []
|
|
10434
|
+
];
|
|
10435
|
+
if (!banned.length)
|
|
10436
|
+
return {
|
|
10437
|
+
score: 100,
|
|
10438
|
+
weight: WEIGHTS.avoidCompliance,
|
|
10439
|
+
issues: [],
|
|
10440
|
+
suggestions: []
|
|
10441
|
+
};
|
|
10442
|
+
const violations = findMatches(content, banned);
|
|
10443
|
+
const score = violations.length === 0 ? 100 : Math.max(0, 100 - violations.length * 25);
|
|
10444
|
+
const issues = [];
|
|
10445
|
+
const suggestions = [];
|
|
10446
|
+
if (violations.length > 0) {
|
|
10447
|
+
issues.push(`Banned/avoided terms found: ${violations.join(", ")}`);
|
|
10448
|
+
suggestions.push(`Remove or replace: ${violations.join(", ")}`);
|
|
10449
|
+
}
|
|
10450
|
+
return { score, weight: WEIGHTS.avoidCompliance, issues, suggestions };
|
|
10451
|
+
}
|
|
10452
|
+
function scoreAudience(content, profile) {
|
|
10453
|
+
const terms = [];
|
|
10454
|
+
const d = profile.targetAudience?.demographics;
|
|
10455
|
+
const p = profile.targetAudience?.psychographics;
|
|
10456
|
+
if (d?.ageRange) terms.push(d.ageRange);
|
|
10457
|
+
if (d?.location) terms.push(d.location);
|
|
10458
|
+
if (p?.interests) terms.push(...p.interests);
|
|
10459
|
+
if (p?.painPoints) terms.push(...p.painPoints);
|
|
10460
|
+
if (p?.aspirations) terms.push(...p.aspirations);
|
|
10461
|
+
const valid = terms.filter(Boolean);
|
|
10462
|
+
if (!valid.length)
|
|
10463
|
+
return {
|
|
10464
|
+
score: 50,
|
|
10465
|
+
weight: WEIGHTS.audienceRelevance,
|
|
10466
|
+
issues: [],
|
|
10467
|
+
suggestions: ["Define target audience details for relevance scoring"]
|
|
10468
|
+
};
|
|
10469
|
+
const matched = findMatches(content, valid);
|
|
10470
|
+
const score = Math.min(100, Math.round(matched.length / valid.length * 100));
|
|
10471
|
+
const issues = [];
|
|
10472
|
+
const suggestions = [];
|
|
10473
|
+
if (score < 40) {
|
|
10474
|
+
issues.push("Content has low audience relevance");
|
|
10475
|
+
suggestions.push(
|
|
10476
|
+
`Reference audience pain points or interests: ${valid.slice(0, 3).join(", ")}`
|
|
10477
|
+
);
|
|
10478
|
+
}
|
|
10479
|
+
return { score, weight: WEIGHTS.audienceRelevance, issues, suggestions };
|
|
10480
|
+
}
|
|
10481
|
+
function scoreBrand(content, profile) {
|
|
10482
|
+
const name = profile.name?.toLowerCase();
|
|
10483
|
+
if (!name)
|
|
10484
|
+
return {
|
|
10485
|
+
score: 50,
|
|
10486
|
+
weight: WEIGHTS.brandMentions,
|
|
10487
|
+
issues: [],
|
|
10488
|
+
suggestions: []
|
|
10489
|
+
};
|
|
10490
|
+
const mentioned = norm(content).includes(name);
|
|
10491
|
+
const issues = [];
|
|
10492
|
+
const suggestions = [];
|
|
10493
|
+
if (!mentioned) {
|
|
10494
|
+
issues.push("Brand name not mentioned");
|
|
10495
|
+
suggestions.push(`Include "${profile.name}" in the content`);
|
|
10496
|
+
}
|
|
10497
|
+
return {
|
|
10498
|
+
score: mentioned ? 100 : 0,
|
|
10499
|
+
weight: WEIGHTS.brandMentions,
|
|
10500
|
+
issues,
|
|
10501
|
+
suggestions
|
|
10502
|
+
};
|
|
10503
|
+
}
|
|
10504
|
+
function scoreStructure(content, profile) {
|
|
10505
|
+
const rules = profile.writingStyleRules;
|
|
10506
|
+
if (!rules)
|
|
10507
|
+
return {
|
|
10508
|
+
score: 50,
|
|
10509
|
+
weight: WEIGHTS.structuralPatterns,
|
|
10510
|
+
issues: [],
|
|
10511
|
+
suggestions: []
|
|
10512
|
+
};
|
|
10513
|
+
let score = 100;
|
|
10514
|
+
const issues = [];
|
|
10515
|
+
const suggestions = [];
|
|
10516
|
+
if (rules.perspective) {
|
|
10517
|
+
const markers = {
|
|
10518
|
+
"first-singular": [/\bI\b/g, /\bmy\b/gi],
|
|
10519
|
+
"first-plural": [/\bwe\b/gi, /\bour\b/gi],
|
|
10520
|
+
second: [/\byou\b/gi, /\byour\b/gi],
|
|
10521
|
+
third: [/\bthey\b/gi, /\btheir\b/gi]
|
|
10522
|
+
};
|
|
10523
|
+
const expected = markers[rules.perspective];
|
|
10524
|
+
if (expected && !expected.some((r) => r.test(content))) {
|
|
10525
|
+
score -= 30;
|
|
10526
|
+
issues.push(`Expected ${rules.perspective} perspective not detected`);
|
|
10527
|
+
suggestions.push(`Use ${rules.perspective} perspective pronouns`);
|
|
10528
|
+
}
|
|
10529
|
+
}
|
|
10530
|
+
if (rules.useContractions === false) {
|
|
10531
|
+
const found = content.match(
|
|
10532
|
+
/\b(don't|won't|can't|isn't|aren't|wasn't|weren't|hasn't|haven't|doesn't|didn't|wouldn't|couldn't|shouldn't|it's|that's|there's|here's|what's|who's|let's|we're|they're|you're|I'm|he's|she's)\b/gi
|
|
10533
|
+
);
|
|
10534
|
+
if (found && found.length > 0) {
|
|
10535
|
+
score -= Math.min(40, found.length * 10);
|
|
10536
|
+
issues.push(`Contractions found (${found.length}): ${found.slice(0, 3).join(", ")}`);
|
|
10537
|
+
suggestions.push("Expand contractions to full forms");
|
|
10538
|
+
}
|
|
10539
|
+
}
|
|
10540
|
+
if (rules.emojiPolicy === "none") {
|
|
10541
|
+
const emojis = content.match(
|
|
10542
|
+
/[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/gu
|
|
10543
|
+
);
|
|
10544
|
+
if (emojis && emojis.length > 0) {
|
|
10545
|
+
score -= 20;
|
|
10546
|
+
issues.push('Emojis found but emoji policy is "none"');
|
|
10547
|
+
suggestions.push("Remove emojis from content");
|
|
10548
|
+
}
|
|
10549
|
+
}
|
|
10550
|
+
return {
|
|
10551
|
+
score: Math.max(0, score),
|
|
10552
|
+
weight: WEIGHTS.structuralPatterns,
|
|
10553
|
+
issues,
|
|
10554
|
+
suggestions
|
|
10555
|
+
};
|
|
10556
|
+
}
|
|
10557
|
+
function computeBrandConsistency(content, profile, threshold = 60) {
|
|
10558
|
+
if (!content || !profile) {
|
|
10559
|
+
const neutral = {
|
|
10560
|
+
score: 50,
|
|
10561
|
+
weight: 0,
|
|
10562
|
+
issues: [],
|
|
10563
|
+
suggestions: []
|
|
10564
|
+
};
|
|
10565
|
+
return {
|
|
10566
|
+
overall: 50,
|
|
10567
|
+
passed: false,
|
|
10568
|
+
dimensions: {
|
|
10569
|
+
toneAlignment: { ...neutral, weight: WEIGHTS.toneAlignment },
|
|
10570
|
+
vocabularyAdherence: { ...neutral, weight: WEIGHTS.vocabularyAdherence },
|
|
10571
|
+
avoidCompliance: { ...neutral, weight: WEIGHTS.avoidCompliance },
|
|
10572
|
+
audienceRelevance: { ...neutral, weight: WEIGHTS.audienceRelevance },
|
|
10573
|
+
brandMentions: { ...neutral, weight: WEIGHTS.brandMentions },
|
|
10574
|
+
structuralPatterns: { ...neutral, weight: WEIGHTS.structuralPatterns }
|
|
10575
|
+
},
|
|
10576
|
+
preferredTermsUsed: [],
|
|
10577
|
+
bannedTermsFound: [],
|
|
10578
|
+
fabricationWarnings: []
|
|
10579
|
+
};
|
|
10580
|
+
}
|
|
10581
|
+
const dimensions = {
|
|
10582
|
+
toneAlignment: scoreTone(content, profile),
|
|
10583
|
+
vocabularyAdherence: scoreVocab(content, profile),
|
|
10584
|
+
avoidCompliance: scoreAvoid(content, profile),
|
|
10585
|
+
audienceRelevance: scoreAudience(content, profile),
|
|
10586
|
+
brandMentions: scoreBrand(content, profile),
|
|
10587
|
+
structuralPatterns: scoreStructure(content, profile)
|
|
10588
|
+
};
|
|
10589
|
+
const overall = Math.round(
|
|
10590
|
+
Object.values(dimensions).reduce((sum, d) => sum + d.score * d.weight, 0)
|
|
10591
|
+
);
|
|
10592
|
+
const preferred = [
|
|
10593
|
+
...profile.voiceProfile?.languagePatterns || [],
|
|
10594
|
+
...profile.vocabularyRules?.preferredTerms || []
|
|
10595
|
+
];
|
|
10596
|
+
const banned = [
|
|
10597
|
+
...profile.voiceProfile?.avoidPatterns || [],
|
|
10598
|
+
...profile.vocabularyRules?.bannedTerms || []
|
|
10599
|
+
];
|
|
10600
|
+
const fabrications = detectFabricationPatterns(content);
|
|
10601
|
+
return {
|
|
10602
|
+
overall: Math.max(0, Math.min(100, overall)),
|
|
10603
|
+
passed: overall >= threshold,
|
|
10604
|
+
dimensions,
|
|
10605
|
+
preferredTermsUsed: findMatches(content, preferred),
|
|
10606
|
+
bannedTermsFound: findMatches(content, banned),
|
|
10607
|
+
fabricationWarnings: fabrications.map((f) => `${f.label}: "${f.match}"`)
|
|
10608
|
+
};
|
|
10609
|
+
}
|
|
10610
|
+
|
|
10611
|
+
// src/tools/brandRuntime.ts
|
|
10288
10612
|
function asEnvelope20(data) {
|
|
10289
10613
|
return {
|
|
10290
10614
|
_meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() },
|
|
@@ -10497,44 +10821,7 @@ function registerBrandRuntimeTools(server) {
|
|
|
10497
10821
|
};
|
|
10498
10822
|
}
|
|
10499
10823
|
const profile = row.profile_data;
|
|
10500
|
-
const
|
|
10501
|
-
const issues = [];
|
|
10502
|
-
let score = 70;
|
|
10503
|
-
const banned = profile.vocabularyRules?.bannedTerms || [];
|
|
10504
|
-
const bannedFound = banned.filter((t) => contentLower.includes(t.toLowerCase()));
|
|
10505
|
-
if (bannedFound.length > 0) {
|
|
10506
|
-
score -= bannedFound.length * 15;
|
|
10507
|
-
issues.push(`Banned terms found: ${bannedFound.join(", ")}`);
|
|
10508
|
-
}
|
|
10509
|
-
const avoid = profile.voiceProfile?.avoidPatterns || [];
|
|
10510
|
-
const avoidFound = avoid.filter((p) => contentLower.includes(p.toLowerCase()));
|
|
10511
|
-
if (avoidFound.length > 0) {
|
|
10512
|
-
score -= avoidFound.length * 10;
|
|
10513
|
-
issues.push(`Avoid patterns found: ${avoidFound.join(", ")}`);
|
|
10514
|
-
}
|
|
10515
|
-
const preferred = profile.vocabularyRules?.preferredTerms || [];
|
|
10516
|
-
const prefUsed = preferred.filter((t) => contentLower.includes(t.toLowerCase()));
|
|
10517
|
-
score += Math.min(15, prefUsed.length * 5);
|
|
10518
|
-
const fabPatterns = [
|
|
10519
|
-
{ regex: /\b\d+[,.]?\d*\s*(%|percent)/gi, label: "unverified percentage" },
|
|
10520
|
-
{ regex: /\b(award[- ]?winning|best[- ]selling|#\s*1)\b/gi, label: "unverified ranking" },
|
|
10521
|
-
{ regex: /\b(guaranteed|proven to|studies show)\b/gi, label: "unverified claim" }
|
|
10522
|
-
];
|
|
10523
|
-
for (const { regex, label } of fabPatterns) {
|
|
10524
|
-
regex.lastIndex = 0;
|
|
10525
|
-
if (regex.test(content)) {
|
|
10526
|
-
score -= 10;
|
|
10527
|
-
issues.push(`Potential ${label} detected`);
|
|
10528
|
-
}
|
|
10529
|
-
}
|
|
10530
|
-
score = Math.max(0, Math.min(100, score));
|
|
10531
|
-
const checkResult = {
|
|
10532
|
-
score,
|
|
10533
|
-
passed: score >= 60,
|
|
10534
|
-
issues,
|
|
10535
|
-
preferredTermsUsed: prefUsed,
|
|
10536
|
-
bannedTermsFound: bannedFound
|
|
10537
|
-
};
|
|
10824
|
+
const checkResult = computeBrandConsistency(content, profile);
|
|
10538
10825
|
const envelope = asEnvelope20(checkResult);
|
|
10539
10826
|
return {
|
|
10540
10827
|
content: [{ type: "text", text: JSON.stringify(envelope, null, 2) }]
|
|
@@ -11386,56 +11673,6 @@ function createOAuthProvider(options) {
|
|
|
11386
11673
|
|
|
11387
11674
|
// src/http.ts
|
|
11388
11675
|
init_posthog();
|
|
11389
|
-
|
|
11390
|
-
// src/lib/sanitize-error.ts
|
|
11391
|
-
var ERROR_PATTERNS = [
|
|
11392
|
-
// Postgres / PostgREST
|
|
11393
|
-
[/PGRST301|permission denied/i, "Access denied. Check your account permissions."],
|
|
11394
|
-
[/42P01|does not exist/i, "Service temporarily unavailable. Please try again."],
|
|
11395
|
-
[/23505|unique.*constraint|duplicate key/i, "A duplicate record already exists."],
|
|
11396
|
-
[/23503|foreign key/i, "Referenced record not found."],
|
|
11397
|
-
// Gemini / Google AI
|
|
11398
|
-
[/google.*api.*key|googleapis\.com.*40[13]/i, "Content generation failed. Please try again."],
|
|
11399
|
-
[
|
|
11400
|
-
/RESOURCE_EXHAUSTED|quota.*exceeded|429.*google/i,
|
|
11401
|
-
"AI service rate limit reached. Please wait and retry."
|
|
11402
|
-
],
|
|
11403
|
-
[
|
|
11404
|
-
/SAFETY|prompt.*blocked|content.*filter/i,
|
|
11405
|
-
"Content was blocked by the AI safety filter. Try rephrasing."
|
|
11406
|
-
],
|
|
11407
|
-
[/gemini.*error|generativelanguage/i, "Content generation failed. Please try again."],
|
|
11408
|
-
// Kie.ai
|
|
11409
|
-
[/kie\.ai|kieai|kie_api/i, "Media generation failed. Please try again."],
|
|
11410
|
-
// Stripe
|
|
11411
|
-
[/stripe.*api|sk_live_|sk_test_/i, "Payment processing error. Please try again."],
|
|
11412
|
-
// Network / fetch
|
|
11413
|
-
[
|
|
11414
|
-
/ECONNREFUSED|ENOTFOUND|ETIMEDOUT|ECONNRESET/i,
|
|
11415
|
-
"External service unavailable. Please try again."
|
|
11416
|
-
],
|
|
11417
|
-
[/fetch failed|network error|abort.*timeout/i, "Network request failed. Please try again."],
|
|
11418
|
-
[/CERT_|certificate|SSL|TLS/i, "Secure connection failed. Please try again."],
|
|
11419
|
-
// Supabase Edge Function internals
|
|
11420
|
-
[/FunctionsHttpError|non-2xx status/i, "Backend service error. Please try again."],
|
|
11421
|
-
[/JWT|token.*expired|token.*invalid/i, "Authentication expired. Please re-authenticate."],
|
|
11422
|
-
// Generic sensitive patterns (API keys, URLs with secrets)
|
|
11423
|
-
[/[a-z0-9]{32,}.*key|Bearer [a-zA-Z0-9._-]+/i, "An internal error occurred. Please try again."]
|
|
11424
|
-
];
|
|
11425
|
-
function sanitizeError(error) {
|
|
11426
|
-
const msg = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
|
|
11427
|
-
if (process.env.NODE_ENV !== "production") {
|
|
11428
|
-
console.error("[Error]", msg);
|
|
11429
|
-
}
|
|
11430
|
-
for (const [pattern, userMessage] of ERROR_PATTERNS) {
|
|
11431
|
-
if (pattern.test(msg)) {
|
|
11432
|
-
return userMessage;
|
|
11433
|
-
}
|
|
11434
|
-
}
|
|
11435
|
-
return "An unexpected error occurred. Please try again.";
|
|
11436
|
-
}
|
|
11437
|
-
|
|
11438
|
-
// src/http.ts
|
|
11439
11676
|
var PORT = parseInt(process.env.PORT ?? "8080", 10);
|
|
11440
11677
|
var SUPABASE_URL2 = process.env.SUPABASE_URL ?? "";
|
|
11441
11678
|
var SUPABASE_ANON_KEY = process.env.SUPABASE_ANON_KEY ?? "";
|
|
@@ -11903,7 +12140,7 @@ app.post("/mcp", authenticateRequest, async (req, res) => {
|
|
|
11903
12140
|
const rawMessage = err instanceof Error ? err.message : "Internal server error";
|
|
11904
12141
|
console.error(`[MCP HTTP] POST /mcp error: ${rawMessage}`);
|
|
11905
12142
|
if (!res.headersSent) {
|
|
11906
|
-
res.status(500).json({ jsonrpc: "2.0", error: { code: -32603, message:
|
|
12143
|
+
res.status(500).json({ jsonrpc: "2.0", error: { code: -32603, message: sanitizeError2(err) } });
|
|
11907
12144
|
}
|
|
11908
12145
|
}
|
|
11909
12146
|
});
|
|
@@ -11945,7 +12182,7 @@ app.delete("/mcp", authenticateRequest, async (req, res) => {
|
|
|
11945
12182
|
app.use((err, _req, res, _next) => {
|
|
11946
12183
|
console.error("[MCP HTTP] Unhandled Express error:", err.stack || err.message || err);
|
|
11947
12184
|
if (!res.headersSent) {
|
|
11948
|
-
res.status(500).json({ error: "internal_error", error_description: err
|
|
12185
|
+
res.status(500).json({ error: "internal_error", error_description: sanitizeError2(err) });
|
|
11949
12186
|
}
|
|
11950
12187
|
});
|
|
11951
12188
|
var httpServer = app.listen(PORT, "0.0.0.0", () => {
|
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.2";
|
|
18
18
|
}
|
|
19
19
|
});
|
|
20
20
|
|
|
@@ -5658,6 +5658,56 @@ Return ONLY valid JSON in this exact format:
|
|
|
5658
5658
|
init_edge_function();
|
|
5659
5659
|
import { z as z3 } from "zod";
|
|
5660
5660
|
import { createHash as createHash2 } from "node:crypto";
|
|
5661
|
+
|
|
5662
|
+
// src/lib/sanitize-error.ts
|
|
5663
|
+
var ERROR_PATTERNS = [
|
|
5664
|
+
// Postgres / PostgREST
|
|
5665
|
+
[/PGRST301|permission denied/i, "Access denied. Check your account permissions."],
|
|
5666
|
+
[/42P01|does not exist/i, "Service temporarily unavailable. Please try again."],
|
|
5667
|
+
[/23505|unique.*constraint|duplicate key/i, "A duplicate record already exists."],
|
|
5668
|
+
[/23503|foreign key/i, "Referenced record not found."],
|
|
5669
|
+
// Gemini / Google AI
|
|
5670
|
+
[/google.*api.*key|googleapis\.com.*40[13]/i, "Content generation failed. Please try again."],
|
|
5671
|
+
[
|
|
5672
|
+
/RESOURCE_EXHAUSTED|quota.*exceeded|429.*google/i,
|
|
5673
|
+
"AI service rate limit reached. Please wait and retry."
|
|
5674
|
+
],
|
|
5675
|
+
[
|
|
5676
|
+
/SAFETY|prompt.*blocked|content.*filter/i,
|
|
5677
|
+
"Content was blocked by the AI safety filter. Try rephrasing."
|
|
5678
|
+
],
|
|
5679
|
+
[/gemini.*error|generativelanguage/i, "Content generation failed. Please try again."],
|
|
5680
|
+
// Kie.ai
|
|
5681
|
+
[/kie\.ai|kieai|kie_api/i, "Media generation failed. Please try again."],
|
|
5682
|
+
// Stripe
|
|
5683
|
+
[/stripe.*api|sk_live_|sk_test_/i, "Payment processing error. Please try again."],
|
|
5684
|
+
// Network / fetch
|
|
5685
|
+
[
|
|
5686
|
+
/ECONNREFUSED|ENOTFOUND|ETIMEDOUT|ECONNRESET/i,
|
|
5687
|
+
"External service unavailable. Please try again."
|
|
5688
|
+
],
|
|
5689
|
+
[/fetch failed|network error|abort.*timeout/i, "Network request failed. Please try again."],
|
|
5690
|
+
[/CERT_|certificate|SSL|TLS/i, "Secure connection failed. Please try again."],
|
|
5691
|
+
// Supabase Edge Function internals
|
|
5692
|
+
[/FunctionsHttpError|non-2xx status/i, "Backend service error. Please try again."],
|
|
5693
|
+
[/JWT|token.*expired|token.*invalid/i, "Authentication expired. Please re-authenticate."],
|
|
5694
|
+
// Generic sensitive patterns (API keys, URLs with secrets)
|
|
5695
|
+
[/[a-z0-9]{32,}.*key|Bearer [a-zA-Z0-9._-]+/i, "An internal error occurred. Please try again."]
|
|
5696
|
+
];
|
|
5697
|
+
function sanitizeError2(error) {
|
|
5698
|
+
const msg = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
|
|
5699
|
+
if (process.env.NODE_ENV !== "production") {
|
|
5700
|
+
console.error("[Error]", msg);
|
|
5701
|
+
}
|
|
5702
|
+
for (const [pattern, userMessage] of ERROR_PATTERNS) {
|
|
5703
|
+
if (pattern.test(msg)) {
|
|
5704
|
+
return userMessage;
|
|
5705
|
+
}
|
|
5706
|
+
}
|
|
5707
|
+
return "An unexpected error occurred. Please try again.";
|
|
5708
|
+
}
|
|
5709
|
+
|
|
5710
|
+
// src/tools/distribution.ts
|
|
5661
5711
|
init_supabase();
|
|
5662
5712
|
init_quality();
|
|
5663
5713
|
init_version();
|
|
@@ -5863,7 +5913,12 @@ function registerDistributionTools(server2) {
|
|
|
5863
5913
|
const signed = await signR2Key(r2_key);
|
|
5864
5914
|
if (!signed) {
|
|
5865
5915
|
return {
|
|
5866
|
-
content: [
|
|
5916
|
+
content: [
|
|
5917
|
+
{
|
|
5918
|
+
type: "text",
|
|
5919
|
+
text: `Failed to sign media key. Verify the key exists and you have access.`
|
|
5920
|
+
}
|
|
5921
|
+
],
|
|
5867
5922
|
isError: true
|
|
5868
5923
|
};
|
|
5869
5924
|
}
|
|
@@ -5891,7 +5946,7 @@ function registerDistributionTools(server2) {
|
|
|
5891
5946
|
content: [
|
|
5892
5947
|
{
|
|
5893
5948
|
type: "text",
|
|
5894
|
-
text: `Failed to sign
|
|
5949
|
+
text: `Failed to sign media key at index ${failIdx}. Verify the key exists and you have access.`
|
|
5895
5950
|
}
|
|
5896
5951
|
],
|
|
5897
5952
|
isError: true
|
|
@@ -5919,7 +5974,7 @@ function registerDistributionTools(server2) {
|
|
|
5919
5974
|
content: [
|
|
5920
5975
|
{
|
|
5921
5976
|
type: "text",
|
|
5922
|
-
text: `Failed to resolve media: ${
|
|
5977
|
+
text: `Failed to resolve media: ${sanitizeError2(resolveErr)}`
|
|
5923
5978
|
}
|
|
5924
5979
|
],
|
|
5925
5980
|
isError: true
|
|
@@ -6284,7 +6339,7 @@ Created with Social Neuron`;
|
|
|
6284
6339
|
return { content: [{ type: "text", text: lines.join("\n") }], isError: false };
|
|
6285
6340
|
} catch (err) {
|
|
6286
6341
|
const durationMs = Date.now() - startedAt;
|
|
6287
|
-
const message =
|
|
6342
|
+
const message = sanitizeError2(err);
|
|
6288
6343
|
logMcpToolInvocation({
|
|
6289
6344
|
toolName: "find_next_slots",
|
|
6290
6345
|
status: "error",
|
|
@@ -6804,7 +6859,7 @@ Created with Social Neuron`;
|
|
|
6804
6859
|
};
|
|
6805
6860
|
} catch (err) {
|
|
6806
6861
|
const durationMs = Date.now() - startedAt;
|
|
6807
|
-
const message =
|
|
6862
|
+
const message = sanitizeError2(err);
|
|
6808
6863
|
logMcpToolInvocation({
|
|
6809
6864
|
toolName: "schedule_content_plan",
|
|
6810
6865
|
status: "error",
|
|
@@ -6960,7 +7015,7 @@ function registerMediaTools(server2) {
|
|
|
6960
7015
|
content: [
|
|
6961
7016
|
{
|
|
6962
7017
|
type: "text",
|
|
6963
|
-
text: `R2 upload failed: ${
|
|
7018
|
+
text: `R2 upload failed: ${sanitizeError(uploadErr)}`
|
|
6964
7019
|
}
|
|
6965
7020
|
],
|
|
6966
7021
|
isError: true
|
|
@@ -8088,7 +8143,7 @@ function registerScreenshotTools(server2) {
|
|
|
8088
8143
|
};
|
|
8089
8144
|
} catch (err) {
|
|
8090
8145
|
await closeBrowser();
|
|
8091
|
-
const message =
|
|
8146
|
+
const message = sanitizeError2(err);
|
|
8092
8147
|
await logMcpToolInvocation({
|
|
8093
8148
|
toolName: "capture_app_page",
|
|
8094
8149
|
status: "error",
|
|
@@ -8244,7 +8299,7 @@ function registerScreenshotTools(server2) {
|
|
|
8244
8299
|
};
|
|
8245
8300
|
} catch (err) {
|
|
8246
8301
|
await closeBrowser();
|
|
8247
|
-
const message =
|
|
8302
|
+
const message = sanitizeError2(err);
|
|
8248
8303
|
await logMcpToolInvocation({
|
|
8249
8304
|
toolName: "capture_screenshot",
|
|
8250
8305
|
status: "error",
|
|
@@ -8538,7 +8593,7 @@ function registerRemotionTools(server2) {
|
|
|
8538
8593
|
]
|
|
8539
8594
|
};
|
|
8540
8595
|
} catch (err) {
|
|
8541
|
-
const message =
|
|
8596
|
+
const message = sanitizeError2(err);
|
|
8542
8597
|
await logMcpToolInvocation({
|
|
8543
8598
|
toolName: "render_demo_video",
|
|
8544
8599
|
status: "error",
|
|
@@ -8666,7 +8721,7 @@ function registerRemotionTools(server2) {
|
|
|
8666
8721
|
]
|
|
8667
8722
|
};
|
|
8668
8723
|
} catch (err) {
|
|
8669
|
-
const message =
|
|
8724
|
+
const message = sanitizeError2(err);
|
|
8670
8725
|
await logMcpToolInvocation({
|
|
8671
8726
|
toolName: "render_template_video",
|
|
8672
8727
|
status: "error",
|
|
@@ -10128,8 +10183,8 @@ Active: ${is_active}`
|
|
|
10128
10183
|
|
|
10129
10184
|
// src/tools/extraction.ts
|
|
10130
10185
|
init_edge_function();
|
|
10131
|
-
init_supabase();
|
|
10132
10186
|
import { z as z17 } from "zod";
|
|
10187
|
+
init_supabase();
|
|
10133
10188
|
init_version();
|
|
10134
10189
|
function asEnvelope13(data) {
|
|
10135
10190
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
@@ -10335,7 +10390,7 @@ function registerExtractionTools(server2) {
|
|
|
10335
10390
|
};
|
|
10336
10391
|
} catch (err) {
|
|
10337
10392
|
const durationMs = Date.now() - startedAt;
|
|
10338
|
-
const message =
|
|
10393
|
+
const message = sanitizeError2(err);
|
|
10339
10394
|
logMcpToolInvocation({
|
|
10340
10395
|
toolName: "extract_url_content",
|
|
10341
10396
|
status: "error",
|
|
@@ -10525,10 +10580,10 @@ function registerQualityTools(server2) {
|
|
|
10525
10580
|
|
|
10526
10581
|
// src/tools/planning.ts
|
|
10527
10582
|
init_edge_function();
|
|
10528
|
-
init_supabase();
|
|
10529
|
-
init_version();
|
|
10530
10583
|
import { z as z19 } from "zod";
|
|
10531
10584
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
10585
|
+
init_supabase();
|
|
10586
|
+
init_version();
|
|
10532
10587
|
|
|
10533
10588
|
// src/lib/parse-utils.ts
|
|
10534
10589
|
function extractJsonArray(text) {
|
|
@@ -10895,7 +10950,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
10895
10950
|
}
|
|
10896
10951
|
} catch (persistErr) {
|
|
10897
10952
|
const durationMs2 = Date.now() - startedAt;
|
|
10898
|
-
const message =
|
|
10953
|
+
const message = sanitizeError2(persistErr);
|
|
10899
10954
|
logMcpToolInvocation({
|
|
10900
10955
|
toolName: "plan_content_week",
|
|
10901
10956
|
status: "error",
|
|
@@ -10927,7 +10982,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
10927
10982
|
};
|
|
10928
10983
|
} catch (err) {
|
|
10929
10984
|
const durationMs = Date.now() - startedAt;
|
|
10930
|
-
const message =
|
|
10985
|
+
const message = sanitizeError2(err);
|
|
10931
10986
|
logMcpToolInvocation({
|
|
10932
10987
|
toolName: "plan_content_week",
|
|
10933
10988
|
status: "error",
|
|
@@ -11019,7 +11074,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
11019
11074
|
};
|
|
11020
11075
|
} catch (err) {
|
|
11021
11076
|
const durationMs = Date.now() - startedAt;
|
|
11022
|
-
const message =
|
|
11077
|
+
const message = sanitizeError2(err);
|
|
11023
11078
|
logMcpToolInvocation({
|
|
11024
11079
|
toolName: "save_content_plan",
|
|
11025
11080
|
status: "error",
|
|
@@ -11497,11 +11552,11 @@ function registerDiscoveryTools(server2) {
|
|
|
11497
11552
|
|
|
11498
11553
|
// src/tools/pipeline.ts
|
|
11499
11554
|
init_edge_function();
|
|
11555
|
+
import { z as z22 } from "zod";
|
|
11556
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
11500
11557
|
init_supabase();
|
|
11501
11558
|
init_quality();
|
|
11502
11559
|
init_version();
|
|
11503
|
-
import { z as z22 } from "zod";
|
|
11504
|
-
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
11505
11560
|
function asEnvelope17(data) {
|
|
11506
11561
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
11507
11562
|
}
|
|
@@ -11631,7 +11686,7 @@ function registerPipelineTools(server2) {
|
|
|
11631
11686
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
11632
11687
|
} catch (err) {
|
|
11633
11688
|
const durationMs = Date.now() - startedAt;
|
|
11634
|
-
const message =
|
|
11689
|
+
const message = sanitizeError2(err);
|
|
11635
11690
|
logMcpToolInvocation({
|
|
11636
11691
|
toolName: "check_pipeline_readiness",
|
|
11637
11692
|
status: "error",
|
|
@@ -11792,7 +11847,7 @@ function registerPipelineTools(server2) {
|
|
|
11792
11847
|
} catch (deductErr) {
|
|
11793
11848
|
errors.push({
|
|
11794
11849
|
stage: "planning",
|
|
11795
|
-
message: `Credit deduction failed: ${
|
|
11850
|
+
message: `Credit deduction failed: ${sanitizeError2(deductErr)}`
|
|
11796
11851
|
});
|
|
11797
11852
|
}
|
|
11798
11853
|
}
|
|
@@ -11963,7 +12018,7 @@ function registerPipelineTools(server2) {
|
|
|
11963
12018
|
} catch (schedErr) {
|
|
11964
12019
|
errors.push({
|
|
11965
12020
|
stage: "schedule",
|
|
11966
|
-
message: `Failed to schedule ${post.id}: ${
|
|
12021
|
+
message: `Failed to schedule ${post.id}: ${sanitizeError2(schedErr)}`
|
|
11967
12022
|
});
|
|
11968
12023
|
}
|
|
11969
12024
|
}
|
|
@@ -12052,7 +12107,7 @@ function registerPipelineTools(server2) {
|
|
|
12052
12107
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
12053
12108
|
} catch (err) {
|
|
12054
12109
|
const durationMs = Date.now() - startedAt;
|
|
12055
|
-
const message =
|
|
12110
|
+
const message = sanitizeError2(err);
|
|
12056
12111
|
logMcpToolInvocation({
|
|
12057
12112
|
toolName: "run_content_pipeline",
|
|
12058
12113
|
status: "error",
|
|
@@ -12276,7 +12331,7 @@ function registerPipelineTools(server2) {
|
|
|
12276
12331
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
12277
12332
|
} catch (err) {
|
|
12278
12333
|
const durationMs = Date.now() - startedAt;
|
|
12279
|
-
const message =
|
|
12334
|
+
const message = sanitizeError2(err);
|
|
12280
12335
|
logMcpToolInvocation({
|
|
12281
12336
|
toolName: "auto_approve_plan",
|
|
12282
12337
|
status: "error",
|
|
@@ -12311,9 +12366,9 @@ function buildPlanPrompt(topic, platforms, days, postsPerDay, sourceUrl) {
|
|
|
12311
12366
|
|
|
12312
12367
|
// src/tools/suggest.ts
|
|
12313
12368
|
init_edge_function();
|
|
12369
|
+
import { z as z23 } from "zod";
|
|
12314
12370
|
init_supabase();
|
|
12315
12371
|
init_version();
|
|
12316
|
-
import { z as z23 } from "zod";
|
|
12317
12372
|
function asEnvelope18(data) {
|
|
12318
12373
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
12319
12374
|
}
|
|
@@ -12456,7 +12511,7 @@ ${i + 1}. ${s.topic}`);
|
|
|
12456
12511
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
12457
12512
|
} catch (err) {
|
|
12458
12513
|
const durationMs = Date.now() - startedAt;
|
|
12459
|
-
const message =
|
|
12514
|
+
const message = sanitizeError2(err);
|
|
12460
12515
|
logMcpToolInvocation({
|
|
12461
12516
|
toolName: "suggest_next_content",
|
|
12462
12517
|
status: "error",
|
|
@@ -12474,8 +12529,8 @@ ${i + 1}. ${s.topic}`);
|
|
|
12474
12529
|
|
|
12475
12530
|
// src/tools/digest.ts
|
|
12476
12531
|
init_edge_function();
|
|
12477
|
-
init_supabase();
|
|
12478
12532
|
import { z as z24 } from "zod";
|
|
12533
|
+
init_supabase();
|
|
12479
12534
|
|
|
12480
12535
|
// src/lib/anomaly-detector.ts
|
|
12481
12536
|
var SENSITIVITY_THRESHOLDS = {
|
|
@@ -12797,7 +12852,7 @@ Best: ${best.id.slice(0, 8)}... (${best.platform}) \u2014 ${best.views.toLocaleS
|
|
|
12797
12852
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
12798
12853
|
} catch (err) {
|
|
12799
12854
|
const durationMs = Date.now() - startedAt;
|
|
12800
|
-
const message =
|
|
12855
|
+
const message = sanitizeError2(err);
|
|
12801
12856
|
logMcpToolInvocation({
|
|
12802
12857
|
toolName: "generate_performance_digest",
|
|
12803
12858
|
status: "error",
|
|
@@ -12894,7 +12949,7 @@ Best: ${best.id.slice(0, 8)}... (${best.platform}) \u2014 ${best.views.toLocaleS
|
|
|
12894
12949
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
12895
12950
|
} catch (err) {
|
|
12896
12951
|
const durationMs = Date.now() - startedAt;
|
|
12897
|
-
const message =
|
|
12952
|
+
const message = sanitizeError2(err);
|
|
12898
12953
|
logMcpToolInvocation({
|
|
12899
12954
|
toolName: "detect_anomalies",
|
|
12900
12955
|
status: "error",
|
|
@@ -12915,6 +12970,275 @@ init_edge_function();
|
|
|
12915
12970
|
init_supabase();
|
|
12916
12971
|
init_version();
|
|
12917
12972
|
import { z as z25 } from "zod";
|
|
12973
|
+
|
|
12974
|
+
// src/lib/brandScoring.ts
|
|
12975
|
+
var WEIGHTS = {
|
|
12976
|
+
toneAlignment: 0.3,
|
|
12977
|
+
vocabularyAdherence: 0.25,
|
|
12978
|
+
avoidCompliance: 0.2,
|
|
12979
|
+
audienceRelevance: 0.15,
|
|
12980
|
+
brandMentions: 0.05,
|
|
12981
|
+
structuralPatterns: 0.05
|
|
12982
|
+
};
|
|
12983
|
+
function norm(content) {
|
|
12984
|
+
return content.toLowerCase().replace(/[^a-z0-9\s]/g, " ");
|
|
12985
|
+
}
|
|
12986
|
+
function findMatches(content, terms) {
|
|
12987
|
+
const n = norm(content);
|
|
12988
|
+
return terms.filter((t) => n.includes(t.toLowerCase()));
|
|
12989
|
+
}
|
|
12990
|
+
function findMissing(content, terms) {
|
|
12991
|
+
const n = norm(content);
|
|
12992
|
+
return terms.filter((t) => !n.includes(t.toLowerCase()));
|
|
12993
|
+
}
|
|
12994
|
+
var FABRICATION_PATTERNS = [
|
|
12995
|
+
{ regex: /\b\d+[,.]?\d*\s*(%|percent)/gi, label: "unverified percentage" },
|
|
12996
|
+
{ regex: /\b(award[- ]?winning|best[- ]selling|#\s*1)\b/gi, label: "unverified ranking" },
|
|
12997
|
+
{
|
|
12998
|
+
regex: /\b(guaranteed|proven to|studies show|scientifically proven)\b/gi,
|
|
12999
|
+
label: "unverified claim"
|
|
13000
|
+
},
|
|
13001
|
+
{
|
|
13002
|
+
regex: /\b(always works|100% effective|risk[- ]?free|no risk)\b/gi,
|
|
13003
|
+
label: "absolute claim"
|
|
13004
|
+
}
|
|
13005
|
+
];
|
|
13006
|
+
function detectFabricationPatterns(content) {
|
|
13007
|
+
const matches = [];
|
|
13008
|
+
for (const { regex, label } of FABRICATION_PATTERNS) {
|
|
13009
|
+
const re = new RegExp(regex.source, regex.flags);
|
|
13010
|
+
let m;
|
|
13011
|
+
while ((m = re.exec(content)) !== null) {
|
|
13012
|
+
matches.push({ label, match: m[0] });
|
|
13013
|
+
}
|
|
13014
|
+
}
|
|
13015
|
+
return matches;
|
|
13016
|
+
}
|
|
13017
|
+
function scoreTone(content, profile) {
|
|
13018
|
+
const terms = profile.voiceProfile?.tone || [];
|
|
13019
|
+
if (!terms.length)
|
|
13020
|
+
return {
|
|
13021
|
+
score: 50,
|
|
13022
|
+
weight: WEIGHTS.toneAlignment,
|
|
13023
|
+
issues: [],
|
|
13024
|
+
suggestions: ["Define brand tone words for better consistency measurement"]
|
|
13025
|
+
};
|
|
13026
|
+
const matched = findMatches(content, terms);
|
|
13027
|
+
const missing = findMissing(content, terms);
|
|
13028
|
+
const score = Math.min(100, Math.round(matched.length / terms.length * 100));
|
|
13029
|
+
const issues = [];
|
|
13030
|
+
const suggestions = [];
|
|
13031
|
+
if (missing.length > 0) {
|
|
13032
|
+
issues.push(`Missing tone signals: ${missing.join(", ")}`);
|
|
13033
|
+
suggestions.push(`Try incorporating tone words: ${missing.slice(0, 3).join(", ")}`);
|
|
13034
|
+
}
|
|
13035
|
+
return { score, weight: WEIGHTS.toneAlignment, issues, suggestions };
|
|
13036
|
+
}
|
|
13037
|
+
function scoreVocab(content, profile) {
|
|
13038
|
+
const preferred = [
|
|
13039
|
+
...profile.voiceProfile?.languagePatterns || [],
|
|
13040
|
+
...profile.vocabularyRules?.preferredTerms || []
|
|
13041
|
+
];
|
|
13042
|
+
if (!preferred.length)
|
|
13043
|
+
return {
|
|
13044
|
+
score: 50,
|
|
13045
|
+
weight: WEIGHTS.vocabularyAdherence,
|
|
13046
|
+
issues: [],
|
|
13047
|
+
suggestions: ["Add preferred terms to improve vocabulary scoring"]
|
|
13048
|
+
};
|
|
13049
|
+
const matched = findMatches(content, preferred);
|
|
13050
|
+
const missing = findMissing(content, preferred);
|
|
13051
|
+
const score = Math.min(100, Math.round(matched.length / preferred.length * 100));
|
|
13052
|
+
const issues = [];
|
|
13053
|
+
const suggestions = [];
|
|
13054
|
+
if (missing.length > 0 && score < 60) {
|
|
13055
|
+
issues.push(`Low preferred term usage (${matched.length}/${preferred.length})`);
|
|
13056
|
+
suggestions.push(`Consider using: ${missing.slice(0, 3).join(", ")}`);
|
|
13057
|
+
}
|
|
13058
|
+
return { score, weight: WEIGHTS.vocabularyAdherence, issues, suggestions };
|
|
13059
|
+
}
|
|
13060
|
+
function scoreAvoid(content, profile) {
|
|
13061
|
+
const banned = [
|
|
13062
|
+
...profile.voiceProfile?.avoidPatterns || [],
|
|
13063
|
+
...profile.vocabularyRules?.bannedTerms || []
|
|
13064
|
+
];
|
|
13065
|
+
if (!banned.length)
|
|
13066
|
+
return {
|
|
13067
|
+
score: 100,
|
|
13068
|
+
weight: WEIGHTS.avoidCompliance,
|
|
13069
|
+
issues: [],
|
|
13070
|
+
suggestions: []
|
|
13071
|
+
};
|
|
13072
|
+
const violations = findMatches(content, banned);
|
|
13073
|
+
const score = violations.length === 0 ? 100 : Math.max(0, 100 - violations.length * 25);
|
|
13074
|
+
const issues = [];
|
|
13075
|
+
const suggestions = [];
|
|
13076
|
+
if (violations.length > 0) {
|
|
13077
|
+
issues.push(`Banned/avoided terms found: ${violations.join(", ")}`);
|
|
13078
|
+
suggestions.push(`Remove or replace: ${violations.join(", ")}`);
|
|
13079
|
+
}
|
|
13080
|
+
return { score, weight: WEIGHTS.avoidCompliance, issues, suggestions };
|
|
13081
|
+
}
|
|
13082
|
+
function scoreAudience(content, profile) {
|
|
13083
|
+
const terms = [];
|
|
13084
|
+
const d = profile.targetAudience?.demographics;
|
|
13085
|
+
const p = profile.targetAudience?.psychographics;
|
|
13086
|
+
if (d?.ageRange) terms.push(d.ageRange);
|
|
13087
|
+
if (d?.location) terms.push(d.location);
|
|
13088
|
+
if (p?.interests) terms.push(...p.interests);
|
|
13089
|
+
if (p?.painPoints) terms.push(...p.painPoints);
|
|
13090
|
+
if (p?.aspirations) terms.push(...p.aspirations);
|
|
13091
|
+
const valid = terms.filter(Boolean);
|
|
13092
|
+
if (!valid.length)
|
|
13093
|
+
return {
|
|
13094
|
+
score: 50,
|
|
13095
|
+
weight: WEIGHTS.audienceRelevance,
|
|
13096
|
+
issues: [],
|
|
13097
|
+
suggestions: ["Define target audience details for relevance scoring"]
|
|
13098
|
+
};
|
|
13099
|
+
const matched = findMatches(content, valid);
|
|
13100
|
+
const score = Math.min(100, Math.round(matched.length / valid.length * 100));
|
|
13101
|
+
const issues = [];
|
|
13102
|
+
const suggestions = [];
|
|
13103
|
+
if (score < 40) {
|
|
13104
|
+
issues.push("Content has low audience relevance");
|
|
13105
|
+
suggestions.push(
|
|
13106
|
+
`Reference audience pain points or interests: ${valid.slice(0, 3).join(", ")}`
|
|
13107
|
+
);
|
|
13108
|
+
}
|
|
13109
|
+
return { score, weight: WEIGHTS.audienceRelevance, issues, suggestions };
|
|
13110
|
+
}
|
|
13111
|
+
function scoreBrand(content, profile) {
|
|
13112
|
+
const name = profile.name?.toLowerCase();
|
|
13113
|
+
if (!name)
|
|
13114
|
+
return {
|
|
13115
|
+
score: 50,
|
|
13116
|
+
weight: WEIGHTS.brandMentions,
|
|
13117
|
+
issues: [],
|
|
13118
|
+
suggestions: []
|
|
13119
|
+
};
|
|
13120
|
+
const mentioned = norm(content).includes(name);
|
|
13121
|
+
const issues = [];
|
|
13122
|
+
const suggestions = [];
|
|
13123
|
+
if (!mentioned) {
|
|
13124
|
+
issues.push("Brand name not mentioned");
|
|
13125
|
+
suggestions.push(`Include "${profile.name}" in the content`);
|
|
13126
|
+
}
|
|
13127
|
+
return {
|
|
13128
|
+
score: mentioned ? 100 : 0,
|
|
13129
|
+
weight: WEIGHTS.brandMentions,
|
|
13130
|
+
issues,
|
|
13131
|
+
suggestions
|
|
13132
|
+
};
|
|
13133
|
+
}
|
|
13134
|
+
function scoreStructure(content, profile) {
|
|
13135
|
+
const rules = profile.writingStyleRules;
|
|
13136
|
+
if (!rules)
|
|
13137
|
+
return {
|
|
13138
|
+
score: 50,
|
|
13139
|
+
weight: WEIGHTS.structuralPatterns,
|
|
13140
|
+
issues: [],
|
|
13141
|
+
suggestions: []
|
|
13142
|
+
};
|
|
13143
|
+
let score = 100;
|
|
13144
|
+
const issues = [];
|
|
13145
|
+
const suggestions = [];
|
|
13146
|
+
if (rules.perspective) {
|
|
13147
|
+
const markers = {
|
|
13148
|
+
"first-singular": [/\bI\b/g, /\bmy\b/gi],
|
|
13149
|
+
"first-plural": [/\bwe\b/gi, /\bour\b/gi],
|
|
13150
|
+
second: [/\byou\b/gi, /\byour\b/gi],
|
|
13151
|
+
third: [/\bthey\b/gi, /\btheir\b/gi]
|
|
13152
|
+
};
|
|
13153
|
+
const expected = markers[rules.perspective];
|
|
13154
|
+
if (expected && !expected.some((r) => r.test(content))) {
|
|
13155
|
+
score -= 30;
|
|
13156
|
+
issues.push(`Expected ${rules.perspective} perspective not detected`);
|
|
13157
|
+
suggestions.push(`Use ${rules.perspective} perspective pronouns`);
|
|
13158
|
+
}
|
|
13159
|
+
}
|
|
13160
|
+
if (rules.useContractions === false) {
|
|
13161
|
+
const found = content.match(
|
|
13162
|
+
/\b(don't|won't|can't|isn't|aren't|wasn't|weren't|hasn't|haven't|doesn't|didn't|wouldn't|couldn't|shouldn't|it's|that's|there's|here's|what's|who's|let's|we're|they're|you're|I'm|he's|she's)\b/gi
|
|
13163
|
+
);
|
|
13164
|
+
if (found && found.length > 0) {
|
|
13165
|
+
score -= Math.min(40, found.length * 10);
|
|
13166
|
+
issues.push(`Contractions found (${found.length}): ${found.slice(0, 3).join(", ")}`);
|
|
13167
|
+
suggestions.push("Expand contractions to full forms");
|
|
13168
|
+
}
|
|
13169
|
+
}
|
|
13170
|
+
if (rules.emojiPolicy === "none") {
|
|
13171
|
+
const emojis = content.match(
|
|
13172
|
+
/[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/gu
|
|
13173
|
+
);
|
|
13174
|
+
if (emojis && emojis.length > 0) {
|
|
13175
|
+
score -= 20;
|
|
13176
|
+
issues.push('Emojis found but emoji policy is "none"');
|
|
13177
|
+
suggestions.push("Remove emojis from content");
|
|
13178
|
+
}
|
|
13179
|
+
}
|
|
13180
|
+
return {
|
|
13181
|
+
score: Math.max(0, score),
|
|
13182
|
+
weight: WEIGHTS.structuralPatterns,
|
|
13183
|
+
issues,
|
|
13184
|
+
suggestions
|
|
13185
|
+
};
|
|
13186
|
+
}
|
|
13187
|
+
function computeBrandConsistency(content, profile, threshold = 60) {
|
|
13188
|
+
if (!content || !profile) {
|
|
13189
|
+
const neutral = {
|
|
13190
|
+
score: 50,
|
|
13191
|
+
weight: 0,
|
|
13192
|
+
issues: [],
|
|
13193
|
+
suggestions: []
|
|
13194
|
+
};
|
|
13195
|
+
return {
|
|
13196
|
+
overall: 50,
|
|
13197
|
+
passed: false,
|
|
13198
|
+
dimensions: {
|
|
13199
|
+
toneAlignment: { ...neutral, weight: WEIGHTS.toneAlignment },
|
|
13200
|
+
vocabularyAdherence: { ...neutral, weight: WEIGHTS.vocabularyAdherence },
|
|
13201
|
+
avoidCompliance: { ...neutral, weight: WEIGHTS.avoidCompliance },
|
|
13202
|
+
audienceRelevance: { ...neutral, weight: WEIGHTS.audienceRelevance },
|
|
13203
|
+
brandMentions: { ...neutral, weight: WEIGHTS.brandMentions },
|
|
13204
|
+
structuralPatterns: { ...neutral, weight: WEIGHTS.structuralPatterns }
|
|
13205
|
+
},
|
|
13206
|
+
preferredTermsUsed: [],
|
|
13207
|
+
bannedTermsFound: [],
|
|
13208
|
+
fabricationWarnings: []
|
|
13209
|
+
};
|
|
13210
|
+
}
|
|
13211
|
+
const dimensions = {
|
|
13212
|
+
toneAlignment: scoreTone(content, profile),
|
|
13213
|
+
vocabularyAdherence: scoreVocab(content, profile),
|
|
13214
|
+
avoidCompliance: scoreAvoid(content, profile),
|
|
13215
|
+
audienceRelevance: scoreAudience(content, profile),
|
|
13216
|
+
brandMentions: scoreBrand(content, profile),
|
|
13217
|
+
structuralPatterns: scoreStructure(content, profile)
|
|
13218
|
+
};
|
|
13219
|
+
const overall = Math.round(
|
|
13220
|
+
Object.values(dimensions).reduce((sum, d) => sum + d.score * d.weight, 0)
|
|
13221
|
+
);
|
|
13222
|
+
const preferred = [
|
|
13223
|
+
...profile.voiceProfile?.languagePatterns || [],
|
|
13224
|
+
...profile.vocabularyRules?.preferredTerms || []
|
|
13225
|
+
];
|
|
13226
|
+
const banned = [
|
|
13227
|
+
...profile.voiceProfile?.avoidPatterns || [],
|
|
13228
|
+
...profile.vocabularyRules?.bannedTerms || []
|
|
13229
|
+
];
|
|
13230
|
+
const fabrications = detectFabricationPatterns(content);
|
|
13231
|
+
return {
|
|
13232
|
+
overall: Math.max(0, Math.min(100, overall)),
|
|
13233
|
+
passed: overall >= threshold,
|
|
13234
|
+
dimensions,
|
|
13235
|
+
preferredTermsUsed: findMatches(content, preferred),
|
|
13236
|
+
bannedTermsFound: findMatches(content, banned),
|
|
13237
|
+
fabricationWarnings: fabrications.map((f) => `${f.label}: "${f.match}"`)
|
|
13238
|
+
};
|
|
13239
|
+
}
|
|
13240
|
+
|
|
13241
|
+
// src/tools/brandRuntime.ts
|
|
12918
13242
|
function asEnvelope20(data) {
|
|
12919
13243
|
return {
|
|
12920
13244
|
_meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() },
|
|
@@ -13127,44 +13451,7 @@ function registerBrandRuntimeTools(server2) {
|
|
|
13127
13451
|
};
|
|
13128
13452
|
}
|
|
13129
13453
|
const profile = row.profile_data;
|
|
13130
|
-
const
|
|
13131
|
-
const issues = [];
|
|
13132
|
-
let score = 70;
|
|
13133
|
-
const banned = profile.vocabularyRules?.bannedTerms || [];
|
|
13134
|
-
const bannedFound = banned.filter((t) => contentLower.includes(t.toLowerCase()));
|
|
13135
|
-
if (bannedFound.length > 0) {
|
|
13136
|
-
score -= bannedFound.length * 15;
|
|
13137
|
-
issues.push(`Banned terms found: ${bannedFound.join(", ")}`);
|
|
13138
|
-
}
|
|
13139
|
-
const avoid = profile.voiceProfile?.avoidPatterns || [];
|
|
13140
|
-
const avoidFound = avoid.filter((p) => contentLower.includes(p.toLowerCase()));
|
|
13141
|
-
if (avoidFound.length > 0) {
|
|
13142
|
-
score -= avoidFound.length * 10;
|
|
13143
|
-
issues.push(`Avoid patterns found: ${avoidFound.join(", ")}`);
|
|
13144
|
-
}
|
|
13145
|
-
const preferred = profile.vocabularyRules?.preferredTerms || [];
|
|
13146
|
-
const prefUsed = preferred.filter((t) => contentLower.includes(t.toLowerCase()));
|
|
13147
|
-
score += Math.min(15, prefUsed.length * 5);
|
|
13148
|
-
const fabPatterns = [
|
|
13149
|
-
{ regex: /\b\d+[,.]?\d*\s*(%|percent)/gi, label: "unverified percentage" },
|
|
13150
|
-
{ regex: /\b(award[- ]?winning|best[- ]selling|#\s*1)\b/gi, label: "unverified ranking" },
|
|
13151
|
-
{ regex: /\b(guaranteed|proven to|studies show)\b/gi, label: "unverified claim" }
|
|
13152
|
-
];
|
|
13153
|
-
for (const { regex, label } of fabPatterns) {
|
|
13154
|
-
regex.lastIndex = 0;
|
|
13155
|
-
if (regex.test(content)) {
|
|
13156
|
-
score -= 10;
|
|
13157
|
-
issues.push(`Potential ${label} detected`);
|
|
13158
|
-
}
|
|
13159
|
-
}
|
|
13160
|
-
score = Math.max(0, Math.min(100, score));
|
|
13161
|
-
const checkResult = {
|
|
13162
|
-
score,
|
|
13163
|
-
passed: score >= 60,
|
|
13164
|
-
issues,
|
|
13165
|
-
preferredTermsUsed: prefUsed,
|
|
13166
|
-
bannedTermsFound: bannedFound
|
|
13167
|
-
};
|
|
13454
|
+
const checkResult = computeBrandConsistency(content, profile);
|
|
13168
13455
|
const envelope = asEnvelope20(checkResult);
|
|
13169
13456
|
return {
|
|
13170
13457
|
content: [{ type: "text", text: JSON.stringify(envelope, null, 2) }]
|