@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/http.js
CHANGED
|
@@ -1375,7 +1375,7 @@ init_supabase();
|
|
|
1375
1375
|
init_request_context();
|
|
1376
1376
|
|
|
1377
1377
|
// src/lib/version.ts
|
|
1378
|
-
var MCP_VERSION = "1.7.
|
|
1378
|
+
var MCP_VERSION = "1.7.2";
|
|
1379
1379
|
|
|
1380
1380
|
// src/tools/content.ts
|
|
1381
1381
|
var MAX_CREDITS_PER_RUN = Math.max(0, Number(process.env.SOCIALNEURON_MAX_CREDITS_PER_RUN || 0));
|
|
@@ -1985,7 +1985,24 @@ function registerContentTools(server) {
|
|
|
1985
1985
|
`Status: ${job.status}`
|
|
1986
1986
|
];
|
|
1987
1987
|
if (job.result_url) {
|
|
1988
|
-
|
|
1988
|
+
const isR2Key = !job.result_url.startsWith("http");
|
|
1989
|
+
if (isR2Key) {
|
|
1990
|
+
const segments = job.result_url.split("/");
|
|
1991
|
+
const filename = segments[segments.length - 1] || "media";
|
|
1992
|
+
lines.push(`Media ready: ${filename}`);
|
|
1993
|
+
lines.push(
|
|
1994
|
+
"(Pass job_id directly to schedule_post, or use get_media_url with job_id for a download link)"
|
|
1995
|
+
);
|
|
1996
|
+
} else {
|
|
1997
|
+
lines.push(`Result URL: ${job.result_url}`);
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
const allUrls = job.result_metadata?.all_urls;
|
|
2001
|
+
if (allUrls && allUrls.length > 1) {
|
|
2002
|
+
lines.push(`Media files: ${allUrls.length} outputs available`);
|
|
2003
|
+
lines.push(
|
|
2004
|
+
"(Use job_id with schedule_post for carousel, or response_format=json for programmatic access)"
|
|
2005
|
+
);
|
|
1989
2006
|
}
|
|
1990
2007
|
if (job.error_message) {
|
|
1991
2008
|
lines.push(`Error: ${job.error_message}`);
|
|
@@ -2002,8 +2019,13 @@ function registerContentTools(server) {
|
|
|
2002
2019
|
details: { status: job.status, jobId: job.id }
|
|
2003
2020
|
});
|
|
2004
2021
|
if (format === "json") {
|
|
2022
|
+
const enriched = {
|
|
2023
|
+
...job,
|
|
2024
|
+
r2_key: job.result_url && !job.result_url.startsWith("http") ? job.result_url : null,
|
|
2025
|
+
all_urls: allUrls ?? null
|
|
2026
|
+
};
|
|
2005
2027
|
return {
|
|
2006
|
-
content: [{ type: "text", text: JSON.stringify(asEnvelope(
|
|
2028
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope(enriched), null, 2) }]
|
|
2007
2029
|
};
|
|
2008
2030
|
}
|
|
2009
2031
|
return {
|
|
@@ -2485,6 +2507,56 @@ Return ONLY valid JSON in this exact format:
|
|
|
2485
2507
|
// src/tools/distribution.ts
|
|
2486
2508
|
import { z as z3 } from "zod";
|
|
2487
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
|
|
2488
2560
|
init_supabase();
|
|
2489
2561
|
|
|
2490
2562
|
// src/lib/quality.ts
|
|
@@ -2610,6 +2682,22 @@ function evaluateQuality(input) {
|
|
|
2610
2682
|
}
|
|
2611
2683
|
|
|
2612
2684
|
// src/tools/distribution.ts
|
|
2685
|
+
function snakeToCamel(obj) {
|
|
2686
|
+
const result = {};
|
|
2687
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
2688
|
+
const camelKey = key.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
2689
|
+
result[camelKey] = value;
|
|
2690
|
+
}
|
|
2691
|
+
return result;
|
|
2692
|
+
}
|
|
2693
|
+
function convertPlatformMetadata(meta) {
|
|
2694
|
+
if (!meta) return void 0;
|
|
2695
|
+
const converted = {};
|
|
2696
|
+
for (const [platform2, fields] of Object.entries(meta)) {
|
|
2697
|
+
converted[platform2] = snakeToCamel(fields);
|
|
2698
|
+
}
|
|
2699
|
+
return converted;
|
|
2700
|
+
}
|
|
2613
2701
|
var PLATFORM_CASE_MAP = {
|
|
2614
2702
|
youtube: "YouTube",
|
|
2615
2703
|
tiktok: "TikTok",
|
|
@@ -2652,8 +2740,53 @@ function registerDistributionTools(server) {
|
|
|
2652
2740
|
job_ids: z3.array(z3.string()).optional().describe(
|
|
2653
2741
|
"Array of async job IDs for carousel posts. Each resolved to its R2 key. Alternative to media_urls/r2_keys."
|
|
2654
2742
|
),
|
|
2655
|
-
platform_metadata: z3.
|
|
2656
|
-
|
|
2743
|
+
platform_metadata: z3.object({
|
|
2744
|
+
tiktok: z3.object({
|
|
2745
|
+
privacy_status: z3.enum([
|
|
2746
|
+
"PUBLIC_TO_EVERYONE",
|
|
2747
|
+
"MUTUAL_FOLLOW_FRIENDS",
|
|
2748
|
+
"FOLLOWER_OF_CREATOR",
|
|
2749
|
+
"SELF_ONLY"
|
|
2750
|
+
]).optional().describe("Required unless useInbox=true. Who can view the video."),
|
|
2751
|
+
enable_duet: z3.boolean().optional(),
|
|
2752
|
+
enable_comment: z3.boolean().optional(),
|
|
2753
|
+
enable_stitch: z3.boolean().optional(),
|
|
2754
|
+
is_ai_generated: z3.boolean().optional(),
|
|
2755
|
+
brand_content: z3.boolean().optional(),
|
|
2756
|
+
brand_organic: z3.boolean().optional(),
|
|
2757
|
+
use_inbox: z3.boolean().optional().describe("Post to TikTok inbox/draft instead of direct publish.")
|
|
2758
|
+
}).optional(),
|
|
2759
|
+
youtube: z3.object({
|
|
2760
|
+
title: z3.string().optional().describe("Video title (required for YouTube)."),
|
|
2761
|
+
description: z3.string().optional(),
|
|
2762
|
+
privacy_status: z3.enum(["public", "unlisted", "private"]).optional(),
|
|
2763
|
+
category_id: z3.string().optional(),
|
|
2764
|
+
tags: z3.array(z3.string()).optional(),
|
|
2765
|
+
made_for_kids: z3.boolean().optional(),
|
|
2766
|
+
notify_subscribers: z3.boolean().optional()
|
|
2767
|
+
}).optional(),
|
|
2768
|
+
facebook: z3.object({
|
|
2769
|
+
page_id: z3.string().optional().describe("Facebook Page ID to post to."),
|
|
2770
|
+
audience: z3.string().optional()
|
|
2771
|
+
}).optional(),
|
|
2772
|
+
instagram: z3.object({
|
|
2773
|
+
location: z3.string().optional(),
|
|
2774
|
+
collaborators: z3.array(z3.string()).optional(),
|
|
2775
|
+
cover_timestamp: z3.number().optional(),
|
|
2776
|
+
share_to_feed: z3.boolean().optional(),
|
|
2777
|
+
first_comment: z3.string().optional(),
|
|
2778
|
+
is_ai_generated: z3.boolean().optional()
|
|
2779
|
+
}).optional(),
|
|
2780
|
+
threads: z3.object({}).passthrough().optional(),
|
|
2781
|
+
bluesky: z3.object({
|
|
2782
|
+
content_labels: z3.array(z3.string()).optional()
|
|
2783
|
+
}).optional(),
|
|
2784
|
+
linkedin: z3.object({
|
|
2785
|
+
article_url: z3.string().optional()
|
|
2786
|
+
}).optional(),
|
|
2787
|
+
twitter: z3.object({}).passthrough().optional()
|
|
2788
|
+
}).optional().describe(
|
|
2789
|
+
'Platform-specific metadata. Example: {"tiktok":{"privacy_status":"PUBLIC_TO_EVERYONE"}, "youtube":{"title":"My Video"}}'
|
|
2657
2790
|
),
|
|
2658
2791
|
media_type: z3.enum(["IMAGE", "VIDEO", "CAROUSEL_ALBUM"]).optional().describe(
|
|
2659
2792
|
"Media type. Set to CAROUSEL_ALBUM with media_urls for Instagram carousels. Default: auto-detected from media_url."
|
|
@@ -2751,7 +2884,12 @@ function registerDistributionTools(server) {
|
|
|
2751
2884
|
const signed = await signR2Key(r2_key);
|
|
2752
2885
|
if (!signed) {
|
|
2753
2886
|
return {
|
|
2754
|
-
content: [
|
|
2887
|
+
content: [
|
|
2888
|
+
{
|
|
2889
|
+
type: "text",
|
|
2890
|
+
text: `Failed to sign media key. Verify the key exists and you have access.`
|
|
2891
|
+
}
|
|
2892
|
+
],
|
|
2755
2893
|
isError: true
|
|
2756
2894
|
};
|
|
2757
2895
|
}
|
|
@@ -2779,7 +2917,7 @@ function registerDistributionTools(server) {
|
|
|
2779
2917
|
content: [
|
|
2780
2918
|
{
|
|
2781
2919
|
type: "text",
|
|
2782
|
-
text: `Failed to sign
|
|
2920
|
+
text: `Failed to sign media key at index ${failIdx}. Verify the key exists and you have access.`
|
|
2783
2921
|
}
|
|
2784
2922
|
],
|
|
2785
2923
|
isError: true
|
|
@@ -2807,7 +2945,7 @@ function registerDistributionTools(server) {
|
|
|
2807
2945
|
content: [
|
|
2808
2946
|
{
|
|
2809
2947
|
type: "text",
|
|
2810
|
-
text: `Failed to resolve media: ${
|
|
2948
|
+
text: `Failed to resolve media: ${sanitizeError2(resolveErr)}`
|
|
2811
2949
|
}
|
|
2812
2950
|
],
|
|
2813
2951
|
isError: true
|
|
@@ -2832,7 +2970,11 @@ Created with Social Neuron`;
|
|
|
2832
2970
|
hashtags,
|
|
2833
2971
|
scheduledAt: schedule_at,
|
|
2834
2972
|
projectId: project_id,
|
|
2835
|
-
...platform_metadata ? {
|
|
2973
|
+
...platform_metadata ? {
|
|
2974
|
+
platformMetadata: convertPlatformMetadata(
|
|
2975
|
+
platform_metadata
|
|
2976
|
+
)
|
|
2977
|
+
} : {}
|
|
2836
2978
|
},
|
|
2837
2979
|
{ timeoutMs: 3e4 }
|
|
2838
2980
|
);
|
|
@@ -3168,7 +3310,7 @@ Created with Social Neuron`;
|
|
|
3168
3310
|
return { content: [{ type: "text", text: lines.join("\n") }], isError: false };
|
|
3169
3311
|
} catch (err) {
|
|
3170
3312
|
const durationMs = Date.now() - startedAt;
|
|
3171
|
-
const message =
|
|
3313
|
+
const message = sanitizeError2(err);
|
|
3172
3314
|
logMcpToolInvocation({
|
|
3173
3315
|
toolName: "find_next_slots",
|
|
3174
3316
|
status: "error",
|
|
@@ -3688,7 +3830,7 @@ Created with Social Neuron`;
|
|
|
3688
3830
|
};
|
|
3689
3831
|
} catch (err) {
|
|
3690
3832
|
const durationMs = Date.now() - startedAt;
|
|
3691
|
-
const message =
|
|
3833
|
+
const message = sanitizeError2(err);
|
|
3692
3834
|
logMcpToolInvocation({
|
|
3693
3835
|
toolName: "schedule_content_plan",
|
|
3694
3836
|
status: "error",
|
|
@@ -3710,6 +3852,10 @@ import { readFile } from "node:fs/promises";
|
|
|
3710
3852
|
import { basename, extname } from "node:path";
|
|
3711
3853
|
init_supabase();
|
|
3712
3854
|
var MAX_BASE64_SIZE = 10 * 1024 * 1024;
|
|
3855
|
+
function maskR2Key(key) {
|
|
3856
|
+
const segments = key.split("/");
|
|
3857
|
+
return segments.length >= 3 ? `\u2026/${segments.slice(-2).join("/")}` : key;
|
|
3858
|
+
}
|
|
3713
3859
|
function inferContentType(filePath) {
|
|
3714
3860
|
const ext = extname(filePath).toLowerCase();
|
|
3715
3861
|
const map = {
|
|
@@ -3794,18 +3940,111 @@ function registerMediaTools(server) {
|
|
|
3794
3940
|
isError: true
|
|
3795
3941
|
};
|
|
3796
3942
|
}
|
|
3943
|
+
const ct = content_type || inferContentType(source);
|
|
3797
3944
|
if (fileBuffer.length > MAX_BASE64_SIZE) {
|
|
3945
|
+
const { data: putData, error: putError } = await callEdgeFunction(
|
|
3946
|
+
"get-signed-url",
|
|
3947
|
+
{
|
|
3948
|
+
operation: "put",
|
|
3949
|
+
contentType: ct,
|
|
3950
|
+
filename: basename(source),
|
|
3951
|
+
projectId: project_id
|
|
3952
|
+
},
|
|
3953
|
+
{ timeoutMs: 1e4 }
|
|
3954
|
+
);
|
|
3955
|
+
if (putError || !putData?.signedUrl) {
|
|
3956
|
+
return {
|
|
3957
|
+
content: [
|
|
3958
|
+
{
|
|
3959
|
+
type: "text",
|
|
3960
|
+
text: `Failed to get presigned upload URL: ${putError || "No URL returned"}`
|
|
3961
|
+
}
|
|
3962
|
+
],
|
|
3963
|
+
isError: true
|
|
3964
|
+
};
|
|
3965
|
+
}
|
|
3966
|
+
try {
|
|
3967
|
+
const putResp = await fetch(putData.signedUrl, {
|
|
3968
|
+
method: "PUT",
|
|
3969
|
+
headers: { "Content-Type": ct },
|
|
3970
|
+
body: fileBuffer
|
|
3971
|
+
});
|
|
3972
|
+
if (!putResp.ok) {
|
|
3973
|
+
return {
|
|
3974
|
+
content: [
|
|
3975
|
+
{
|
|
3976
|
+
type: "text",
|
|
3977
|
+
text: `R2 upload failed (HTTP ${putResp.status}): ${await putResp.text().catch(() => "Unknown error")}`
|
|
3978
|
+
}
|
|
3979
|
+
],
|
|
3980
|
+
isError: true
|
|
3981
|
+
};
|
|
3982
|
+
}
|
|
3983
|
+
} catch (uploadErr) {
|
|
3984
|
+
return {
|
|
3985
|
+
content: [
|
|
3986
|
+
{
|
|
3987
|
+
type: "text",
|
|
3988
|
+
text: `R2 upload failed: ${sanitizeError(uploadErr)}`
|
|
3989
|
+
}
|
|
3990
|
+
],
|
|
3991
|
+
isError: true
|
|
3992
|
+
};
|
|
3993
|
+
}
|
|
3994
|
+
const { data: signData } = await callEdgeFunction(
|
|
3995
|
+
"get-signed-url",
|
|
3996
|
+
{ key: putData.key, operation: "get" },
|
|
3997
|
+
{ timeoutMs: 1e4 }
|
|
3998
|
+
);
|
|
3999
|
+
await logMcpToolInvocation({
|
|
4000
|
+
toolName: "upload_media",
|
|
4001
|
+
status: "success",
|
|
4002
|
+
durationMs: Date.now() - startedAt,
|
|
4003
|
+
details: {
|
|
4004
|
+
source: "local-presigned-put",
|
|
4005
|
+
r2Key: putData.key,
|
|
4006
|
+
size: fileBuffer.length,
|
|
4007
|
+
contentType: ct
|
|
4008
|
+
}
|
|
4009
|
+
});
|
|
4010
|
+
if (format === "json") {
|
|
4011
|
+
return {
|
|
4012
|
+
content: [
|
|
4013
|
+
{
|
|
4014
|
+
type: "text",
|
|
4015
|
+
text: JSON.stringify(
|
|
4016
|
+
{
|
|
4017
|
+
r2_key: putData.key,
|
|
4018
|
+
signed_url: signData?.signedUrl ?? null,
|
|
4019
|
+
size: fileBuffer.length,
|
|
4020
|
+
content_type: ct
|
|
4021
|
+
},
|
|
4022
|
+
null,
|
|
4023
|
+
2
|
|
4024
|
+
)
|
|
4025
|
+
}
|
|
4026
|
+
],
|
|
4027
|
+
isError: false
|
|
4028
|
+
};
|
|
4029
|
+
}
|
|
3798
4030
|
return {
|
|
3799
4031
|
content: [
|
|
3800
4032
|
{
|
|
3801
4033
|
type: "text",
|
|
3802
|
-
text:
|
|
4034
|
+
text: [
|
|
4035
|
+
"Media uploaded successfully (presigned PUT).",
|
|
4036
|
+
`Media key: ${maskR2Key(putData.key)}`,
|
|
4037
|
+
signData?.signedUrl ? `Signed URL: ${signData.signedUrl}` : "",
|
|
4038
|
+
`Size: ${(fileBuffer.length / 1024 / 1024).toFixed(1)}MB`,
|
|
4039
|
+
`Type: ${ct}`,
|
|
4040
|
+
"",
|
|
4041
|
+
"Use job_id or response_format=json with schedule_post to post to any platform."
|
|
4042
|
+
].filter(Boolean).join("\n")
|
|
3803
4043
|
}
|
|
3804
4044
|
],
|
|
3805
|
-
isError:
|
|
4045
|
+
isError: false
|
|
3806
4046
|
};
|
|
3807
4047
|
}
|
|
3808
|
-
const ct = content_type || inferContentType(source);
|
|
3809
4048
|
const base64 = `data:${ct};base64,${fileBuffer.toString("base64")}`;
|
|
3810
4049
|
uploadBody = {
|
|
3811
4050
|
fileData: base64,
|
|
@@ -3870,12 +4109,12 @@ function registerMediaTools(server) {
|
|
|
3870
4109
|
type: "text",
|
|
3871
4110
|
text: [
|
|
3872
4111
|
"Media uploaded successfully.",
|
|
3873
|
-
`
|
|
4112
|
+
`Media key: ${maskR2Key(data.key)}`,
|
|
3874
4113
|
`Signed URL: ${data.url}`,
|
|
3875
4114
|
`Size: ${(data.size / 1024).toFixed(0)}KB`,
|
|
3876
4115
|
`Type: ${data.contentType}`,
|
|
3877
4116
|
"",
|
|
3878
|
-
"Use
|
|
4117
|
+
"Use job_id or response_format=json with schedule_post to post to any platform."
|
|
3879
4118
|
].join("\n")
|
|
3880
4119
|
}
|
|
3881
4120
|
],
|
|
@@ -3939,7 +4178,7 @@ function registerMediaTools(server) {
|
|
|
3939
4178
|
type: "text",
|
|
3940
4179
|
text: [
|
|
3941
4180
|
`Signed URL: ${data.signedUrl}`,
|
|
3942
|
-
`
|
|
4181
|
+
`Media key: ${maskR2Key(r2_key)}`,
|
|
3943
4182
|
`Expires in: ${data.expiresIn ?? 3600}s`
|
|
3944
4183
|
].join("\n")
|
|
3945
4184
|
}
|
|
@@ -4870,7 +5109,7 @@ function registerScreenshotTools(server) {
|
|
|
4870
5109
|
};
|
|
4871
5110
|
} catch (err) {
|
|
4872
5111
|
await closeBrowser();
|
|
4873
|
-
const message =
|
|
5112
|
+
const message = sanitizeError2(err);
|
|
4874
5113
|
await logMcpToolInvocation({
|
|
4875
5114
|
toolName: "capture_app_page",
|
|
4876
5115
|
status: "error",
|
|
@@ -5026,7 +5265,7 @@ function registerScreenshotTools(server) {
|
|
|
5026
5265
|
};
|
|
5027
5266
|
} catch (err) {
|
|
5028
5267
|
await closeBrowser();
|
|
5029
|
-
const message =
|
|
5268
|
+
const message = sanitizeError2(err);
|
|
5030
5269
|
await logMcpToolInvocation({
|
|
5031
5270
|
toolName: "capture_screenshot",
|
|
5032
5271
|
status: "error",
|
|
@@ -5319,7 +5558,7 @@ function registerRemotionTools(server) {
|
|
|
5319
5558
|
]
|
|
5320
5559
|
};
|
|
5321
5560
|
} catch (err) {
|
|
5322
|
-
const message =
|
|
5561
|
+
const message = sanitizeError2(err);
|
|
5323
5562
|
await logMcpToolInvocation({
|
|
5324
5563
|
toolName: "render_demo_video",
|
|
5325
5564
|
status: "error",
|
|
@@ -5447,7 +5686,7 @@ function registerRemotionTools(server) {
|
|
|
5447
5686
|
]
|
|
5448
5687
|
};
|
|
5449
5688
|
} catch (err) {
|
|
5450
|
-
const message =
|
|
5689
|
+
const message = sanitizeError2(err);
|
|
5451
5690
|
await logMcpToolInvocation({
|
|
5452
5691
|
toolName: "render_template_video",
|
|
5453
5692
|
status: "error",
|
|
@@ -7098,7 +7337,7 @@ function registerExtractionTools(server) {
|
|
|
7098
7337
|
};
|
|
7099
7338
|
} catch (err) {
|
|
7100
7339
|
const durationMs = Date.now() - startedAt;
|
|
7101
|
-
const message =
|
|
7340
|
+
const message = sanitizeError2(err);
|
|
7102
7341
|
logMcpToolInvocation({
|
|
7103
7342
|
toolName: "extract_url_content",
|
|
7104
7343
|
status: "error",
|
|
@@ -7654,7 +7893,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
7654
7893
|
}
|
|
7655
7894
|
} catch (persistErr) {
|
|
7656
7895
|
const durationMs2 = Date.now() - startedAt;
|
|
7657
|
-
const message =
|
|
7896
|
+
const message = sanitizeError2(persistErr);
|
|
7658
7897
|
logMcpToolInvocation({
|
|
7659
7898
|
toolName: "plan_content_week",
|
|
7660
7899
|
status: "error",
|
|
@@ -7686,7 +7925,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
7686
7925
|
};
|
|
7687
7926
|
} catch (err) {
|
|
7688
7927
|
const durationMs = Date.now() - startedAt;
|
|
7689
|
-
const message =
|
|
7928
|
+
const message = sanitizeError2(err);
|
|
7690
7929
|
logMcpToolInvocation({
|
|
7691
7930
|
toolName: "plan_content_week",
|
|
7692
7931
|
status: "error",
|
|
@@ -7778,7 +8017,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
7778
8017
|
};
|
|
7779
8018
|
} catch (err) {
|
|
7780
8019
|
const durationMs = Date.now() - startedAt;
|
|
7781
|
-
const message =
|
|
8020
|
+
const message = sanitizeError2(err);
|
|
7782
8021
|
logMcpToolInvocation({
|
|
7783
8022
|
toolName: "save_content_plan",
|
|
7784
8023
|
status: "error",
|
|
@@ -8823,7 +9062,7 @@ function registerPipelineTools(server) {
|
|
|
8823
9062
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
8824
9063
|
} catch (err) {
|
|
8825
9064
|
const durationMs = Date.now() - startedAt;
|
|
8826
|
-
const message =
|
|
9065
|
+
const message = sanitizeError2(err);
|
|
8827
9066
|
logMcpToolInvocation({
|
|
8828
9067
|
toolName: "check_pipeline_readiness",
|
|
8829
9068
|
status: "error",
|
|
@@ -8984,7 +9223,7 @@ function registerPipelineTools(server) {
|
|
|
8984
9223
|
} catch (deductErr) {
|
|
8985
9224
|
errors.push({
|
|
8986
9225
|
stage: "planning",
|
|
8987
|
-
message: `Credit deduction failed: ${
|
|
9226
|
+
message: `Credit deduction failed: ${sanitizeError2(deductErr)}`
|
|
8988
9227
|
});
|
|
8989
9228
|
}
|
|
8990
9229
|
}
|
|
@@ -9155,7 +9394,7 @@ function registerPipelineTools(server) {
|
|
|
9155
9394
|
} catch (schedErr) {
|
|
9156
9395
|
errors.push({
|
|
9157
9396
|
stage: "schedule",
|
|
9158
|
-
message: `Failed to schedule ${post.id}: ${
|
|
9397
|
+
message: `Failed to schedule ${post.id}: ${sanitizeError2(schedErr)}`
|
|
9159
9398
|
});
|
|
9160
9399
|
}
|
|
9161
9400
|
}
|
|
@@ -9244,7 +9483,7 @@ function registerPipelineTools(server) {
|
|
|
9244
9483
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
9245
9484
|
} catch (err) {
|
|
9246
9485
|
const durationMs = Date.now() - startedAt;
|
|
9247
|
-
const message =
|
|
9486
|
+
const message = sanitizeError2(err);
|
|
9248
9487
|
logMcpToolInvocation({
|
|
9249
9488
|
toolName: "run_content_pipeline",
|
|
9250
9489
|
status: "error",
|
|
@@ -9468,7 +9707,7 @@ function registerPipelineTools(server) {
|
|
|
9468
9707
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
9469
9708
|
} catch (err) {
|
|
9470
9709
|
const durationMs = Date.now() - startedAt;
|
|
9471
|
-
const message =
|
|
9710
|
+
const message = sanitizeError2(err);
|
|
9472
9711
|
logMcpToolInvocation({
|
|
9473
9712
|
toolName: "auto_approve_plan",
|
|
9474
9713
|
status: "error",
|
|
@@ -9646,7 +9885,7 @@ ${i + 1}. ${s.topic}`);
|
|
|
9646
9885
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
9647
9886
|
} catch (err) {
|
|
9648
9887
|
const durationMs = Date.now() - startedAt;
|
|
9649
|
-
const message =
|
|
9888
|
+
const message = sanitizeError2(err);
|
|
9650
9889
|
logMcpToolInvocation({
|
|
9651
9890
|
toolName: "suggest_next_content",
|
|
9652
9891
|
status: "error",
|
|
@@ -9985,7 +10224,7 @@ Best: ${best.id.slice(0, 8)}... (${best.platform}) \u2014 ${best.views.toLocaleS
|
|
|
9985
10224
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
9986
10225
|
} catch (err) {
|
|
9987
10226
|
const durationMs = Date.now() - startedAt;
|
|
9988
|
-
const message =
|
|
10227
|
+
const message = sanitizeError2(err);
|
|
9989
10228
|
logMcpToolInvocation({
|
|
9990
10229
|
toolName: "generate_performance_digest",
|
|
9991
10230
|
status: "error",
|
|
@@ -10082,7 +10321,7 @@ Best: ${best.id.slice(0, 8)}... (${best.platform}) \u2014 ${best.views.toLocaleS
|
|
|
10082
10321
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
10083
10322
|
} catch (err) {
|
|
10084
10323
|
const durationMs = Date.now() - startedAt;
|
|
10085
|
-
const message =
|
|
10324
|
+
const message = sanitizeError2(err);
|
|
10086
10325
|
logMcpToolInvocation({
|
|
10087
10326
|
toolName: "detect_anomalies",
|
|
10088
10327
|
status: "error",
|
|
@@ -10101,6 +10340,275 @@ Best: ${best.id.slice(0, 8)}... (${best.platform}) \u2014 ${best.views.toLocaleS
|
|
|
10101
10340
|
// src/tools/brandRuntime.ts
|
|
10102
10341
|
import { z as z25 } from "zod";
|
|
10103
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
|
|
10104
10612
|
function asEnvelope20(data) {
|
|
10105
10613
|
return {
|
|
10106
10614
|
_meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() },
|
|
@@ -10313,44 +10821,7 @@ function registerBrandRuntimeTools(server) {
|
|
|
10313
10821
|
};
|
|
10314
10822
|
}
|
|
10315
10823
|
const profile = row.profile_data;
|
|
10316
|
-
const
|
|
10317
|
-
const issues = [];
|
|
10318
|
-
let score = 70;
|
|
10319
|
-
const banned = profile.vocabularyRules?.bannedTerms || [];
|
|
10320
|
-
const bannedFound = banned.filter((t) => contentLower.includes(t.toLowerCase()));
|
|
10321
|
-
if (bannedFound.length > 0) {
|
|
10322
|
-
score -= bannedFound.length * 15;
|
|
10323
|
-
issues.push(`Banned terms found: ${bannedFound.join(", ")}`);
|
|
10324
|
-
}
|
|
10325
|
-
const avoid = profile.voiceProfile?.avoidPatterns || [];
|
|
10326
|
-
const avoidFound = avoid.filter((p) => contentLower.includes(p.toLowerCase()));
|
|
10327
|
-
if (avoidFound.length > 0) {
|
|
10328
|
-
score -= avoidFound.length * 10;
|
|
10329
|
-
issues.push(`Avoid patterns found: ${avoidFound.join(", ")}`);
|
|
10330
|
-
}
|
|
10331
|
-
const preferred = profile.vocabularyRules?.preferredTerms || [];
|
|
10332
|
-
const prefUsed = preferred.filter((t) => contentLower.includes(t.toLowerCase()));
|
|
10333
|
-
score += Math.min(15, prefUsed.length * 5);
|
|
10334
|
-
const fabPatterns = [
|
|
10335
|
-
{ regex: /\b\d+[,.]?\d*\s*(%|percent)/gi, label: "unverified percentage" },
|
|
10336
|
-
{ regex: /\b(award[- ]?winning|best[- ]selling|#\s*1)\b/gi, label: "unverified ranking" },
|
|
10337
|
-
{ regex: /\b(guaranteed|proven to|studies show)\b/gi, label: "unverified claim" }
|
|
10338
|
-
];
|
|
10339
|
-
for (const { regex, label } of fabPatterns) {
|
|
10340
|
-
regex.lastIndex = 0;
|
|
10341
|
-
if (regex.test(content)) {
|
|
10342
|
-
score -= 10;
|
|
10343
|
-
issues.push(`Potential ${label} detected`);
|
|
10344
|
-
}
|
|
10345
|
-
}
|
|
10346
|
-
score = Math.max(0, Math.min(100, score));
|
|
10347
|
-
const checkResult = {
|
|
10348
|
-
score,
|
|
10349
|
-
passed: score >= 60,
|
|
10350
|
-
issues,
|
|
10351
|
-
preferredTermsUsed: prefUsed,
|
|
10352
|
-
bannedTermsFound: bannedFound
|
|
10353
|
-
};
|
|
10824
|
+
const checkResult = computeBrandConsistency(content, profile);
|
|
10354
10825
|
const envelope = asEnvelope20(checkResult);
|
|
10355
10826
|
return {
|
|
10356
10827
|
content: [{ type: "text", text: JSON.stringify(envelope, null, 2) }]
|
|
@@ -11202,56 +11673,6 @@ function createOAuthProvider(options) {
|
|
|
11202
11673
|
|
|
11203
11674
|
// src/http.ts
|
|
11204
11675
|
init_posthog();
|
|
11205
|
-
|
|
11206
|
-
// src/lib/sanitize-error.ts
|
|
11207
|
-
var ERROR_PATTERNS = [
|
|
11208
|
-
// Postgres / PostgREST
|
|
11209
|
-
[/PGRST301|permission denied/i, "Access denied. Check your account permissions."],
|
|
11210
|
-
[/42P01|does not exist/i, "Service temporarily unavailable. Please try again."],
|
|
11211
|
-
[/23505|unique.*constraint|duplicate key/i, "A duplicate record already exists."],
|
|
11212
|
-
[/23503|foreign key/i, "Referenced record not found."],
|
|
11213
|
-
// Gemini / Google AI
|
|
11214
|
-
[/google.*api.*key|googleapis\.com.*40[13]/i, "Content generation failed. Please try again."],
|
|
11215
|
-
[
|
|
11216
|
-
/RESOURCE_EXHAUSTED|quota.*exceeded|429.*google/i,
|
|
11217
|
-
"AI service rate limit reached. Please wait and retry."
|
|
11218
|
-
],
|
|
11219
|
-
[
|
|
11220
|
-
/SAFETY|prompt.*blocked|content.*filter/i,
|
|
11221
|
-
"Content was blocked by the AI safety filter. Try rephrasing."
|
|
11222
|
-
],
|
|
11223
|
-
[/gemini.*error|generativelanguage/i, "Content generation failed. Please try again."],
|
|
11224
|
-
// Kie.ai
|
|
11225
|
-
[/kie\.ai|kieai|kie_api/i, "Media generation failed. Please try again."],
|
|
11226
|
-
// Stripe
|
|
11227
|
-
[/stripe.*api|sk_live_|sk_test_/i, "Payment processing error. Please try again."],
|
|
11228
|
-
// Network / fetch
|
|
11229
|
-
[
|
|
11230
|
-
/ECONNREFUSED|ENOTFOUND|ETIMEDOUT|ECONNRESET/i,
|
|
11231
|
-
"External service unavailable. Please try again."
|
|
11232
|
-
],
|
|
11233
|
-
[/fetch failed|network error|abort.*timeout/i, "Network request failed. Please try again."],
|
|
11234
|
-
[/CERT_|certificate|SSL|TLS/i, "Secure connection failed. Please try again."],
|
|
11235
|
-
// Supabase Edge Function internals
|
|
11236
|
-
[/FunctionsHttpError|non-2xx status/i, "Backend service error. Please try again."],
|
|
11237
|
-
[/JWT|token.*expired|token.*invalid/i, "Authentication expired. Please re-authenticate."],
|
|
11238
|
-
// Generic sensitive patterns (API keys, URLs with secrets)
|
|
11239
|
-
[/[a-z0-9]{32,}.*key|Bearer [a-zA-Z0-9._-]+/i, "An internal error occurred. Please try again."]
|
|
11240
|
-
];
|
|
11241
|
-
function sanitizeError(error) {
|
|
11242
|
-
const msg = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
|
|
11243
|
-
if (process.env.NODE_ENV !== "production") {
|
|
11244
|
-
console.error("[Error]", msg);
|
|
11245
|
-
}
|
|
11246
|
-
for (const [pattern, userMessage] of ERROR_PATTERNS) {
|
|
11247
|
-
if (pattern.test(msg)) {
|
|
11248
|
-
return userMessage;
|
|
11249
|
-
}
|
|
11250
|
-
}
|
|
11251
|
-
return "An unexpected error occurred. Please try again.";
|
|
11252
|
-
}
|
|
11253
|
-
|
|
11254
|
-
// src/http.ts
|
|
11255
11676
|
var PORT = parseInt(process.env.PORT ?? "8080", 10);
|
|
11256
11677
|
var SUPABASE_URL2 = process.env.SUPABASE_URL ?? "";
|
|
11257
11678
|
var SUPABASE_ANON_KEY = process.env.SUPABASE_ANON_KEY ?? "";
|
|
@@ -11719,7 +12140,7 @@ app.post("/mcp", authenticateRequest, async (req, res) => {
|
|
|
11719
12140
|
const rawMessage = err instanceof Error ? err.message : "Internal server error";
|
|
11720
12141
|
console.error(`[MCP HTTP] POST /mcp error: ${rawMessage}`);
|
|
11721
12142
|
if (!res.headersSent) {
|
|
11722
|
-
res.status(500).json({ jsonrpc: "2.0", error: { code: -32603, message:
|
|
12143
|
+
res.status(500).json({ jsonrpc: "2.0", error: { code: -32603, message: sanitizeError2(err) } });
|
|
11723
12144
|
}
|
|
11724
12145
|
}
|
|
11725
12146
|
});
|
|
@@ -11761,7 +12182,7 @@ app.delete("/mcp", authenticateRequest, async (req, res) => {
|
|
|
11761
12182
|
app.use((err, _req, res, _next) => {
|
|
11762
12183
|
console.error("[MCP HTTP] Unhandled Express error:", err.stack || err.message || err);
|
|
11763
12184
|
if (!res.headersSent) {
|
|
11764
|
-
res.status(500).json({ error: "internal_error", error_description: err
|
|
12185
|
+
res.status(500).json({ error: "internal_error", error_description: sanitizeError2(err) });
|
|
11765
12186
|
}
|
|
11766
12187
|
});
|
|
11767
12188
|
var httpServer = app.listen(PORT, "0.0.0.0", () => {
|