@socialneuron/mcp-server 1.4.0 → 1.4.2
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 +1223 -524
- package/dist/index.js +1073 -440
- 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.4.
|
|
17
|
+
MCP_VERSION = "1.4.2";
|
|
18
18
|
}
|
|
19
19
|
});
|
|
20
20
|
|
|
@@ -883,7 +883,7 @@ var init_tool_catalog = __esm({
|
|
|
883
883
|
name: "check_status",
|
|
884
884
|
description: "Check status of async content generation job",
|
|
885
885
|
module: "content",
|
|
886
|
-
scope: "mcp:
|
|
886
|
+
scope: "mcp:read"
|
|
887
887
|
},
|
|
888
888
|
{
|
|
889
889
|
name: "create_storyboard",
|
|
@@ -3305,17 +3305,13 @@ async function runLoginDevice() {
|
|
|
3305
3305
|
if (pollResponse.status === 200) {
|
|
3306
3306
|
const pollData = await pollResponse.json();
|
|
3307
3307
|
if (pollData.api_key) {
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
console.error(` Key prefix: ${pollData.api_key.substring(0, 12)}...`);
|
|
3316
|
-
console.error("");
|
|
3317
|
-
return;
|
|
3318
|
-
}
|
|
3308
|
+
await saveApiKey(pollData.api_key);
|
|
3309
|
+
await saveSupabaseUrl(supabaseUrl);
|
|
3310
|
+
console.error("");
|
|
3311
|
+
console.error(" Authorized!");
|
|
3312
|
+
console.error(` Key prefix: ${pollData.api_key.substring(0, 12)}...`);
|
|
3313
|
+
console.error("");
|
|
3314
|
+
return;
|
|
3319
3315
|
}
|
|
3320
3316
|
}
|
|
3321
3317
|
if (pollResponse.status === 410) {
|
|
@@ -3746,7 +3742,7 @@ var TOOL_SCOPES = {
|
|
|
3746
3742
|
adapt_content: "mcp:write",
|
|
3747
3743
|
generate_video: "mcp:write",
|
|
3748
3744
|
generate_image: "mcp:write",
|
|
3749
|
-
check_status: "mcp:
|
|
3745
|
+
check_status: "mcp:read",
|
|
3750
3746
|
render_demo_video: "mcp:write",
|
|
3751
3747
|
save_brand_profile: "mcp:write",
|
|
3752
3748
|
update_platform_voice: "mcp:write",
|
|
@@ -3800,6 +3796,77 @@ function hasScope(userScopes, required) {
|
|
|
3800
3796
|
// src/tools/ideation.ts
|
|
3801
3797
|
init_edge_function();
|
|
3802
3798
|
import { z } from "zod";
|
|
3799
|
+
|
|
3800
|
+
// src/lib/rate-limit.ts
|
|
3801
|
+
var CATEGORY_CONFIGS = {
|
|
3802
|
+
posting: { maxTokens: 30, refillRate: 30 / 60 },
|
|
3803
|
+
// 30 req/min
|
|
3804
|
+
screenshot: { maxTokens: 10, refillRate: 10 / 60 },
|
|
3805
|
+
// 10 req/min
|
|
3806
|
+
read: { maxTokens: 60, refillRate: 60 / 60 }
|
|
3807
|
+
// 60 req/min
|
|
3808
|
+
};
|
|
3809
|
+
var RateLimiter = class {
|
|
3810
|
+
tokens;
|
|
3811
|
+
lastRefill;
|
|
3812
|
+
maxTokens;
|
|
3813
|
+
refillRate;
|
|
3814
|
+
// tokens per second
|
|
3815
|
+
constructor(config) {
|
|
3816
|
+
this.maxTokens = config.maxTokens;
|
|
3817
|
+
this.refillRate = config.refillRate;
|
|
3818
|
+
this.tokens = config.maxTokens;
|
|
3819
|
+
this.lastRefill = Date.now();
|
|
3820
|
+
}
|
|
3821
|
+
/**
|
|
3822
|
+
* Try to consume one token. Returns true if the request is allowed,
|
|
3823
|
+
* false if rate-limited.
|
|
3824
|
+
*/
|
|
3825
|
+
consume() {
|
|
3826
|
+
this.refill();
|
|
3827
|
+
if (this.tokens >= 1) {
|
|
3828
|
+
this.tokens -= 1;
|
|
3829
|
+
return true;
|
|
3830
|
+
}
|
|
3831
|
+
return false;
|
|
3832
|
+
}
|
|
3833
|
+
/**
|
|
3834
|
+
* Seconds until at least one token is available.
|
|
3835
|
+
*/
|
|
3836
|
+
retryAfter() {
|
|
3837
|
+
this.refill();
|
|
3838
|
+
if (this.tokens >= 1) return 0;
|
|
3839
|
+
return Math.ceil((1 - this.tokens) / this.refillRate);
|
|
3840
|
+
}
|
|
3841
|
+
refill() {
|
|
3842
|
+
const now = Date.now();
|
|
3843
|
+
const elapsed = (now - this.lastRefill) / 1e3;
|
|
3844
|
+
this.tokens = Math.min(this.maxTokens, this.tokens + elapsed * this.refillRate);
|
|
3845
|
+
this.lastRefill = now;
|
|
3846
|
+
}
|
|
3847
|
+
};
|
|
3848
|
+
var limiters = /* @__PURE__ */ new Map();
|
|
3849
|
+
function getRateLimiter(category) {
|
|
3850
|
+
let limiter = limiters.get(category);
|
|
3851
|
+
if (!limiter) {
|
|
3852
|
+
const config = CATEGORY_CONFIGS[category] ?? CATEGORY_CONFIGS.read;
|
|
3853
|
+
limiter = new RateLimiter(config);
|
|
3854
|
+
limiters.set(category, limiter);
|
|
3855
|
+
}
|
|
3856
|
+
return limiter;
|
|
3857
|
+
}
|
|
3858
|
+
function checkRateLimit(category, key) {
|
|
3859
|
+
const bucketKey = key ? `${category}:${key}` : category;
|
|
3860
|
+
const limiter = getRateLimiter(bucketKey);
|
|
3861
|
+
const allowed = limiter.consume();
|
|
3862
|
+
return {
|
|
3863
|
+
allowed,
|
|
3864
|
+
retryAfter: allowed ? 0 : limiter.retryAfter()
|
|
3865
|
+
};
|
|
3866
|
+
}
|
|
3867
|
+
|
|
3868
|
+
// src/tools/ideation.ts
|
|
3869
|
+
init_supabase();
|
|
3803
3870
|
function registerIdeationTools(server2) {
|
|
3804
3871
|
server2.tool(
|
|
3805
3872
|
"generate_content",
|
|
@@ -3820,7 +3887,9 @@ function registerIdeationTools(server2) {
|
|
|
3820
3887
|
"facebook",
|
|
3821
3888
|
"threads",
|
|
3822
3889
|
"bluesky"
|
|
3823
|
-
]).optional().describe(
|
|
3890
|
+
]).optional().describe(
|
|
3891
|
+
"Target social media platform. Helps tailor tone, length, and format."
|
|
3892
|
+
),
|
|
3824
3893
|
brand_voice: z.string().max(500).optional().describe(
|
|
3825
3894
|
'Brand voice guidelines to follow (e.g. "professional and empathetic", "playful and Gen-Z"). Leave blank to use a neutral tone.'
|
|
3826
3895
|
),
|
|
@@ -3831,7 +3900,30 @@ function registerIdeationTools(server2) {
|
|
|
3831
3900
|
"Project ID to auto-load brand profile and performance context for prompt enrichment."
|
|
3832
3901
|
)
|
|
3833
3902
|
},
|
|
3834
|
-
async ({
|
|
3903
|
+
async ({
|
|
3904
|
+
prompt: prompt2,
|
|
3905
|
+
content_type,
|
|
3906
|
+
platform: platform3,
|
|
3907
|
+
brand_voice,
|
|
3908
|
+
model,
|
|
3909
|
+
project_id
|
|
3910
|
+
}) => {
|
|
3911
|
+
try {
|
|
3912
|
+
const userId = await getDefaultUserId();
|
|
3913
|
+
const rl = checkRateLimit("posting", userId);
|
|
3914
|
+
if (!rl.allowed) {
|
|
3915
|
+
return {
|
|
3916
|
+
content: [
|
|
3917
|
+
{
|
|
3918
|
+
type: "text",
|
|
3919
|
+
text: `Rate limited. Retry after ${rl.retryAfter}s.`
|
|
3920
|
+
}
|
|
3921
|
+
],
|
|
3922
|
+
isError: true
|
|
3923
|
+
};
|
|
3924
|
+
}
|
|
3925
|
+
} catch {
|
|
3926
|
+
}
|
|
3835
3927
|
let enrichedPrompt = prompt2;
|
|
3836
3928
|
if (platform3) {
|
|
3837
3929
|
enrichedPrompt += `
|
|
@@ -3963,8 +4055,12 @@ Content Type: ${content_type}`;
|
|
|
3963
4055
|
category: z.string().optional().describe(
|
|
3964
4056
|
"Category filter (for YouTube). Examples: general, entertainment, education, tech, music, gaming, sports, news."
|
|
3965
4057
|
),
|
|
3966
|
-
niche: z.string().optional().describe(
|
|
3967
|
-
|
|
4058
|
+
niche: z.string().optional().describe(
|
|
4059
|
+
"Niche keyword filter. Only return trends matching these keywords."
|
|
4060
|
+
),
|
|
4061
|
+
url: z.string().optional().describe(
|
|
4062
|
+
'Required when source is "rss" or "url". The feed or page URL to fetch.'
|
|
4063
|
+
),
|
|
3968
4064
|
force_refresh: z.boolean().optional().describe("Skip the server-side cache and fetch fresh data.")
|
|
3969
4065
|
},
|
|
3970
4066
|
async ({ source, category, niche, url, force_refresh }) => {
|
|
@@ -4039,7 +4135,9 @@ Content Type: ${content_type}`;
|
|
|
4039
4135
|
"adapt_content",
|
|
4040
4136
|
"Adapt existing content for a different social media platform. Rewrites content to match the target platform's norms including character limits, hashtag style, tone, and CTA conventions.",
|
|
4041
4137
|
{
|
|
4042
|
-
content: z.string().max(5e3).describe(
|
|
4138
|
+
content: z.string().max(5e3).describe(
|
|
4139
|
+
"The content to adapt. Can be a caption, script, blog excerpt, or any text."
|
|
4140
|
+
),
|
|
4043
4141
|
source_platform: z.enum([
|
|
4044
4142
|
"youtube",
|
|
4045
4143
|
"tiktok",
|
|
@@ -4049,7 +4147,9 @@ Content Type: ${content_type}`;
|
|
|
4049
4147
|
"facebook",
|
|
4050
4148
|
"threads",
|
|
4051
4149
|
"bluesky"
|
|
4052
|
-
]).optional().describe(
|
|
4150
|
+
]).optional().describe(
|
|
4151
|
+
"The platform the content was originally written for. Helps preserve intent."
|
|
4152
|
+
),
|
|
4053
4153
|
target_platform: z.enum([
|
|
4054
4154
|
"youtube",
|
|
4055
4155
|
"tiktok",
|
|
@@ -4063,9 +4163,33 @@ Content Type: ${content_type}`;
|
|
|
4063
4163
|
brand_voice: z.string().max(500).optional().describe(
|
|
4064
4164
|
'Brand voice guidelines to maintain during adaptation (e.g. "professional", "playful").'
|
|
4065
4165
|
),
|
|
4066
|
-
project_id: z.string().uuid().optional().describe(
|
|
4166
|
+
project_id: z.string().uuid().optional().describe(
|
|
4167
|
+
"Optional project ID to load platform voice overrides from brand profile."
|
|
4168
|
+
)
|
|
4067
4169
|
},
|
|
4068
|
-
async ({
|
|
4170
|
+
async ({
|
|
4171
|
+
content,
|
|
4172
|
+
source_platform,
|
|
4173
|
+
target_platform,
|
|
4174
|
+
brand_voice,
|
|
4175
|
+
project_id
|
|
4176
|
+
}) => {
|
|
4177
|
+
try {
|
|
4178
|
+
const userId = await getDefaultUserId();
|
|
4179
|
+
const rl = checkRateLimit("posting", userId);
|
|
4180
|
+
if (!rl.allowed) {
|
|
4181
|
+
return {
|
|
4182
|
+
content: [
|
|
4183
|
+
{
|
|
4184
|
+
type: "text",
|
|
4185
|
+
text: `Rate limited. Retry after ${rl.retryAfter}s.`
|
|
4186
|
+
}
|
|
4187
|
+
],
|
|
4188
|
+
isError: true
|
|
4189
|
+
};
|
|
4190
|
+
}
|
|
4191
|
+
} catch {
|
|
4192
|
+
}
|
|
4069
4193
|
const platformGuidelines = {
|
|
4070
4194
|
twitter: "Max 280 characters. Concise, punchy. 1-3 hashtags max. Thread-friendly.",
|
|
4071
4195
|
threads: "Max 500 characters. Conversational, opinion-driven. Minimal hashtags.",
|
|
@@ -4148,76 +4272,6 @@ ${content}`,
|
|
|
4148
4272
|
// src/tools/content.ts
|
|
4149
4273
|
init_edge_function();
|
|
4150
4274
|
import { z as z2 } from "zod";
|
|
4151
|
-
|
|
4152
|
-
// src/lib/rate-limit.ts
|
|
4153
|
-
var CATEGORY_CONFIGS = {
|
|
4154
|
-
posting: { maxTokens: 30, refillRate: 30 / 60 },
|
|
4155
|
-
// 30 req/min
|
|
4156
|
-
screenshot: { maxTokens: 10, refillRate: 10 / 60 },
|
|
4157
|
-
// 10 req/min
|
|
4158
|
-
read: { maxTokens: 60, refillRate: 60 / 60 }
|
|
4159
|
-
// 60 req/min
|
|
4160
|
-
};
|
|
4161
|
-
var RateLimiter = class {
|
|
4162
|
-
tokens;
|
|
4163
|
-
lastRefill;
|
|
4164
|
-
maxTokens;
|
|
4165
|
-
refillRate;
|
|
4166
|
-
// tokens per second
|
|
4167
|
-
constructor(config) {
|
|
4168
|
-
this.maxTokens = config.maxTokens;
|
|
4169
|
-
this.refillRate = config.refillRate;
|
|
4170
|
-
this.tokens = config.maxTokens;
|
|
4171
|
-
this.lastRefill = Date.now();
|
|
4172
|
-
}
|
|
4173
|
-
/**
|
|
4174
|
-
* Try to consume one token. Returns true if the request is allowed,
|
|
4175
|
-
* false if rate-limited.
|
|
4176
|
-
*/
|
|
4177
|
-
consume() {
|
|
4178
|
-
this.refill();
|
|
4179
|
-
if (this.tokens >= 1) {
|
|
4180
|
-
this.tokens -= 1;
|
|
4181
|
-
return true;
|
|
4182
|
-
}
|
|
4183
|
-
return false;
|
|
4184
|
-
}
|
|
4185
|
-
/**
|
|
4186
|
-
* Seconds until at least one token is available.
|
|
4187
|
-
*/
|
|
4188
|
-
retryAfter() {
|
|
4189
|
-
this.refill();
|
|
4190
|
-
if (this.tokens >= 1) return 0;
|
|
4191
|
-
return Math.ceil((1 - this.tokens) / this.refillRate);
|
|
4192
|
-
}
|
|
4193
|
-
refill() {
|
|
4194
|
-
const now = Date.now();
|
|
4195
|
-
const elapsed = (now - this.lastRefill) / 1e3;
|
|
4196
|
-
this.tokens = Math.min(this.maxTokens, this.tokens + elapsed * this.refillRate);
|
|
4197
|
-
this.lastRefill = now;
|
|
4198
|
-
}
|
|
4199
|
-
};
|
|
4200
|
-
var limiters = /* @__PURE__ */ new Map();
|
|
4201
|
-
function getRateLimiter(category) {
|
|
4202
|
-
let limiter = limiters.get(category);
|
|
4203
|
-
if (!limiter) {
|
|
4204
|
-
const config = CATEGORY_CONFIGS[category] ?? CATEGORY_CONFIGS.read;
|
|
4205
|
-
limiter = new RateLimiter(config);
|
|
4206
|
-
limiters.set(category, limiter);
|
|
4207
|
-
}
|
|
4208
|
-
return limiter;
|
|
4209
|
-
}
|
|
4210
|
-
function checkRateLimit(category, key) {
|
|
4211
|
-
const bucketKey = key ? `${category}:${key}` : category;
|
|
4212
|
-
const limiter = getRateLimiter(bucketKey);
|
|
4213
|
-
const allowed = limiter.consume();
|
|
4214
|
-
return {
|
|
4215
|
-
allowed,
|
|
4216
|
-
retryAfter: allowed ? 0 : limiter.retryAfter()
|
|
4217
|
-
};
|
|
4218
|
-
}
|
|
4219
|
-
|
|
4220
|
-
// src/tools/content.ts
|
|
4221
4275
|
init_supabase();
|
|
4222
4276
|
|
|
4223
4277
|
// src/lib/sanitize-error.ts
|
|
@@ -4271,8 +4325,15 @@ function sanitizeDbError(error) {
|
|
|
4271
4325
|
|
|
4272
4326
|
// src/tools/content.ts
|
|
4273
4327
|
init_request_context();
|
|
4274
|
-
|
|
4275
|
-
var
|
|
4328
|
+
init_version();
|
|
4329
|
+
var MAX_CREDITS_PER_RUN = Math.max(
|
|
4330
|
+
0,
|
|
4331
|
+
Number(process.env.SOCIALNEURON_MAX_CREDITS_PER_RUN || 0)
|
|
4332
|
+
);
|
|
4333
|
+
var MAX_ASSETS_PER_RUN = Math.max(
|
|
4334
|
+
0,
|
|
4335
|
+
Number(process.env.SOCIALNEURON_MAX_ASSETS_PER_RUN || 0)
|
|
4336
|
+
);
|
|
4276
4337
|
var _globalCreditsUsed = 0;
|
|
4277
4338
|
var _globalAssetsGenerated = 0;
|
|
4278
4339
|
function getCreditsUsed() {
|
|
@@ -4314,7 +4375,7 @@ function getCurrentBudgetStatus() {
|
|
|
4314
4375
|
function asEnvelope(data) {
|
|
4315
4376
|
return {
|
|
4316
4377
|
_meta: {
|
|
4317
|
-
version:
|
|
4378
|
+
version: MCP_VERSION,
|
|
4318
4379
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4319
4380
|
},
|
|
4320
4381
|
data
|
|
@@ -4396,8 +4457,12 @@ function registerContentTools(server2) {
|
|
|
4396
4457
|
enable_audio: z2.boolean().optional().describe(
|
|
4397
4458
|
"Enable native audio generation. Kling 2.6: doubles cost. Kling 3.0: 50% more (std 30/sec, pro 40/sec). 5+ languages."
|
|
4398
4459
|
),
|
|
4399
|
-
image_url: z2.string().optional().describe(
|
|
4400
|
-
|
|
4460
|
+
image_url: z2.string().optional().describe(
|
|
4461
|
+
"Start frame image URL for image-to-video (Kling 3.0 frame control)."
|
|
4462
|
+
),
|
|
4463
|
+
end_frame_url: z2.string().optional().describe(
|
|
4464
|
+
"End frame image URL (Kling 3.0 only). Enables seamless loop transitions."
|
|
4465
|
+
),
|
|
4401
4466
|
response_format: z2.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
4402
4467
|
},
|
|
4403
4468
|
async ({
|
|
@@ -4835,10 +4900,13 @@ function registerContentTools(server2) {
|
|
|
4835
4900
|
};
|
|
4836
4901
|
}
|
|
4837
4902
|
if (job.external_id && (job.status === "pending" || job.status === "processing")) {
|
|
4838
|
-
const { data: liveStatus } = await callEdgeFunction(
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4903
|
+
const { data: liveStatus } = await callEdgeFunction(
|
|
4904
|
+
"kie-task-status",
|
|
4905
|
+
{
|
|
4906
|
+
taskId: job.external_id,
|
|
4907
|
+
model: job.model
|
|
4908
|
+
}
|
|
4909
|
+
);
|
|
4842
4910
|
if (liveStatus) {
|
|
4843
4911
|
const lines2 = [
|
|
4844
4912
|
`Job: ${job.id}`,
|
|
@@ -4912,7 +4980,12 @@ function registerContentTools(server2) {
|
|
|
4912
4980
|
});
|
|
4913
4981
|
if (format === "json") {
|
|
4914
4982
|
return {
|
|
4915
|
-
content: [
|
|
4983
|
+
content: [
|
|
4984
|
+
{
|
|
4985
|
+
type: "text",
|
|
4986
|
+
text: JSON.stringify(asEnvelope(job), null, 2)
|
|
4987
|
+
}
|
|
4988
|
+
]
|
|
4916
4989
|
};
|
|
4917
4990
|
}
|
|
4918
4991
|
return {
|
|
@@ -4930,7 +5003,15 @@ function registerContentTools(server2) {
|
|
|
4930
5003
|
brand_context: z2.string().max(3e3).optional().describe(
|
|
4931
5004
|
"Brand context JSON from extract_brand. Include colors, voice tone, visual style keywords for consistent branding across frames."
|
|
4932
5005
|
),
|
|
4933
|
-
platform: z2.enum([
|
|
5006
|
+
platform: z2.enum([
|
|
5007
|
+
"tiktok",
|
|
5008
|
+
"instagram-reels",
|
|
5009
|
+
"youtube-shorts",
|
|
5010
|
+
"youtube",
|
|
5011
|
+
"general"
|
|
5012
|
+
]).describe(
|
|
5013
|
+
"Target platform. Determines aspect ratio, duration, and pacing."
|
|
5014
|
+
),
|
|
4934
5015
|
target_duration: z2.number().min(5).max(120).optional().describe(
|
|
4935
5016
|
"Target total duration in seconds. Defaults to 30s for short-form, 60s for YouTube."
|
|
4936
5017
|
),
|
|
@@ -4938,7 +5019,9 @@ function registerContentTools(server2) {
|
|
|
4938
5019
|
style: z2.string().optional().describe(
|
|
4939
5020
|
'Visual style direction (e.g., "cinematic", "anime", "documentary", "motion graphics").'
|
|
4940
5021
|
),
|
|
4941
|
-
response_format: z2.enum(["text", "json"]).optional().describe(
|
|
5022
|
+
response_format: z2.enum(["text", "json"]).optional().describe(
|
|
5023
|
+
"Response format. Defaults to json for structured storyboard data."
|
|
5024
|
+
)
|
|
4942
5025
|
},
|
|
4943
5026
|
async ({
|
|
4944
5027
|
concept,
|
|
@@ -4951,7 +5034,11 @@ function registerContentTools(server2) {
|
|
|
4951
5034
|
}) => {
|
|
4952
5035
|
const format = response_format ?? "json";
|
|
4953
5036
|
const startedAt = Date.now();
|
|
4954
|
-
const isShortForm = [
|
|
5037
|
+
const isShortForm = [
|
|
5038
|
+
"tiktok",
|
|
5039
|
+
"instagram-reels",
|
|
5040
|
+
"youtube-shorts"
|
|
5041
|
+
].includes(platform3);
|
|
4955
5042
|
const duration = target_duration ?? (isShortForm ? 30 : 60);
|
|
4956
5043
|
const scenes = num_scenes ?? (isShortForm ? 7 : 10);
|
|
4957
5044
|
const aspectRatio = isShortForm ? "9:16" : "16:9";
|
|
@@ -5044,7 +5131,12 @@ Return ONLY valid JSON in this exact format:
|
|
|
5044
5131
|
details: { error }
|
|
5045
5132
|
});
|
|
5046
5133
|
return {
|
|
5047
|
-
content: [
|
|
5134
|
+
content: [
|
|
5135
|
+
{
|
|
5136
|
+
type: "text",
|
|
5137
|
+
text: `Storyboard generation failed: ${error}`
|
|
5138
|
+
}
|
|
5139
|
+
],
|
|
5048
5140
|
isError: true
|
|
5049
5141
|
};
|
|
5050
5142
|
}
|
|
@@ -5060,7 +5152,12 @@ Return ONLY valid JSON in this exact format:
|
|
|
5060
5152
|
try {
|
|
5061
5153
|
const parsed = JSON.parse(rawContent);
|
|
5062
5154
|
return {
|
|
5063
|
-
content: [
|
|
5155
|
+
content: [
|
|
5156
|
+
{
|
|
5157
|
+
type: "text",
|
|
5158
|
+
text: JSON.stringify(asEnvelope(parsed), null, 2)
|
|
5159
|
+
}
|
|
5160
|
+
]
|
|
5064
5161
|
};
|
|
5065
5162
|
} catch {
|
|
5066
5163
|
return {
|
|
@@ -5124,7 +5221,10 @@ Return ONLY valid JSON in this exact format:
|
|
|
5124
5221
|
isError: true
|
|
5125
5222
|
};
|
|
5126
5223
|
}
|
|
5127
|
-
const rateLimit = checkRateLimit(
|
|
5224
|
+
const rateLimit = checkRateLimit(
|
|
5225
|
+
"posting",
|
|
5226
|
+
`generate_voiceover:${userId}`
|
|
5227
|
+
);
|
|
5128
5228
|
if (!rateLimit.allowed) {
|
|
5129
5229
|
await logMcpToolInvocation({
|
|
5130
5230
|
toolName: "generate_voiceover",
|
|
@@ -5159,7 +5259,12 @@ Return ONLY valid JSON in this exact format:
|
|
|
5159
5259
|
details: { error }
|
|
5160
5260
|
});
|
|
5161
5261
|
return {
|
|
5162
|
-
content: [
|
|
5262
|
+
content: [
|
|
5263
|
+
{
|
|
5264
|
+
type: "text",
|
|
5265
|
+
text: `Voiceover generation failed: ${error}`
|
|
5266
|
+
}
|
|
5267
|
+
],
|
|
5163
5268
|
isError: true
|
|
5164
5269
|
};
|
|
5165
5270
|
}
|
|
@@ -5172,7 +5277,10 @@ Return ONLY valid JSON in this exact format:
|
|
|
5172
5277
|
});
|
|
5173
5278
|
return {
|
|
5174
5279
|
content: [
|
|
5175
|
-
{
|
|
5280
|
+
{
|
|
5281
|
+
type: "text",
|
|
5282
|
+
text: "Voiceover generation failed: no audio URL returned."
|
|
5283
|
+
}
|
|
5176
5284
|
],
|
|
5177
5285
|
isError: true
|
|
5178
5286
|
};
|
|
@@ -5244,7 +5352,9 @@ Return ONLY valid JSON in this exact format:
|
|
|
5244
5352
|
"Carousel template. hormozi-authority: bold typography, one idea per slide, dark backgrounds. educational-series: numbered tips. Default: hormozi-authority."
|
|
5245
5353
|
),
|
|
5246
5354
|
slide_count: z2.number().min(3).max(10).optional().describe("Number of slides (3-10). Default: 7."),
|
|
5247
|
-
aspect_ratio: z2.enum(["1:1", "4:5", "9:16"]).optional().describe(
|
|
5355
|
+
aspect_ratio: z2.enum(["1:1", "4:5", "9:16"]).optional().describe(
|
|
5356
|
+
"Aspect ratio. 1:1 square (default), 4:5 portrait, 9:16 story."
|
|
5357
|
+
),
|
|
5248
5358
|
style: z2.enum(["minimal", "bold", "professional", "playful", "hormozi"]).optional().describe(
|
|
5249
5359
|
"Visual style. hormozi: black bg, bold white text, gold accents. Default: hormozi (when using hormozi-authority template)."
|
|
5250
5360
|
),
|
|
@@ -5281,7 +5391,10 @@ Return ONLY valid JSON in this exact format:
|
|
|
5281
5391
|
};
|
|
5282
5392
|
}
|
|
5283
5393
|
const userId = await getDefaultUserId();
|
|
5284
|
-
const rateLimit = checkRateLimit(
|
|
5394
|
+
const rateLimit = checkRateLimit(
|
|
5395
|
+
"posting",
|
|
5396
|
+
`generate_carousel:${userId}`
|
|
5397
|
+
);
|
|
5285
5398
|
if (!rateLimit.allowed) {
|
|
5286
5399
|
await logMcpToolInvocation({
|
|
5287
5400
|
toolName: "generate_carousel",
|
|
@@ -5319,7 +5432,12 @@ Return ONLY valid JSON in this exact format:
|
|
|
5319
5432
|
details: { error }
|
|
5320
5433
|
});
|
|
5321
5434
|
return {
|
|
5322
|
-
content: [
|
|
5435
|
+
content: [
|
|
5436
|
+
{
|
|
5437
|
+
type: "text",
|
|
5438
|
+
text: `Carousel generation failed: ${error}`
|
|
5439
|
+
}
|
|
5440
|
+
],
|
|
5323
5441
|
isError: true
|
|
5324
5442
|
};
|
|
5325
5443
|
}
|
|
@@ -5331,7 +5449,12 @@ Return ONLY valid JSON in this exact format:
|
|
|
5331
5449
|
details: { error: "No carousel data returned" }
|
|
5332
5450
|
});
|
|
5333
5451
|
return {
|
|
5334
|
-
content: [
|
|
5452
|
+
content: [
|
|
5453
|
+
{
|
|
5454
|
+
type: "text",
|
|
5455
|
+
text: "Carousel generation returned no data."
|
|
5456
|
+
}
|
|
5457
|
+
],
|
|
5335
5458
|
isError: true
|
|
5336
5459
|
};
|
|
5337
5460
|
}
|
|
@@ -5397,6 +5520,7 @@ import { z as z3 } from "zod";
|
|
|
5397
5520
|
import { createHash as createHash2 } from "node:crypto";
|
|
5398
5521
|
init_supabase();
|
|
5399
5522
|
init_quality();
|
|
5523
|
+
init_version();
|
|
5400
5524
|
var PLATFORM_CASE_MAP = {
|
|
5401
5525
|
youtube: "YouTube",
|
|
5402
5526
|
tiktok: "TikTok",
|
|
@@ -5410,7 +5534,7 @@ var PLATFORM_CASE_MAP = {
|
|
|
5410
5534
|
function asEnvelope2(data) {
|
|
5411
5535
|
return {
|
|
5412
5536
|
_meta: {
|
|
5413
|
-
version:
|
|
5537
|
+
version: MCP_VERSION,
|
|
5414
5538
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
5415
5539
|
},
|
|
5416
5540
|
data
|
|
@@ -5442,15 +5566,21 @@ function registerDistributionTools(server2) {
|
|
|
5442
5566
|
"threads",
|
|
5443
5567
|
"bluesky"
|
|
5444
5568
|
])
|
|
5445
|
-
).min(1).describe(
|
|
5569
|
+
).min(1).describe(
|
|
5570
|
+
"Target platforms to post to. Each must have an active OAuth connection."
|
|
5571
|
+
),
|
|
5446
5572
|
title: z3.string().optional().describe("Post title (used by YouTube and some other platforms)."),
|
|
5447
|
-
hashtags: z3.array(z3.string()).optional().describe(
|
|
5573
|
+
hashtags: z3.array(z3.string()).optional().describe(
|
|
5574
|
+
'Hashtags to append to the caption. Include or omit the "#" prefix.'
|
|
5575
|
+
),
|
|
5448
5576
|
schedule_at: z3.string().optional().describe(
|
|
5449
5577
|
'ISO 8601 datetime for scheduled posting (e.g. "2026-03-15T14:00:00Z"). Omit for immediate posting.'
|
|
5450
5578
|
),
|
|
5451
5579
|
project_id: z3.string().optional().describe("Social Neuron project ID to associate this post with."),
|
|
5452
5580
|
response_format: z3.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text."),
|
|
5453
|
-
attribution: z3.boolean().optional().describe(
|
|
5581
|
+
attribution: z3.boolean().optional().describe(
|
|
5582
|
+
'If true, appends "Created with Social Neuron" to the caption. Default: false.'
|
|
5583
|
+
)
|
|
5454
5584
|
},
|
|
5455
5585
|
async ({
|
|
5456
5586
|
media_url,
|
|
@@ -5469,7 +5599,12 @@ function registerDistributionTools(server2) {
|
|
|
5469
5599
|
const startedAt = Date.now();
|
|
5470
5600
|
if ((!caption || caption.trim().length === 0) && (!title || title.trim().length === 0)) {
|
|
5471
5601
|
return {
|
|
5472
|
-
content: [
|
|
5602
|
+
content: [
|
|
5603
|
+
{
|
|
5604
|
+
type: "text",
|
|
5605
|
+
text: "Either caption or title is required."
|
|
5606
|
+
}
|
|
5607
|
+
],
|
|
5473
5608
|
isError: true
|
|
5474
5609
|
};
|
|
5475
5610
|
}
|
|
@@ -5492,7 +5627,9 @@ function registerDistributionTools(server2) {
|
|
|
5492
5627
|
isError: true
|
|
5493
5628
|
};
|
|
5494
5629
|
}
|
|
5495
|
-
const normalizedPlatforms = platforms.map(
|
|
5630
|
+
const normalizedPlatforms = platforms.map(
|
|
5631
|
+
(p) => PLATFORM_CASE_MAP[p.toLowerCase()] || p
|
|
5632
|
+
);
|
|
5496
5633
|
let finalCaption = caption;
|
|
5497
5634
|
if (attribution && finalCaption) {
|
|
5498
5635
|
finalCaption = `${finalCaption}
|
|
@@ -5556,7 +5693,9 @@ Created with Social Neuron`;
|
|
|
5556
5693
|
];
|
|
5557
5694
|
for (const [platform3, result] of Object.entries(data.results)) {
|
|
5558
5695
|
if (result.success) {
|
|
5559
|
-
lines.push(
|
|
5696
|
+
lines.push(
|
|
5697
|
+
` ${platform3}: OK (jobId=${result.jobId}, postId=${result.postId})`
|
|
5698
|
+
);
|
|
5560
5699
|
} else {
|
|
5561
5700
|
lines.push(` ${platform3}: FAILED - ${result.error}`);
|
|
5562
5701
|
}
|
|
@@ -5573,10 +5712,15 @@ Created with Social Neuron`;
|
|
|
5573
5712
|
});
|
|
5574
5713
|
if (format === "json") {
|
|
5575
5714
|
return {
|
|
5576
|
-
content: [
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5715
|
+
content: [
|
|
5716
|
+
{
|
|
5717
|
+
type: "text",
|
|
5718
|
+
text: JSON.stringify(asEnvelope2(data), null, 2)
|
|
5719
|
+
}
|
|
5720
|
+
],
|
|
5721
|
+
isError: !data.success
|
|
5722
|
+
};
|
|
5723
|
+
}
|
|
5580
5724
|
return {
|
|
5581
5725
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
5582
5726
|
isError: !data.success
|
|
@@ -5629,12 +5773,17 @@ Created with Social Neuron`;
|
|
|
5629
5773
|
for (const account of accounts) {
|
|
5630
5774
|
const name = account.username || "(unnamed)";
|
|
5631
5775
|
const platformLower = account.platform.toLowerCase();
|
|
5632
|
-
lines.push(
|
|
5776
|
+
lines.push(
|
|
5777
|
+
` ${platformLower}: ${name} (connected ${account.created_at.split("T")[0]})`
|
|
5778
|
+
);
|
|
5633
5779
|
}
|
|
5634
5780
|
if (format === "json") {
|
|
5635
5781
|
return {
|
|
5636
5782
|
content: [
|
|
5637
|
-
{
|
|
5783
|
+
{
|
|
5784
|
+
type: "text",
|
|
5785
|
+
text: JSON.stringify(asEnvelope2({ accounts }), null, 2)
|
|
5786
|
+
}
|
|
5638
5787
|
]
|
|
5639
5788
|
};
|
|
5640
5789
|
}
|
|
@@ -5696,7 +5845,10 @@ Created with Social Neuron`;
|
|
|
5696
5845
|
if (format === "json") {
|
|
5697
5846
|
return {
|
|
5698
5847
|
content: [
|
|
5699
|
-
{
|
|
5848
|
+
{
|
|
5849
|
+
type: "text",
|
|
5850
|
+
text: JSON.stringify(asEnvelope2({ posts: [] }), null, 2)
|
|
5851
|
+
}
|
|
5700
5852
|
]
|
|
5701
5853
|
};
|
|
5702
5854
|
}
|
|
@@ -5713,7 +5865,10 @@ Created with Social Neuron`;
|
|
|
5713
5865
|
if (format === "json") {
|
|
5714
5866
|
return {
|
|
5715
5867
|
content: [
|
|
5716
|
-
{
|
|
5868
|
+
{
|
|
5869
|
+
type: "text",
|
|
5870
|
+
text: JSON.stringify(asEnvelope2({ posts }), null, 2)
|
|
5871
|
+
}
|
|
5717
5872
|
]
|
|
5718
5873
|
};
|
|
5719
5874
|
}
|
|
@@ -5774,7 +5929,13 @@ Created with Social Neuron`;
|
|
|
5774
5929
|
min_gap_hours: z3.number().min(1).max(24).default(4).describe("Minimum gap between posts on same platform"),
|
|
5775
5930
|
response_format: z3.enum(["text", "json"]).default("text")
|
|
5776
5931
|
},
|
|
5777
|
-
async ({
|
|
5932
|
+
async ({
|
|
5933
|
+
platforms,
|
|
5934
|
+
count,
|
|
5935
|
+
start_after,
|
|
5936
|
+
min_gap_hours,
|
|
5937
|
+
response_format
|
|
5938
|
+
}) => {
|
|
5778
5939
|
const startedAt = Date.now();
|
|
5779
5940
|
try {
|
|
5780
5941
|
const userId = await getDefaultUserId();
|
|
@@ -5785,7 +5946,9 @@ Created with Social Neuron`;
|
|
|
5785
5946
|
const gapMs = min_gap_hours * 60 * 60 * 1e3;
|
|
5786
5947
|
const candidates = [];
|
|
5787
5948
|
for (let dayOffset = 0; dayOffset < 7; dayOffset++) {
|
|
5788
|
-
const date = new Date(
|
|
5949
|
+
const date = new Date(
|
|
5950
|
+
startDate.getTime() + dayOffset * 24 * 60 * 60 * 1e3
|
|
5951
|
+
);
|
|
5789
5952
|
const dayOfWeek = date.getUTCDay();
|
|
5790
5953
|
for (const platform3 of platforms) {
|
|
5791
5954
|
const hours = PREFERRED_HOURS[platform3] ?? [12, 16];
|
|
@@ -5794,8 +5957,11 @@ Created with Social Neuron`;
|
|
|
5794
5957
|
slotDate.setUTCHours(hours[hourIdx], 0, 0, 0);
|
|
5795
5958
|
if (slotDate <= startDate) continue;
|
|
5796
5959
|
const hasConflict = (existingPosts ?? []).some((post) => {
|
|
5797
|
-
if (String(post.platform).toLowerCase() !== platform3)
|
|
5798
|
-
|
|
5960
|
+
if (String(post.platform).toLowerCase() !== platform3)
|
|
5961
|
+
return false;
|
|
5962
|
+
const postTime = new Date(
|
|
5963
|
+
post.scheduled_at ?? post.published_at
|
|
5964
|
+
).getTime();
|
|
5799
5965
|
return Math.abs(postTime - slotDate.getTime()) < gapMs;
|
|
5800
5966
|
});
|
|
5801
5967
|
let engagementScore = hours.length - hourIdx;
|
|
@@ -5840,15 +6006,22 @@ Created with Social Neuron`;
|
|
|
5840
6006
|
};
|
|
5841
6007
|
}
|
|
5842
6008
|
const lines = [];
|
|
5843
|
-
lines.push(
|
|
6009
|
+
lines.push(
|
|
6010
|
+
`Found ${slots.length} optimal slots (${conflictsAvoided} conflicts avoided):`
|
|
6011
|
+
);
|
|
5844
6012
|
lines.push("");
|
|
5845
6013
|
lines.push("Datetime (UTC) | Platform | Score");
|
|
5846
6014
|
lines.push("-------------------------+------------+------");
|
|
5847
6015
|
for (const s of slots) {
|
|
5848
6016
|
const dt = s.datetime.replace("T", " ").slice(0, 19);
|
|
5849
|
-
lines.push(
|
|
6017
|
+
lines.push(
|
|
6018
|
+
`${dt.padEnd(25)}| ${s.platform.padEnd(11)}| ${s.engagement_score}`
|
|
6019
|
+
);
|
|
5850
6020
|
}
|
|
5851
|
-
return {
|
|
6021
|
+
return {
|
|
6022
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
6023
|
+
isError: false
|
|
6024
|
+
};
|
|
5852
6025
|
} catch (err) {
|
|
5853
6026
|
const durationMs = Date.now() - startedAt;
|
|
5854
6027
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -5859,7 +6032,9 @@ Created with Social Neuron`;
|
|
|
5859
6032
|
details: { error: message }
|
|
5860
6033
|
});
|
|
5861
6034
|
return {
|
|
5862
|
-
content: [
|
|
6035
|
+
content: [
|
|
6036
|
+
{ type: "text", text: `Failed to find slots: ${message}` }
|
|
6037
|
+
],
|
|
5863
6038
|
isError: true
|
|
5864
6039
|
};
|
|
5865
6040
|
}
|
|
@@ -5886,8 +6061,12 @@ Created with Social Neuron`;
|
|
|
5886
6061
|
auto_slot: z3.boolean().default(true).describe("Auto-assign time slots for posts without schedule_at"),
|
|
5887
6062
|
dry_run: z3.boolean().default(false).describe("Preview without actually scheduling"),
|
|
5888
6063
|
response_format: z3.enum(["text", "json"]).default("text"),
|
|
5889
|
-
enforce_quality: z3.boolean().default(true).describe(
|
|
5890
|
-
|
|
6064
|
+
enforce_quality: z3.boolean().default(true).describe(
|
|
6065
|
+
"When true, block scheduling for posts that fail quality checks."
|
|
6066
|
+
),
|
|
6067
|
+
quality_threshold: z3.number().int().min(0).max(35).optional().describe(
|
|
6068
|
+
"Optional quality threshold override. Defaults to project setting or 26."
|
|
6069
|
+
),
|
|
5891
6070
|
batch_size: z3.number().int().min(1).max(10).default(4).describe("Concurrent schedule calls per platform batch."),
|
|
5892
6071
|
idempotency_seed: z3.string().max(128).optional().describe("Optional stable seed used when building idempotency keys.")
|
|
5893
6072
|
},
|
|
@@ -5926,17 +6105,25 @@ Created with Social Neuron`;
|
|
|
5926
6105
|
if (!stored?.plan_payload) {
|
|
5927
6106
|
return {
|
|
5928
6107
|
content: [
|
|
5929
|
-
{
|
|
6108
|
+
{
|
|
6109
|
+
type: "text",
|
|
6110
|
+
text: `No content plan found for plan_id=${plan_id}`
|
|
6111
|
+
}
|
|
5930
6112
|
],
|
|
5931
6113
|
isError: true
|
|
5932
6114
|
};
|
|
5933
6115
|
}
|
|
5934
6116
|
const payload = stored.plan_payload;
|
|
5935
|
-
const postsFromPayload = Array.isArray(payload.posts) ? payload.posts : Array.isArray(
|
|
6117
|
+
const postsFromPayload = Array.isArray(payload.posts) ? payload.posts : Array.isArray(
|
|
6118
|
+
payload.data?.posts
|
|
6119
|
+
) ? payload.data.posts : null;
|
|
5936
6120
|
if (!postsFromPayload) {
|
|
5937
6121
|
return {
|
|
5938
6122
|
content: [
|
|
5939
|
-
{
|
|
6123
|
+
{
|
|
6124
|
+
type: "text",
|
|
6125
|
+
text: `Stored plan ${plan_id} has no posts array.`
|
|
6126
|
+
}
|
|
5940
6127
|
],
|
|
5941
6128
|
isError: true
|
|
5942
6129
|
};
|
|
@@ -6028,7 +6215,10 @@ Created with Social Neuron`;
|
|
|
6028
6215
|
approvalSummary = {
|
|
6029
6216
|
total: approvals.length,
|
|
6030
6217
|
eligible: approvedPosts.length,
|
|
6031
|
-
skipped: Math.max(
|
|
6218
|
+
skipped: Math.max(
|
|
6219
|
+
0,
|
|
6220
|
+
workingPlan.posts.length - approvedPosts.length
|
|
6221
|
+
)
|
|
6032
6222
|
};
|
|
6033
6223
|
workingPlan = {
|
|
6034
6224
|
...workingPlan,
|
|
@@ -6062,9 +6252,14 @@ Created with Social Neuron`;
|
|
|
6062
6252
|
try {
|
|
6063
6253
|
const { data: settingsData } = await supabase.from("system_settings").select("value").eq("key", "content_safety").maybeSingle();
|
|
6064
6254
|
if (settingsData?.value?.quality_threshold !== void 0) {
|
|
6065
|
-
const parsedThreshold = Number(
|
|
6255
|
+
const parsedThreshold = Number(
|
|
6256
|
+
settingsData.value.quality_threshold
|
|
6257
|
+
);
|
|
6066
6258
|
if (Number.isFinite(parsedThreshold)) {
|
|
6067
|
-
effectiveQualityThreshold = Math.max(
|
|
6259
|
+
effectiveQualityThreshold = Math.max(
|
|
6260
|
+
0,
|
|
6261
|
+
Math.min(35, Math.trunc(parsedThreshold))
|
|
6262
|
+
);
|
|
6068
6263
|
}
|
|
6069
6264
|
}
|
|
6070
6265
|
if (Array.isArray(settingsData?.value?.custom_banned_terms)) {
|
|
@@ -6100,13 +6295,18 @@ Created with Social Neuron`;
|
|
|
6100
6295
|
}
|
|
6101
6296
|
};
|
|
6102
6297
|
});
|
|
6103
|
-
const qualityPassed = postsWithResults.filter(
|
|
6298
|
+
const qualityPassed = postsWithResults.filter(
|
|
6299
|
+
(post) => post.quality.passed
|
|
6300
|
+
).length;
|
|
6104
6301
|
const qualitySummary = {
|
|
6105
6302
|
total_posts: postsWithResults.length,
|
|
6106
6303
|
passed: qualityPassed,
|
|
6107
6304
|
failed: postsWithResults.length - qualityPassed,
|
|
6108
6305
|
avg_score: postsWithResults.length > 0 ? Number(
|
|
6109
|
-
(postsWithResults.reduce(
|
|
6306
|
+
(postsWithResults.reduce(
|
|
6307
|
+
(sum, post) => sum + post.quality.score,
|
|
6308
|
+
0
|
|
6309
|
+
) / postsWithResults.length).toFixed(2)
|
|
6110
6310
|
) : 0
|
|
6111
6311
|
};
|
|
6112
6312
|
if (dry_run) {
|
|
@@ -6176,8 +6376,13 @@ Created with Social Neuron`;
|
|
|
6176
6376
|
}
|
|
6177
6377
|
}
|
|
6178
6378
|
lines2.push("");
|
|
6179
|
-
lines2.push(
|
|
6180
|
-
|
|
6379
|
+
lines2.push(
|
|
6380
|
+
`Summary: ${passed}/${workingPlan.posts.length} passed quality check`
|
|
6381
|
+
);
|
|
6382
|
+
return {
|
|
6383
|
+
content: [{ type: "text", text: lines2.join("\n") }],
|
|
6384
|
+
isError: false
|
|
6385
|
+
};
|
|
6181
6386
|
}
|
|
6182
6387
|
let scheduled = 0;
|
|
6183
6388
|
let failed = 0;
|
|
@@ -6269,7 +6474,8 @@ Created with Social Neuron`;
|
|
|
6269
6474
|
}
|
|
6270
6475
|
const chunk = (arr, size) => {
|
|
6271
6476
|
const out = [];
|
|
6272
|
-
for (let i = 0; i < arr.length; i += size)
|
|
6477
|
+
for (let i = 0; i < arr.length; i += size)
|
|
6478
|
+
out.push(arr.slice(i, i + size));
|
|
6273
6479
|
return out;
|
|
6274
6480
|
};
|
|
6275
6481
|
const platformBatches = Array.from(grouped.entries()).map(
|
|
@@ -6277,7 +6483,9 @@ Created with Social Neuron`;
|
|
|
6277
6483
|
const platformResults = [];
|
|
6278
6484
|
const batches = chunk(platformPosts, batch_size);
|
|
6279
6485
|
for (const batch of batches) {
|
|
6280
|
-
const settled = await Promise.allSettled(
|
|
6486
|
+
const settled = await Promise.allSettled(
|
|
6487
|
+
batch.map((post) => scheduleOne(post))
|
|
6488
|
+
);
|
|
6281
6489
|
for (const outcome of settled) {
|
|
6282
6490
|
if (outcome.status === "fulfilled") {
|
|
6283
6491
|
platformResults.push(outcome.value);
|
|
@@ -6343,7 +6551,11 @@ Created with Social Neuron`;
|
|
|
6343
6551
|
plan_id: effectivePlanId,
|
|
6344
6552
|
approvals: approvalSummary,
|
|
6345
6553
|
posts: results,
|
|
6346
|
-
summary: {
|
|
6554
|
+
summary: {
|
|
6555
|
+
total_posts: workingPlan.posts.length,
|
|
6556
|
+
scheduled,
|
|
6557
|
+
failed
|
|
6558
|
+
}
|
|
6347
6559
|
}),
|
|
6348
6560
|
null,
|
|
6349
6561
|
2
|
|
@@ -6381,7 +6593,12 @@ Created with Social Neuron`;
|
|
|
6381
6593
|
details: { error: message }
|
|
6382
6594
|
});
|
|
6383
6595
|
return {
|
|
6384
|
-
content: [
|
|
6596
|
+
content: [
|
|
6597
|
+
{
|
|
6598
|
+
type: "text",
|
|
6599
|
+
text: `Batch scheduling failed: ${message}`
|
|
6600
|
+
}
|
|
6601
|
+
],
|
|
6385
6602
|
isError: true
|
|
6386
6603
|
};
|
|
6387
6604
|
}
|
|
@@ -6393,10 +6610,11 @@ Created with Social Neuron`;
|
|
|
6393
6610
|
init_supabase();
|
|
6394
6611
|
init_edge_function();
|
|
6395
6612
|
import { z as z4 } from "zod";
|
|
6613
|
+
init_version();
|
|
6396
6614
|
function asEnvelope3(data) {
|
|
6397
6615
|
return {
|
|
6398
6616
|
_meta: {
|
|
6399
|
-
version:
|
|
6617
|
+
version: MCP_VERSION,
|
|
6400
6618
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6401
6619
|
},
|
|
6402
6620
|
data
|
|
@@ -6509,7 +6727,9 @@ function registerAnalyticsTools(server2) {
|
|
|
6509
6727
|
]
|
|
6510
6728
|
};
|
|
6511
6729
|
}
|
|
6512
|
-
const { data: simpleRows, error: simpleError } = await supabase.from("post_analytics").select(
|
|
6730
|
+
const { data: simpleRows, error: simpleError } = await supabase.from("post_analytics").select(
|
|
6731
|
+
"id, post_id, platform, views, likes, comments, shares, captured_at"
|
|
6732
|
+
).in("post_id", postIds).gte("captured_at", sinceIso).order("captured_at", { ascending: false }).limit(maxPosts);
|
|
6513
6733
|
if (simpleError) {
|
|
6514
6734
|
return {
|
|
6515
6735
|
content: [
|
|
@@ -6521,7 +6741,12 @@ function registerAnalyticsTools(server2) {
|
|
|
6521
6741
|
isError: true
|
|
6522
6742
|
};
|
|
6523
6743
|
}
|
|
6524
|
-
return formatSimpleAnalytics(
|
|
6744
|
+
return formatSimpleAnalytics(
|
|
6745
|
+
simpleRows,
|
|
6746
|
+
platform3,
|
|
6747
|
+
lookbackDays,
|
|
6748
|
+
format
|
|
6749
|
+
);
|
|
6525
6750
|
}
|
|
6526
6751
|
if (!rows || rows.length === 0) {
|
|
6527
6752
|
if (format === "json") {
|
|
@@ -6598,7 +6823,10 @@ function registerAnalyticsTools(server2) {
|
|
|
6598
6823
|
const format = response_format ?? "text";
|
|
6599
6824
|
const startedAt = Date.now();
|
|
6600
6825
|
const userId = await getDefaultUserId();
|
|
6601
|
-
const rateLimit = checkRateLimit(
|
|
6826
|
+
const rateLimit = checkRateLimit(
|
|
6827
|
+
"posting",
|
|
6828
|
+
`refresh_platform_analytics:${userId}`
|
|
6829
|
+
);
|
|
6602
6830
|
if (!rateLimit.allowed) {
|
|
6603
6831
|
await logMcpToolInvocation({
|
|
6604
6832
|
toolName: "refresh_platform_analytics",
|
|
@@ -6616,7 +6844,9 @@ function registerAnalyticsTools(server2) {
|
|
|
6616
6844
|
isError: true
|
|
6617
6845
|
};
|
|
6618
6846
|
}
|
|
6619
|
-
const { data, error } = await callEdgeFunction("fetch-analytics", {
|
|
6847
|
+
const { data, error } = await callEdgeFunction("fetch-analytics", {
|
|
6848
|
+
userId
|
|
6849
|
+
});
|
|
6620
6850
|
if (error) {
|
|
6621
6851
|
await logMcpToolInvocation({
|
|
6622
6852
|
toolName: "refresh_platform_analytics",
|
|
@@ -6625,7 +6855,12 @@ function registerAnalyticsTools(server2) {
|
|
|
6625
6855
|
details: { error }
|
|
6626
6856
|
});
|
|
6627
6857
|
return {
|
|
6628
|
-
content: [
|
|
6858
|
+
content: [
|
|
6859
|
+
{
|
|
6860
|
+
type: "text",
|
|
6861
|
+
text: `Error refreshing analytics: ${error}`
|
|
6862
|
+
}
|
|
6863
|
+
],
|
|
6629
6864
|
isError: true
|
|
6630
6865
|
};
|
|
6631
6866
|
}
|
|
@@ -6638,12 +6873,18 @@ function registerAnalyticsTools(server2) {
|
|
|
6638
6873
|
details: { error: "Edge function returned success=false" }
|
|
6639
6874
|
});
|
|
6640
6875
|
return {
|
|
6641
|
-
content: [
|
|
6876
|
+
content: [
|
|
6877
|
+
{ type: "text", text: "Analytics refresh failed." }
|
|
6878
|
+
],
|
|
6642
6879
|
isError: true
|
|
6643
6880
|
};
|
|
6644
6881
|
}
|
|
6645
|
-
const queued = (result.results ?? []).filter(
|
|
6646
|
-
|
|
6882
|
+
const queued = (result.results ?? []).filter(
|
|
6883
|
+
(r) => r.status === "queued"
|
|
6884
|
+
).length;
|
|
6885
|
+
const errored = (result.results ?? []).filter(
|
|
6886
|
+
(r) => r.status === "error"
|
|
6887
|
+
).length;
|
|
6647
6888
|
const lines = [
|
|
6648
6889
|
`Analytics refresh triggered successfully.`,
|
|
6649
6890
|
` Posts processed: ${result.postsProcessed}`,
|
|
@@ -6689,7 +6930,10 @@ function formatAnalytics(summary, days, format) {
|
|
|
6689
6930
|
if (format === "json") {
|
|
6690
6931
|
return {
|
|
6691
6932
|
content: [
|
|
6692
|
-
{
|
|
6933
|
+
{
|
|
6934
|
+
type: "text",
|
|
6935
|
+
text: JSON.stringify(asEnvelope3({ ...summary, days }), null, 2)
|
|
6936
|
+
}
|
|
6693
6937
|
]
|
|
6694
6938
|
};
|
|
6695
6939
|
}
|
|
@@ -6807,10 +7051,160 @@ function formatSimpleAnalytics(rows, platform3, days, format) {
|
|
|
6807
7051
|
init_edge_function();
|
|
6808
7052
|
init_supabase();
|
|
6809
7053
|
import { z as z5 } from "zod";
|
|
7054
|
+
|
|
7055
|
+
// src/lib/ssrf.ts
|
|
7056
|
+
var BLOCKED_IP_PATTERNS = [
|
|
7057
|
+
// IPv4 localhost/loopback
|
|
7058
|
+
/^127\./,
|
|
7059
|
+
/^0\./,
|
|
7060
|
+
// IPv4 private ranges (RFC 1918)
|
|
7061
|
+
/^10\./,
|
|
7062
|
+
/^172\.(1[6-9]|2[0-9]|3[0-1])\./,
|
|
7063
|
+
/^192\.168\./,
|
|
7064
|
+
// IPv4 link-local
|
|
7065
|
+
/^169\.254\./,
|
|
7066
|
+
// Cloud metadata endpoint (AWS, GCP, Azure)
|
|
7067
|
+
/^169\.254\.169\.254$/,
|
|
7068
|
+
// IPv4 broadcast
|
|
7069
|
+
/^255\./,
|
|
7070
|
+
// Shared address space (RFC 6598)
|
|
7071
|
+
/^100\.(6[4-9]|[7-9][0-9]|1[0-1][0-9]|12[0-7])\./
|
|
7072
|
+
];
|
|
7073
|
+
var BLOCKED_IPV6_PATTERNS = [
|
|
7074
|
+
/^::1$/i,
|
|
7075
|
+
// loopback
|
|
7076
|
+
/^::$/i,
|
|
7077
|
+
// unspecified
|
|
7078
|
+
/^fe[89ab][0-9a-f]:/i,
|
|
7079
|
+
// link-local fe80::/10
|
|
7080
|
+
/^fc[0-9a-f]:/i,
|
|
7081
|
+
// unique local fc00::/7
|
|
7082
|
+
/^fd[0-9a-f]:/i,
|
|
7083
|
+
// unique local fc00::/7
|
|
7084
|
+
/^::ffff:127\./i,
|
|
7085
|
+
// IPv4-mapped localhost
|
|
7086
|
+
/^::ffff:(0|10|127|169\.254|172\.(1[6-9]|2[0-9]|3[0-1])|192\.168)\./i
|
|
7087
|
+
// IPv4-mapped private
|
|
7088
|
+
];
|
|
7089
|
+
var BLOCKED_HOSTNAMES = [
|
|
7090
|
+
"localhost",
|
|
7091
|
+
"localhost.localdomain",
|
|
7092
|
+
"local",
|
|
7093
|
+
"127.0.0.1",
|
|
7094
|
+
"0.0.0.0",
|
|
7095
|
+
"[::1]",
|
|
7096
|
+
"[::ffff:127.0.0.1]",
|
|
7097
|
+
// Cloud metadata endpoints
|
|
7098
|
+
"metadata.google.internal",
|
|
7099
|
+
"metadata.goog",
|
|
7100
|
+
"instance-data",
|
|
7101
|
+
"instance-data.ec2.internal"
|
|
7102
|
+
];
|
|
7103
|
+
var ALLOWED_PROTOCOLS = ["http:", "https:"];
|
|
7104
|
+
var BLOCKED_PORTS = [22, 23, 25, 110, 143, 445, 3306, 5432, 6379, 27017, 11211];
|
|
7105
|
+
function isBlockedIP(ip) {
|
|
7106
|
+
const normalized = ip.replace(/^\[/, "").replace(/\]$/, "");
|
|
7107
|
+
if (normalized.includes(":")) {
|
|
7108
|
+
return BLOCKED_IPV6_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
7109
|
+
}
|
|
7110
|
+
return BLOCKED_IP_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
7111
|
+
}
|
|
7112
|
+
function isBlockedHostname(hostname) {
|
|
7113
|
+
return BLOCKED_HOSTNAMES.includes(hostname.toLowerCase());
|
|
7114
|
+
}
|
|
7115
|
+
function isIPAddress(hostname) {
|
|
7116
|
+
const ipv4Pattern = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
7117
|
+
const ipv6Pattern = /^\[?[a-fA-F0-9:]+\]?$/;
|
|
7118
|
+
return ipv4Pattern.test(hostname) || ipv6Pattern.test(hostname);
|
|
7119
|
+
}
|
|
7120
|
+
async function validateUrlForSSRF(urlString) {
|
|
7121
|
+
try {
|
|
7122
|
+
const url = new URL(urlString);
|
|
7123
|
+
if (!ALLOWED_PROTOCOLS.includes(url.protocol)) {
|
|
7124
|
+
return {
|
|
7125
|
+
isValid: false,
|
|
7126
|
+
error: `Invalid protocol: ${url.protocol}. Only HTTP and HTTPS are allowed.`
|
|
7127
|
+
};
|
|
7128
|
+
}
|
|
7129
|
+
if (url.username || url.password) {
|
|
7130
|
+
return {
|
|
7131
|
+
isValid: false,
|
|
7132
|
+
error: "URLs with embedded credentials are not allowed."
|
|
7133
|
+
};
|
|
7134
|
+
}
|
|
7135
|
+
const hostname = url.hostname.toLowerCase();
|
|
7136
|
+
if (isBlockedHostname(hostname)) {
|
|
7137
|
+
return {
|
|
7138
|
+
isValid: false,
|
|
7139
|
+
error: "Access to internal/localhost addresses is not allowed."
|
|
7140
|
+
};
|
|
7141
|
+
}
|
|
7142
|
+
if (isIPAddress(hostname) && isBlockedIP(hostname)) {
|
|
7143
|
+
return {
|
|
7144
|
+
isValid: false,
|
|
7145
|
+
error: "Access to private/internal IP addresses is not allowed."
|
|
7146
|
+
};
|
|
7147
|
+
}
|
|
7148
|
+
const port = url.port ? parseInt(url.port, 10) : url.protocol === "https:" ? 443 : 80;
|
|
7149
|
+
if (BLOCKED_PORTS.includes(port)) {
|
|
7150
|
+
return {
|
|
7151
|
+
isValid: false,
|
|
7152
|
+
error: `Access to port ${port} is not allowed.`
|
|
7153
|
+
};
|
|
7154
|
+
}
|
|
7155
|
+
let resolvedIP;
|
|
7156
|
+
if (!isIPAddress(hostname)) {
|
|
7157
|
+
try {
|
|
7158
|
+
const dns = await import("node:dns");
|
|
7159
|
+
const resolver = new dns.promises.Resolver();
|
|
7160
|
+
const resolvedIPs = [];
|
|
7161
|
+
try {
|
|
7162
|
+
const aRecords = await resolver.resolve4(hostname);
|
|
7163
|
+
resolvedIPs.push(...aRecords);
|
|
7164
|
+
} catch {
|
|
7165
|
+
}
|
|
7166
|
+
try {
|
|
7167
|
+
const aaaaRecords = await resolver.resolve6(hostname);
|
|
7168
|
+
resolvedIPs.push(...aaaaRecords);
|
|
7169
|
+
} catch {
|
|
7170
|
+
}
|
|
7171
|
+
if (resolvedIPs.length === 0) {
|
|
7172
|
+
return {
|
|
7173
|
+
isValid: false,
|
|
7174
|
+
error: "DNS resolution failed: hostname did not resolve to any address."
|
|
7175
|
+
};
|
|
7176
|
+
}
|
|
7177
|
+
for (const ip of resolvedIPs) {
|
|
7178
|
+
if (isBlockedIP(ip)) {
|
|
7179
|
+
return {
|
|
7180
|
+
isValid: false,
|
|
7181
|
+
error: "Hostname resolves to a private/internal IP address."
|
|
7182
|
+
};
|
|
7183
|
+
}
|
|
7184
|
+
}
|
|
7185
|
+
resolvedIP = resolvedIPs[0];
|
|
7186
|
+
} catch {
|
|
7187
|
+
return {
|
|
7188
|
+
isValid: false,
|
|
7189
|
+
error: "DNS resolution failed. Cannot verify hostname safety."
|
|
7190
|
+
};
|
|
7191
|
+
}
|
|
7192
|
+
}
|
|
7193
|
+
return { isValid: true, sanitizedUrl: url.toString(), resolvedIP };
|
|
7194
|
+
} catch (error) {
|
|
7195
|
+
return {
|
|
7196
|
+
isValid: false,
|
|
7197
|
+
error: `Invalid URL format: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
7198
|
+
};
|
|
7199
|
+
}
|
|
7200
|
+
}
|
|
7201
|
+
|
|
7202
|
+
// src/tools/brand.ts
|
|
7203
|
+
init_version();
|
|
6810
7204
|
function asEnvelope4(data) {
|
|
6811
7205
|
return {
|
|
6812
7206
|
_meta: {
|
|
6813
|
-
version:
|
|
7207
|
+
version: MCP_VERSION,
|
|
6814
7208
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6815
7209
|
},
|
|
6816
7210
|
data
|
|
@@ -6827,6 +7221,15 @@ function registerBrandTools(server2) {
|
|
|
6827
7221
|
response_format: z5.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
6828
7222
|
},
|
|
6829
7223
|
async ({ url, response_format }) => {
|
|
7224
|
+
const ssrfCheck = await validateUrlForSSRF(url);
|
|
7225
|
+
if (!ssrfCheck.isValid) {
|
|
7226
|
+
return {
|
|
7227
|
+
content: [
|
|
7228
|
+
{ type: "text", text: `URL blocked: ${ssrfCheck.error}` }
|
|
7229
|
+
],
|
|
7230
|
+
isError: true
|
|
7231
|
+
};
|
|
7232
|
+
}
|
|
6830
7233
|
const { data, error } = await callEdgeFunction(
|
|
6831
7234
|
"brand-extract",
|
|
6832
7235
|
{ url },
|
|
@@ -6856,7 +7259,12 @@ function registerBrandTools(server2) {
|
|
|
6856
7259
|
}
|
|
6857
7260
|
if ((response_format || "text") === "json") {
|
|
6858
7261
|
return {
|
|
6859
|
-
content: [
|
|
7262
|
+
content: [
|
|
7263
|
+
{
|
|
7264
|
+
type: "text",
|
|
7265
|
+
text: JSON.stringify(asEnvelope4(data), null, 2)
|
|
7266
|
+
}
|
|
7267
|
+
]
|
|
6860
7268
|
};
|
|
6861
7269
|
}
|
|
6862
7270
|
const lines = [
|
|
@@ -6920,7 +7328,12 @@ function registerBrandTools(server2) {
|
|
|
6920
7328
|
const { data: membership } = await supabase.from("organization_members").select("organization_id").eq("user_id", userId).eq("organization_id", project.organization_id).maybeSingle();
|
|
6921
7329
|
if (!membership) {
|
|
6922
7330
|
return {
|
|
6923
|
-
content: [
|
|
7331
|
+
content: [
|
|
7332
|
+
{
|
|
7333
|
+
type: "text",
|
|
7334
|
+
text: "Project is not accessible to current user."
|
|
7335
|
+
}
|
|
7336
|
+
],
|
|
6924
7337
|
isError: true
|
|
6925
7338
|
};
|
|
6926
7339
|
}
|
|
@@ -6939,13 +7352,21 @@ function registerBrandTools(server2) {
|
|
|
6939
7352
|
if (!data) {
|
|
6940
7353
|
return {
|
|
6941
7354
|
content: [
|
|
6942
|
-
{
|
|
7355
|
+
{
|
|
7356
|
+
type: "text",
|
|
7357
|
+
text: "No active brand profile found for this project."
|
|
7358
|
+
}
|
|
6943
7359
|
]
|
|
6944
7360
|
};
|
|
6945
7361
|
}
|
|
6946
7362
|
if ((response_format || "text") === "json") {
|
|
6947
7363
|
return {
|
|
6948
|
-
content: [
|
|
7364
|
+
content: [
|
|
7365
|
+
{
|
|
7366
|
+
type: "text",
|
|
7367
|
+
text: JSON.stringify(asEnvelope4(data), null, 2)
|
|
7368
|
+
}
|
|
7369
|
+
]
|
|
6949
7370
|
};
|
|
6950
7371
|
}
|
|
6951
7372
|
const lines = [
|
|
@@ -6966,11 +7387,18 @@ function registerBrandTools(server2) {
|
|
|
6966
7387
|
"Persist a brand profile as the active profile for a project.",
|
|
6967
7388
|
{
|
|
6968
7389
|
project_id: z5.string().uuid().optional().describe("Project ID. Defaults to active project context."),
|
|
6969
|
-
brand_context: z5.record(z5.string(), z5.unknown()).describe(
|
|
7390
|
+
brand_context: z5.record(z5.string(), z5.unknown()).describe(
|
|
7391
|
+
"Brand context payload to save to brand_profiles.brand_context."
|
|
7392
|
+
),
|
|
6970
7393
|
change_summary: z5.string().max(500).optional().describe("Optional summary of changes."),
|
|
6971
7394
|
changed_paths: z5.array(z5.string()).optional().describe("Optional changed path list."),
|
|
6972
7395
|
source_url: z5.string().url().optional().describe("Optional source URL for provenance."),
|
|
6973
|
-
extraction_method: z5.enum([
|
|
7396
|
+
extraction_method: z5.enum([
|
|
7397
|
+
"manual",
|
|
7398
|
+
"url_extract",
|
|
7399
|
+
"business_profiler",
|
|
7400
|
+
"product_showcase"
|
|
7401
|
+
]).optional().describe("Extraction method metadata."),
|
|
6974
7402
|
overall_confidence: z5.number().min(0).max(1).optional().describe("Optional overall confidence score in range 0..1."),
|
|
6975
7403
|
extraction_metadata: z5.record(z5.string(), z5.unknown()).optional(),
|
|
6976
7404
|
response_format: z5.enum(["text", "json"]).optional()
|
|
@@ -7010,20 +7438,28 @@ function registerBrandTools(server2) {
|
|
|
7010
7438
|
const { data: membership } = await supabase.from("organization_members").select("organization_id").eq("user_id", userId).eq("organization_id", project.organization_id).maybeSingle();
|
|
7011
7439
|
if (!membership) {
|
|
7012
7440
|
return {
|
|
7013
|
-
content: [
|
|
7441
|
+
content: [
|
|
7442
|
+
{
|
|
7443
|
+
type: "text",
|
|
7444
|
+
text: "Project is not accessible to current user."
|
|
7445
|
+
}
|
|
7446
|
+
],
|
|
7014
7447
|
isError: true
|
|
7015
7448
|
};
|
|
7016
7449
|
}
|
|
7017
|
-
const { data: profileId, error } = await supabase.rpc(
|
|
7018
|
-
|
|
7019
|
-
|
|
7020
|
-
|
|
7021
|
-
|
|
7022
|
-
|
|
7023
|
-
|
|
7024
|
-
|
|
7025
|
-
|
|
7026
|
-
|
|
7450
|
+
const { data: profileId, error } = await supabase.rpc(
|
|
7451
|
+
"set_active_brand_profile",
|
|
7452
|
+
{
|
|
7453
|
+
p_project_id: projectId,
|
|
7454
|
+
p_brand_context: brand_context,
|
|
7455
|
+
p_change_summary: change_summary || null,
|
|
7456
|
+
p_changed_paths: changed_paths || [],
|
|
7457
|
+
p_source_url: source_url || null,
|
|
7458
|
+
p_extraction_method: extraction_method || "manual",
|
|
7459
|
+
p_overall_confidence: overall_confidence ?? null,
|
|
7460
|
+
p_extraction_metadata: extraction_metadata || null
|
|
7461
|
+
}
|
|
7462
|
+
);
|
|
7027
7463
|
if (error) {
|
|
7028
7464
|
return {
|
|
7029
7465
|
content: [
|
|
@@ -7044,7 +7480,12 @@ function registerBrandTools(server2) {
|
|
|
7044
7480
|
};
|
|
7045
7481
|
if ((response_format || "text") === "json") {
|
|
7046
7482
|
return {
|
|
7047
|
-
content: [
|
|
7483
|
+
content: [
|
|
7484
|
+
{
|
|
7485
|
+
type: "text",
|
|
7486
|
+
text: JSON.stringify(asEnvelope4(payload), null, 2)
|
|
7487
|
+
}
|
|
7488
|
+
]
|
|
7048
7489
|
};
|
|
7049
7490
|
}
|
|
7050
7491
|
return {
|
|
@@ -7100,7 +7541,10 @@ Version: ${payload.version ?? "N/A"}`
|
|
|
7100
7541
|
if (!projectId) {
|
|
7101
7542
|
return {
|
|
7102
7543
|
content: [
|
|
7103
|
-
{
|
|
7544
|
+
{
|
|
7545
|
+
type: "text",
|
|
7546
|
+
text: "No project_id provided and no default project found."
|
|
7547
|
+
}
|
|
7104
7548
|
],
|
|
7105
7549
|
isError: true
|
|
7106
7550
|
};
|
|
@@ -7115,7 +7559,12 @@ Version: ${payload.version ?? "N/A"}`
|
|
|
7115
7559
|
const { data: membership } = await supabase.from("organization_members").select("organization_id").eq("user_id", userId).eq("organization_id", project.organization_id).maybeSingle();
|
|
7116
7560
|
if (!membership) {
|
|
7117
7561
|
return {
|
|
7118
|
-
content: [
|
|
7562
|
+
content: [
|
|
7563
|
+
{
|
|
7564
|
+
type: "text",
|
|
7565
|
+
text: "Project is not accessible to current user."
|
|
7566
|
+
}
|
|
7567
|
+
],
|
|
7119
7568
|
isError: true
|
|
7120
7569
|
};
|
|
7121
7570
|
}
|
|
@@ -7131,7 +7580,9 @@ Version: ${payload.version ?? "N/A"}`
|
|
|
7131
7580
|
isError: true
|
|
7132
7581
|
};
|
|
7133
7582
|
}
|
|
7134
|
-
const brandContext = {
|
|
7583
|
+
const brandContext = {
|
|
7584
|
+
...existingProfile.brand_context
|
|
7585
|
+
};
|
|
7135
7586
|
const voiceProfile = brandContext.voiceProfile ?? {};
|
|
7136
7587
|
const platformOverrides = voiceProfile.platformOverrides ?? {};
|
|
7137
7588
|
const existingOverride = platformOverrides[platform3] ?? {};
|
|
@@ -7155,16 +7606,19 @@ Version: ${payload.version ?? "N/A"}`
|
|
|
7155
7606
|
...brandContext,
|
|
7156
7607
|
voiceProfile: updatedVoiceProfile
|
|
7157
7608
|
};
|
|
7158
|
-
const { data: profileId, error: saveError } = await supabase.rpc(
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
|
|
7165
|
-
|
|
7166
|
-
|
|
7167
|
-
|
|
7609
|
+
const { data: profileId, error: saveError } = await supabase.rpc(
|
|
7610
|
+
"set_active_brand_profile",
|
|
7611
|
+
{
|
|
7612
|
+
p_project_id: projectId,
|
|
7613
|
+
p_brand_context: updatedContext,
|
|
7614
|
+
p_change_summary: `Updated platform voice override for ${platform3}`,
|
|
7615
|
+
p_changed_paths: [`voiceProfile.platformOverrides.${platform3}`],
|
|
7616
|
+
p_source_url: null,
|
|
7617
|
+
p_extraction_method: "manual",
|
|
7618
|
+
p_overall_confidence: null,
|
|
7619
|
+
p_extraction_metadata: null
|
|
7620
|
+
}
|
|
7621
|
+
);
|
|
7168
7622
|
if (saveError) {
|
|
7169
7623
|
return {
|
|
7170
7624
|
content: [
|
|
@@ -7185,7 +7639,12 @@ Version: ${payload.version ?? "N/A"}`
|
|
|
7185
7639
|
};
|
|
7186
7640
|
if ((response_format || "text") === "json") {
|
|
7187
7641
|
return {
|
|
7188
|
-
content: [
|
|
7642
|
+
content: [
|
|
7643
|
+
{
|
|
7644
|
+
type: "text",
|
|
7645
|
+
text: JSON.stringify(asEnvelope4(payload), null, 2)
|
|
7646
|
+
}
|
|
7647
|
+
],
|
|
7189
7648
|
isError: false
|
|
7190
7649
|
};
|
|
7191
7650
|
}
|
|
@@ -7283,155 +7742,6 @@ async function capturePageScreenshot(page, outputPath, selector) {
|
|
|
7283
7742
|
// src/tools/screenshot.ts
|
|
7284
7743
|
import { resolve, relative } from "node:path";
|
|
7285
7744
|
import { mkdir } from "node:fs/promises";
|
|
7286
|
-
|
|
7287
|
-
// src/lib/ssrf.ts
|
|
7288
|
-
var BLOCKED_IP_PATTERNS = [
|
|
7289
|
-
// IPv4 localhost/loopback
|
|
7290
|
-
/^127\./,
|
|
7291
|
-
/^0\./,
|
|
7292
|
-
// IPv4 private ranges (RFC 1918)
|
|
7293
|
-
/^10\./,
|
|
7294
|
-
/^172\.(1[6-9]|2[0-9]|3[0-1])\./,
|
|
7295
|
-
/^192\.168\./,
|
|
7296
|
-
// IPv4 link-local
|
|
7297
|
-
/^169\.254\./,
|
|
7298
|
-
// Cloud metadata endpoint (AWS, GCP, Azure)
|
|
7299
|
-
/^169\.254\.169\.254$/,
|
|
7300
|
-
// IPv4 broadcast
|
|
7301
|
-
/^255\./,
|
|
7302
|
-
// Shared address space (RFC 6598)
|
|
7303
|
-
/^100\.(6[4-9]|[7-9][0-9]|1[0-1][0-9]|12[0-7])\./
|
|
7304
|
-
];
|
|
7305
|
-
var BLOCKED_IPV6_PATTERNS = [
|
|
7306
|
-
/^::1$/i,
|
|
7307
|
-
// loopback
|
|
7308
|
-
/^::$/i,
|
|
7309
|
-
// unspecified
|
|
7310
|
-
/^fe[89ab][0-9a-f]:/i,
|
|
7311
|
-
// link-local fe80::/10
|
|
7312
|
-
/^fc[0-9a-f]:/i,
|
|
7313
|
-
// unique local fc00::/7
|
|
7314
|
-
/^fd[0-9a-f]:/i,
|
|
7315
|
-
// unique local fc00::/7
|
|
7316
|
-
/^::ffff:127\./i,
|
|
7317
|
-
// IPv4-mapped localhost
|
|
7318
|
-
/^::ffff:(0|10|127|169\.254|172\.(1[6-9]|2[0-9]|3[0-1])|192\.168)\./i
|
|
7319
|
-
// IPv4-mapped private
|
|
7320
|
-
];
|
|
7321
|
-
var BLOCKED_HOSTNAMES = [
|
|
7322
|
-
"localhost",
|
|
7323
|
-
"localhost.localdomain",
|
|
7324
|
-
"local",
|
|
7325
|
-
"127.0.0.1",
|
|
7326
|
-
"0.0.0.0",
|
|
7327
|
-
"[::1]",
|
|
7328
|
-
"[::ffff:127.0.0.1]",
|
|
7329
|
-
// Cloud metadata endpoints
|
|
7330
|
-
"metadata.google.internal",
|
|
7331
|
-
"metadata.goog",
|
|
7332
|
-
"instance-data",
|
|
7333
|
-
"instance-data.ec2.internal"
|
|
7334
|
-
];
|
|
7335
|
-
var ALLOWED_PROTOCOLS = ["http:", "https:"];
|
|
7336
|
-
var BLOCKED_PORTS = [22, 23, 25, 110, 143, 445, 3306, 5432, 6379, 27017, 11211];
|
|
7337
|
-
function isBlockedIP(ip) {
|
|
7338
|
-
const normalized = ip.replace(/^\[/, "").replace(/\]$/, "");
|
|
7339
|
-
if (normalized.includes(":")) {
|
|
7340
|
-
return BLOCKED_IPV6_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
7341
|
-
}
|
|
7342
|
-
return BLOCKED_IP_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
7343
|
-
}
|
|
7344
|
-
function isBlockedHostname(hostname) {
|
|
7345
|
-
return BLOCKED_HOSTNAMES.includes(hostname.toLowerCase());
|
|
7346
|
-
}
|
|
7347
|
-
function isIPAddress(hostname) {
|
|
7348
|
-
const ipv4Pattern = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
7349
|
-
const ipv6Pattern = /^\[?[a-fA-F0-9:]+\]?$/;
|
|
7350
|
-
return ipv4Pattern.test(hostname) || ipv6Pattern.test(hostname);
|
|
7351
|
-
}
|
|
7352
|
-
async function validateUrlForSSRF(urlString) {
|
|
7353
|
-
try {
|
|
7354
|
-
const url = new URL(urlString);
|
|
7355
|
-
if (!ALLOWED_PROTOCOLS.includes(url.protocol)) {
|
|
7356
|
-
return {
|
|
7357
|
-
isValid: false,
|
|
7358
|
-
error: `Invalid protocol: ${url.protocol}. Only HTTP and HTTPS are allowed.`
|
|
7359
|
-
};
|
|
7360
|
-
}
|
|
7361
|
-
if (url.username || url.password) {
|
|
7362
|
-
return {
|
|
7363
|
-
isValid: false,
|
|
7364
|
-
error: "URLs with embedded credentials are not allowed."
|
|
7365
|
-
};
|
|
7366
|
-
}
|
|
7367
|
-
const hostname = url.hostname.toLowerCase();
|
|
7368
|
-
if (isBlockedHostname(hostname)) {
|
|
7369
|
-
return {
|
|
7370
|
-
isValid: false,
|
|
7371
|
-
error: "Access to internal/localhost addresses is not allowed."
|
|
7372
|
-
};
|
|
7373
|
-
}
|
|
7374
|
-
if (isIPAddress(hostname) && isBlockedIP(hostname)) {
|
|
7375
|
-
return {
|
|
7376
|
-
isValid: false,
|
|
7377
|
-
error: "Access to private/internal IP addresses is not allowed."
|
|
7378
|
-
};
|
|
7379
|
-
}
|
|
7380
|
-
const port = url.port ? parseInt(url.port, 10) : url.protocol === "https:" ? 443 : 80;
|
|
7381
|
-
if (BLOCKED_PORTS.includes(port)) {
|
|
7382
|
-
return {
|
|
7383
|
-
isValid: false,
|
|
7384
|
-
error: `Access to port ${port} is not allowed.`
|
|
7385
|
-
};
|
|
7386
|
-
}
|
|
7387
|
-
let resolvedIP;
|
|
7388
|
-
if (!isIPAddress(hostname)) {
|
|
7389
|
-
try {
|
|
7390
|
-
const dns = await import("node:dns");
|
|
7391
|
-
const resolver = new dns.promises.Resolver();
|
|
7392
|
-
const resolvedIPs = [];
|
|
7393
|
-
try {
|
|
7394
|
-
const aRecords = await resolver.resolve4(hostname);
|
|
7395
|
-
resolvedIPs.push(...aRecords);
|
|
7396
|
-
} catch {
|
|
7397
|
-
}
|
|
7398
|
-
try {
|
|
7399
|
-
const aaaaRecords = await resolver.resolve6(hostname);
|
|
7400
|
-
resolvedIPs.push(...aaaaRecords);
|
|
7401
|
-
} catch {
|
|
7402
|
-
}
|
|
7403
|
-
if (resolvedIPs.length === 0) {
|
|
7404
|
-
return {
|
|
7405
|
-
isValid: false,
|
|
7406
|
-
error: "DNS resolution failed: hostname did not resolve to any address."
|
|
7407
|
-
};
|
|
7408
|
-
}
|
|
7409
|
-
for (const ip of resolvedIPs) {
|
|
7410
|
-
if (isBlockedIP(ip)) {
|
|
7411
|
-
return {
|
|
7412
|
-
isValid: false,
|
|
7413
|
-
error: "Hostname resolves to a private/internal IP address."
|
|
7414
|
-
};
|
|
7415
|
-
}
|
|
7416
|
-
}
|
|
7417
|
-
resolvedIP = resolvedIPs[0];
|
|
7418
|
-
} catch {
|
|
7419
|
-
return {
|
|
7420
|
-
isValid: false,
|
|
7421
|
-
error: "DNS resolution failed. Cannot verify hostname safety."
|
|
7422
|
-
};
|
|
7423
|
-
}
|
|
7424
|
-
}
|
|
7425
|
-
return { isValid: true, sanitizedUrl: url.toString(), resolvedIP };
|
|
7426
|
-
} catch (error) {
|
|
7427
|
-
return {
|
|
7428
|
-
isValid: false,
|
|
7429
|
-
error: `Invalid URL format: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
7430
|
-
};
|
|
7431
|
-
}
|
|
7432
|
-
}
|
|
7433
|
-
|
|
7434
|
-
// src/tools/screenshot.ts
|
|
7435
7745
|
init_supabase();
|
|
7436
7746
|
function registerScreenshotTools(server2) {
|
|
7437
7747
|
server2.tool(
|
|
@@ -8007,6 +8317,8 @@ function registerRemotionTools(server2) {
|
|
|
8007
8317
|
// src/tools/insights.ts
|
|
8008
8318
|
init_supabase();
|
|
8009
8319
|
import { z as z8 } from "zod";
|
|
8320
|
+
init_version();
|
|
8321
|
+
var MAX_INSIGHT_AGE_DAYS = 30;
|
|
8010
8322
|
var PLATFORM_ENUM = [
|
|
8011
8323
|
"youtube",
|
|
8012
8324
|
"tiktok",
|
|
@@ -8017,11 +8329,19 @@ var PLATFORM_ENUM = [
|
|
|
8017
8329
|
"threads",
|
|
8018
8330
|
"bluesky"
|
|
8019
8331
|
];
|
|
8020
|
-
var DAY_NAMES = [
|
|
8332
|
+
var DAY_NAMES = [
|
|
8333
|
+
"Sunday",
|
|
8334
|
+
"Monday",
|
|
8335
|
+
"Tuesday",
|
|
8336
|
+
"Wednesday",
|
|
8337
|
+
"Thursday",
|
|
8338
|
+
"Friday",
|
|
8339
|
+
"Saturday"
|
|
8340
|
+
];
|
|
8021
8341
|
function asEnvelope5(data) {
|
|
8022
8342
|
return {
|
|
8023
8343
|
_meta: {
|
|
8024
|
-
version:
|
|
8344
|
+
version: MCP_VERSION,
|
|
8025
8345
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
8026
8346
|
},
|
|
8027
8347
|
data
|
|
@@ -8032,7 +8352,12 @@ function registerInsightsTools(server2) {
|
|
|
8032
8352
|
"get_performance_insights",
|
|
8033
8353
|
"Query performance insights derived from post analytics. Returns metrics like engagement rate, view velocity, and click rate aggregated over time. Use this to understand what content is performing well.",
|
|
8034
8354
|
{
|
|
8035
|
-
insight_type: z8.enum([
|
|
8355
|
+
insight_type: z8.enum([
|
|
8356
|
+
"top_hooks",
|
|
8357
|
+
"optimal_timing",
|
|
8358
|
+
"best_models",
|
|
8359
|
+
"competitor_patterns"
|
|
8360
|
+
]).optional().describe("Filter to a specific insight type."),
|
|
8036
8361
|
days: z8.number().min(1).max(90).optional().describe("Number of days to look back. Defaults to 30. Max 90."),
|
|
8037
8362
|
limit: z8.number().min(1).max(50).optional().describe("Maximum number of insights to return. Defaults to 10."),
|
|
8038
8363
|
response_format: z8.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
@@ -8051,10 +8376,13 @@ function registerInsightsTools(server2) {
|
|
|
8051
8376
|
projectIds.push(...projects.map((p) => p.id));
|
|
8052
8377
|
}
|
|
8053
8378
|
}
|
|
8379
|
+
const effectiveDays = Math.min(lookbackDays, MAX_INSIGHT_AGE_DAYS);
|
|
8054
8380
|
const since = /* @__PURE__ */ new Date();
|
|
8055
|
-
since.setDate(since.getDate() -
|
|
8381
|
+
since.setDate(since.getDate() - effectiveDays);
|
|
8056
8382
|
const sinceIso = since.toISOString();
|
|
8057
|
-
let query = supabase.from("performance_insights").select(
|
|
8383
|
+
let query = supabase.from("performance_insights").select(
|
|
8384
|
+
"id, project_id, insight_type, insight_data, confidence_score, generated_at"
|
|
8385
|
+
).gte("generated_at", sinceIso).order("generated_at", { ascending: false }).limit(maxRows);
|
|
8058
8386
|
if (projectIds.length > 0) {
|
|
8059
8387
|
query = query.in("project_id", projectIds);
|
|
8060
8388
|
} else {
|
|
@@ -8410,10 +8738,11 @@ function registerYouTubeAnalyticsTools(server2) {
|
|
|
8410
8738
|
init_edge_function();
|
|
8411
8739
|
import { z as z10 } from "zod";
|
|
8412
8740
|
init_supabase();
|
|
8741
|
+
init_version();
|
|
8413
8742
|
function asEnvelope6(data) {
|
|
8414
8743
|
return {
|
|
8415
8744
|
_meta: {
|
|
8416
|
-
version:
|
|
8745
|
+
version: MCP_VERSION,
|
|
8417
8746
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
8418
8747
|
},
|
|
8419
8748
|
data
|
|
@@ -8424,7 +8753,9 @@ function registerCommentsTools(server2) {
|
|
|
8424
8753
|
"list_comments",
|
|
8425
8754
|
"List YouTube comments. Without a video_id, returns recent comments across all channel videos. With a video_id, returns comments for that specific video.",
|
|
8426
8755
|
{
|
|
8427
|
-
video_id: z10.string().optional().describe(
|
|
8756
|
+
video_id: z10.string().optional().describe(
|
|
8757
|
+
"YouTube video ID. If omitted, returns comments across all channel videos."
|
|
8758
|
+
),
|
|
8428
8759
|
max_results: z10.number().min(1).max(100).optional().describe("Maximum number of comments to return. Defaults to 50."),
|
|
8429
8760
|
page_token: z10.string().optional().describe("Pagination token from a previous list_comments call."),
|
|
8430
8761
|
response_format: z10.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
@@ -8439,7 +8770,9 @@ function registerCommentsTools(server2) {
|
|
|
8439
8770
|
});
|
|
8440
8771
|
if (error) {
|
|
8441
8772
|
return {
|
|
8442
|
-
content: [
|
|
8773
|
+
content: [
|
|
8774
|
+
{ type: "text", text: `Error listing comments: ${error}` }
|
|
8775
|
+
],
|
|
8443
8776
|
isError: true
|
|
8444
8777
|
};
|
|
8445
8778
|
}
|
|
@@ -8451,7 +8784,10 @@ function registerCommentsTools(server2) {
|
|
|
8451
8784
|
{
|
|
8452
8785
|
type: "text",
|
|
8453
8786
|
text: JSON.stringify(
|
|
8454
|
-
asEnvelope6({
|
|
8787
|
+
asEnvelope6({
|
|
8788
|
+
comments,
|
|
8789
|
+
nextPageToken: result.nextPageToken ?? null
|
|
8790
|
+
}),
|
|
8455
8791
|
null,
|
|
8456
8792
|
2
|
|
8457
8793
|
)
|
|
@@ -8488,7 +8824,9 @@ function registerCommentsTools(server2) {
|
|
|
8488
8824
|
"reply_to_comment",
|
|
8489
8825
|
"Reply to a YouTube comment. Requires the parent comment ID and reply text.",
|
|
8490
8826
|
{
|
|
8491
|
-
parent_id: z10.string().describe(
|
|
8827
|
+
parent_id: z10.string().describe(
|
|
8828
|
+
"The ID of the parent comment to reply to (from list_comments)."
|
|
8829
|
+
),
|
|
8492
8830
|
text: z10.string().min(1).describe("The reply text."),
|
|
8493
8831
|
response_format: z10.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
8494
8832
|
},
|
|
@@ -8527,7 +8865,12 @@ function registerCommentsTools(server2) {
|
|
|
8527
8865
|
details: { error }
|
|
8528
8866
|
});
|
|
8529
8867
|
return {
|
|
8530
|
-
content: [
|
|
8868
|
+
content: [
|
|
8869
|
+
{
|
|
8870
|
+
type: "text",
|
|
8871
|
+
text: `Error replying to comment: ${error}`
|
|
8872
|
+
}
|
|
8873
|
+
],
|
|
8531
8874
|
isError: true
|
|
8532
8875
|
};
|
|
8533
8876
|
}
|
|
@@ -8540,7 +8883,12 @@ function registerCommentsTools(server2) {
|
|
|
8540
8883
|
});
|
|
8541
8884
|
if (format === "json") {
|
|
8542
8885
|
return {
|
|
8543
|
-
content: [
|
|
8886
|
+
content: [
|
|
8887
|
+
{
|
|
8888
|
+
type: "text",
|
|
8889
|
+
text: JSON.stringify(asEnvelope6(result), null, 2)
|
|
8890
|
+
}
|
|
8891
|
+
]
|
|
8544
8892
|
};
|
|
8545
8893
|
}
|
|
8546
8894
|
return {
|
|
@@ -8598,7 +8946,9 @@ function registerCommentsTools(server2) {
|
|
|
8598
8946
|
details: { error }
|
|
8599
8947
|
});
|
|
8600
8948
|
return {
|
|
8601
|
-
content: [
|
|
8949
|
+
content: [
|
|
8950
|
+
{ type: "text", text: `Error posting comment: ${error}` }
|
|
8951
|
+
],
|
|
8602
8952
|
isError: true
|
|
8603
8953
|
};
|
|
8604
8954
|
}
|
|
@@ -8611,7 +8961,12 @@ function registerCommentsTools(server2) {
|
|
|
8611
8961
|
});
|
|
8612
8962
|
if (format === "json") {
|
|
8613
8963
|
return {
|
|
8614
|
-
content: [
|
|
8964
|
+
content: [
|
|
8965
|
+
{
|
|
8966
|
+
type: "text",
|
|
8967
|
+
text: JSON.stringify(asEnvelope6(result), null, 2)
|
|
8968
|
+
}
|
|
8969
|
+
]
|
|
8615
8970
|
};
|
|
8616
8971
|
}
|
|
8617
8972
|
return {
|
|
@@ -8669,7 +9024,12 @@ function registerCommentsTools(server2) {
|
|
|
8669
9024
|
details: { error }
|
|
8670
9025
|
});
|
|
8671
9026
|
return {
|
|
8672
|
-
content: [
|
|
9027
|
+
content: [
|
|
9028
|
+
{
|
|
9029
|
+
type: "text",
|
|
9030
|
+
text: `Error moderating comment: ${error}`
|
|
9031
|
+
}
|
|
9032
|
+
],
|
|
8673
9033
|
isError: true
|
|
8674
9034
|
};
|
|
8675
9035
|
}
|
|
@@ -8748,7 +9108,9 @@ function registerCommentsTools(server2) {
|
|
|
8748
9108
|
details: { error }
|
|
8749
9109
|
});
|
|
8750
9110
|
return {
|
|
8751
|
-
content: [
|
|
9111
|
+
content: [
|
|
9112
|
+
{ type: "text", text: `Error deleting comment: ${error}` }
|
|
9113
|
+
],
|
|
8752
9114
|
isError: true
|
|
8753
9115
|
};
|
|
8754
9116
|
}
|
|
@@ -8763,13 +9125,22 @@ function registerCommentsTools(server2) {
|
|
|
8763
9125
|
content: [
|
|
8764
9126
|
{
|
|
8765
9127
|
type: "text",
|
|
8766
|
-
text: JSON.stringify(
|
|
9128
|
+
text: JSON.stringify(
|
|
9129
|
+
asEnvelope6({ success: true, commentId: comment_id }),
|
|
9130
|
+
null,
|
|
9131
|
+
2
|
|
9132
|
+
)
|
|
8767
9133
|
}
|
|
8768
9134
|
]
|
|
8769
9135
|
};
|
|
8770
9136
|
}
|
|
8771
9137
|
return {
|
|
8772
|
-
content: [
|
|
9138
|
+
content: [
|
|
9139
|
+
{
|
|
9140
|
+
type: "text",
|
|
9141
|
+
text: `Comment ${comment_id} deleted successfully.`
|
|
9142
|
+
}
|
|
9143
|
+
]
|
|
8773
9144
|
};
|
|
8774
9145
|
}
|
|
8775
9146
|
);
|
|
@@ -8778,6 +9149,7 @@ function registerCommentsTools(server2) {
|
|
|
8778
9149
|
// src/tools/ideation-context.ts
|
|
8779
9150
|
init_supabase();
|
|
8780
9151
|
import { z as z11 } from "zod";
|
|
9152
|
+
init_version();
|
|
8781
9153
|
function transformInsightsToPerformanceContext(projectId, insights) {
|
|
8782
9154
|
if (!insights.length) {
|
|
8783
9155
|
return {
|
|
@@ -8797,8 +9169,12 @@ function transformInsightsToPerformanceContext(projectId, insights) {
|
|
|
8797
9169
|
};
|
|
8798
9170
|
}
|
|
8799
9171
|
const topHooksInsight = insights.find((i) => i.insight_type === "top_hooks");
|
|
8800
|
-
const optimalTimingInsight = insights.find(
|
|
8801
|
-
|
|
9172
|
+
const optimalTimingInsight = insights.find(
|
|
9173
|
+
(i) => i.insight_type === "optimal_timing"
|
|
9174
|
+
);
|
|
9175
|
+
const bestModelsInsight = insights.find(
|
|
9176
|
+
(i) => i.insight_type === "best_models"
|
|
9177
|
+
);
|
|
8802
9178
|
const topHooks = topHooksInsight?.insight_data?.hooks || [];
|
|
8803
9179
|
const hooksSummary = topHooksInsight?.insight_data?.summary || "";
|
|
8804
9180
|
const timingSummary = optimalTimingInsight?.insight_data?.summary || "";
|
|
@@ -8809,7 +9185,10 @@ function transformInsightsToPerformanceContext(projectId, insights) {
|
|
|
8809
9185
|
if (hooksSummary) promptParts.push(hooksSummary);
|
|
8810
9186
|
if (timingSummary) promptParts.push(timingSummary);
|
|
8811
9187
|
if (modelSummary) promptParts.push(modelSummary);
|
|
8812
|
-
if (topHooks.length)
|
|
9188
|
+
if (topHooks.length)
|
|
9189
|
+
promptParts.push(
|
|
9190
|
+
`Top performing hooks: ${topHooks.slice(0, 3).join(", ")}`
|
|
9191
|
+
);
|
|
8813
9192
|
return {
|
|
8814
9193
|
projectId,
|
|
8815
9194
|
hasHistoricalData: true,
|
|
@@ -8835,7 +9214,7 @@ function transformInsightsToPerformanceContext(projectId, insights) {
|
|
|
8835
9214
|
function asEnvelope7(data) {
|
|
8836
9215
|
return {
|
|
8837
9216
|
_meta: {
|
|
8838
|
-
version:
|
|
9217
|
+
version: MCP_VERSION,
|
|
8839
9218
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
8840
9219
|
},
|
|
8841
9220
|
data
|
|
@@ -8858,7 +9237,12 @@ function registerIdeationContextTools(server2) {
|
|
|
8858
9237
|
const { data: member } = await supabase.from("organization_members").select("organization_id").eq("user_id", userId).limit(1).single();
|
|
8859
9238
|
if (!member?.organization_id) {
|
|
8860
9239
|
return {
|
|
8861
|
-
content: [
|
|
9240
|
+
content: [
|
|
9241
|
+
{
|
|
9242
|
+
type: "text",
|
|
9243
|
+
text: "No organization found for current user."
|
|
9244
|
+
}
|
|
9245
|
+
],
|
|
8862
9246
|
isError: true
|
|
8863
9247
|
};
|
|
8864
9248
|
}
|
|
@@ -8866,7 +9250,10 @@ function registerIdeationContextTools(server2) {
|
|
|
8866
9250
|
if (projectsError) {
|
|
8867
9251
|
return {
|
|
8868
9252
|
content: [
|
|
8869
|
-
{
|
|
9253
|
+
{
|
|
9254
|
+
type: "text",
|
|
9255
|
+
text: `Failed to resolve projects: ${projectsError.message}`
|
|
9256
|
+
}
|
|
8870
9257
|
],
|
|
8871
9258
|
isError: true
|
|
8872
9259
|
};
|
|
@@ -8874,7 +9261,12 @@ function registerIdeationContextTools(server2) {
|
|
|
8874
9261
|
const projectIds = (projects || []).map((p) => p.id);
|
|
8875
9262
|
if (projectIds.length === 0) {
|
|
8876
9263
|
return {
|
|
8877
|
-
content: [
|
|
9264
|
+
content: [
|
|
9265
|
+
{
|
|
9266
|
+
type: "text",
|
|
9267
|
+
text: "No projects found for current user."
|
|
9268
|
+
}
|
|
9269
|
+
],
|
|
8878
9270
|
isError: true
|
|
8879
9271
|
};
|
|
8880
9272
|
}
|
|
@@ -8883,7 +9275,10 @@ function registerIdeationContextTools(server2) {
|
|
|
8883
9275
|
if (!selectedProjectId) {
|
|
8884
9276
|
return {
|
|
8885
9277
|
content: [
|
|
8886
|
-
{
|
|
9278
|
+
{
|
|
9279
|
+
type: "text",
|
|
9280
|
+
text: "No accessible project found for current user."
|
|
9281
|
+
}
|
|
8887
9282
|
],
|
|
8888
9283
|
isError: true
|
|
8889
9284
|
};
|
|
@@ -8901,7 +9296,9 @@ function registerIdeationContextTools(server2) {
|
|
|
8901
9296
|
}
|
|
8902
9297
|
const since = /* @__PURE__ */ new Date();
|
|
8903
9298
|
since.setDate(since.getDate() - lookbackDays);
|
|
8904
|
-
const { data: insights, error } = await supabase.from("performance_insights").select(
|
|
9299
|
+
const { data: insights, error } = await supabase.from("performance_insights").select(
|
|
9300
|
+
"id, project_id, insight_type, insight_data, generated_at, expires_at"
|
|
9301
|
+
).eq("project_id", selectedProjectId).gte("generated_at", since.toISOString()).gt("expires_at", (/* @__PURE__ */ new Date()).toISOString()).order("generated_at", { ascending: false }).limit(30);
|
|
8905
9302
|
if (error) {
|
|
8906
9303
|
return {
|
|
8907
9304
|
content: [
|
|
@@ -8919,7 +9316,12 @@ function registerIdeationContextTools(server2) {
|
|
|
8919
9316
|
);
|
|
8920
9317
|
if (format === "json") {
|
|
8921
9318
|
return {
|
|
8922
|
-
content: [
|
|
9319
|
+
content: [
|
|
9320
|
+
{
|
|
9321
|
+
type: "text",
|
|
9322
|
+
text: JSON.stringify(asEnvelope7(context), null, 2)
|
|
9323
|
+
}
|
|
9324
|
+
]
|
|
8923
9325
|
};
|
|
8924
9326
|
}
|
|
8925
9327
|
const lines = [
|
|
@@ -8940,10 +9342,11 @@ function registerIdeationContextTools(server2) {
|
|
|
8940
9342
|
// src/tools/credits.ts
|
|
8941
9343
|
init_supabase();
|
|
8942
9344
|
import { z as z12 } from "zod";
|
|
9345
|
+
init_version();
|
|
8943
9346
|
function asEnvelope8(data) {
|
|
8944
9347
|
return {
|
|
8945
9348
|
_meta: {
|
|
8946
|
-
version:
|
|
9349
|
+
version: MCP_VERSION,
|
|
8947
9350
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
8948
9351
|
},
|
|
8949
9352
|
data
|
|
@@ -8982,7 +9385,12 @@ function registerCreditsTools(server2) {
|
|
|
8982
9385
|
};
|
|
8983
9386
|
if ((response_format || "text") === "json") {
|
|
8984
9387
|
return {
|
|
8985
|
-
content: [
|
|
9388
|
+
content: [
|
|
9389
|
+
{
|
|
9390
|
+
type: "text",
|
|
9391
|
+
text: JSON.stringify(asEnvelope8(payload), null, 2)
|
|
9392
|
+
}
|
|
9393
|
+
]
|
|
8986
9394
|
};
|
|
8987
9395
|
}
|
|
8988
9396
|
return {
|
|
@@ -9016,7 +9424,12 @@ Monthly used: ${payload.monthlyUsed}` + (payload.monthlyLimit ? ` / ${payload.mo
|
|
|
9016
9424
|
};
|
|
9017
9425
|
if ((response_format || "text") === "json") {
|
|
9018
9426
|
return {
|
|
9019
|
-
content: [
|
|
9427
|
+
content: [
|
|
9428
|
+
{
|
|
9429
|
+
type: "text",
|
|
9430
|
+
text: JSON.stringify(asEnvelope8(payload), null, 2)
|
|
9431
|
+
}
|
|
9432
|
+
]
|
|
9020
9433
|
};
|
|
9021
9434
|
}
|
|
9022
9435
|
return {
|
|
@@ -9039,11 +9452,12 @@ Assets remaining: ${payload.remainingAssets ?? "unlimited"}`
|
|
|
9039
9452
|
|
|
9040
9453
|
// src/tools/loop-summary.ts
|
|
9041
9454
|
init_supabase();
|
|
9455
|
+
init_version();
|
|
9042
9456
|
import { z as z13 } from "zod";
|
|
9043
9457
|
function asEnvelope9(data) {
|
|
9044
9458
|
return {
|
|
9045
9459
|
_meta: {
|
|
9046
|
-
version:
|
|
9460
|
+
version: MCP_VERSION,
|
|
9047
9461
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
9048
9462
|
},
|
|
9049
9463
|
data
|
|
@@ -9082,7 +9496,12 @@ function registerLoopSummaryTools(server2) {
|
|
|
9082
9496
|
const { data: membership } = await supabase.from("organization_members").select("organization_id").eq("user_id", userId).eq("organization_id", project.organization_id).maybeSingle();
|
|
9083
9497
|
if (!membership) {
|
|
9084
9498
|
return {
|
|
9085
|
-
content: [
|
|
9499
|
+
content: [
|
|
9500
|
+
{
|
|
9501
|
+
type: "text",
|
|
9502
|
+
text: "Project is not accessible to current user."
|
|
9503
|
+
}
|
|
9504
|
+
],
|
|
9086
9505
|
isError: true
|
|
9087
9506
|
};
|
|
9088
9507
|
}
|
|
@@ -9105,7 +9524,12 @@ function registerLoopSummaryTools(server2) {
|
|
|
9105
9524
|
};
|
|
9106
9525
|
if ((response_format || "text") === "json") {
|
|
9107
9526
|
return {
|
|
9108
|
-
content: [
|
|
9527
|
+
content: [
|
|
9528
|
+
{
|
|
9529
|
+
type: "text",
|
|
9530
|
+
text: JSON.stringify(asEnvelope9(payload), null, 2)
|
|
9531
|
+
}
|
|
9532
|
+
]
|
|
9109
9533
|
};
|
|
9110
9534
|
}
|
|
9111
9535
|
return {
|
|
@@ -9435,8 +9859,12 @@ ${"=".repeat(40)}
|
|
|
9435
9859
|
init_edge_function();
|
|
9436
9860
|
init_supabase();
|
|
9437
9861
|
import { z as z16 } from "zod";
|
|
9862
|
+
init_version();
|
|
9438
9863
|
function asEnvelope12(data) {
|
|
9439
|
-
return {
|
|
9864
|
+
return {
|
|
9865
|
+
_meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() },
|
|
9866
|
+
data
|
|
9867
|
+
};
|
|
9440
9868
|
}
|
|
9441
9869
|
function isYouTubeUrl(url) {
|
|
9442
9870
|
if (/youtube\.com\/watch|youtu\.be\//.test(url)) return "video";
|
|
@@ -9467,13 +9895,17 @@ Metadata:`);
|
|
|
9467
9895
|
if (m.tags?.length) lines.push(` Tags: ${m.tags.join(", ")}`);
|
|
9468
9896
|
}
|
|
9469
9897
|
if (content.features?.length)
|
|
9470
|
-
lines.push(
|
|
9898
|
+
lines.push(
|
|
9899
|
+
`
|
|
9471
9900
|
Features:
|
|
9472
|
-
${content.features.map((f) => ` - ${f}`).join("\n")}`
|
|
9901
|
+
${content.features.map((f) => ` - ${f}`).join("\n")}`
|
|
9902
|
+
);
|
|
9473
9903
|
if (content.benefits?.length)
|
|
9474
|
-
lines.push(
|
|
9904
|
+
lines.push(
|
|
9905
|
+
`
|
|
9475
9906
|
Benefits:
|
|
9476
|
-
${content.benefits.map((b) => ` - ${b}`).join("\n")}`
|
|
9907
|
+
${content.benefits.map((b) => ` - ${b}`).join("\n")}`
|
|
9908
|
+
);
|
|
9477
9909
|
if (content.usp) lines.push(`
|
|
9478
9910
|
USP: ${content.usp}`);
|
|
9479
9911
|
if (content.suggested_hooks?.length)
|
|
@@ -9495,12 +9927,20 @@ function registerExtractionTools(server2) {
|
|
|
9495
9927
|
max_results: z16.number().min(1).max(100).default(10).describe("Max comments to include"),
|
|
9496
9928
|
response_format: z16.enum(["text", "json"]).default("text")
|
|
9497
9929
|
},
|
|
9498
|
-
async ({
|
|
9930
|
+
async ({
|
|
9931
|
+
url,
|
|
9932
|
+
extract_type,
|
|
9933
|
+
include_comments,
|
|
9934
|
+
max_results,
|
|
9935
|
+
response_format
|
|
9936
|
+
}) => {
|
|
9499
9937
|
const startedAt = Date.now();
|
|
9500
9938
|
const ssrfCheck = await validateUrlForSSRF(url);
|
|
9501
9939
|
if (!ssrfCheck.isValid) {
|
|
9502
9940
|
return {
|
|
9503
|
-
content: [
|
|
9941
|
+
content: [
|
|
9942
|
+
{ type: "text", text: `URL blocked: ${ssrfCheck.error}` }
|
|
9943
|
+
],
|
|
9504
9944
|
isError: true
|
|
9505
9945
|
};
|
|
9506
9946
|
}
|
|
@@ -9628,13 +10068,21 @@ function registerExtractionTools(server2) {
|
|
|
9628
10068
|
if (response_format === "json") {
|
|
9629
10069
|
return {
|
|
9630
10070
|
content: [
|
|
9631
|
-
{
|
|
10071
|
+
{
|
|
10072
|
+
type: "text",
|
|
10073
|
+
text: JSON.stringify(asEnvelope12(extracted), null, 2)
|
|
10074
|
+
}
|
|
9632
10075
|
],
|
|
9633
10076
|
isError: false
|
|
9634
10077
|
};
|
|
9635
10078
|
}
|
|
9636
10079
|
return {
|
|
9637
|
-
content: [
|
|
10080
|
+
content: [
|
|
10081
|
+
{
|
|
10082
|
+
type: "text",
|
|
10083
|
+
text: formatExtractedContentAsText(extracted)
|
|
10084
|
+
}
|
|
10085
|
+
],
|
|
9638
10086
|
isError: false
|
|
9639
10087
|
};
|
|
9640
10088
|
} catch (err) {
|
|
@@ -9647,7 +10095,9 @@ function registerExtractionTools(server2) {
|
|
|
9647
10095
|
details: { url, error: message }
|
|
9648
10096
|
});
|
|
9649
10097
|
return {
|
|
9650
|
-
content: [
|
|
10098
|
+
content: [
|
|
10099
|
+
{ type: "text", text: `Extraction failed: ${message}` }
|
|
10100
|
+
],
|
|
9651
10101
|
isError: true
|
|
9652
10102
|
};
|
|
9653
10103
|
}
|
|
@@ -9658,9 +10108,13 @@ function registerExtractionTools(server2) {
|
|
|
9658
10108
|
// src/tools/quality.ts
|
|
9659
10109
|
init_quality();
|
|
9660
10110
|
init_supabase();
|
|
10111
|
+
init_version();
|
|
9661
10112
|
import { z as z17 } from "zod";
|
|
9662
10113
|
function asEnvelope13(data) {
|
|
9663
|
-
return {
|
|
10114
|
+
return {
|
|
10115
|
+
_meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() },
|
|
10116
|
+
data
|
|
10117
|
+
};
|
|
9664
10118
|
}
|
|
9665
10119
|
function registerQualityTools(server2) {
|
|
9666
10120
|
server2.tool(
|
|
@@ -9716,7 +10170,12 @@ function registerQualityTools(server2) {
|
|
|
9716
10170
|
});
|
|
9717
10171
|
if (response_format === "json") {
|
|
9718
10172
|
return {
|
|
9719
|
-
content: [
|
|
10173
|
+
content: [
|
|
10174
|
+
{
|
|
10175
|
+
type: "text",
|
|
10176
|
+
text: JSON.stringify(asEnvelope13(result), null, 2)
|
|
10177
|
+
}
|
|
10178
|
+
],
|
|
9720
10179
|
isError: false
|
|
9721
10180
|
};
|
|
9722
10181
|
}
|
|
@@ -9726,7 +10185,9 @@ function registerQualityTools(server2) {
|
|
|
9726
10185
|
);
|
|
9727
10186
|
lines.push("");
|
|
9728
10187
|
for (const cat of result.categories) {
|
|
9729
|
-
lines.push(
|
|
10188
|
+
lines.push(
|
|
10189
|
+
` ${cat.name}: ${cat.score}/${cat.maxScore} \u2014 ${cat.detail}`
|
|
10190
|
+
);
|
|
9730
10191
|
}
|
|
9731
10192
|
if (result.blockers.length > 0) {
|
|
9732
10193
|
lines.push("");
|
|
@@ -9737,7 +10198,10 @@ function registerQualityTools(server2) {
|
|
|
9737
10198
|
}
|
|
9738
10199
|
lines.push("");
|
|
9739
10200
|
lines.push(`Threshold: ${result.threshold}/${result.maxTotal}`);
|
|
9740
|
-
return {
|
|
10201
|
+
return {
|
|
10202
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
10203
|
+
isError: false
|
|
10204
|
+
};
|
|
9741
10205
|
}
|
|
9742
10206
|
);
|
|
9743
10207
|
server2.tool(
|
|
@@ -9778,7 +10242,9 @@ function registerQualityTools(server2) {
|
|
|
9778
10242
|
});
|
|
9779
10243
|
const scores = postsWithQuality.map((p) => p.quality.score);
|
|
9780
10244
|
const passed = postsWithQuality.filter((p) => p.quality.passed).length;
|
|
9781
|
-
const avgScore = scores.length > 0 ? Math.round(
|
|
10245
|
+
const avgScore = scores.length > 0 ? Math.round(
|
|
10246
|
+
scores.reduce((a, b) => a + b, 0) / scores.length * 10
|
|
10247
|
+
) / 10 : 0;
|
|
9782
10248
|
const summary = {
|
|
9783
10249
|
total_posts: plan.posts.length,
|
|
9784
10250
|
passed,
|
|
@@ -9797,25 +10263,36 @@ function registerQualityTools(server2) {
|
|
|
9797
10263
|
content: [
|
|
9798
10264
|
{
|
|
9799
10265
|
type: "text",
|
|
9800
|
-
text: JSON.stringify(
|
|
10266
|
+
text: JSON.stringify(
|
|
10267
|
+
asEnvelope13({ posts: postsWithQuality, summary }),
|
|
10268
|
+
null,
|
|
10269
|
+
2
|
|
10270
|
+
)
|
|
9801
10271
|
}
|
|
9802
10272
|
],
|
|
9803
10273
|
isError: false
|
|
9804
10274
|
};
|
|
9805
10275
|
}
|
|
9806
10276
|
const lines = [];
|
|
9807
|
-
lines.push(
|
|
10277
|
+
lines.push(
|
|
10278
|
+
`PLAN QUALITY: ${passed}/${plan.posts.length} passed (avg: ${avgScore}/35)`
|
|
10279
|
+
);
|
|
9808
10280
|
lines.push("");
|
|
9809
10281
|
for (const post of postsWithQuality) {
|
|
9810
10282
|
const icon = post.quality.passed ? "[PASS]" : "[FAIL]";
|
|
9811
|
-
lines.push(
|
|
10283
|
+
lines.push(
|
|
10284
|
+
`${icon} ${post.id} | ${post.platform} | ${post.quality.score}/35`
|
|
10285
|
+
);
|
|
9812
10286
|
if (post.quality.blockers.length > 0) {
|
|
9813
10287
|
for (const b of post.quality.blockers) {
|
|
9814
10288
|
lines.push(` - ${b}`);
|
|
9815
10289
|
}
|
|
9816
10290
|
}
|
|
9817
10291
|
}
|
|
9818
|
-
return {
|
|
10292
|
+
return {
|
|
10293
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
10294
|
+
isError: false
|
|
10295
|
+
};
|
|
9819
10296
|
}
|
|
9820
10297
|
);
|
|
9821
10298
|
}
|
|
@@ -9825,11 +10302,15 @@ init_edge_function();
|
|
|
9825
10302
|
init_supabase();
|
|
9826
10303
|
import { z as z18 } from "zod";
|
|
9827
10304
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
10305
|
+
init_version();
|
|
9828
10306
|
function toRecord(value) {
|
|
9829
10307
|
return value && typeof value === "object" ? value : void 0;
|
|
9830
10308
|
}
|
|
9831
10309
|
function asEnvelope14(data) {
|
|
9832
|
-
return {
|
|
10310
|
+
return {
|
|
10311
|
+
_meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() },
|
|
10312
|
+
data
|
|
10313
|
+
};
|
|
9833
10314
|
}
|
|
9834
10315
|
function tomorrowIsoDate() {
|
|
9835
10316
|
const d = /* @__PURE__ */ new Date();
|
|
@@ -9867,7 +10348,9 @@ function formatPlanAsText(plan) {
|
|
|
9867
10348
|
lines.push(`WEEKLY CONTENT PLAN: "${plan.topic}"`);
|
|
9868
10349
|
lines.push(`Period: ${plan.start_date} to ${plan.end_date}`);
|
|
9869
10350
|
lines.push(`Platforms: ${plan.platforms.join(", ")}`);
|
|
9870
|
-
lines.push(
|
|
10351
|
+
lines.push(
|
|
10352
|
+
`Posts: ${plan.posts.length} | Estimated credits: ~${plan.estimated_credits}`
|
|
10353
|
+
);
|
|
9871
10354
|
if (plan.plan_id) lines.push(`Plan ID: ${plan.plan_id}`);
|
|
9872
10355
|
if (plan.insights_applied?.has_historical_data) {
|
|
9873
10356
|
lines.push("");
|
|
@@ -9882,7 +10365,9 @@ function formatPlanAsText(plan) {
|
|
|
9882
10365
|
`- Best posting time: ${days[timing.dayOfWeek] ?? timing.dayOfWeek} ${timing.hourOfDay}:00`
|
|
9883
10366
|
);
|
|
9884
10367
|
}
|
|
9885
|
-
lines.push(
|
|
10368
|
+
lines.push(
|
|
10369
|
+
`- Recommended model: ${plan.insights_applied.recommended_model ?? "N/A"}`
|
|
10370
|
+
);
|
|
9886
10371
|
lines.push(`- Insights count: ${plan.insights_applied.insights_count}`);
|
|
9887
10372
|
}
|
|
9888
10373
|
lines.push("");
|
|
@@ -9903,9 +10388,11 @@ function formatPlanAsText(plan) {
|
|
|
9903
10388
|
` Caption: ${post.caption.slice(0, 200)}${post.caption.length > 200 ? "..." : ""}`
|
|
9904
10389
|
);
|
|
9905
10390
|
if (post.title) lines.push(` Title: ${post.title}`);
|
|
9906
|
-
if (post.visual_direction)
|
|
10391
|
+
if (post.visual_direction)
|
|
10392
|
+
lines.push(` Visual: ${post.visual_direction}`);
|
|
9907
10393
|
if (post.media_type) lines.push(` Media: ${post.media_type}`);
|
|
9908
|
-
if (post.hashtags?.length)
|
|
10394
|
+
if (post.hashtags?.length)
|
|
10395
|
+
lines.push(` Hashtags: ${post.hashtags.join(" ")}`);
|
|
9909
10396
|
lines.push("");
|
|
9910
10397
|
}
|
|
9911
10398
|
}
|
|
@@ -9988,7 +10475,10 @@ function registerPlanningTools(server2) {
|
|
|
9988
10475
|
"mcp-data",
|
|
9989
10476
|
{
|
|
9990
10477
|
action: "brand-profile",
|
|
9991
|
-
...resolvedProjectId ? {
|
|
10478
|
+
...resolvedProjectId ? {
|
|
10479
|
+
projectId: resolvedProjectId,
|
|
10480
|
+
project_id: resolvedProjectId
|
|
10481
|
+
} : {}
|
|
9992
10482
|
},
|
|
9993
10483
|
{ timeoutMs: 15e3 }
|
|
9994
10484
|
);
|
|
@@ -10192,7 +10682,12 @@ ${rawText.slice(0, 1e3)}`
|
|
|
10192
10682
|
details: { topic, error: `plan persistence failed: ${message}` }
|
|
10193
10683
|
});
|
|
10194
10684
|
return {
|
|
10195
|
-
content: [
|
|
10685
|
+
content: [
|
|
10686
|
+
{
|
|
10687
|
+
type: "text",
|
|
10688
|
+
text: `Plan persistence failed: ${message}`
|
|
10689
|
+
}
|
|
10690
|
+
],
|
|
10196
10691
|
isError: true
|
|
10197
10692
|
};
|
|
10198
10693
|
}
|
|
@@ -10206,7 +10701,12 @@ ${rawText.slice(0, 1e3)}`
|
|
|
10206
10701
|
});
|
|
10207
10702
|
if (response_format === "json") {
|
|
10208
10703
|
return {
|
|
10209
|
-
content: [
|
|
10704
|
+
content: [
|
|
10705
|
+
{
|
|
10706
|
+
type: "text",
|
|
10707
|
+
text: JSON.stringify(asEnvelope14(plan), null, 2)
|
|
10708
|
+
}
|
|
10709
|
+
],
|
|
10210
10710
|
isError: false
|
|
10211
10711
|
};
|
|
10212
10712
|
}
|
|
@@ -10224,7 +10724,12 @@ ${rawText.slice(0, 1e3)}`
|
|
|
10224
10724
|
details: { topic, error: message }
|
|
10225
10725
|
});
|
|
10226
10726
|
return {
|
|
10227
|
-
content: [
|
|
10727
|
+
content: [
|
|
10728
|
+
{
|
|
10729
|
+
type: "text",
|
|
10730
|
+
text: `Plan generation failed: ${message}`
|
|
10731
|
+
}
|
|
10732
|
+
],
|
|
10228
10733
|
isError: true
|
|
10229
10734
|
};
|
|
10230
10735
|
}
|
|
@@ -10284,7 +10789,11 @@ ${rawText.slice(0, 1e3)}`
|
|
|
10284
10789
|
toolName: "save_content_plan",
|
|
10285
10790
|
status: "success",
|
|
10286
10791
|
durationMs,
|
|
10287
|
-
details: {
|
|
10792
|
+
details: {
|
|
10793
|
+
plan_id: planId,
|
|
10794
|
+
project_id: resolvedProjectId,
|
|
10795
|
+
status: normalizedStatus
|
|
10796
|
+
}
|
|
10288
10797
|
});
|
|
10289
10798
|
const result = {
|
|
10290
10799
|
plan_id: planId,
|
|
@@ -10293,13 +10802,21 @@ ${rawText.slice(0, 1e3)}`
|
|
|
10293
10802
|
};
|
|
10294
10803
|
if (response_format === "json") {
|
|
10295
10804
|
return {
|
|
10296
|
-
content: [
|
|
10805
|
+
content: [
|
|
10806
|
+
{
|
|
10807
|
+
type: "text",
|
|
10808
|
+
text: JSON.stringify(asEnvelope14(result), null, 2)
|
|
10809
|
+
}
|
|
10810
|
+
],
|
|
10297
10811
|
isError: false
|
|
10298
10812
|
};
|
|
10299
10813
|
}
|
|
10300
10814
|
return {
|
|
10301
10815
|
content: [
|
|
10302
|
-
{
|
|
10816
|
+
{
|
|
10817
|
+
type: "text",
|
|
10818
|
+
text: `Saved content plan ${planId} (${normalizedStatus}).`
|
|
10819
|
+
}
|
|
10303
10820
|
],
|
|
10304
10821
|
isError: false
|
|
10305
10822
|
};
|
|
@@ -10313,7 +10830,12 @@ ${rawText.slice(0, 1e3)}`
|
|
|
10313
10830
|
details: { error: message }
|
|
10314
10831
|
});
|
|
10315
10832
|
return {
|
|
10316
|
-
content: [
|
|
10833
|
+
content: [
|
|
10834
|
+
{
|
|
10835
|
+
type: "text",
|
|
10836
|
+
text: `Failed to save content plan: ${message}`
|
|
10837
|
+
}
|
|
10838
|
+
],
|
|
10317
10839
|
isError: true
|
|
10318
10840
|
};
|
|
10319
10841
|
}
|
|
@@ -10329,7 +10851,9 @@ ${rawText.slice(0, 1e3)}`
|
|
|
10329
10851
|
async ({ plan_id, response_format }) => {
|
|
10330
10852
|
const supabase = getSupabaseClient();
|
|
10331
10853
|
const userId = await getDefaultUserId();
|
|
10332
|
-
const { data, error } = await supabase.from("content_plans").select(
|
|
10854
|
+
const { data, error } = await supabase.from("content_plans").select(
|
|
10855
|
+
"id, topic, status, plan_payload, insights_applied, created_at, updated_at"
|
|
10856
|
+
).eq("id", plan_id).eq("user_id", userId).maybeSingle();
|
|
10333
10857
|
if (error) {
|
|
10334
10858
|
return {
|
|
10335
10859
|
content: [
|
|
@@ -10344,7 +10868,10 @@ ${rawText.slice(0, 1e3)}`
|
|
|
10344
10868
|
if (!data) {
|
|
10345
10869
|
return {
|
|
10346
10870
|
content: [
|
|
10347
|
-
{
|
|
10871
|
+
{
|
|
10872
|
+
type: "text",
|
|
10873
|
+
text: `No content plan found for plan_id=${plan_id}`
|
|
10874
|
+
}
|
|
10348
10875
|
],
|
|
10349
10876
|
isError: true
|
|
10350
10877
|
};
|
|
@@ -10360,7 +10887,12 @@ ${rawText.slice(0, 1e3)}`
|
|
|
10360
10887
|
};
|
|
10361
10888
|
if (response_format === "json") {
|
|
10362
10889
|
return {
|
|
10363
|
-
content: [
|
|
10890
|
+
content: [
|
|
10891
|
+
{
|
|
10892
|
+
type: "text",
|
|
10893
|
+
text: JSON.stringify(asEnvelope14(payload), null, 2)
|
|
10894
|
+
}
|
|
10895
|
+
],
|
|
10364
10896
|
isError: false
|
|
10365
10897
|
};
|
|
10366
10898
|
}
|
|
@@ -10371,7 +10903,10 @@ ${rawText.slice(0, 1e3)}`
|
|
|
10371
10903
|
`Status: ${data.status}`,
|
|
10372
10904
|
`Posts: ${Array.isArray(plan?.posts) ? plan.posts.length : 0}`
|
|
10373
10905
|
];
|
|
10374
|
-
return {
|
|
10906
|
+
return {
|
|
10907
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
10908
|
+
isError: false
|
|
10909
|
+
};
|
|
10375
10910
|
}
|
|
10376
10911
|
);
|
|
10377
10912
|
server2.tool(
|
|
@@ -10414,14 +10949,19 @@ ${rawText.slice(0, 1e3)}`
|
|
|
10414
10949
|
if (!stored?.plan_payload) {
|
|
10415
10950
|
return {
|
|
10416
10951
|
content: [
|
|
10417
|
-
{
|
|
10952
|
+
{
|
|
10953
|
+
type: "text",
|
|
10954
|
+
text: `No content plan found for plan_id=${plan_id}`
|
|
10955
|
+
}
|
|
10418
10956
|
],
|
|
10419
10957
|
isError: true
|
|
10420
10958
|
};
|
|
10421
10959
|
}
|
|
10422
10960
|
const plan = stored.plan_payload;
|
|
10423
10961
|
const existingPosts = Array.isArray(plan.posts) ? plan.posts : [];
|
|
10424
|
-
const updatesById = new Map(
|
|
10962
|
+
const updatesById = new Map(
|
|
10963
|
+
post_updates.map((update) => [update.post_id, update])
|
|
10964
|
+
);
|
|
10425
10965
|
const updatedPosts = existingPosts.map((post) => {
|
|
10426
10966
|
const update = updatesById.get(post.id);
|
|
10427
10967
|
if (!update) return post;
|
|
@@ -10439,7 +10979,9 @@ ${rawText.slice(0, 1e3)}`
|
|
|
10439
10979
|
...update.status !== void 0 ? { status: update.status } : {}
|
|
10440
10980
|
};
|
|
10441
10981
|
});
|
|
10442
|
-
const nextStatus = updatedPosts.length > 0 && updatedPosts.every(
|
|
10982
|
+
const nextStatus = updatedPosts.length > 0 && updatedPosts.every(
|
|
10983
|
+
(post) => post.status === "approved" || post.status === "edited"
|
|
10984
|
+
) ? "approved" : stored.status === "scheduled" || stored.status === "completed" ? stored.status : "draft";
|
|
10443
10985
|
const updatedPlan = {
|
|
10444
10986
|
...plan,
|
|
10445
10987
|
posts: updatedPosts
|
|
@@ -10466,7 +11008,12 @@ ${rawText.slice(0, 1e3)}`
|
|
|
10466
11008
|
};
|
|
10467
11009
|
if (response_format === "json") {
|
|
10468
11010
|
return {
|
|
10469
|
-
content: [
|
|
11011
|
+
content: [
|
|
11012
|
+
{
|
|
11013
|
+
type: "text",
|
|
11014
|
+
text: JSON.stringify(asEnvelope14(payload), null, 2)
|
|
11015
|
+
}
|
|
11016
|
+
],
|
|
10470
11017
|
isError: false
|
|
10471
11018
|
};
|
|
10472
11019
|
}
|
|
@@ -10506,7 +11053,10 @@ ${rawText.slice(0, 1e3)}`
|
|
|
10506
11053
|
if (!stored?.plan_payload || !stored.project_id) {
|
|
10507
11054
|
return {
|
|
10508
11055
|
content: [
|
|
10509
|
-
{
|
|
11056
|
+
{
|
|
11057
|
+
type: "text",
|
|
11058
|
+
text: `No content plan found for plan_id=${plan_id}`
|
|
11059
|
+
}
|
|
10510
11060
|
],
|
|
10511
11061
|
isError: true
|
|
10512
11062
|
};
|
|
@@ -10515,7 +11065,12 @@ ${rawText.slice(0, 1e3)}`
|
|
|
10515
11065
|
const posts = Array.isArray(plan.posts) ? plan.posts : [];
|
|
10516
11066
|
if (posts.length === 0) {
|
|
10517
11067
|
return {
|
|
10518
|
-
content: [
|
|
11068
|
+
content: [
|
|
11069
|
+
{
|
|
11070
|
+
type: "text",
|
|
11071
|
+
text: `Plan ${plan_id} has no posts to submit.`
|
|
11072
|
+
}
|
|
11073
|
+
],
|
|
10519
11074
|
isError: true
|
|
10520
11075
|
};
|
|
10521
11076
|
}
|
|
@@ -10558,7 +11113,12 @@ ${rawText.slice(0, 1e3)}`
|
|
|
10558
11113
|
};
|
|
10559
11114
|
if (response_format === "json") {
|
|
10560
11115
|
return {
|
|
10561
|
-
content: [
|
|
11116
|
+
content: [
|
|
11117
|
+
{
|
|
11118
|
+
type: "text",
|
|
11119
|
+
text: JSON.stringify(asEnvelope14(payload), null, 2)
|
|
11120
|
+
}
|
|
11121
|
+
],
|
|
10562
11122
|
isError: false
|
|
10563
11123
|
};
|
|
10564
11124
|
}
|
|
@@ -10578,10 +11138,11 @@ ${rawText.slice(0, 1e3)}`
|
|
|
10578
11138
|
// src/tools/plan-approvals.ts
|
|
10579
11139
|
init_supabase();
|
|
10580
11140
|
import { z as z19 } from "zod";
|
|
11141
|
+
init_version();
|
|
10581
11142
|
function asEnvelope15(data) {
|
|
10582
11143
|
return {
|
|
10583
11144
|
_meta: {
|
|
10584
|
-
version:
|
|
11145
|
+
version: MCP_VERSION,
|
|
10585
11146
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
10586
11147
|
},
|
|
10587
11148
|
data
|
|
@@ -10620,14 +11181,24 @@ function registerPlanApprovalTools(server2) {
|
|
|
10620
11181
|
if (!projectId) {
|
|
10621
11182
|
return {
|
|
10622
11183
|
content: [
|
|
10623
|
-
{
|
|
11184
|
+
{
|
|
11185
|
+
type: "text",
|
|
11186
|
+
text: "No project_id provided and no default project found."
|
|
11187
|
+
}
|
|
10624
11188
|
],
|
|
10625
11189
|
isError: true
|
|
10626
11190
|
};
|
|
10627
11191
|
}
|
|
10628
|
-
const accessError = await assertProjectAccess(
|
|
11192
|
+
const accessError = await assertProjectAccess(
|
|
11193
|
+
supabase,
|
|
11194
|
+
userId,
|
|
11195
|
+
projectId
|
|
11196
|
+
);
|
|
10629
11197
|
if (accessError) {
|
|
10630
|
-
return {
|
|
11198
|
+
return {
|
|
11199
|
+
content: [{ type: "text", text: accessError }],
|
|
11200
|
+
isError: true
|
|
11201
|
+
};
|
|
10631
11202
|
}
|
|
10632
11203
|
const rows = posts.map((post) => ({
|
|
10633
11204
|
plan_id,
|
|
@@ -10656,7 +11227,12 @@ function registerPlanApprovalTools(server2) {
|
|
|
10656
11227
|
};
|
|
10657
11228
|
if ((response_format || "text") === "json") {
|
|
10658
11229
|
return {
|
|
10659
|
-
content: [
|
|
11230
|
+
content: [
|
|
11231
|
+
{
|
|
11232
|
+
type: "text",
|
|
11233
|
+
text: JSON.stringify(asEnvelope15(payload), null, 2)
|
|
11234
|
+
}
|
|
11235
|
+
],
|
|
10660
11236
|
isError: false
|
|
10661
11237
|
};
|
|
10662
11238
|
}
|
|
@@ -10705,14 +11281,22 @@ function registerPlanApprovalTools(server2) {
|
|
|
10705
11281
|
};
|
|
10706
11282
|
if ((response_format || "text") === "json") {
|
|
10707
11283
|
return {
|
|
10708
|
-
content: [
|
|
11284
|
+
content: [
|
|
11285
|
+
{
|
|
11286
|
+
type: "text",
|
|
11287
|
+
text: JSON.stringify(asEnvelope15(payload), null, 2)
|
|
11288
|
+
}
|
|
11289
|
+
],
|
|
10709
11290
|
isError: false
|
|
10710
11291
|
};
|
|
10711
11292
|
}
|
|
10712
11293
|
if (!data || data.length === 0) {
|
|
10713
11294
|
return {
|
|
10714
11295
|
content: [
|
|
10715
|
-
{
|
|
11296
|
+
{
|
|
11297
|
+
type: "text",
|
|
11298
|
+
text: `No approval items found for plan ${plan_id}.`
|
|
11299
|
+
}
|
|
10716
11300
|
],
|
|
10717
11301
|
isError: false
|
|
10718
11302
|
};
|
|
@@ -10725,7 +11309,10 @@ function registerPlanApprovalTools(server2) {
|
|
|
10725
11309
|
}
|
|
10726
11310
|
lines.push("");
|
|
10727
11311
|
lines.push(`Total: ${data.length}`);
|
|
10728
|
-
return {
|
|
11312
|
+
return {
|
|
11313
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
11314
|
+
isError: false
|
|
11315
|
+
};
|
|
10729
11316
|
}
|
|
10730
11317
|
);
|
|
10731
11318
|
server2.tool(
|
|
@@ -10744,7 +11331,10 @@ function registerPlanApprovalTools(server2) {
|
|
|
10744
11331
|
if (decision === "edited" && !edited_post) {
|
|
10745
11332
|
return {
|
|
10746
11333
|
content: [
|
|
10747
|
-
{
|
|
11334
|
+
{
|
|
11335
|
+
type: "text",
|
|
11336
|
+
text: 'edited_post is required when decision is "edited".'
|
|
11337
|
+
}
|
|
10748
11338
|
],
|
|
10749
11339
|
isError: true
|
|
10750
11340
|
};
|
|
@@ -10757,7 +11347,9 @@ function registerPlanApprovalTools(server2) {
|
|
|
10757
11347
|
if (decision === "edited") {
|
|
10758
11348
|
updates.edited_post = edited_post;
|
|
10759
11349
|
}
|
|
10760
|
-
const { data, error } = await supabase.from("content_plan_approvals").update(updates).eq("id", approval_id).eq("user_id", userId).eq("status", "pending").select(
|
|
11350
|
+
const { data, error } = await supabase.from("content_plan_approvals").update(updates).eq("id", approval_id).eq("user_id", userId).eq("status", "pending").select(
|
|
11351
|
+
"id, plan_id, post_id, status, reason, decided_at, original_post, edited_post"
|
|
11352
|
+
).maybeSingle();
|
|
10761
11353
|
if (error) {
|
|
10762
11354
|
return {
|
|
10763
11355
|
content: [
|
|
@@ -10782,7 +11374,12 @@ function registerPlanApprovalTools(server2) {
|
|
|
10782
11374
|
}
|
|
10783
11375
|
if ((response_format || "text") === "json") {
|
|
10784
11376
|
return {
|
|
10785
|
-
content: [
|
|
11377
|
+
content: [
|
|
11378
|
+
{
|
|
11379
|
+
type: "text",
|
|
11380
|
+
text: JSON.stringify(asEnvelope15(data), null, 2)
|
|
11381
|
+
}
|
|
11382
|
+
],
|
|
10786
11383
|
isError: false
|
|
10787
11384
|
};
|
|
10788
11385
|
}
|
|
@@ -10922,16 +11519,40 @@ function registerAllTools(server2, options) {
|
|
|
10922
11519
|
init_posthog();
|
|
10923
11520
|
init_supabase();
|
|
10924
11521
|
init_sn();
|
|
11522
|
+
function flushAndExit(code) {
|
|
11523
|
+
const done = { out: false, err: false };
|
|
11524
|
+
const tryExit = () => {
|
|
11525
|
+
if (done.out && done.err) process.exit(code);
|
|
11526
|
+
};
|
|
11527
|
+
if (process.stdout.writableFinished) {
|
|
11528
|
+
done.out = true;
|
|
11529
|
+
} else {
|
|
11530
|
+
process.stdout.end(() => {
|
|
11531
|
+
done.out = true;
|
|
11532
|
+
tryExit();
|
|
11533
|
+
});
|
|
11534
|
+
}
|
|
11535
|
+
if (process.stderr.writableFinished) {
|
|
11536
|
+
done.err = true;
|
|
11537
|
+
} else {
|
|
11538
|
+
process.stderr.end(() => {
|
|
11539
|
+
done.err = true;
|
|
11540
|
+
tryExit();
|
|
11541
|
+
});
|
|
11542
|
+
}
|
|
11543
|
+
tryExit();
|
|
11544
|
+
setTimeout(() => process.exit(code), 2e3).unref();
|
|
11545
|
+
}
|
|
10925
11546
|
process.on("uncaughtException", (err) => {
|
|
10926
11547
|
process.stderr.write(`MCP server error: ${err.message}
|
|
10927
11548
|
`);
|
|
10928
|
-
|
|
11549
|
+
flushAndExit(1);
|
|
10929
11550
|
});
|
|
10930
11551
|
process.on("unhandledRejection", (reason) => {
|
|
10931
11552
|
const message = reason instanceof Error ? reason.message : String(reason);
|
|
10932
11553
|
process.stderr.write(`MCP server error: ${message}
|
|
10933
11554
|
`);
|
|
10934
|
-
|
|
11555
|
+
flushAndExit(1);
|
|
10935
11556
|
});
|
|
10936
11557
|
var command = process.argv[2];
|
|
10937
11558
|
if (command === "--version" || command === "-v") {
|
|
@@ -10940,7 +11561,11 @@ if (command === "--version" || command === "-v") {
|
|
|
10940
11561
|
const { fileURLToPath } = await import("node:url");
|
|
10941
11562
|
let version = MCP_VERSION;
|
|
10942
11563
|
try {
|
|
10943
|
-
const pkgPath = resolve3(
|
|
11564
|
+
const pkgPath = resolve3(
|
|
11565
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
11566
|
+
"..",
|
|
11567
|
+
"package.json"
|
|
11568
|
+
);
|
|
10944
11569
|
const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
10945
11570
|
version = pkg.version;
|
|
10946
11571
|
} catch {
|
|
@@ -10972,7 +11597,11 @@ if (command === "--help" || command === "-h") {
|
|
|
10972
11597
|
ok: true,
|
|
10973
11598
|
command: "help",
|
|
10974
11599
|
commands: [
|
|
10975
|
-
{
|
|
11600
|
+
{
|
|
11601
|
+
name: "setup",
|
|
11602
|
+
aliases: ["login"],
|
|
11603
|
+
description: "Interactive OAuth setup"
|
|
11604
|
+
},
|
|
10976
11605
|
{ name: "logout", description: "Remove credentials" },
|
|
10977
11606
|
{ name: "whoami", description: "Show auth info" },
|
|
10978
11607
|
{ name: "health", description: "Check connectivity" },
|
|
@@ -11061,10 +11690,14 @@ if (command === "sn") {
|
|
|
11061
11690
|
await runSnCli(process.argv.slice(3));
|
|
11062
11691
|
process.exit(0);
|
|
11063
11692
|
}
|
|
11064
|
-
if (command && !["setup", "login", "logout", "whoami", "health", "sn", "repl"].includes(
|
|
11065
|
-
|
|
11693
|
+
if (command && !["setup", "login", "logout", "whoami", "health", "sn", "repl"].includes(
|
|
11694
|
+
command
|
|
11695
|
+
)) {
|
|
11696
|
+
process.stderr.write(
|
|
11697
|
+
`Unknown command: ${command}
|
|
11066
11698
|
Run socialneuron-mcp --help for usage.
|
|
11067
|
-
`
|
|
11699
|
+
`
|
|
11700
|
+
);
|
|
11068
11701
|
process.exit(1);
|
|
11069
11702
|
}
|
|
11070
11703
|
await initializeAuth();
|