@socialneuron/mcp-server 1.7.1 → 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 +544 -123
- package/dist/index.js +549 -78
- package/package.json +1 -1
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
|
|
|
@@ -5135,7 +5135,24 @@ function registerContentTools(server2) {
|
|
|
5135
5135
|
`Status: ${job.status}`
|
|
5136
5136
|
];
|
|
5137
5137
|
if (job.result_url) {
|
|
5138
|
-
|
|
5138
|
+
const isR2Key = !job.result_url.startsWith("http");
|
|
5139
|
+
if (isR2Key) {
|
|
5140
|
+
const segments = job.result_url.split("/");
|
|
5141
|
+
const filename = segments[segments.length - 1] || "media";
|
|
5142
|
+
lines.push(`Media ready: ${filename}`);
|
|
5143
|
+
lines.push(
|
|
5144
|
+
"(Pass job_id directly to schedule_post, or use get_media_url with job_id for a download link)"
|
|
5145
|
+
);
|
|
5146
|
+
} else {
|
|
5147
|
+
lines.push(`Result URL: ${job.result_url}`);
|
|
5148
|
+
}
|
|
5149
|
+
}
|
|
5150
|
+
const allUrls = job.result_metadata?.all_urls;
|
|
5151
|
+
if (allUrls && allUrls.length > 1) {
|
|
5152
|
+
lines.push(`Media files: ${allUrls.length} outputs available`);
|
|
5153
|
+
lines.push(
|
|
5154
|
+
"(Use job_id with schedule_post for carousel, or response_format=json for programmatic access)"
|
|
5155
|
+
);
|
|
5139
5156
|
}
|
|
5140
5157
|
if (job.error_message) {
|
|
5141
5158
|
lines.push(`Error: ${job.error_message}`);
|
|
@@ -5152,8 +5169,13 @@ function registerContentTools(server2) {
|
|
|
5152
5169
|
details: { status: job.status, jobId: job.id }
|
|
5153
5170
|
});
|
|
5154
5171
|
if (format === "json") {
|
|
5172
|
+
const enriched = {
|
|
5173
|
+
...job,
|
|
5174
|
+
r2_key: job.result_url && !job.result_url.startsWith("http") ? job.result_url : null,
|
|
5175
|
+
all_urls: allUrls ?? null
|
|
5176
|
+
};
|
|
5155
5177
|
return {
|
|
5156
|
-
content: [{ type: "text", text: JSON.stringify(asEnvelope(
|
|
5178
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope(enriched), null, 2) }]
|
|
5157
5179
|
};
|
|
5158
5180
|
}
|
|
5159
5181
|
return {
|
|
@@ -5636,9 +5658,75 @@ Return ONLY valid JSON in this exact format:
|
|
|
5636
5658
|
init_edge_function();
|
|
5637
5659
|
import { z as z3 } from "zod";
|
|
5638
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
|
|
5639
5711
|
init_supabase();
|
|
5640
5712
|
init_quality();
|
|
5641
5713
|
init_version();
|
|
5714
|
+
function snakeToCamel(obj) {
|
|
5715
|
+
const result = {};
|
|
5716
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
5717
|
+
const camelKey = key.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
5718
|
+
result[camelKey] = value;
|
|
5719
|
+
}
|
|
5720
|
+
return result;
|
|
5721
|
+
}
|
|
5722
|
+
function convertPlatformMetadata(meta) {
|
|
5723
|
+
if (!meta) return void 0;
|
|
5724
|
+
const converted = {};
|
|
5725
|
+
for (const [platform3, fields] of Object.entries(meta)) {
|
|
5726
|
+
converted[platform3] = snakeToCamel(fields);
|
|
5727
|
+
}
|
|
5728
|
+
return converted;
|
|
5729
|
+
}
|
|
5642
5730
|
var PLATFORM_CASE_MAP = {
|
|
5643
5731
|
youtube: "YouTube",
|
|
5644
5732
|
tiktok: "TikTok",
|
|
@@ -5681,8 +5769,53 @@ function registerDistributionTools(server2) {
|
|
|
5681
5769
|
job_ids: z3.array(z3.string()).optional().describe(
|
|
5682
5770
|
"Array of async job IDs for carousel posts. Each resolved to its R2 key. Alternative to media_urls/r2_keys."
|
|
5683
5771
|
),
|
|
5684
|
-
platform_metadata: z3.
|
|
5685
|
-
|
|
5772
|
+
platform_metadata: z3.object({
|
|
5773
|
+
tiktok: z3.object({
|
|
5774
|
+
privacy_status: z3.enum([
|
|
5775
|
+
"PUBLIC_TO_EVERYONE",
|
|
5776
|
+
"MUTUAL_FOLLOW_FRIENDS",
|
|
5777
|
+
"FOLLOWER_OF_CREATOR",
|
|
5778
|
+
"SELF_ONLY"
|
|
5779
|
+
]).optional().describe("Required unless useInbox=true. Who can view the video."),
|
|
5780
|
+
enable_duet: z3.boolean().optional(),
|
|
5781
|
+
enable_comment: z3.boolean().optional(),
|
|
5782
|
+
enable_stitch: z3.boolean().optional(),
|
|
5783
|
+
is_ai_generated: z3.boolean().optional(),
|
|
5784
|
+
brand_content: z3.boolean().optional(),
|
|
5785
|
+
brand_organic: z3.boolean().optional(),
|
|
5786
|
+
use_inbox: z3.boolean().optional().describe("Post to TikTok inbox/draft instead of direct publish.")
|
|
5787
|
+
}).optional(),
|
|
5788
|
+
youtube: z3.object({
|
|
5789
|
+
title: z3.string().optional().describe("Video title (required for YouTube)."),
|
|
5790
|
+
description: z3.string().optional(),
|
|
5791
|
+
privacy_status: z3.enum(["public", "unlisted", "private"]).optional(),
|
|
5792
|
+
category_id: z3.string().optional(),
|
|
5793
|
+
tags: z3.array(z3.string()).optional(),
|
|
5794
|
+
made_for_kids: z3.boolean().optional(),
|
|
5795
|
+
notify_subscribers: z3.boolean().optional()
|
|
5796
|
+
}).optional(),
|
|
5797
|
+
facebook: z3.object({
|
|
5798
|
+
page_id: z3.string().optional().describe("Facebook Page ID to post to."),
|
|
5799
|
+
audience: z3.string().optional()
|
|
5800
|
+
}).optional(),
|
|
5801
|
+
instagram: z3.object({
|
|
5802
|
+
location: z3.string().optional(),
|
|
5803
|
+
collaborators: z3.array(z3.string()).optional(),
|
|
5804
|
+
cover_timestamp: z3.number().optional(),
|
|
5805
|
+
share_to_feed: z3.boolean().optional(),
|
|
5806
|
+
first_comment: z3.string().optional(),
|
|
5807
|
+
is_ai_generated: z3.boolean().optional()
|
|
5808
|
+
}).optional(),
|
|
5809
|
+
threads: z3.object({}).passthrough().optional(),
|
|
5810
|
+
bluesky: z3.object({
|
|
5811
|
+
content_labels: z3.array(z3.string()).optional()
|
|
5812
|
+
}).optional(),
|
|
5813
|
+
linkedin: z3.object({
|
|
5814
|
+
article_url: z3.string().optional()
|
|
5815
|
+
}).optional(),
|
|
5816
|
+
twitter: z3.object({}).passthrough().optional()
|
|
5817
|
+
}).optional().describe(
|
|
5818
|
+
'Platform-specific metadata. Example: {"tiktok":{"privacy_status":"PUBLIC_TO_EVERYONE"}, "youtube":{"title":"My Video"}}'
|
|
5686
5819
|
),
|
|
5687
5820
|
media_type: z3.enum(["IMAGE", "VIDEO", "CAROUSEL_ALBUM"]).optional().describe(
|
|
5688
5821
|
"Media type. Set to CAROUSEL_ALBUM with media_urls for Instagram carousels. Default: auto-detected from media_url."
|
|
@@ -5780,7 +5913,12 @@ function registerDistributionTools(server2) {
|
|
|
5780
5913
|
const signed = await signR2Key(r2_key);
|
|
5781
5914
|
if (!signed) {
|
|
5782
5915
|
return {
|
|
5783
|
-
content: [
|
|
5916
|
+
content: [
|
|
5917
|
+
{
|
|
5918
|
+
type: "text",
|
|
5919
|
+
text: `Failed to sign media key. Verify the key exists and you have access.`
|
|
5920
|
+
}
|
|
5921
|
+
],
|
|
5784
5922
|
isError: true
|
|
5785
5923
|
};
|
|
5786
5924
|
}
|
|
@@ -5808,7 +5946,7 @@ function registerDistributionTools(server2) {
|
|
|
5808
5946
|
content: [
|
|
5809
5947
|
{
|
|
5810
5948
|
type: "text",
|
|
5811
|
-
text: `Failed to sign
|
|
5949
|
+
text: `Failed to sign media key at index ${failIdx}. Verify the key exists and you have access.`
|
|
5812
5950
|
}
|
|
5813
5951
|
],
|
|
5814
5952
|
isError: true
|
|
@@ -5836,7 +5974,7 @@ function registerDistributionTools(server2) {
|
|
|
5836
5974
|
content: [
|
|
5837
5975
|
{
|
|
5838
5976
|
type: "text",
|
|
5839
|
-
text: `Failed to resolve media: ${
|
|
5977
|
+
text: `Failed to resolve media: ${sanitizeError2(resolveErr)}`
|
|
5840
5978
|
}
|
|
5841
5979
|
],
|
|
5842
5980
|
isError: true
|
|
@@ -5861,7 +5999,11 @@ Created with Social Neuron`;
|
|
|
5861
5999
|
hashtags,
|
|
5862
6000
|
scheduledAt: schedule_at,
|
|
5863
6001
|
projectId: project_id,
|
|
5864
|
-
...platform_metadata ? {
|
|
6002
|
+
...platform_metadata ? {
|
|
6003
|
+
platformMetadata: convertPlatformMetadata(
|
|
6004
|
+
platform_metadata
|
|
6005
|
+
)
|
|
6006
|
+
} : {}
|
|
5865
6007
|
},
|
|
5866
6008
|
{ timeoutMs: 3e4 }
|
|
5867
6009
|
);
|
|
@@ -6197,7 +6339,7 @@ Created with Social Neuron`;
|
|
|
6197
6339
|
return { content: [{ type: "text", text: lines.join("\n") }], isError: false };
|
|
6198
6340
|
} catch (err) {
|
|
6199
6341
|
const durationMs = Date.now() - startedAt;
|
|
6200
|
-
const message =
|
|
6342
|
+
const message = sanitizeError2(err);
|
|
6201
6343
|
logMcpToolInvocation({
|
|
6202
6344
|
toolName: "find_next_slots",
|
|
6203
6345
|
status: "error",
|
|
@@ -6717,7 +6859,7 @@ Created with Social Neuron`;
|
|
|
6717
6859
|
};
|
|
6718
6860
|
} catch (err) {
|
|
6719
6861
|
const durationMs = Date.now() - startedAt;
|
|
6720
|
-
const message =
|
|
6862
|
+
const message = sanitizeError2(err);
|
|
6721
6863
|
logMcpToolInvocation({
|
|
6722
6864
|
toolName: "schedule_content_plan",
|
|
6723
6865
|
status: "error",
|
|
@@ -6740,6 +6882,10 @@ import { readFile } from "node:fs/promises";
|
|
|
6740
6882
|
import { basename, extname } from "node:path";
|
|
6741
6883
|
init_supabase();
|
|
6742
6884
|
var MAX_BASE64_SIZE = 10 * 1024 * 1024;
|
|
6885
|
+
function maskR2Key(key) {
|
|
6886
|
+
const segments = key.split("/");
|
|
6887
|
+
return segments.length >= 3 ? `\u2026/${segments.slice(-2).join("/")}` : key;
|
|
6888
|
+
}
|
|
6743
6889
|
function inferContentType(filePath) {
|
|
6744
6890
|
const ext = extname(filePath).toLowerCase();
|
|
6745
6891
|
const map = {
|
|
@@ -6824,18 +6970,111 @@ function registerMediaTools(server2) {
|
|
|
6824
6970
|
isError: true
|
|
6825
6971
|
};
|
|
6826
6972
|
}
|
|
6973
|
+
const ct = content_type || inferContentType(source);
|
|
6827
6974
|
if (fileBuffer.length > MAX_BASE64_SIZE) {
|
|
6975
|
+
const { data: putData, error: putError } = await callEdgeFunction(
|
|
6976
|
+
"get-signed-url",
|
|
6977
|
+
{
|
|
6978
|
+
operation: "put",
|
|
6979
|
+
contentType: ct,
|
|
6980
|
+
filename: basename(source),
|
|
6981
|
+
projectId: project_id
|
|
6982
|
+
},
|
|
6983
|
+
{ timeoutMs: 1e4 }
|
|
6984
|
+
);
|
|
6985
|
+
if (putError || !putData?.signedUrl) {
|
|
6986
|
+
return {
|
|
6987
|
+
content: [
|
|
6988
|
+
{
|
|
6989
|
+
type: "text",
|
|
6990
|
+
text: `Failed to get presigned upload URL: ${putError || "No URL returned"}`
|
|
6991
|
+
}
|
|
6992
|
+
],
|
|
6993
|
+
isError: true
|
|
6994
|
+
};
|
|
6995
|
+
}
|
|
6996
|
+
try {
|
|
6997
|
+
const putResp = await fetch(putData.signedUrl, {
|
|
6998
|
+
method: "PUT",
|
|
6999
|
+
headers: { "Content-Type": ct },
|
|
7000
|
+
body: fileBuffer
|
|
7001
|
+
});
|
|
7002
|
+
if (!putResp.ok) {
|
|
7003
|
+
return {
|
|
7004
|
+
content: [
|
|
7005
|
+
{
|
|
7006
|
+
type: "text",
|
|
7007
|
+
text: `R2 upload failed (HTTP ${putResp.status}): ${await putResp.text().catch(() => "Unknown error")}`
|
|
7008
|
+
}
|
|
7009
|
+
],
|
|
7010
|
+
isError: true
|
|
7011
|
+
};
|
|
7012
|
+
}
|
|
7013
|
+
} catch (uploadErr) {
|
|
7014
|
+
return {
|
|
7015
|
+
content: [
|
|
7016
|
+
{
|
|
7017
|
+
type: "text",
|
|
7018
|
+
text: `R2 upload failed: ${sanitizeError(uploadErr)}`
|
|
7019
|
+
}
|
|
7020
|
+
],
|
|
7021
|
+
isError: true
|
|
7022
|
+
};
|
|
7023
|
+
}
|
|
7024
|
+
const { data: signData } = await callEdgeFunction(
|
|
7025
|
+
"get-signed-url",
|
|
7026
|
+
{ key: putData.key, operation: "get" },
|
|
7027
|
+
{ timeoutMs: 1e4 }
|
|
7028
|
+
);
|
|
7029
|
+
await logMcpToolInvocation({
|
|
7030
|
+
toolName: "upload_media",
|
|
7031
|
+
status: "success",
|
|
7032
|
+
durationMs: Date.now() - startedAt,
|
|
7033
|
+
details: {
|
|
7034
|
+
source: "local-presigned-put",
|
|
7035
|
+
r2Key: putData.key,
|
|
7036
|
+
size: fileBuffer.length,
|
|
7037
|
+
contentType: ct
|
|
7038
|
+
}
|
|
7039
|
+
});
|
|
7040
|
+
if (format === "json") {
|
|
7041
|
+
return {
|
|
7042
|
+
content: [
|
|
7043
|
+
{
|
|
7044
|
+
type: "text",
|
|
7045
|
+
text: JSON.stringify(
|
|
7046
|
+
{
|
|
7047
|
+
r2_key: putData.key,
|
|
7048
|
+
signed_url: signData?.signedUrl ?? null,
|
|
7049
|
+
size: fileBuffer.length,
|
|
7050
|
+
content_type: ct
|
|
7051
|
+
},
|
|
7052
|
+
null,
|
|
7053
|
+
2
|
|
7054
|
+
)
|
|
7055
|
+
}
|
|
7056
|
+
],
|
|
7057
|
+
isError: false
|
|
7058
|
+
};
|
|
7059
|
+
}
|
|
6828
7060
|
return {
|
|
6829
7061
|
content: [
|
|
6830
7062
|
{
|
|
6831
7063
|
type: "text",
|
|
6832
|
-
text:
|
|
7064
|
+
text: [
|
|
7065
|
+
"Media uploaded successfully (presigned PUT).",
|
|
7066
|
+
`Media key: ${maskR2Key(putData.key)}`,
|
|
7067
|
+
signData?.signedUrl ? `Signed URL: ${signData.signedUrl}` : "",
|
|
7068
|
+
`Size: ${(fileBuffer.length / 1024 / 1024).toFixed(1)}MB`,
|
|
7069
|
+
`Type: ${ct}`,
|
|
7070
|
+
"",
|
|
7071
|
+
"Use job_id or response_format=json with schedule_post to post to any platform."
|
|
7072
|
+
].filter(Boolean).join("\n")
|
|
6833
7073
|
}
|
|
6834
7074
|
],
|
|
6835
|
-
isError:
|
|
7075
|
+
isError: false
|
|
6836
7076
|
};
|
|
6837
7077
|
}
|
|
6838
|
-
const ct = content_type || inferContentType(source);
|
|
6839
7078
|
const base64 = `data:${ct};base64,${fileBuffer.toString("base64")}`;
|
|
6840
7079
|
uploadBody = {
|
|
6841
7080
|
fileData: base64,
|
|
@@ -6900,12 +7139,12 @@ function registerMediaTools(server2) {
|
|
|
6900
7139
|
type: "text",
|
|
6901
7140
|
text: [
|
|
6902
7141
|
"Media uploaded successfully.",
|
|
6903
|
-
`
|
|
7142
|
+
`Media key: ${maskR2Key(data.key)}`,
|
|
6904
7143
|
`Signed URL: ${data.url}`,
|
|
6905
7144
|
`Size: ${(data.size / 1024).toFixed(0)}KB`,
|
|
6906
7145
|
`Type: ${data.contentType}`,
|
|
6907
7146
|
"",
|
|
6908
|
-
"Use
|
|
7147
|
+
"Use job_id or response_format=json with schedule_post to post to any platform."
|
|
6909
7148
|
].join("\n")
|
|
6910
7149
|
}
|
|
6911
7150
|
],
|
|
@@ -6969,7 +7208,7 @@ function registerMediaTools(server2) {
|
|
|
6969
7208
|
type: "text",
|
|
6970
7209
|
text: [
|
|
6971
7210
|
`Signed URL: ${data.signedUrl}`,
|
|
6972
|
-
`
|
|
7211
|
+
`Media key: ${maskR2Key(r2_key)}`,
|
|
6973
7212
|
`Expires in: ${data.expiresIn ?? 3600}s`
|
|
6974
7213
|
].join("\n")
|
|
6975
7214
|
}
|
|
@@ -7904,7 +8143,7 @@ function registerScreenshotTools(server2) {
|
|
|
7904
8143
|
};
|
|
7905
8144
|
} catch (err) {
|
|
7906
8145
|
await closeBrowser();
|
|
7907
|
-
const message =
|
|
8146
|
+
const message = sanitizeError2(err);
|
|
7908
8147
|
await logMcpToolInvocation({
|
|
7909
8148
|
toolName: "capture_app_page",
|
|
7910
8149
|
status: "error",
|
|
@@ -8060,7 +8299,7 @@ function registerScreenshotTools(server2) {
|
|
|
8060
8299
|
};
|
|
8061
8300
|
} catch (err) {
|
|
8062
8301
|
await closeBrowser();
|
|
8063
|
-
const message =
|
|
8302
|
+
const message = sanitizeError2(err);
|
|
8064
8303
|
await logMcpToolInvocation({
|
|
8065
8304
|
toolName: "capture_screenshot",
|
|
8066
8305
|
status: "error",
|
|
@@ -8354,7 +8593,7 @@ function registerRemotionTools(server2) {
|
|
|
8354
8593
|
]
|
|
8355
8594
|
};
|
|
8356
8595
|
} catch (err) {
|
|
8357
|
-
const message =
|
|
8596
|
+
const message = sanitizeError2(err);
|
|
8358
8597
|
await logMcpToolInvocation({
|
|
8359
8598
|
toolName: "render_demo_video",
|
|
8360
8599
|
status: "error",
|
|
@@ -8482,7 +8721,7 @@ function registerRemotionTools(server2) {
|
|
|
8482
8721
|
]
|
|
8483
8722
|
};
|
|
8484
8723
|
} catch (err) {
|
|
8485
|
-
const message =
|
|
8724
|
+
const message = sanitizeError2(err);
|
|
8486
8725
|
await logMcpToolInvocation({
|
|
8487
8726
|
toolName: "render_template_video",
|
|
8488
8727
|
status: "error",
|
|
@@ -9944,8 +10183,8 @@ Active: ${is_active}`
|
|
|
9944
10183
|
|
|
9945
10184
|
// src/tools/extraction.ts
|
|
9946
10185
|
init_edge_function();
|
|
9947
|
-
init_supabase();
|
|
9948
10186
|
import { z as z17 } from "zod";
|
|
10187
|
+
init_supabase();
|
|
9949
10188
|
init_version();
|
|
9950
10189
|
function asEnvelope13(data) {
|
|
9951
10190
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
@@ -10151,7 +10390,7 @@ function registerExtractionTools(server2) {
|
|
|
10151
10390
|
};
|
|
10152
10391
|
} catch (err) {
|
|
10153
10392
|
const durationMs = Date.now() - startedAt;
|
|
10154
|
-
const message =
|
|
10393
|
+
const message = sanitizeError2(err);
|
|
10155
10394
|
logMcpToolInvocation({
|
|
10156
10395
|
toolName: "extract_url_content",
|
|
10157
10396
|
status: "error",
|
|
@@ -10341,10 +10580,10 @@ function registerQualityTools(server2) {
|
|
|
10341
10580
|
|
|
10342
10581
|
// src/tools/planning.ts
|
|
10343
10582
|
init_edge_function();
|
|
10344
|
-
init_supabase();
|
|
10345
|
-
init_version();
|
|
10346
10583
|
import { z as z19 } from "zod";
|
|
10347
10584
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
10585
|
+
init_supabase();
|
|
10586
|
+
init_version();
|
|
10348
10587
|
|
|
10349
10588
|
// src/lib/parse-utils.ts
|
|
10350
10589
|
function extractJsonArray(text) {
|
|
@@ -10711,7 +10950,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
10711
10950
|
}
|
|
10712
10951
|
} catch (persistErr) {
|
|
10713
10952
|
const durationMs2 = Date.now() - startedAt;
|
|
10714
|
-
const message =
|
|
10953
|
+
const message = sanitizeError2(persistErr);
|
|
10715
10954
|
logMcpToolInvocation({
|
|
10716
10955
|
toolName: "plan_content_week",
|
|
10717
10956
|
status: "error",
|
|
@@ -10743,7 +10982,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
10743
10982
|
};
|
|
10744
10983
|
} catch (err) {
|
|
10745
10984
|
const durationMs = Date.now() - startedAt;
|
|
10746
|
-
const message =
|
|
10985
|
+
const message = sanitizeError2(err);
|
|
10747
10986
|
logMcpToolInvocation({
|
|
10748
10987
|
toolName: "plan_content_week",
|
|
10749
10988
|
status: "error",
|
|
@@ -10835,7 +11074,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
10835
11074
|
};
|
|
10836
11075
|
} catch (err) {
|
|
10837
11076
|
const durationMs = Date.now() - startedAt;
|
|
10838
|
-
const message =
|
|
11077
|
+
const message = sanitizeError2(err);
|
|
10839
11078
|
logMcpToolInvocation({
|
|
10840
11079
|
toolName: "save_content_plan",
|
|
10841
11080
|
status: "error",
|
|
@@ -11313,11 +11552,11 @@ function registerDiscoveryTools(server2) {
|
|
|
11313
11552
|
|
|
11314
11553
|
// src/tools/pipeline.ts
|
|
11315
11554
|
init_edge_function();
|
|
11555
|
+
import { z as z22 } from "zod";
|
|
11556
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
11316
11557
|
init_supabase();
|
|
11317
11558
|
init_quality();
|
|
11318
11559
|
init_version();
|
|
11319
|
-
import { z as z22 } from "zod";
|
|
11320
|
-
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
11321
11560
|
function asEnvelope17(data) {
|
|
11322
11561
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
11323
11562
|
}
|
|
@@ -11447,7 +11686,7 @@ function registerPipelineTools(server2) {
|
|
|
11447
11686
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
11448
11687
|
} catch (err) {
|
|
11449
11688
|
const durationMs = Date.now() - startedAt;
|
|
11450
|
-
const message =
|
|
11689
|
+
const message = sanitizeError2(err);
|
|
11451
11690
|
logMcpToolInvocation({
|
|
11452
11691
|
toolName: "check_pipeline_readiness",
|
|
11453
11692
|
status: "error",
|
|
@@ -11608,7 +11847,7 @@ function registerPipelineTools(server2) {
|
|
|
11608
11847
|
} catch (deductErr) {
|
|
11609
11848
|
errors.push({
|
|
11610
11849
|
stage: "planning",
|
|
11611
|
-
message: `Credit deduction failed: ${
|
|
11850
|
+
message: `Credit deduction failed: ${sanitizeError2(deductErr)}`
|
|
11612
11851
|
});
|
|
11613
11852
|
}
|
|
11614
11853
|
}
|
|
@@ -11779,7 +12018,7 @@ function registerPipelineTools(server2) {
|
|
|
11779
12018
|
} catch (schedErr) {
|
|
11780
12019
|
errors.push({
|
|
11781
12020
|
stage: "schedule",
|
|
11782
|
-
message: `Failed to schedule ${post.id}: ${
|
|
12021
|
+
message: `Failed to schedule ${post.id}: ${sanitizeError2(schedErr)}`
|
|
11783
12022
|
});
|
|
11784
12023
|
}
|
|
11785
12024
|
}
|
|
@@ -11868,7 +12107,7 @@ function registerPipelineTools(server2) {
|
|
|
11868
12107
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
11869
12108
|
} catch (err) {
|
|
11870
12109
|
const durationMs = Date.now() - startedAt;
|
|
11871
|
-
const message =
|
|
12110
|
+
const message = sanitizeError2(err);
|
|
11872
12111
|
logMcpToolInvocation({
|
|
11873
12112
|
toolName: "run_content_pipeline",
|
|
11874
12113
|
status: "error",
|
|
@@ -12092,7 +12331,7 @@ function registerPipelineTools(server2) {
|
|
|
12092
12331
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
12093
12332
|
} catch (err) {
|
|
12094
12333
|
const durationMs = Date.now() - startedAt;
|
|
12095
|
-
const message =
|
|
12334
|
+
const message = sanitizeError2(err);
|
|
12096
12335
|
logMcpToolInvocation({
|
|
12097
12336
|
toolName: "auto_approve_plan",
|
|
12098
12337
|
status: "error",
|
|
@@ -12127,9 +12366,9 @@ function buildPlanPrompt(topic, platforms, days, postsPerDay, sourceUrl) {
|
|
|
12127
12366
|
|
|
12128
12367
|
// src/tools/suggest.ts
|
|
12129
12368
|
init_edge_function();
|
|
12369
|
+
import { z as z23 } from "zod";
|
|
12130
12370
|
init_supabase();
|
|
12131
12371
|
init_version();
|
|
12132
|
-
import { z as z23 } from "zod";
|
|
12133
12372
|
function asEnvelope18(data) {
|
|
12134
12373
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
12135
12374
|
}
|
|
@@ -12272,7 +12511,7 @@ ${i + 1}. ${s.topic}`);
|
|
|
12272
12511
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
12273
12512
|
} catch (err) {
|
|
12274
12513
|
const durationMs = Date.now() - startedAt;
|
|
12275
|
-
const message =
|
|
12514
|
+
const message = sanitizeError2(err);
|
|
12276
12515
|
logMcpToolInvocation({
|
|
12277
12516
|
toolName: "suggest_next_content",
|
|
12278
12517
|
status: "error",
|
|
@@ -12290,8 +12529,8 @@ ${i + 1}. ${s.topic}`);
|
|
|
12290
12529
|
|
|
12291
12530
|
// src/tools/digest.ts
|
|
12292
12531
|
init_edge_function();
|
|
12293
|
-
init_supabase();
|
|
12294
12532
|
import { z as z24 } from "zod";
|
|
12533
|
+
init_supabase();
|
|
12295
12534
|
|
|
12296
12535
|
// src/lib/anomaly-detector.ts
|
|
12297
12536
|
var SENSITIVITY_THRESHOLDS = {
|
|
@@ -12613,7 +12852,7 @@ Best: ${best.id.slice(0, 8)}... (${best.platform}) \u2014 ${best.views.toLocaleS
|
|
|
12613
12852
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
12614
12853
|
} catch (err) {
|
|
12615
12854
|
const durationMs = Date.now() - startedAt;
|
|
12616
|
-
const message =
|
|
12855
|
+
const message = sanitizeError2(err);
|
|
12617
12856
|
logMcpToolInvocation({
|
|
12618
12857
|
toolName: "generate_performance_digest",
|
|
12619
12858
|
status: "error",
|
|
@@ -12710,7 +12949,7 @@ Best: ${best.id.slice(0, 8)}... (${best.platform}) \u2014 ${best.views.toLocaleS
|
|
|
12710
12949
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
12711
12950
|
} catch (err) {
|
|
12712
12951
|
const durationMs = Date.now() - startedAt;
|
|
12713
|
-
const message =
|
|
12952
|
+
const message = sanitizeError2(err);
|
|
12714
12953
|
logMcpToolInvocation({
|
|
12715
12954
|
toolName: "detect_anomalies",
|
|
12716
12955
|
status: "error",
|
|
@@ -12731,6 +12970,275 @@ init_edge_function();
|
|
|
12731
12970
|
init_supabase();
|
|
12732
12971
|
init_version();
|
|
12733
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
|
|
12734
13242
|
function asEnvelope20(data) {
|
|
12735
13243
|
return {
|
|
12736
13244
|
_meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() },
|
|
@@ -12943,44 +13451,7 @@ function registerBrandRuntimeTools(server2) {
|
|
|
12943
13451
|
};
|
|
12944
13452
|
}
|
|
12945
13453
|
const profile = row.profile_data;
|
|
12946
|
-
const
|
|
12947
|
-
const issues = [];
|
|
12948
|
-
let score = 70;
|
|
12949
|
-
const banned = profile.vocabularyRules?.bannedTerms || [];
|
|
12950
|
-
const bannedFound = banned.filter((t) => contentLower.includes(t.toLowerCase()));
|
|
12951
|
-
if (bannedFound.length > 0) {
|
|
12952
|
-
score -= bannedFound.length * 15;
|
|
12953
|
-
issues.push(`Banned terms found: ${bannedFound.join(", ")}`);
|
|
12954
|
-
}
|
|
12955
|
-
const avoid = profile.voiceProfile?.avoidPatterns || [];
|
|
12956
|
-
const avoidFound = avoid.filter((p) => contentLower.includes(p.toLowerCase()));
|
|
12957
|
-
if (avoidFound.length > 0) {
|
|
12958
|
-
score -= avoidFound.length * 10;
|
|
12959
|
-
issues.push(`Avoid patterns found: ${avoidFound.join(", ")}`);
|
|
12960
|
-
}
|
|
12961
|
-
const preferred = profile.vocabularyRules?.preferredTerms || [];
|
|
12962
|
-
const prefUsed = preferred.filter((t) => contentLower.includes(t.toLowerCase()));
|
|
12963
|
-
score += Math.min(15, prefUsed.length * 5);
|
|
12964
|
-
const fabPatterns = [
|
|
12965
|
-
{ regex: /\b\d+[,.]?\d*\s*(%|percent)/gi, label: "unverified percentage" },
|
|
12966
|
-
{ regex: /\b(award[- ]?winning|best[- ]selling|#\s*1)\b/gi, label: "unverified ranking" },
|
|
12967
|
-
{ regex: /\b(guaranteed|proven to|studies show)\b/gi, label: "unverified claim" }
|
|
12968
|
-
];
|
|
12969
|
-
for (const { regex, label } of fabPatterns) {
|
|
12970
|
-
regex.lastIndex = 0;
|
|
12971
|
-
if (regex.test(content)) {
|
|
12972
|
-
score -= 10;
|
|
12973
|
-
issues.push(`Potential ${label} detected`);
|
|
12974
|
-
}
|
|
12975
|
-
}
|
|
12976
|
-
score = Math.max(0, Math.min(100, score));
|
|
12977
|
-
const checkResult = {
|
|
12978
|
-
score,
|
|
12979
|
-
passed: score >= 60,
|
|
12980
|
-
issues,
|
|
12981
|
-
preferredTermsUsed: prefUsed,
|
|
12982
|
-
bannedTermsFound: bannedFound
|
|
12983
|
-
};
|
|
13454
|
+
const checkResult = computeBrandConsistency(content, profile);
|
|
12984
13455
|
const envelope = asEnvelope20(checkResult);
|
|
12985
13456
|
return {
|
|
12986
13457
|
content: [{ type: "text", text: JSON.stringify(envelope, null, 2) }]
|