@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.
Files changed (3) hide show
  1. package/dist/http.js +350 -113
  2. package/dist/index.js +355 -68
  3. 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.0";
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: [{ type: "text", text: `Failed to sign R2 key: ${r2_key}` }],
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 R2 key at index ${failIdx}: ${r2_keys[failIdx]}`
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: ${resolveErr instanceof Error ? resolveErr.message : String(resolveErr)}`
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 = err instanceof Error ? err.message : String(err);
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 = err instanceof Error ? err.message : String(err);
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: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`
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 = err instanceof Error ? err.message : String(err);
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 = err instanceof Error ? err.message : String(err);
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 = err instanceof Error ? err.message : String(err);
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 = err instanceof Error ? err.message : String(err);
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 = err instanceof Error ? err.message : String(err);
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 = persistErr instanceof Error ? persistErr.message : String(persistErr);
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 = err instanceof Error ? err.message : String(err);
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 = err instanceof Error ? err.message : String(err);
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 = err instanceof Error ? err.message : String(err);
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: ${deductErr instanceof Error ? deductErr.message : String(deductErr)}`
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}: ${schedErr instanceof Error ? schedErr.message : String(schedErr)}`
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 = err instanceof Error ? err.message : String(err);
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 = err instanceof Error ? err.message : String(err);
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 = err instanceof Error ? err.message : String(err);
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 = err instanceof Error ? err.message : String(err);
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 = err instanceof Error ? err.message : String(err);
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 contentLower = content.toLowerCase();
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: sanitizeError(err) } });
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.message });
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.0";
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: [{ type: "text", text: `Failed to sign R2 key: ${r2_key}` }],
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 R2 key at index ${failIdx}: ${r2_keys[failIdx]}`
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: ${resolveErr instanceof Error ? resolveErr.message : String(resolveErr)}`
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 = err instanceof Error ? err.message : String(err);
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 = err instanceof Error ? err.message : String(err);
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: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`
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 = err instanceof Error ? err.message : String(err);
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 = err instanceof Error ? err.message : String(err);
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 = err instanceof Error ? err.message : String(err);
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 = err instanceof Error ? err.message : String(err);
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 = err instanceof Error ? err.message : String(err);
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 = persistErr instanceof Error ? persistErr.message : String(persistErr);
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 = err instanceof Error ? err.message : String(err);
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 = err instanceof Error ? err.message : String(err);
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 = err instanceof Error ? err.message : String(err);
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: ${deductErr instanceof Error ? deductErr.message : String(deductErr)}`
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}: ${schedErr instanceof Error ? schedErr.message : String(schedErr)}`
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 = err instanceof Error ? err.message : String(err);
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 = err instanceof Error ? err.message : String(err);
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 = err instanceof Error ? err.message : String(err);
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 = err instanceof Error ? err.message : String(err);
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 = err instanceof Error ? err.message : String(err);
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 contentLower = content.toLowerCase();
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) }]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@socialneuron/mcp-server",
3
- "version": "1.7.2",
3
+ "version": "1.7.3",
4
4
  "description": "MCP server for Social Neuron - AI content creation platform",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",