@socialneuron/mcp-server 1.7.12 → 1.7.14
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/README.md +6 -6
- package/dist/http.js +1841 -674
- package/dist/index.js +1537 -669
- package/dist/sn.js +4201 -0
- package/package.json +44 -20
- package/tools.lock.json +119 -0
package/dist/index.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
-
var __esm = (fn, res) => function __init() {
|
|
5
|
-
|
|
4
|
+
var __esm = (fn, res, err) => function __init() {
|
|
5
|
+
if (err) throw err[0];
|
|
6
|
+
try {
|
|
7
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
8
|
+
} catch (e) {
|
|
9
|
+
throw err = [e], e;
|
|
10
|
+
}
|
|
6
11
|
};
|
|
7
12
|
var __export = (target, all) => {
|
|
8
13
|
for (var name in all)
|
|
@@ -14,54 +19,7 @@ var MCP_VERSION;
|
|
|
14
19
|
var init_version = __esm({
|
|
15
20
|
"src/lib/version.ts"() {
|
|
16
21
|
"use strict";
|
|
17
|
-
MCP_VERSION = "1.7.
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
// src/lib/posthog.ts
|
|
22
|
-
import { createHash } from "node:crypto";
|
|
23
|
-
import { PostHog } from "posthog-node";
|
|
24
|
-
function hashUserId(userId) {
|
|
25
|
-
return createHash("sha256").update(`${POSTHOG_SALT}:${userId}`).digest("hex").substring(0, 32);
|
|
26
|
-
}
|
|
27
|
-
function initPostHog() {
|
|
28
|
-
if (isTelemetryDisabled()) return;
|
|
29
|
-
const key = process.env.POSTHOG_KEY || process.env.VITE_POSTHOG_KEY;
|
|
30
|
-
const host = process.env.POSTHOG_HOST || process.env.VITE_POSTHOG_HOST || "https://eu.i.posthog.com";
|
|
31
|
-
if (!key) return;
|
|
32
|
-
client = new PostHog(key, { host, flushAt: 5, flushInterval: 1e4 });
|
|
33
|
-
}
|
|
34
|
-
async function captureToolEvent(args) {
|
|
35
|
-
if (!client) return;
|
|
36
|
-
let userId;
|
|
37
|
-
try {
|
|
38
|
-
userId = await getDefaultUserId();
|
|
39
|
-
} catch {
|
|
40
|
-
userId = "anonymous_mcp";
|
|
41
|
-
}
|
|
42
|
-
client.capture({
|
|
43
|
-
distinctId: hashUserId(userId),
|
|
44
|
-
event: `mcp_tool_${args.status}`,
|
|
45
|
-
properties: {
|
|
46
|
-
tool_name: args.toolName,
|
|
47
|
-
duration_ms: args.durationMs,
|
|
48
|
-
...args.details
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
async function shutdownPostHog() {
|
|
53
|
-
if (client) {
|
|
54
|
-
await client.shutdown();
|
|
55
|
-
client = null;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
var POSTHOG_SALT, client;
|
|
59
|
-
var init_posthog = __esm({
|
|
60
|
-
"src/lib/posthog.ts"() {
|
|
61
|
-
"use strict";
|
|
62
|
-
init_supabase();
|
|
63
|
-
POSTHOG_SALT = "socialneuron-mcp-ph-v1";
|
|
64
|
-
client = null;
|
|
22
|
+
MCP_VERSION = "1.7.14";
|
|
65
23
|
}
|
|
66
24
|
});
|
|
67
25
|
|
|
@@ -70,6 +28,9 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
70
28
|
function getRequestUserId() {
|
|
71
29
|
return requestContext.getStore()?.userId ?? null;
|
|
72
30
|
}
|
|
31
|
+
function getRequestScopes() {
|
|
32
|
+
return requestContext.getStore()?.scopes ?? null;
|
|
33
|
+
}
|
|
73
34
|
function getRequestToken() {
|
|
74
35
|
return requestContext.getStore()?.token ?? null;
|
|
75
36
|
}
|
|
@@ -298,83 +259,12 @@ var init_credentials = __esm({
|
|
|
298
259
|
}
|
|
299
260
|
});
|
|
300
261
|
|
|
301
|
-
// src/lib/validation-cache.ts
|
|
302
|
-
var validation_cache_exports = {};
|
|
303
|
-
__export(validation_cache_exports, {
|
|
304
|
-
clearValidationCache: () => clearValidationCache,
|
|
305
|
-
keyFingerprint: () => keyFingerprint,
|
|
306
|
-
readValidationCache: () => readValidationCache,
|
|
307
|
-
writeValidationCache: () => writeValidationCache
|
|
308
|
-
});
|
|
309
|
-
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, chmodSync } from "node:fs";
|
|
310
|
-
import { join as join2 } from "node:path";
|
|
311
|
-
import { homedir as homedir2 } from "node:os";
|
|
312
|
-
function keyFingerprint(apiKey) {
|
|
313
|
-
return apiKey.substring(0, 6) + "..." + apiKey.slice(-4);
|
|
314
|
-
}
|
|
315
|
-
function readValidationCache(apiKey) {
|
|
316
|
-
try {
|
|
317
|
-
const raw = readFileSync2(CACHE_FILE, "utf-8");
|
|
318
|
-
const cached = JSON.parse(raw);
|
|
319
|
-
if (cached.keyFingerprint !== keyFingerprint(apiKey)) {
|
|
320
|
-
return null;
|
|
321
|
-
}
|
|
322
|
-
if (Date.now() - cached.cachedAt > CACHE_TTL_MS) {
|
|
323
|
-
return null;
|
|
324
|
-
}
|
|
325
|
-
if (!cached.result.valid) {
|
|
326
|
-
return null;
|
|
327
|
-
}
|
|
328
|
-
if (cached.result.expiresAt) {
|
|
329
|
-
const expiresMs = new Date(cached.result.expiresAt).getTime();
|
|
330
|
-
if (expiresMs <= Date.now()) {
|
|
331
|
-
return null;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
return cached.result;
|
|
335
|
-
} catch {
|
|
336
|
-
return null;
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
function writeValidationCache(apiKey, result) {
|
|
340
|
-
if (!result.valid) return;
|
|
341
|
-
try {
|
|
342
|
-
mkdirSync2(CONFIG_DIR2, { recursive: true });
|
|
343
|
-
const entry = {
|
|
344
|
-
keyFingerprint: keyFingerprint(apiKey),
|
|
345
|
-
result,
|
|
346
|
-
cachedAt: Date.now()
|
|
347
|
-
};
|
|
348
|
-
writeFileSync2(CACHE_FILE, JSON.stringify(entry, null, 2), { mode: 384 });
|
|
349
|
-
try {
|
|
350
|
-
chmodSync(CACHE_FILE, 384);
|
|
351
|
-
} catch {
|
|
352
|
-
}
|
|
353
|
-
} catch {
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
function clearValidationCache() {
|
|
357
|
-
try {
|
|
358
|
-
writeFileSync2(CACHE_FILE, "{}", { mode: 384 });
|
|
359
|
-
} catch {
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
var CONFIG_DIR2, CACHE_FILE, CACHE_TTL_MS;
|
|
363
|
-
var init_validation_cache = __esm({
|
|
364
|
-
"src/lib/validation-cache.ts"() {
|
|
365
|
-
"use strict";
|
|
366
|
-
CONFIG_DIR2 = join2(homedir2(), ".config", "socialneuron");
|
|
367
|
-
CACHE_FILE = join2(CONFIG_DIR2, "validation-cache.json");
|
|
368
|
-
CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
369
|
-
}
|
|
370
|
-
});
|
|
371
|
-
|
|
372
262
|
// src/auth/api-keys.ts
|
|
373
263
|
var api_keys_exports = {};
|
|
374
264
|
__export(api_keys_exports, {
|
|
375
265
|
validateApiKey: () => validateApiKey
|
|
376
266
|
});
|
|
377
|
-
async function validateApiKey(apiKey) {
|
|
267
|
+
async function validateApiKey(apiKey, _attempt = 0) {
|
|
378
268
|
const supabaseUrl = getSupabaseUrl();
|
|
379
269
|
try {
|
|
380
270
|
const anonKey = process.env.SUPABASE_ANON_KEY || process.env.SOCIALNEURON_ANON_KEY || process.env.VITE_SUPABASE_ANON_KEY || CLOUD_SUPABASE_ANON_KEY;
|
|
@@ -391,21 +281,90 @@ async function validateApiKey(apiKey) {
|
|
|
391
281
|
);
|
|
392
282
|
if (!response.ok) {
|
|
393
283
|
const text = await response.text();
|
|
394
|
-
|
|
284
|
+
const retryable = response.status === 429 || response.status >= 500;
|
|
285
|
+
if (retryable && _attempt < VALIDATE_MAX_RETRIES) {
|
|
286
|
+
await sleep(300 * (_attempt + 1));
|
|
287
|
+
return validateApiKey(apiKey, _attempt + 1);
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
valid: false,
|
|
291
|
+
retryable,
|
|
292
|
+
error: `Validation failed (HTTP ${response.status}): ${text}`
|
|
293
|
+
};
|
|
395
294
|
}
|
|
396
|
-
|
|
397
|
-
return result;
|
|
295
|
+
return await response.json();
|
|
398
296
|
} catch (err) {
|
|
297
|
+
if (_attempt < VALIDATE_MAX_RETRIES) {
|
|
298
|
+
await sleep(300 * (_attempt + 1));
|
|
299
|
+
return validateApiKey(apiKey, _attempt + 1);
|
|
300
|
+
}
|
|
399
301
|
return {
|
|
400
302
|
valid: false,
|
|
303
|
+
retryable: true,
|
|
401
304
|
error: err instanceof Error ? err.message : String(err)
|
|
402
305
|
};
|
|
403
306
|
}
|
|
404
307
|
}
|
|
308
|
+
var VALIDATE_MAX_RETRIES, sleep;
|
|
405
309
|
var init_api_keys = __esm({
|
|
406
310
|
"src/auth/api-keys.ts"() {
|
|
407
311
|
"use strict";
|
|
408
312
|
init_supabase();
|
|
313
|
+
VALIDATE_MAX_RETRIES = 2;
|
|
314
|
+
sleep = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// src/lib/posthog.ts
|
|
319
|
+
var posthog_exports = {};
|
|
320
|
+
__export(posthog_exports, {
|
|
321
|
+
captureToolEvent: () => captureToolEvent,
|
|
322
|
+
initPostHog: () => initPostHog,
|
|
323
|
+
shutdownPostHog: () => shutdownPostHog
|
|
324
|
+
});
|
|
325
|
+
import { createHash } from "node:crypto";
|
|
326
|
+
import { PostHog } from "posthog-node";
|
|
327
|
+
function hashUserId(userId) {
|
|
328
|
+
return createHash("sha256").update(`${POSTHOG_SALT}:${userId}`).digest("hex").substring(0, 32);
|
|
329
|
+
}
|
|
330
|
+
function initPostHog() {
|
|
331
|
+
if (isTelemetryDisabled()) return;
|
|
332
|
+
const key = process.env.POSTHOG_KEY || process.env.VITE_POSTHOG_KEY;
|
|
333
|
+
const host = process.env.POSTHOG_HOST || process.env.VITE_POSTHOG_HOST || "https://eu.i.posthog.com";
|
|
334
|
+
if (!key) return;
|
|
335
|
+
client = new PostHog(key, { host, flushAt: 5, flushInterval: 1e4 });
|
|
336
|
+
}
|
|
337
|
+
async function captureToolEvent(args) {
|
|
338
|
+
if (!client) return;
|
|
339
|
+
let userId;
|
|
340
|
+
try {
|
|
341
|
+
userId = await getDefaultUserId();
|
|
342
|
+
} catch {
|
|
343
|
+
userId = "anonymous_mcp";
|
|
344
|
+
}
|
|
345
|
+
client.capture({
|
|
346
|
+
distinctId: hashUserId(userId),
|
|
347
|
+
event: `mcp_tool_${args.status}`,
|
|
348
|
+
properties: {
|
|
349
|
+
tool_name: args.toolName,
|
|
350
|
+
duration_ms: args.durationMs,
|
|
351
|
+
...args.details
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
async function shutdownPostHog() {
|
|
356
|
+
if (client) {
|
|
357
|
+
await client.shutdown();
|
|
358
|
+
client = null;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
var POSTHOG_SALT, client;
|
|
362
|
+
var init_posthog = __esm({
|
|
363
|
+
"src/lib/posthog.ts"() {
|
|
364
|
+
"use strict";
|
|
365
|
+
init_supabase();
|
|
366
|
+
POSTHOG_SALT = "socialneuron-mcp-ph-v1";
|
|
367
|
+
client = null;
|
|
409
368
|
}
|
|
410
369
|
});
|
|
411
370
|
|
|
@@ -482,7 +441,10 @@ async function getDefaultProjectId() {
|
|
|
482
441
|
if (!userId) return null;
|
|
483
442
|
try {
|
|
484
443
|
const supabase = getSupabaseClient();
|
|
485
|
-
const { data } = await supabase.from("
|
|
444
|
+
const { data: memberships } = await supabase.from("organization_members").select("organization_id").eq("user_id", userId);
|
|
445
|
+
const orgIds = (memberships ?? []).map((m) => m.organization_id);
|
|
446
|
+
if (orgIds.length === 0) return null;
|
|
447
|
+
const { data } = await supabase.from("projects").select("id").in("organization_id", orgIds).order("created_at", { ascending: false }).limit(1).maybeSingle();
|
|
486
448
|
if (data?.id) {
|
|
487
449
|
projectIdCache.set(userId, data.id);
|
|
488
450
|
return data.id;
|
|
@@ -496,33 +458,27 @@ async function initializeAuth() {
|
|
|
496
458
|
const apiKey = await loadApiKey2();
|
|
497
459
|
if (apiKey) {
|
|
498
460
|
authenticatedApiKey = apiKey;
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
console.error("[MCP] Using cached validation (skip remote check)");
|
|
505
|
-
} else {
|
|
506
|
-
const { validateApiKey: validateApiKey2 } = await Promise.resolve().then(() => (init_api_keys(), api_keys_exports));
|
|
507
|
-
result = await validateApiKey2(apiKey);
|
|
508
|
-
if (result.valid) {
|
|
509
|
-
writeValidationCache2(apiKey, result);
|
|
510
|
-
}
|
|
511
|
-
}
|
|
461
|
+
const _quietAuth = !process.argv.includes("--verbose") && (process.env.SN_CLI_QUIET === "1" || ["setup", "login", "logout", "whoami", "health", "sn", "repl"].includes(
|
|
462
|
+
process.argv[2] ?? ""
|
|
463
|
+
));
|
|
464
|
+
const { validateApiKey: validateApiKey2 } = await Promise.resolve().then(() => (init_api_keys(), api_keys_exports));
|
|
465
|
+
const result = await validateApiKey2(apiKey);
|
|
512
466
|
if (result.valid && result.userId) {
|
|
513
467
|
_authMode = "api-key";
|
|
514
468
|
authenticatedUserId = result.userId;
|
|
515
469
|
authenticatedScopes = result.scopes && result.scopes.length > 0 ? result.scopes : ["mcp:read"];
|
|
516
470
|
authenticatedEmail = result.email || null;
|
|
517
471
|
authenticatedExpiresAt = result.expiresAt || null;
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
472
|
+
if (!_quietAuth) {
|
|
473
|
+
console.error(
|
|
474
|
+
"[MCP] Authenticated via API key (prefix: " + apiKey.substring(0, 6) + "..." + apiKey.slice(-4) + ")"
|
|
475
|
+
);
|
|
476
|
+
console.error("[MCP] Scopes: " + authenticatedScopes.join(", "));
|
|
477
|
+
}
|
|
522
478
|
if (authenticatedExpiresAt) {
|
|
523
479
|
const expiresMs = new Date(authenticatedExpiresAt).getTime();
|
|
524
480
|
const daysLeft = Math.ceil((expiresMs - Date.now()) / (1e3 * 60 * 60 * 24));
|
|
525
|
-
console.error("[MCP] Key expires: " + authenticatedExpiresAt);
|
|
481
|
+
if (!_quietAuth) console.error("[MCP] Key expires: " + authenticatedExpiresAt);
|
|
526
482
|
if (daysLeft <= 7) {
|
|
527
483
|
console.error(
|
|
528
484
|
`[MCP] Warning: API key expires in ${daysLeft} day(s). Run: npx @socialneuron/mcp-server login`
|
|
@@ -532,8 +488,13 @@ async function initializeAuth() {
|
|
|
532
488
|
return;
|
|
533
489
|
} else {
|
|
534
490
|
authenticatedApiKey = null;
|
|
491
|
+
if (result.retryable) {
|
|
492
|
+
throw new Error(
|
|
493
|
+
"Temporary issue reaching the auth service \u2014 your session is likely still valid. Wait a moment and retry. If it persists, run `sn login`."
|
|
494
|
+
);
|
|
495
|
+
}
|
|
535
496
|
throw new Error(
|
|
536
|
-
"
|
|
497
|
+
"API key invalid, expired, or revoked. Run `sn login` to reconnect (or `socialneuron-mcp login`)."
|
|
537
498
|
);
|
|
538
499
|
}
|
|
539
500
|
}
|
|
@@ -590,14 +551,13 @@ async function logMcpToolInvocation(args) {
|
|
|
590
551
|
});
|
|
591
552
|
} catch {
|
|
592
553
|
}
|
|
593
|
-
captureToolEvent(args).catch(() => {
|
|
554
|
+
Promise.resolve().then(() => (init_posthog(), posthog_exports)).then(({ captureToolEvent: captureToolEvent2 }) => captureToolEvent2(args)).catch(() => {
|
|
594
555
|
});
|
|
595
556
|
}
|
|
596
557
|
var SUPABASE_URL, SUPABASE_SERVICE_KEY, client2, _authMode, authenticatedUserId, authenticatedScopes, authenticatedEmail, authenticatedExpiresAt, authenticatedApiKey, MCP_RUN_ID, CLOUD_SUPABASE_URL, CLOUD_SUPABASE_ANON_KEY, projectIdCache;
|
|
597
558
|
var init_supabase = __esm({
|
|
598
559
|
"src/lib/supabase.ts"() {
|
|
599
560
|
"use strict";
|
|
600
|
-
init_posthog();
|
|
601
561
|
init_request_context();
|
|
602
562
|
SUPABASE_URL = process.env.SOCIALNEURON_SUPABASE_URL || process.env.SUPABASE_URL || "";
|
|
603
563
|
SUPABASE_SERVICE_KEY = process.env.SOCIALNEURON_SERVICE_KEY || process.env.SUPABASE_SERVICE_ROLE_KEY || "";
|
|
@@ -697,10 +657,16 @@ async function callEdgeFunction(functionName, body, options) {
|
|
|
697
657
|
} catch {
|
|
698
658
|
errorMessage = responseText || `HTTP ${response.status}`;
|
|
699
659
|
}
|
|
700
|
-
if (response.status === 401
|
|
660
|
+
if (response.status === 401) {
|
|
661
|
+
return {
|
|
662
|
+
data: null,
|
|
663
|
+
error: `Authentication failed (HTTP 401). Run 'npx @socialneuron/mcp-server login' to re-authenticate.`
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
if (response.status === 403) {
|
|
701
667
|
return {
|
|
702
668
|
data: null,
|
|
703
|
-
error: `
|
|
669
|
+
error: `Forbidden (HTTP 403): ${errorMessage}. This action isn't permitted for your account, plan, or scope \u2014 your connection is still valid.`
|
|
704
670
|
};
|
|
705
671
|
}
|
|
706
672
|
if (response.status === 429) {
|
|
@@ -1073,13 +1039,15 @@ var init_tool_catalog = __esm({
|
|
|
1073
1039
|
name: "capture_screenshot",
|
|
1074
1040
|
description: "Capture a screenshot of a URL",
|
|
1075
1041
|
module: "screenshot",
|
|
1076
|
-
scope: "mcp:read"
|
|
1042
|
+
scope: "mcp:read",
|
|
1043
|
+
localOnly: true
|
|
1077
1044
|
},
|
|
1078
1045
|
{
|
|
1079
1046
|
name: "capture_app_page",
|
|
1080
1047
|
description: "Capture a screenshot of an app page",
|
|
1081
1048
|
module: "screenshot",
|
|
1082
|
-
scope: "mcp:read"
|
|
1049
|
+
scope: "mcp:read",
|
|
1050
|
+
localOnly: true
|
|
1083
1051
|
},
|
|
1084
1052
|
// remotion
|
|
1085
1053
|
{
|
|
@@ -1292,6 +1260,18 @@ var init_tool_catalog = __esm({
|
|
|
1292
1260
|
module: "discovery",
|
|
1293
1261
|
scope: "mcp:read"
|
|
1294
1262
|
},
|
|
1263
|
+
{
|
|
1264
|
+
name: "search",
|
|
1265
|
+
description: "Search public Social Neuron product, integration, developer, and MCP tool knowledge using the ChatGPT-compatible search schema.",
|
|
1266
|
+
module: "discovery",
|
|
1267
|
+
scope: "mcp:read"
|
|
1268
|
+
},
|
|
1269
|
+
{
|
|
1270
|
+
name: "fetch",
|
|
1271
|
+
description: "Fetch one public Social Neuron knowledge document by ID using the ChatGPT-compatible fetch schema.",
|
|
1272
|
+
module: "discovery",
|
|
1273
|
+
scope: "mcp:read"
|
|
1274
|
+
},
|
|
1295
1275
|
// pipeline
|
|
1296
1276
|
{
|
|
1297
1277
|
name: "check_pipeline_readiness",
|
|
@@ -1459,6 +1439,32 @@ var init_tool_catalog = __esm({
|
|
|
1459
1439
|
description: "List currently-running campaigns with thesis, budget, hero format, and current spend. Used by Hermes pitch skill to bias drafts.",
|
|
1460
1440
|
module: "hermes",
|
|
1461
1441
|
scope: "mcp:read"
|
|
1442
|
+
},
|
|
1443
|
+
// skills (workflow skills — multi-step brand-locked content pipelines)
|
|
1444
|
+
{
|
|
1445
|
+
name: "list_skills",
|
|
1446
|
+
description: "List Social Neuron content workflow skills available to the user. A skill is a brand-locked multi-step pipeline inspired by documented viral patterns (MrBeast 3-second hook, Hormozi pattern interrupt, etc.).",
|
|
1447
|
+
module: "skills",
|
|
1448
|
+
scope: "mcp:read"
|
|
1449
|
+
},
|
|
1450
|
+
{
|
|
1451
|
+
name: "run_skill",
|
|
1452
|
+
description: "Run a Social Neuron workflow skill end-to-end (brand-locked content production). PR #4.4 v1 returns a structured run preview with the step plan, credit cost, and a deep-link to launch in the SN dashboard.",
|
|
1453
|
+
module: "skills",
|
|
1454
|
+
scope: "mcp:write"
|
|
1455
|
+
},
|
|
1456
|
+
// loop observability (growth-loop KPIs + Thompson Sampling bandit posteriors)
|
|
1457
|
+
{
|
|
1458
|
+
name: "get_loop_pulse",
|
|
1459
|
+
description: "Read dynamic loop-health KPIs for the growth loop over the last 7 days (reflection/decision coverage, visual gate pass rate, bandit-update application rate, per-platform uptake, autopilot lag) \u2014 each with an ok/warn/bad status. Use to decide whether the loop is closing or where it is stuck.",
|
|
1460
|
+
module: "loop",
|
|
1461
|
+
scope: "mcp:read"
|
|
1462
|
+
},
|
|
1463
|
+
{
|
|
1464
|
+
name: "get_bandit_state",
|
|
1465
|
+
description: "Read the current Thompson Sampling bandit posteriors for a project \u2014 top-K arms per (arm_type, platform) with Beta(alpha,beta) posterior mean and uncertainty. Use to reason about which hook family / format / timing slot the bandit currently prefers per platform.",
|
|
1466
|
+
module: "loop",
|
|
1467
|
+
scope: "mcp:read"
|
|
1462
1468
|
}
|
|
1463
1469
|
];
|
|
1464
1470
|
}
|
|
@@ -1602,7 +1608,7 @@ function classifyError(err) {
|
|
|
1602
1608
|
hint: "Run: npx @socialneuron/mcp-server login \u2014 Requires a paid plan (Starter+). See https://socialneuron.com/pricing"
|
|
1603
1609
|
};
|
|
1604
1610
|
}
|
|
1605
|
-
if (lower.includes("econnrefused") || lower.includes("etimedout") || lower.includes("fetch failed") || lower.includes("aborterror") || lower.includes("network") || lower.includes("dns")) {
|
|
1611
|
+
if (lower.includes("econnrefused") || lower.includes("etimedout") || lower.includes("fetch failed") || lower.includes("aborterror") || lower.includes("network") || lower.includes("dns") || lower.includes("temporary issue")) {
|
|
1606
1612
|
return {
|
|
1607
1613
|
message,
|
|
1608
1614
|
errorType: "NETWORK",
|
|
@@ -2872,13 +2878,13 @@ __export(presets_exports, {
|
|
|
2872
2878
|
handlePreset: () => handlePreset,
|
|
2873
2879
|
resolvePreset: () => resolvePreset
|
|
2874
2880
|
});
|
|
2875
|
-
import { readFileSync as
|
|
2876
|
-
import { join as
|
|
2877
|
-
import { homedir as
|
|
2881
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "node:fs";
|
|
2882
|
+
import { join as join2 } from "node:path";
|
|
2883
|
+
import { homedir as homedir2 } from "node:os";
|
|
2878
2884
|
function loadUserPresets() {
|
|
2879
2885
|
if (!existsSync2(PRESETS_FILE)) return [];
|
|
2880
2886
|
try {
|
|
2881
|
-
const raw =
|
|
2887
|
+
const raw = readFileSync2(PRESETS_FILE, "utf-8");
|
|
2882
2888
|
const parsed = JSON.parse(raw);
|
|
2883
2889
|
if (!Array.isArray(parsed)) return [];
|
|
2884
2890
|
return parsed;
|
|
@@ -2888,9 +2894,9 @@ function loadUserPresets() {
|
|
|
2888
2894
|
}
|
|
2889
2895
|
function saveUserPresets(presets) {
|
|
2890
2896
|
if (!existsSync2(PRESETS_DIR)) {
|
|
2891
|
-
|
|
2897
|
+
mkdirSync2(PRESETS_DIR, { recursive: true });
|
|
2892
2898
|
}
|
|
2893
|
-
|
|
2899
|
+
writeFileSync2(PRESETS_FILE, JSON.stringify(presets, null, 2) + "\n", "utf-8");
|
|
2894
2900
|
}
|
|
2895
2901
|
function resolvePreset(name) {
|
|
2896
2902
|
const lower = name.toLowerCase();
|
|
@@ -3055,8 +3061,8 @@ var init_presets = __esm({
|
|
|
3055
3061
|
{ name: "linkedin-post", platform: "LinkedIn", maxLength: 3e3, builtin: true },
|
|
3056
3062
|
{ name: "twitter-post", platform: "Twitter", maxLength: 280, builtin: true }
|
|
3057
3063
|
];
|
|
3058
|
-
PRESETS_DIR =
|
|
3059
|
-
PRESETS_FILE =
|
|
3064
|
+
PRESETS_DIR = join2(homedir2(), ".config", "socialneuron");
|
|
3065
|
+
PRESETS_FILE = join2(PRESETS_DIR, "presets.json");
|
|
3060
3066
|
USAGE = `Usage: sn preset <sub-command> [options]
|
|
3061
3067
|
|
|
3062
3068
|
Sub-commands:
|
|
@@ -3259,9 +3265,9 @@ __export(setup_exports, {
|
|
|
3259
3265
|
});
|
|
3260
3266
|
import { createHash as createHash4, randomBytes, randomUUID as randomUUID4 } from "node:crypto";
|
|
3261
3267
|
import { createServer } from "node:http";
|
|
3262
|
-
import { existsSync as existsSync3, mkdirSync as
|
|
3263
|
-
import { homedir as
|
|
3264
|
-
import { join as
|
|
3268
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
3269
|
+
import { homedir as homedir3, platform as platform2 } from "node:os";
|
|
3270
|
+
import { join as join3 } from "node:path";
|
|
3265
3271
|
function base64url(buffer) {
|
|
3266
3272
|
return buffer.toString("base64url");
|
|
3267
3273
|
}
|
|
@@ -3283,8 +3289,8 @@ function getConfigPaths() {
|
|
|
3283
3289
|
const os = platform2();
|
|
3284
3290
|
if (os === "darwin") {
|
|
3285
3291
|
paths.push({
|
|
3286
|
-
path:
|
|
3287
|
-
|
|
3292
|
+
path: join3(
|
|
3293
|
+
homedir3(),
|
|
3288
3294
|
"Library",
|
|
3289
3295
|
"Application Support",
|
|
3290
3296
|
"Claude",
|
|
@@ -3294,15 +3300,15 @@ function getConfigPaths() {
|
|
|
3294
3300
|
});
|
|
3295
3301
|
} else if (os === "linux") {
|
|
3296
3302
|
paths.push({
|
|
3297
|
-
path:
|
|
3303
|
+
path: join3(homedir3(), ".config", "claude", "claude_desktop_config.json"),
|
|
3298
3304
|
name: "Claude Desktop"
|
|
3299
3305
|
});
|
|
3300
3306
|
}
|
|
3301
|
-
const claudeCodeGlobal =
|
|
3307
|
+
const claudeCodeGlobal = join3(homedir3(), ".claude", ".mcp.json");
|
|
3302
3308
|
if (existsSync3(claudeCodeGlobal)) {
|
|
3303
3309
|
paths.push({ path: claudeCodeGlobal, name: "Claude Code (global)" });
|
|
3304
3310
|
}
|
|
3305
|
-
const projectConfig =
|
|
3311
|
+
const projectConfig = join3(process.cwd(), ".mcp.json");
|
|
3306
3312
|
if (existsSync3(projectConfig)) {
|
|
3307
3313
|
paths.push({ path: projectConfig, name: "Claude Code (project)" });
|
|
3308
3314
|
}
|
|
@@ -3312,12 +3318,12 @@ function configureMcpClient(configPath) {
|
|
|
3312
3318
|
try {
|
|
3313
3319
|
let config = {};
|
|
3314
3320
|
if (existsSync3(configPath)) {
|
|
3315
|
-
const raw =
|
|
3321
|
+
const raw = readFileSync3(configPath, "utf-8");
|
|
3316
3322
|
config = JSON.parse(raw);
|
|
3317
3323
|
} else {
|
|
3318
|
-
const dir =
|
|
3324
|
+
const dir = join3(configPath, "..");
|
|
3319
3325
|
if (!existsSync3(dir)) {
|
|
3320
|
-
|
|
3326
|
+
mkdirSync3(dir, { recursive: true });
|
|
3321
3327
|
}
|
|
3322
3328
|
}
|
|
3323
3329
|
if (!config.mcpServers) {
|
|
@@ -3327,7 +3333,7 @@ function configureMcpClient(configPath) {
|
|
|
3327
3333
|
command: "npx",
|
|
3328
3334
|
args: ["-y", "@socialneuron/mcp-server"]
|
|
3329
3335
|
};
|
|
3330
|
-
|
|
3336
|
+
writeFileSync3(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
3331
3337
|
return true;
|
|
3332
3338
|
} catch {
|
|
3333
3339
|
return false;
|
|
@@ -3504,45 +3510,117 @@ var init_setup = __esm({
|
|
|
3504
3510
|
}
|
|
3505
3511
|
});
|
|
3506
3512
|
|
|
3507
|
-
// src/
|
|
3508
|
-
var
|
|
3509
|
-
__export(
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3513
|
+
// src/lib/validation-cache.ts
|
|
3514
|
+
var validation_cache_exports = {};
|
|
3515
|
+
__export(validation_cache_exports, {
|
|
3516
|
+
clearValidationCache: () => clearValidationCache,
|
|
3517
|
+
keyFingerprint: () => keyFingerprint,
|
|
3518
|
+
readValidationCache: () => readValidationCache,
|
|
3519
|
+
writeValidationCache: () => writeValidationCache
|
|
3514
3520
|
});
|
|
3515
|
-
import {
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
resolve3(answer.trim());
|
|
3522
|
-
});
|
|
3523
|
-
});
|
|
3524
|
-
}
|
|
3525
|
-
function getDefaultSupabaseUrl2() {
|
|
3526
|
-
return process.env.SOCIALNEURON_SUPABASE_URL || process.env.SUPABASE_URL || CLOUD_SUPABASE_URL;
|
|
3527
|
-
}
|
|
3528
|
-
async function runLogin(method) {
|
|
3529
|
-
if (method === "browser") {
|
|
3530
|
-
await runSetup();
|
|
3531
|
-
return;
|
|
3532
|
-
}
|
|
3533
|
-
if (method === "paste") {
|
|
3534
|
-
await runLoginPaste();
|
|
3535
|
-
return;
|
|
3536
|
-
}
|
|
3537
|
-
if (method === "device") {
|
|
3538
|
-
await runLoginDevice();
|
|
3539
|
-
return;
|
|
3540
|
-
}
|
|
3521
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
3522
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, chmodSync } from "node:fs";
|
|
3523
|
+
import { join as join4 } from "node:path";
|
|
3524
|
+
import { homedir as homedir4 } from "node:os";
|
|
3525
|
+
function keyFingerprint(apiKey) {
|
|
3526
|
+
return `sha256:${createHash5("sha256").update(apiKey, "utf8").digest("hex")}`;
|
|
3541
3527
|
}
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3528
|
+
function readValidationCache(apiKey) {
|
|
3529
|
+
try {
|
|
3530
|
+
const raw = readFileSync4(CACHE_FILE, "utf-8");
|
|
3531
|
+
const cached = JSON.parse(raw);
|
|
3532
|
+
if (cached.keyFingerprint !== keyFingerprint(apiKey)) {
|
|
3533
|
+
return null;
|
|
3534
|
+
}
|
|
3535
|
+
if (Date.now() - cached.cachedAt > CACHE_TTL_MS) {
|
|
3536
|
+
return null;
|
|
3537
|
+
}
|
|
3538
|
+
if (!cached.result.valid) {
|
|
3539
|
+
return null;
|
|
3540
|
+
}
|
|
3541
|
+
if (cached.result.expiresAt) {
|
|
3542
|
+
const expiresMs = new Date(cached.result.expiresAt).getTime();
|
|
3543
|
+
if (expiresMs <= Date.now()) {
|
|
3544
|
+
return null;
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
return cached.result;
|
|
3548
|
+
} catch {
|
|
3549
|
+
return null;
|
|
3550
|
+
}
|
|
3551
|
+
}
|
|
3552
|
+
function writeValidationCache(apiKey, result) {
|
|
3553
|
+
if (!result.valid) return;
|
|
3554
|
+
try {
|
|
3555
|
+
mkdirSync4(CONFIG_DIR2, { recursive: true });
|
|
3556
|
+
const entry = {
|
|
3557
|
+
keyFingerprint: keyFingerprint(apiKey),
|
|
3558
|
+
result,
|
|
3559
|
+
cachedAt: Date.now()
|
|
3560
|
+
};
|
|
3561
|
+
writeFileSync4(CACHE_FILE, JSON.stringify(entry, null, 2), { mode: 384 });
|
|
3562
|
+
try {
|
|
3563
|
+
chmodSync(CACHE_FILE, 384);
|
|
3564
|
+
} catch {
|
|
3565
|
+
}
|
|
3566
|
+
} catch {
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
function clearValidationCache() {
|
|
3570
|
+
try {
|
|
3571
|
+
writeFileSync4(CACHE_FILE, "{}", { mode: 384 });
|
|
3572
|
+
} catch {
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
var CONFIG_DIR2, CACHE_FILE, CACHE_TTL_MS;
|
|
3576
|
+
var init_validation_cache = __esm({
|
|
3577
|
+
"src/lib/validation-cache.ts"() {
|
|
3578
|
+
"use strict";
|
|
3579
|
+
CONFIG_DIR2 = join4(homedir4(), ".config", "socialneuron");
|
|
3580
|
+
CACHE_FILE = join4(CONFIG_DIR2, "validation-cache.json");
|
|
3581
|
+
CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
3582
|
+
}
|
|
3583
|
+
});
|
|
3584
|
+
|
|
3585
|
+
// src/cli/commands.ts
|
|
3586
|
+
var commands_exports = {};
|
|
3587
|
+
__export(commands_exports, {
|
|
3588
|
+
runHealthCheck: () => runHealthCheck,
|
|
3589
|
+
runLogin: () => runLogin,
|
|
3590
|
+
runLogoutCommand: () => runLogoutCommand,
|
|
3591
|
+
runWhoami: () => runWhoami
|
|
3592
|
+
});
|
|
3593
|
+
import { createInterface } from "node:readline";
|
|
3594
|
+
function prompt(question) {
|
|
3595
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
3596
|
+
return new Promise((resolve3) => {
|
|
3597
|
+
rl.question(question, (answer) => {
|
|
3598
|
+
rl.close();
|
|
3599
|
+
resolve3(answer.trim());
|
|
3600
|
+
});
|
|
3601
|
+
});
|
|
3602
|
+
}
|
|
3603
|
+
function getDefaultSupabaseUrl2() {
|
|
3604
|
+
return process.env.SOCIALNEURON_SUPABASE_URL || process.env.SUPABASE_URL || CLOUD_SUPABASE_URL;
|
|
3605
|
+
}
|
|
3606
|
+
async function runLogin(method) {
|
|
3607
|
+
if (method === "browser") {
|
|
3608
|
+
await runSetup();
|
|
3609
|
+
return;
|
|
3610
|
+
}
|
|
3611
|
+
if (method === "paste") {
|
|
3612
|
+
await runLoginPaste();
|
|
3613
|
+
return;
|
|
3614
|
+
}
|
|
3615
|
+
if (method === "device") {
|
|
3616
|
+
await runLoginDevice();
|
|
3617
|
+
return;
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
async function runLoginPaste() {
|
|
3621
|
+
console.error("");
|
|
3622
|
+
console.error(" Social Neuron - Paste API Key");
|
|
3623
|
+
console.error(" =============================");
|
|
3546
3624
|
console.error("");
|
|
3547
3625
|
console.error(" Paste your API key (starts with snk_live_):");
|
|
3548
3626
|
const key = await prompt(" > ");
|
|
@@ -3626,20 +3704,27 @@ async function runLoginDevice() {
|
|
|
3626
3704
|
if (pollData.api_key) {
|
|
3627
3705
|
try {
|
|
3628
3706
|
await saveApiKey(pollData.api_key);
|
|
3629
|
-
await saveSupabaseUrl(supabaseUrl);
|
|
3630
3707
|
} catch (saveErr) {
|
|
3631
3708
|
console.error("");
|
|
3632
|
-
console.error(" Warning: Could not save key
|
|
3709
|
+
console.error(" Warning: Could not save API key securely.");
|
|
3633
3710
|
console.error(` Error: ${saveErr instanceof Error ? saveErr.message : String(saveErr)}`);
|
|
3634
3711
|
console.error("");
|
|
3635
|
-
console.error("
|
|
3636
|
-
console.error(
|
|
3637
|
-
console.error("");
|
|
3638
|
-
console.error(" To authenticate manually, run:");
|
|
3712
|
+
console.error(" For security, the API key was not printed to the terminal.");
|
|
3713
|
+
console.error(" Create or copy an API key from your Social Neuron dashboard, then run:");
|
|
3639
3714
|
console.error(" npx @socialneuron/mcp-server login --paste");
|
|
3640
3715
|
console.error("");
|
|
3641
|
-
|
|
3642
|
-
|
|
3716
|
+
return;
|
|
3717
|
+
}
|
|
3718
|
+
try {
|
|
3719
|
+
await saveSupabaseUrl(supabaseUrl);
|
|
3720
|
+
} catch (saveErr) {
|
|
3721
|
+
console.error("");
|
|
3722
|
+
console.error(" Authorized!");
|
|
3723
|
+
console.error(` Key prefix: ${pollData.api_key.substring(0, 12)}...`);
|
|
3724
|
+
console.error("");
|
|
3725
|
+
console.error(" Warning: API key saved, but could not save the Supabase URL.");
|
|
3726
|
+
console.error(` Error: ${saveErr instanceof Error ? saveErr.message : String(saveErr)}`);
|
|
3727
|
+
console.error(" If needed, set SOCIALNEURON_SUPABASE_URL to your Supabase URL.");
|
|
3643
3728
|
console.error("");
|
|
3644
3729
|
return;
|
|
3645
3730
|
}
|
|
@@ -4155,6 +4240,8 @@ var TOOL_SCOPES = {
|
|
|
4155
4240
|
get_mcp_usage: "mcp:read",
|
|
4156
4241
|
list_plan_approvals: "mcp:read",
|
|
4157
4242
|
search_tools: "mcp:read",
|
|
4243
|
+
search: "mcp:read",
|
|
4244
|
+
fetch: "mcp:read",
|
|
4158
4245
|
// mcp:read (pipeline readiness + status are read-only)
|
|
4159
4246
|
check_pipeline_readiness: "mcp:read",
|
|
4160
4247
|
get_pipeline_status: "mcp:read",
|
|
@@ -4182,7 +4269,13 @@ var TOOL_SCOPES = {
|
|
|
4182
4269
|
record_intel_signal: "mcp:write",
|
|
4183
4270
|
record_campaign_spend: "mcp:write",
|
|
4184
4271
|
// mcp:read
|
|
4185
|
-
get_active_campaigns: "mcp:read"
|
|
4272
|
+
get_active_campaigns: "mcp:read",
|
|
4273
|
+
// mcp:read / mcp:write (Skills — PR #4.4)
|
|
4274
|
+
list_skills: "mcp:read",
|
|
4275
|
+
run_skill: "mcp:write",
|
|
4276
|
+
// mcp:read (Loop observability — growth-loop KPIs + bandit posteriors)
|
|
4277
|
+
get_loop_pulse: "mcp:read",
|
|
4278
|
+
get_bandit_state: "mcp:read"
|
|
4186
4279
|
};
|
|
4187
4280
|
function hasScope(userScopes, required) {
|
|
4188
4281
|
if (userScopes.includes(required)) return true;
|
|
@@ -4282,6 +4375,10 @@ var OVERRIDES = {
|
|
|
4282
4375
|
generate_performance_digest: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
4283
4376
|
detect_anomalies: { readOnlyHint: true, destructiveHint: false, idempotentHint: true }
|
|
4284
4377
|
};
|
|
4378
|
+
function buildToolSecuritySchemes(toolName) {
|
|
4379
|
+
const scope = TOOL_SCOPES[toolName];
|
|
4380
|
+
return scope ? [{ type: "oauth2", scopes: [scope] }] : [];
|
|
4381
|
+
}
|
|
4285
4382
|
function buildAnnotationsMap() {
|
|
4286
4383
|
const map = /* @__PURE__ */ new Map();
|
|
4287
4384
|
for (const [toolName, scope] of Object.entries(TOOL_SCOPES)) {
|
|
@@ -4326,17 +4423,37 @@ function applyAnnotations(server2) {
|
|
|
4326
4423
|
destructiveHint: ann.destructiveHint,
|
|
4327
4424
|
idempotentHint: ann.idempotentHint,
|
|
4328
4425
|
openWorldHint: ann.openWorldHint
|
|
4329
|
-
}
|
|
4426
|
+
},
|
|
4427
|
+
securitySchemes: buildToolSecuritySchemes(toolName)
|
|
4330
4428
|
});
|
|
4331
4429
|
applied++;
|
|
4332
4430
|
}
|
|
4333
4431
|
}
|
|
4334
|
-
console.
|
|
4432
|
+
console.error(`[annotations] Applied annotations to ${applied}/${entries.length} tools`);
|
|
4335
4433
|
}
|
|
4336
4434
|
|
|
4337
4435
|
// src/lib/register-tools.ts
|
|
4338
4436
|
init_supabase();
|
|
4339
4437
|
|
|
4438
|
+
// src/lib/www-authenticate.ts
|
|
4439
|
+
function buildWwwAuthenticateHeader(opts) {
|
|
4440
|
+
const SANITIZE_CONTROL = /[\x00-\x1f\x7f]/g;
|
|
4441
|
+
const sanitize = (s) => s.replace(/[\\"]/g, "_").replace(SANITIZE_CONTROL, " ");
|
|
4442
|
+
const params = ['realm="socialneuron"'];
|
|
4443
|
+
if (opts.error) {
|
|
4444
|
+
params.push(`error="${sanitize(opts.error)}"`);
|
|
4445
|
+
}
|
|
4446
|
+
if (opts.errorDescription) {
|
|
4447
|
+
params.push(`error_description="${sanitize(opts.errorDescription)}"`);
|
|
4448
|
+
}
|
|
4449
|
+
if (opts.scope) {
|
|
4450
|
+
params.push(`scope="${sanitize(opts.scope)}"`);
|
|
4451
|
+
}
|
|
4452
|
+
const issuer = opts.issuerUrl.replace(/\/+$/, "");
|
|
4453
|
+
params.push(`resource_metadata="${issuer}/.well-known/oauth-protected-resource"`);
|
|
4454
|
+
return `Bearer ${params.join(", ")}`;
|
|
4455
|
+
}
|
|
4456
|
+
|
|
4340
4457
|
// src/lib/agent-harness/constants.json
|
|
4341
4458
|
var constants_default = {
|
|
4342
4459
|
ZERO_WIDTH_CHARS: "[\\u200B\\u200C\\u200D\\u2060\\uFEFF\\u{E0020}-\\u{E007F}]",
|
|
@@ -4538,6 +4655,16 @@ function checkRateLimit(category, key) {
|
|
|
4538
4655
|
|
|
4539
4656
|
// src/tools/ideation.ts
|
|
4540
4657
|
init_supabase();
|
|
4658
|
+
init_version();
|
|
4659
|
+
function asEnvelope(data) {
|
|
4660
|
+
return {
|
|
4661
|
+
_meta: {
|
|
4662
|
+
version: MCP_VERSION,
|
|
4663
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4664
|
+
},
|
|
4665
|
+
data
|
|
4666
|
+
};
|
|
4667
|
+
}
|
|
4541
4668
|
function registerIdeationTools(server2) {
|
|
4542
4669
|
server2.tool(
|
|
4543
4670
|
"generate_content",
|
|
@@ -4699,7 +4826,14 @@ Content Type: ${content_type}`;
|
|
|
4699
4826
|
};
|
|
4700
4827
|
}
|
|
4701
4828
|
const text = data?.text ?? "(empty response)";
|
|
4829
|
+
const structuredContent = asEnvelope({
|
|
4830
|
+
text,
|
|
4831
|
+
content_type,
|
|
4832
|
+
platform: platform3 ?? null,
|
|
4833
|
+
model: model ?? "gemini-2.5-flash"
|
|
4834
|
+
});
|
|
4702
4835
|
return {
|
|
4836
|
+
structuredContent,
|
|
4703
4837
|
content: [{ type: "text", text }]
|
|
4704
4838
|
};
|
|
4705
4839
|
}
|
|
@@ -4733,7 +4867,10 @@ Content Type: ${content_type}`;
|
|
|
4733
4867
|
const { data, error } = await callEdgeFunction(
|
|
4734
4868
|
"fetch-trends",
|
|
4735
4869
|
{
|
|
4736
|
-
source
|
|
4870
|
+
// Forward as `trend_source`: the mcp-gateway overwrites top-level `source`
|
|
4871
|
+
// with 'mcp' (attribution) before reaching the EF, which collided with the
|
|
4872
|
+
// routing param. The EF reads `trend_source ?? source`.
|
|
4873
|
+
trend_source: source,
|
|
4737
4874
|
category: category ?? "general",
|
|
4738
4875
|
niche,
|
|
4739
4876
|
url,
|
|
@@ -4902,7 +5039,14 @@ ${content}`,
|
|
|
4902
5039
|
const header = `Adapted for ${target_platform}${source_platform ? ` (from ${source_platform})` : ""}:
|
|
4903
5040
|
|
|
4904
5041
|
`;
|
|
5042
|
+
const structuredContent = asEnvelope({
|
|
5043
|
+
text,
|
|
5044
|
+
source_platform: source_platform ?? null,
|
|
5045
|
+
target_platform,
|
|
5046
|
+
model: "gemini-2.5-flash"
|
|
5047
|
+
});
|
|
4905
5048
|
return {
|
|
5049
|
+
structuredContent,
|
|
4906
5050
|
content: [{ type: "text", text: header + text }]
|
|
4907
5051
|
};
|
|
4908
5052
|
}
|
|
@@ -4913,8 +5057,10 @@ ${content}`,
|
|
|
4913
5057
|
init_edge_function();
|
|
4914
5058
|
import { z as z2 } from "zod";
|
|
4915
5059
|
init_supabase();
|
|
4916
|
-
init_request_context();
|
|
4917
5060
|
init_version();
|
|
5061
|
+
|
|
5062
|
+
// src/lib/budget.ts
|
|
5063
|
+
init_request_context();
|
|
4918
5064
|
var MAX_CREDITS_PER_RUN = Math.max(0, Number(process.env.SOCIALNEURON_MAX_CREDITS_PER_RUN || 0));
|
|
4919
5065
|
var MAX_ASSETS_PER_RUN = Math.max(0, Number(process.env.SOCIALNEURON_MAX_ASSETS_PER_RUN || 0));
|
|
4920
5066
|
var _globalCreditsUsed = 0;
|
|
@@ -4955,7 +5101,35 @@ function getCurrentBudgetStatus() {
|
|
|
4955
5101
|
remainingAssets: MAX_ASSETS_PER_RUN > 0 ? Math.max(0, MAX_ASSETS_PER_RUN - assetsGen) : null
|
|
4956
5102
|
};
|
|
4957
5103
|
}
|
|
4958
|
-
function
|
|
5104
|
+
function checkCreditBudget(estimatedCost) {
|
|
5105
|
+
if (MAX_CREDITS_PER_RUN <= 0) {
|
|
5106
|
+
return { ok: true };
|
|
5107
|
+
}
|
|
5108
|
+
const used = getCreditsUsed();
|
|
5109
|
+
if (used + estimatedCost > MAX_CREDITS_PER_RUN) {
|
|
5110
|
+
return {
|
|
5111
|
+
ok: false,
|
|
5112
|
+
message: `Credit budget exceeded for this MCP run. Used=${used}, next~=${estimatedCost}, limit=${MAX_CREDITS_PER_RUN}.`
|
|
5113
|
+
};
|
|
5114
|
+
}
|
|
5115
|
+
return { ok: true };
|
|
5116
|
+
}
|
|
5117
|
+
function checkAssetBudget(requestedCount = 1) {
|
|
5118
|
+
if (MAX_ASSETS_PER_RUN <= 0) {
|
|
5119
|
+
return { ok: true };
|
|
5120
|
+
}
|
|
5121
|
+
const generated = getAssetsGenerated();
|
|
5122
|
+
if (generated + requestedCount > MAX_ASSETS_PER_RUN) {
|
|
5123
|
+
return {
|
|
5124
|
+
ok: false,
|
|
5125
|
+
message: `Asset budget exceeded for this MCP run. Generated=${generated}, next=${requestedCount}, limit=${MAX_ASSETS_PER_RUN}.`
|
|
5126
|
+
};
|
|
5127
|
+
}
|
|
5128
|
+
return { ok: true };
|
|
5129
|
+
}
|
|
5130
|
+
|
|
5131
|
+
// src/tools/content.ts
|
|
5132
|
+
function asEnvelope2(data) {
|
|
4959
5133
|
return {
|
|
4960
5134
|
_meta: {
|
|
4961
5135
|
version: MCP_VERSION,
|
|
@@ -4982,35 +5156,9 @@ var IMAGE_CREDIT_ESTIMATES = {
|
|
|
4982
5156
|
"flux-max": 50,
|
|
4983
5157
|
"gpt4o-image": 40,
|
|
4984
5158
|
imagen4: 35,
|
|
4985
|
-
"imagen4-fast":
|
|
5159
|
+
"imagen4-fast": 35,
|
|
4986
5160
|
seedream: 20
|
|
4987
5161
|
};
|
|
4988
|
-
function checkCreditBudget(estimatedCost) {
|
|
4989
|
-
if (MAX_CREDITS_PER_RUN <= 0) {
|
|
4990
|
-
return { ok: true };
|
|
4991
|
-
}
|
|
4992
|
-
const used = getCreditsUsed();
|
|
4993
|
-
if (used + estimatedCost > MAX_CREDITS_PER_RUN) {
|
|
4994
|
-
return {
|
|
4995
|
-
ok: false,
|
|
4996
|
-
message: `Credit budget exceeded for this MCP run. Used=${used}, next~=${estimatedCost}, limit=${MAX_CREDITS_PER_RUN}.`
|
|
4997
|
-
};
|
|
4998
|
-
}
|
|
4999
|
-
return { ok: true };
|
|
5000
|
-
}
|
|
5001
|
-
function checkAssetBudget() {
|
|
5002
|
-
if (MAX_ASSETS_PER_RUN <= 0) {
|
|
5003
|
-
return { ok: true };
|
|
5004
|
-
}
|
|
5005
|
-
const generated = getAssetsGenerated();
|
|
5006
|
-
if (generated + 1 > MAX_ASSETS_PER_RUN) {
|
|
5007
|
-
return {
|
|
5008
|
-
ok: false,
|
|
5009
|
-
message: `Asset budget exceeded for this MCP run. Generated=${generated}, next=1, limit=${MAX_ASSETS_PER_RUN}.`
|
|
5010
|
-
};
|
|
5011
|
-
}
|
|
5012
|
-
return { ok: true };
|
|
5013
|
-
}
|
|
5014
5162
|
function registerContentTools(server2) {
|
|
5015
5163
|
server2.tool(
|
|
5016
5164
|
"generate_video",
|
|
@@ -5129,7 +5277,7 @@ function registerContentTools(server2) {
|
|
|
5129
5277
|
{
|
|
5130
5278
|
type: "text",
|
|
5131
5279
|
text: JSON.stringify(
|
|
5132
|
-
|
|
5280
|
+
asEnvelope2({
|
|
5133
5281
|
jobId,
|
|
5134
5282
|
taskId: data.taskId,
|
|
5135
5283
|
asyncJobId: data.asyncJobId,
|
|
@@ -5259,7 +5407,7 @@ function registerContentTools(server2) {
|
|
|
5259
5407
|
{
|
|
5260
5408
|
type: "text",
|
|
5261
5409
|
text: JSON.stringify(
|
|
5262
|
-
|
|
5410
|
+
asEnvelope2({
|
|
5263
5411
|
jobId,
|
|
5264
5412
|
taskId: data.taskId,
|
|
5265
5413
|
asyncJobId: data.asyncJobId,
|
|
@@ -5362,7 +5510,7 @@ function registerContentTools(server2) {
|
|
|
5362
5510
|
{
|
|
5363
5511
|
type: "text",
|
|
5364
5512
|
text: JSON.stringify(
|
|
5365
|
-
|
|
5513
|
+
asEnvelope2({
|
|
5366
5514
|
jobId: job.id,
|
|
5367
5515
|
jobType: job.job_type,
|
|
5368
5516
|
model: job.model,
|
|
@@ -5423,7 +5571,7 @@ function registerContentTools(server2) {
|
|
|
5423
5571
|
all_urls: allUrls ?? null
|
|
5424
5572
|
};
|
|
5425
5573
|
return {
|
|
5426
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
5574
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope2(enriched), null, 2) }]
|
|
5427
5575
|
};
|
|
5428
5576
|
}
|
|
5429
5577
|
return {
|
|
@@ -5552,7 +5700,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
5552
5700
|
try {
|
|
5553
5701
|
const parsed = JSON.parse(rawContent);
|
|
5554
5702
|
return {
|
|
5555
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
5703
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope2(parsed), null, 2) }]
|
|
5556
5704
|
};
|
|
5557
5705
|
} catch {
|
|
5558
5706
|
return {
|
|
@@ -5580,20 +5728,8 @@ Return ONLY valid JSON in this exact format:
|
|
|
5580
5728
|
"Generate a voiceover audio file for video narration. Returns an R2-hosted audio URL. Use after create_storyboard to add narration to each scene, or standalone for podcast intros and ad reads. Costs ~2 credits per generation.",
|
|
5581
5729
|
{
|
|
5582
5730
|
text: z2.string().max(5e3).describe("The script/text to convert to speech."),
|
|
5583
|
-
voice: z2.enum([
|
|
5584
|
-
"rachel"
|
|
5585
|
-
"drew",
|
|
5586
|
-
"clyde",
|
|
5587
|
-
"paul",
|
|
5588
|
-
"domi",
|
|
5589
|
-
"dave",
|
|
5590
|
-
"fin",
|
|
5591
|
-
"sarah",
|
|
5592
|
-
"antoni",
|
|
5593
|
-
"thomas",
|
|
5594
|
-
"charlie"
|
|
5595
|
-
]).optional().describe(
|
|
5596
|
-
"Voice selection. rachel=warm female, drew=confident male, paul=authoritative male, sarah=friendly female. Defaults to rachel."
|
|
5731
|
+
voice: z2.enum(["rachel", "domi"]).optional().describe(
|
|
5732
|
+
"Voice selection. rachel=warm female, domi=confident female. Defaults to rachel."
|
|
5597
5733
|
),
|
|
5598
5734
|
speed: z2.number().min(0.5).max(2).optional().describe("Speech speed multiplier. 1.0 is normal. Defaults to 1.0."),
|
|
5599
5735
|
response_format: z2.enum(["text", "json"]).optional().describe("Response format. Defaults to text.")
|
|
@@ -5621,11 +5757,15 @@ Return ONLY valid JSON in this exact format:
|
|
|
5621
5757
|
isError: true
|
|
5622
5758
|
};
|
|
5623
5759
|
}
|
|
5760
|
+
const ELEVENLABS_VOICE_IDS = {
|
|
5761
|
+
rachel: "21m00Tcm4TlvDq8ikWAM",
|
|
5762
|
+
domi: "AZnzlk1XvdvUeBnXmlld"
|
|
5763
|
+
};
|
|
5624
5764
|
const { data, error } = await callEdgeFunction(
|
|
5625
5765
|
"elevenlabs-tts",
|
|
5626
5766
|
{
|
|
5627
5767
|
text,
|
|
5628
|
-
|
|
5768
|
+
voiceId: ELEVENLABS_VOICE_IDS[voice ?? "rachel"],
|
|
5629
5769
|
speed: speed ?? 1
|
|
5630
5770
|
},
|
|
5631
5771
|
{ timeoutMs: 6e4 }
|
|
@@ -5651,7 +5791,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
5651
5791
|
{
|
|
5652
5792
|
type: "text",
|
|
5653
5793
|
text: JSON.stringify(
|
|
5654
|
-
|
|
5794
|
+
asEnvelope2({
|
|
5655
5795
|
audioUrl: data.audioUrl,
|
|
5656
5796
|
durationSeconds: data.durationSeconds,
|
|
5657
5797
|
voice: voice ?? "rachel"
|
|
@@ -5800,7 +5940,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
5800
5940
|
{
|
|
5801
5941
|
type: "text",
|
|
5802
5942
|
text: JSON.stringify(
|
|
5803
|
-
|
|
5943
|
+
asEnvelope2({
|
|
5804
5944
|
carouselId: data.carousel.id,
|
|
5805
5945
|
templateId,
|
|
5806
5946
|
style: resolvedStyle,
|
|
@@ -6069,7 +6209,7 @@ var PLATFORM_CASE_MAP = {
|
|
|
6069
6209
|
};
|
|
6070
6210
|
var TIKTOK_AUDIT_APPROVED = false;
|
|
6071
6211
|
var MCP_NOT_LIVE_FOR_POSTING = /* @__PURE__ */ new Set(["LinkedIn", "Pinterest", "Reddit"]);
|
|
6072
|
-
function
|
|
6212
|
+
function asEnvelope3(data) {
|
|
6073
6213
|
return {
|
|
6074
6214
|
_meta: {
|
|
6075
6215
|
version: MCP_VERSION,
|
|
@@ -6167,7 +6307,24 @@ function registerDistributionTools(server2) {
|
|
|
6167
6307
|
linkedin: z3.object({
|
|
6168
6308
|
article_url: z3.string().optional()
|
|
6169
6309
|
}).optional(),
|
|
6170
|
-
twitter: z3.object({
|
|
6310
|
+
twitter: z3.object({
|
|
6311
|
+
paid_partnership: z3.boolean().optional().describe(
|
|
6312
|
+
"Set true for sponsored, affiliate, or branded campaign content. Forwarded to X as paid_partnership and persisted on posts.metadata.paid_partnership."
|
|
6313
|
+
),
|
|
6314
|
+
disclosure: z3.union([
|
|
6315
|
+
z3.string(),
|
|
6316
|
+
z3.object({
|
|
6317
|
+
label: z3.string().optional(),
|
|
6318
|
+
text: z3.string().optional(),
|
|
6319
|
+
source: z3.string().optional(),
|
|
6320
|
+
required: z3.boolean().optional()
|
|
6321
|
+
}).passthrough()
|
|
6322
|
+
]).optional().describe(
|
|
6323
|
+
"Campaign disclosure metadata for X posts. String values become disclosure.text; object values persist to posts.metadata.disclosure."
|
|
6324
|
+
),
|
|
6325
|
+
disclosure_text: z3.string().optional(),
|
|
6326
|
+
disclosure_label: z3.string().optional()
|
|
6327
|
+
}).passthrough().optional()
|
|
6171
6328
|
}).optional().describe(
|
|
6172
6329
|
'Platform-specific metadata. Example: {"tiktok":{"privacy_status":"PUBLIC_TO_EVERYONE"}, "youtube":{"title":"My Video"}}'
|
|
6173
6330
|
),
|
|
@@ -6211,11 +6368,11 @@ function registerDistributionTools(server2) {
|
|
|
6211
6368
|
visual_gate_result: z3.object({ passed: z3.boolean() }).passthrough().optional().describe(
|
|
6212
6369
|
"Visual QA gate verdict from the carousel/image generation pipeline. Required when mediaType is CAROUSEL_ALBUM, IMAGE, or VIDEO and VISUAL_GATE_ENFORCE is enabled on the EF. Produce with visual_quality_check tool. Must have passed=true for the gate to allow the publish."
|
|
6213
6370
|
),
|
|
6214
|
-
origin: z3.
|
|
6215
|
-
"
|
|
6371
|
+
origin: z3.enum(["human", "hermes", "user"]).optional().describe(
|
|
6372
|
+
"Originator lineage for the post. 'hermes' marks the post as scheduled by the Hermes autonomous agent \u2014 surfaces the Hermes badge + filter in Distribution. Default 'human'. Use only when calling from an autonomous workflow (cron, Slack bot, etc.)."
|
|
6216
6373
|
),
|
|
6217
6374
|
hermes_run_id: z3.string().optional().describe(
|
|
6218
|
-
"Hermes cron run identifier
|
|
6375
|
+
"Hermes cron run identifier \u2014 e.g. 'cron_<id>_<timestamp>'. Persists to posts.hermes_run_id when origin='hermes'. Use for traceability between drafts and the cron that produced them."
|
|
6219
6376
|
)
|
|
6220
6377
|
},
|
|
6221
6378
|
async ({
|
|
@@ -6498,11 +6655,11 @@ Created with Social Neuron`;
|
|
|
6498
6655
|
// Forward visual gate verdict + source so the EF can enforce on media posts.
|
|
6499
6656
|
...visual_gate_result ? { visualGateResult: visual_gate_result } : {},
|
|
6500
6657
|
visualGateSource: "mcp",
|
|
6501
|
-
//
|
|
6502
|
-
//
|
|
6503
|
-
//
|
|
6658
|
+
// Originator lineage (Hermes integration, 2026-05-22). EF validates origin
|
|
6659
|
+
// against the posts.origin CHECK constraint and persists hermesRunId when
|
|
6660
|
+
// origin === 'hermes'.
|
|
6504
6661
|
...origin ? { origin } : {},
|
|
6505
|
-
...hermes_run_id ? { hermes_run_id } : {}
|
|
6662
|
+
...hermes_run_id ? { hermesRunId: hermes_run_id } : {}
|
|
6506
6663
|
},
|
|
6507
6664
|
{ timeoutMs: 3e4 }
|
|
6508
6665
|
);
|
|
@@ -6547,12 +6704,15 @@ Created with Social Neuron`;
|
|
|
6547
6704
|
}
|
|
6548
6705
|
}
|
|
6549
6706
|
if (format === "json") {
|
|
6707
|
+
const structuredContent = asEnvelope3(data);
|
|
6550
6708
|
return {
|
|
6551
|
-
|
|
6709
|
+
structuredContent,
|
|
6710
|
+
content: [{ type: "text", text: JSON.stringify(structuredContent, null, 2) }],
|
|
6552
6711
|
isError: !data.success
|
|
6553
6712
|
};
|
|
6554
6713
|
}
|
|
6555
6714
|
return {
|
|
6715
|
+
structuredContent: asEnvelope3(data),
|
|
6556
6716
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
6557
6717
|
isError: !data.success
|
|
6558
6718
|
};
|
|
@@ -6585,7 +6745,7 @@ Created with Social Neuron`;
|
|
|
6585
6745
|
content: [
|
|
6586
6746
|
{
|
|
6587
6747
|
type: "text",
|
|
6588
|
-
text: JSON.stringify(
|
|
6748
|
+
text: JSON.stringify(asEnvelope3({ accounts: [] }), null, 2)
|
|
6589
6749
|
}
|
|
6590
6750
|
]
|
|
6591
6751
|
};
|
|
@@ -6608,7 +6768,7 @@ Created with Social Neuron`;
|
|
|
6608
6768
|
if (format === "json") {
|
|
6609
6769
|
return {
|
|
6610
6770
|
content: [
|
|
6611
|
-
{ type: "text", text: JSON.stringify(
|
|
6771
|
+
{ type: "text", text: JSON.stringify(asEnvelope3({ accounts }), null, 2) }
|
|
6612
6772
|
]
|
|
6613
6773
|
};
|
|
6614
6774
|
}
|
|
@@ -6660,10 +6820,10 @@ Created with Social Neuron`;
|
|
|
6660
6820
|
const rows = result.posts ?? [];
|
|
6661
6821
|
if (rows.length === 0) {
|
|
6662
6822
|
if (format === "json") {
|
|
6823
|
+
const structuredContent2 = asEnvelope3({ posts: [] });
|
|
6663
6824
|
return {
|
|
6664
|
-
|
|
6665
|
-
|
|
6666
|
-
]
|
|
6825
|
+
structuredContent: structuredContent2,
|
|
6826
|
+
content: [{ type: "text", text: JSON.stringify(structuredContent2, null, 2) }]
|
|
6667
6827
|
};
|
|
6668
6828
|
}
|
|
6669
6829
|
return {
|
|
@@ -6676,11 +6836,11 @@ Created with Social Neuron`;
|
|
|
6676
6836
|
};
|
|
6677
6837
|
}
|
|
6678
6838
|
const posts = rows;
|
|
6839
|
+
const structuredContent = asEnvelope3({ posts });
|
|
6679
6840
|
if (format === "json") {
|
|
6680
6841
|
return {
|
|
6681
|
-
|
|
6682
|
-
|
|
6683
|
-
]
|
|
6842
|
+
structuredContent,
|
|
6843
|
+
content: [{ type: "text", text: JSON.stringify(structuredContent, null, 2) }]
|
|
6684
6844
|
};
|
|
6685
6845
|
}
|
|
6686
6846
|
const statusIcon = {
|
|
@@ -6705,6 +6865,7 @@ Created with Social Neuron`;
|
|
|
6705
6865
|
lines.push(line);
|
|
6706
6866
|
}
|
|
6707
6867
|
return {
|
|
6868
|
+
structuredContent,
|
|
6708
6869
|
content: [{ type: "text", text: lines.join("\n") }]
|
|
6709
6870
|
};
|
|
6710
6871
|
}
|
|
@@ -6788,7 +6949,7 @@ Created with Social Neuron`;
|
|
|
6788
6949
|
{
|
|
6789
6950
|
type: "text",
|
|
6790
6951
|
text: JSON.stringify(
|
|
6791
|
-
|
|
6952
|
+
asEnvelope3({
|
|
6792
6953
|
slots,
|
|
6793
6954
|
total_candidates: candidates.length,
|
|
6794
6955
|
conflicts_avoided: conflictsAvoided
|
|
@@ -7077,7 +7238,7 @@ Created with Social Neuron`;
|
|
|
7077
7238
|
{
|
|
7078
7239
|
type: "text",
|
|
7079
7240
|
text: JSON.stringify(
|
|
7080
|
-
|
|
7241
|
+
asEnvelope3({
|
|
7081
7242
|
dry_run: true,
|
|
7082
7243
|
plan_id: effectivePlanId,
|
|
7083
7244
|
approvals: approvalSummary,
|
|
@@ -7265,7 +7426,7 @@ Created with Social Neuron`;
|
|
|
7265
7426
|
{
|
|
7266
7427
|
type: "text",
|
|
7267
7428
|
text: JSON.stringify(
|
|
7268
|
-
|
|
7429
|
+
asEnvelope3({
|
|
7269
7430
|
plan_id: effectivePlanId,
|
|
7270
7431
|
approvals: approvalSummary,
|
|
7271
7432
|
posts: results,
|
|
@@ -7540,6 +7701,17 @@ function registerMediaTools(server2) {
|
|
|
7540
7701
|
projectId: project_id
|
|
7541
7702
|
};
|
|
7542
7703
|
} else {
|
|
7704
|
+
if (process.env.MCP_TRANSPORT !== "stdio") {
|
|
7705
|
+
return {
|
|
7706
|
+
content: [
|
|
7707
|
+
{
|
|
7708
|
+
type: "text",
|
|
7709
|
+
text: `Local file paths are not accepted on the cloud MCP server. Pass the bytes via the \`file_data\` parameter (base64, up to 10MB) or supply a public URL via \`source\`.`
|
|
7710
|
+
}
|
|
7711
|
+
],
|
|
7712
|
+
isError: true
|
|
7713
|
+
};
|
|
7714
|
+
}
|
|
7543
7715
|
let fileBuffer;
|
|
7544
7716
|
try {
|
|
7545
7717
|
fileBuffer = await readFile(src);
|
|
@@ -7582,7 +7754,8 @@ function registerMediaTools(server2) {
|
|
|
7582
7754
|
const putResp = await fetch(putUrl, {
|
|
7583
7755
|
method: "PUT",
|
|
7584
7756
|
headers: { "Content-Type": ct },
|
|
7585
|
-
|
|
7757
|
+
// Uint8Array: Buffer no longer satisfies BodyInit under @types/node 26 fetch types
|
|
7758
|
+
body: new Uint8Array(fileBuffer)
|
|
7586
7759
|
});
|
|
7587
7760
|
if (!putResp.ok) {
|
|
7588
7761
|
return {
|
|
@@ -7766,7 +7939,7 @@ init_supabase();
|
|
|
7766
7939
|
init_edge_function();
|
|
7767
7940
|
import { z as z5 } from "zod";
|
|
7768
7941
|
init_version();
|
|
7769
|
-
function
|
|
7942
|
+
function asEnvelope4(data) {
|
|
7770
7943
|
return {
|
|
7771
7944
|
_meta: {
|
|
7772
7945
|
version: MCP_VERSION,
|
|
@@ -7819,22 +7992,20 @@ function registerAnalyticsTools(server2) {
|
|
|
7819
7992
|
const rows = result?.rows ?? [];
|
|
7820
7993
|
if (rows.length === 0) {
|
|
7821
7994
|
if (format === "json") {
|
|
7995
|
+
const structuredContent = asEnvelope4({
|
|
7996
|
+
platform: platform3 ?? null,
|
|
7997
|
+
days: lookbackDays,
|
|
7998
|
+
totalViews: 0,
|
|
7999
|
+
totalEngagement: 0,
|
|
8000
|
+
postCount: 0,
|
|
8001
|
+
posts: []
|
|
8002
|
+
});
|
|
7822
8003
|
return {
|
|
8004
|
+
structuredContent,
|
|
7823
8005
|
content: [
|
|
7824
8006
|
{
|
|
7825
8007
|
type: "text",
|
|
7826
|
-
text: JSON.stringify(
|
|
7827
|
-
asEnvelope3({
|
|
7828
|
-
platform: platform3 ?? null,
|
|
7829
|
-
days: lookbackDays,
|
|
7830
|
-
totalViews: 0,
|
|
7831
|
-
totalEngagement: 0,
|
|
7832
|
-
postCount: 0,
|
|
7833
|
-
posts: []
|
|
7834
|
-
}),
|
|
7835
|
-
null,
|
|
7836
|
-
2
|
|
7837
|
-
)
|
|
8008
|
+
text: JSON.stringify(structuredContent, null, 2)
|
|
7838
8009
|
}
|
|
7839
8010
|
]
|
|
7840
8011
|
};
|
|
@@ -7924,20 +8095,18 @@ function registerAnalyticsTools(server2) {
|
|
|
7924
8095
|
lines.push(` Errors: ${errored}`);
|
|
7925
8096
|
}
|
|
7926
8097
|
if (format === "json") {
|
|
8098
|
+
const structuredContent = asEnvelope4({
|
|
8099
|
+
success: true,
|
|
8100
|
+
postsProcessed: result.postsProcessed,
|
|
8101
|
+
queued,
|
|
8102
|
+
errored
|
|
8103
|
+
});
|
|
7927
8104
|
return {
|
|
8105
|
+
structuredContent,
|
|
7928
8106
|
content: [
|
|
7929
8107
|
{
|
|
7930
8108
|
type: "text",
|
|
7931
|
-
text: JSON.stringify(
|
|
7932
|
-
asEnvelope3({
|
|
7933
|
-
success: true,
|
|
7934
|
-
postsProcessed: result.postsProcessed,
|
|
7935
|
-
queued,
|
|
7936
|
-
errored
|
|
7937
|
-
}),
|
|
7938
|
-
null,
|
|
7939
|
-
2
|
|
7940
|
-
)
|
|
8109
|
+
text: JSON.stringify(structuredContent, null, 2)
|
|
7941
8110
|
}
|
|
7942
8111
|
]
|
|
7943
8112
|
};
|
|
@@ -7947,11 +8116,11 @@ function registerAnalyticsTools(server2) {
|
|
|
7947
8116
|
);
|
|
7948
8117
|
}
|
|
7949
8118
|
function formatAnalytics(summary, days, format) {
|
|
8119
|
+
const structuredContent = asEnvelope4({ ...summary, days });
|
|
7950
8120
|
if (format === "json") {
|
|
7951
8121
|
return {
|
|
7952
|
-
|
|
7953
|
-
|
|
7954
|
-
]
|
|
8122
|
+
structuredContent,
|
|
8123
|
+
content: [{ type: "text", text: JSON.stringify(structuredContent, null, 2) }]
|
|
7955
8124
|
};
|
|
7956
8125
|
}
|
|
7957
8126
|
const lines = [
|
|
@@ -7976,6 +8145,7 @@ function formatAnalytics(summary, days, format) {
|
|
|
7976
8145
|
}
|
|
7977
8146
|
}
|
|
7978
8147
|
return {
|
|
8148
|
+
structuredContent,
|
|
7979
8149
|
content: [{ type: "text", text: lines.join("\n") }]
|
|
7980
8150
|
};
|
|
7981
8151
|
}
|
|
@@ -7985,7 +8155,7 @@ init_edge_function();
|
|
|
7985
8155
|
init_supabase();
|
|
7986
8156
|
import { z as z6 } from "zod";
|
|
7987
8157
|
init_version();
|
|
7988
|
-
function
|
|
8158
|
+
function asEnvelope5(data) {
|
|
7989
8159
|
return {
|
|
7990
8160
|
_meta: {
|
|
7991
8161
|
version: MCP_VERSION,
|
|
@@ -8040,8 +8210,10 @@ function registerBrandTools(server2) {
|
|
|
8040
8210
|
};
|
|
8041
8211
|
}
|
|
8042
8212
|
if ((response_format || "text") === "json") {
|
|
8213
|
+
const structuredContent = asEnvelope5(data);
|
|
8043
8214
|
return {
|
|
8044
|
-
|
|
8215
|
+
structuredContent,
|
|
8216
|
+
content: [{ type: "text", text: JSON.stringify(structuredContent, null, 2) }]
|
|
8045
8217
|
};
|
|
8046
8218
|
}
|
|
8047
8219
|
const lines = [
|
|
@@ -8082,18 +8254,10 @@ function registerBrandTools(server2) {
|
|
|
8082
8254
|
},
|
|
8083
8255
|
async ({ project_id, response_format }) => {
|
|
8084
8256
|
const projectId = project_id || await getDefaultProjectId();
|
|
8085
|
-
|
|
8086
|
-
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
type: "text",
|
|
8090
|
-
text: "No project_id provided and no default project is configured."
|
|
8091
|
-
}
|
|
8092
|
-
],
|
|
8093
|
-
isError: true
|
|
8094
|
-
};
|
|
8095
|
-
}
|
|
8096
|
-
const { data: result, error: efError } = await callEdgeFunction("mcp-data", { action: "brand-profile", projectId });
|
|
8257
|
+
const { data: result, error: efError } = await callEdgeFunction("mcp-data", {
|
|
8258
|
+
action: "brand-profile",
|
|
8259
|
+
...projectId ? { projectId } : {}
|
|
8260
|
+
});
|
|
8097
8261
|
if (efError || result && !result.success) {
|
|
8098
8262
|
return {
|
|
8099
8263
|
content: [
|
|
@@ -8113,20 +8277,24 @@ function registerBrandTools(server2) {
|
|
|
8113
8277
|
]
|
|
8114
8278
|
};
|
|
8115
8279
|
}
|
|
8280
|
+
const structuredContent = asEnvelope5(data);
|
|
8116
8281
|
if ((response_format || "text") === "json") {
|
|
8117
8282
|
return {
|
|
8118
|
-
|
|
8283
|
+
structuredContent,
|
|
8284
|
+
content: [{ type: "text", text: JSON.stringify(structuredContent, null, 2) }]
|
|
8119
8285
|
};
|
|
8120
8286
|
}
|
|
8287
|
+
const brandContext = data.brand_context;
|
|
8121
8288
|
const lines = [
|
|
8122
8289
|
`Active Brand Profile`,
|
|
8123
|
-
`Project: ${projectId}`,
|
|
8124
|
-
`Brand Name: ${data.brand_name ||
|
|
8290
|
+
`Project: ${data.project_id || projectId || "default"}`,
|
|
8291
|
+
`Brand Name: ${data.brand_name || brandContext?.name || "N/A"}`,
|
|
8125
8292
|
`Version: ${data.version ?? "N/A"}`,
|
|
8126
8293
|
`Updated: ${data.updated_at || "N/A"}`,
|
|
8127
8294
|
`Extraction Method: ${data.extraction_method || "manual"}`
|
|
8128
8295
|
];
|
|
8129
8296
|
return {
|
|
8297
|
+
structuredContent,
|
|
8130
8298
|
content: [{ type: "text", text: lines.join("\n") }]
|
|
8131
8299
|
};
|
|
8132
8300
|
}
|
|
@@ -8201,7 +8369,7 @@ function registerBrandTools(server2) {
|
|
|
8201
8369
|
};
|
|
8202
8370
|
if ((response_format || "text") === "json") {
|
|
8203
8371
|
return {
|
|
8204
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
8372
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope5(payload), null, 2) }]
|
|
8205
8373
|
};
|
|
8206
8374
|
}
|
|
8207
8375
|
return {
|
|
@@ -8291,7 +8459,7 @@ Version: ${payload.version ?? "N/A"}`
|
|
|
8291
8459
|
};
|
|
8292
8460
|
if ((response_format || "text") === "json") {
|
|
8293
8461
|
return {
|
|
8294
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
8462
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope5(payload), null, 2) }],
|
|
8295
8463
|
isError: false
|
|
8296
8464
|
};
|
|
8297
8465
|
}
|
|
@@ -9000,7 +9168,7 @@ var PLATFORM_ENUM = [
|
|
|
9000
9168
|
"bluesky"
|
|
9001
9169
|
];
|
|
9002
9170
|
var DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
9003
|
-
function
|
|
9171
|
+
function asEnvelope6(data) {
|
|
9004
9172
|
return {
|
|
9005
9173
|
_meta: {
|
|
9006
9174
|
version: MCP_VERSION,
|
|
@@ -9046,19 +9214,17 @@ function registerInsightsTools(server2) {
|
|
|
9046
9214
|
}
|
|
9047
9215
|
if (rows.length === 0) {
|
|
9048
9216
|
if (format === "json") {
|
|
9217
|
+
const structuredContent2 = asEnvelope6({
|
|
9218
|
+
insights: [],
|
|
9219
|
+
days: lookbackDays,
|
|
9220
|
+
insightType: insight_type ?? null
|
|
9221
|
+
});
|
|
9049
9222
|
return {
|
|
9223
|
+
structuredContent: structuredContent2,
|
|
9050
9224
|
content: [
|
|
9051
9225
|
{
|
|
9052
9226
|
type: "text",
|
|
9053
|
-
text: JSON.stringify(
|
|
9054
|
-
asEnvelope5({
|
|
9055
|
-
insights: [],
|
|
9056
|
-
days: lookbackDays,
|
|
9057
|
-
insightType: insight_type ?? null
|
|
9058
|
-
}),
|
|
9059
|
-
null,
|
|
9060
|
-
2
|
|
9061
|
-
)
|
|
9227
|
+
text: JSON.stringify(structuredContent2, null, 2)
|
|
9062
9228
|
}
|
|
9063
9229
|
]
|
|
9064
9230
|
};
|
|
@@ -9073,20 +9239,18 @@ function registerInsightsTools(server2) {
|
|
|
9073
9239
|
};
|
|
9074
9240
|
}
|
|
9075
9241
|
const insights = rows;
|
|
9242
|
+
const structuredContent = asEnvelope6({
|
|
9243
|
+
insights,
|
|
9244
|
+
days: lookbackDays,
|
|
9245
|
+
insightType: insight_type ?? null
|
|
9246
|
+
});
|
|
9076
9247
|
if (format === "json") {
|
|
9077
9248
|
return {
|
|
9249
|
+
structuredContent,
|
|
9078
9250
|
content: [
|
|
9079
9251
|
{
|
|
9080
9252
|
type: "text",
|
|
9081
|
-
text: JSON.stringify(
|
|
9082
|
-
asEnvelope5({
|
|
9083
|
-
insights,
|
|
9084
|
-
days: lookbackDays,
|
|
9085
|
-
insightType: insight_type ?? null
|
|
9086
|
-
}),
|
|
9087
|
-
null,
|
|
9088
|
-
2
|
|
9089
|
-
)
|
|
9253
|
+
text: JSON.stringify(structuredContent, null, 2)
|
|
9090
9254
|
}
|
|
9091
9255
|
]
|
|
9092
9256
|
};
|
|
@@ -9111,6 +9275,7 @@ function registerInsightsTools(server2) {
|
|
|
9111
9275
|
lines.push(line);
|
|
9112
9276
|
}
|
|
9113
9277
|
return {
|
|
9278
|
+
structuredContent,
|
|
9114
9279
|
content: [{ type: "text", text: lines.join("\n") }]
|
|
9115
9280
|
};
|
|
9116
9281
|
}
|
|
@@ -9204,7 +9369,7 @@ function registerInsightsTools(server2) {
|
|
|
9204
9369
|
{
|
|
9205
9370
|
type: "text",
|
|
9206
9371
|
text: JSON.stringify(
|
|
9207
|
-
|
|
9372
|
+
asEnvelope6({
|
|
9208
9373
|
platform: platform3 ?? null,
|
|
9209
9374
|
days: lookbackDays,
|
|
9210
9375
|
recordsAnalyzed: rows.length,
|
|
@@ -9243,7 +9408,7 @@ function registerInsightsTools(server2) {
|
|
|
9243
9408
|
init_edge_function();
|
|
9244
9409
|
init_version();
|
|
9245
9410
|
import { z as z10 } from "zod";
|
|
9246
|
-
function
|
|
9411
|
+
function asEnvelope7(data) {
|
|
9247
9412
|
return {
|
|
9248
9413
|
_meta: {
|
|
9249
9414
|
version: MCP_VERSION,
|
|
@@ -9298,7 +9463,7 @@ function registerYouTubeAnalyticsTools(server2) {
|
|
|
9298
9463
|
{
|
|
9299
9464
|
type: "text",
|
|
9300
9465
|
text: JSON.stringify(
|
|
9301
|
-
|
|
9466
|
+
asEnvelope7({ action, startDate: start_date, endDate: end_date, analytics: a }),
|
|
9302
9467
|
null,
|
|
9303
9468
|
2
|
|
9304
9469
|
)
|
|
@@ -9335,7 +9500,7 @@ function registerYouTubeAnalyticsTools(server2) {
|
|
|
9335
9500
|
{
|
|
9336
9501
|
type: "text",
|
|
9337
9502
|
text: JSON.stringify(
|
|
9338
|
-
|
|
9503
|
+
asEnvelope7({
|
|
9339
9504
|
action,
|
|
9340
9505
|
startDate: start_date,
|
|
9341
9506
|
endDate: end_date,
|
|
@@ -9364,7 +9529,7 @@ function registerYouTubeAnalyticsTools(server2) {
|
|
|
9364
9529
|
{
|
|
9365
9530
|
type: "text",
|
|
9366
9531
|
text: JSON.stringify(
|
|
9367
|
-
|
|
9532
|
+
asEnvelope7({
|
|
9368
9533
|
action,
|
|
9369
9534
|
videoId: video_id,
|
|
9370
9535
|
startDate: start_date,
|
|
@@ -9404,7 +9569,7 @@ function registerYouTubeAnalyticsTools(server2) {
|
|
|
9404
9569
|
{
|
|
9405
9570
|
type: "text",
|
|
9406
9571
|
text: JSON.stringify(
|
|
9407
|
-
|
|
9572
|
+
asEnvelope7({
|
|
9408
9573
|
action,
|
|
9409
9574
|
startDate: start_date,
|
|
9410
9575
|
endDate: end_date,
|
|
@@ -9430,7 +9595,7 @@ function registerYouTubeAnalyticsTools(server2) {
|
|
|
9430
9595
|
}
|
|
9431
9596
|
if (format === "json") {
|
|
9432
9597
|
return {
|
|
9433
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
9598
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope7(result), null, 2) }]
|
|
9434
9599
|
};
|
|
9435
9600
|
}
|
|
9436
9601
|
return {
|
|
@@ -9445,7 +9610,7 @@ init_edge_function();
|
|
|
9445
9610
|
import { z as z11 } from "zod";
|
|
9446
9611
|
init_supabase();
|
|
9447
9612
|
init_version();
|
|
9448
|
-
function
|
|
9613
|
+
function asEnvelope8(data) {
|
|
9449
9614
|
return {
|
|
9450
9615
|
_meta: {
|
|
9451
9616
|
version: MCP_VERSION,
|
|
@@ -9490,7 +9655,7 @@ function registerCommentsTools(server2) {
|
|
|
9490
9655
|
{
|
|
9491
9656
|
type: "text",
|
|
9492
9657
|
text: JSON.stringify(
|
|
9493
|
-
|
|
9658
|
+
asEnvelope8({ comments, nextPageToken: result.nextPageToken ?? null }),
|
|
9494
9659
|
null,
|
|
9495
9660
|
2
|
|
9496
9661
|
)
|
|
@@ -9560,7 +9725,7 @@ function registerCommentsTools(server2) {
|
|
|
9560
9725
|
const result = data;
|
|
9561
9726
|
if (format === "json") {
|
|
9562
9727
|
return {
|
|
9563
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
9728
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope8(result), null, 2) }]
|
|
9564
9729
|
};
|
|
9565
9730
|
}
|
|
9566
9731
|
return {
|
|
@@ -9612,7 +9777,7 @@ function registerCommentsTools(server2) {
|
|
|
9612
9777
|
const result = data;
|
|
9613
9778
|
if (format === "json") {
|
|
9614
9779
|
return {
|
|
9615
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
9780
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope8(result), null, 2) }]
|
|
9616
9781
|
};
|
|
9617
9782
|
}
|
|
9618
9783
|
return {
|
|
@@ -9667,7 +9832,7 @@ function registerCommentsTools(server2) {
|
|
|
9667
9832
|
{
|
|
9668
9833
|
type: "text",
|
|
9669
9834
|
text: JSON.stringify(
|
|
9670
|
-
|
|
9835
|
+
asEnvelope8({
|
|
9671
9836
|
success: true,
|
|
9672
9837
|
commentId: comment_id,
|
|
9673
9838
|
moderationStatus: moderation_status
|
|
@@ -9726,7 +9891,7 @@ function registerCommentsTools(server2) {
|
|
|
9726
9891
|
content: [
|
|
9727
9892
|
{
|
|
9728
9893
|
type: "text",
|
|
9729
|
-
text: JSON.stringify(
|
|
9894
|
+
text: JSON.stringify(asEnvelope8({ success: true, commentId: comment_id }), null, 2)
|
|
9730
9895
|
}
|
|
9731
9896
|
]
|
|
9732
9897
|
};
|
|
@@ -9743,7 +9908,7 @@ init_supabase();
|
|
|
9743
9908
|
init_edge_function();
|
|
9744
9909
|
init_version();
|
|
9745
9910
|
import { z as z12 } from "zod";
|
|
9746
|
-
function
|
|
9911
|
+
function asEnvelope9(data) {
|
|
9747
9912
|
return {
|
|
9748
9913
|
_meta: {
|
|
9749
9914
|
version: MCP_VERSION,
|
|
@@ -9795,7 +9960,7 @@ function registerIdeationContextTools(server2) {
|
|
|
9795
9960
|
const context = result.context;
|
|
9796
9961
|
if (format === "json") {
|
|
9797
9962
|
return {
|
|
9798
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
9963
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope9(context), null, 2) }]
|
|
9799
9964
|
};
|
|
9800
9965
|
}
|
|
9801
9966
|
const lines = [
|
|
@@ -9817,7 +9982,7 @@ function registerIdeationContextTools(server2) {
|
|
|
9817
9982
|
init_edge_function();
|
|
9818
9983
|
import { z as z13 } from "zod";
|
|
9819
9984
|
init_version();
|
|
9820
|
-
function
|
|
9985
|
+
function asEnvelope10(data) {
|
|
9821
9986
|
return {
|
|
9822
9987
|
_meta: {
|
|
9823
9988
|
version: MCP_VERSION,
|
|
@@ -9854,7 +10019,7 @@ function registerCreditsTools(server2) {
|
|
|
9854
10019
|
};
|
|
9855
10020
|
if ((response_format || "text") === "json") {
|
|
9856
10021
|
return {
|
|
9857
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
10022
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope10(payload), null, 2) }]
|
|
9858
10023
|
};
|
|
9859
10024
|
}
|
|
9860
10025
|
return {
|
|
@@ -9888,7 +10053,7 @@ Monthly used: ${payload.monthlyUsed}` + (payload.monthlyLimit ? ` / ${payload.mo
|
|
|
9888
10053
|
};
|
|
9889
10054
|
if ((response_format || "text") === "json") {
|
|
9890
10055
|
return {
|
|
9891
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
10056
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope10(payload), null, 2) }]
|
|
9892
10057
|
};
|
|
9893
10058
|
}
|
|
9894
10059
|
return {
|
|
@@ -9914,7 +10079,7 @@ init_supabase();
|
|
|
9914
10079
|
init_edge_function();
|
|
9915
10080
|
init_version();
|
|
9916
10081
|
import { z as z14 } from "zod";
|
|
9917
|
-
function
|
|
10082
|
+
function asEnvelope11(data) {
|
|
9918
10083
|
return {
|
|
9919
10084
|
_meta: {
|
|
9920
10085
|
version: MCP_VERSION,
|
|
@@ -9965,7 +10130,7 @@ function registerLoopSummaryTools(server2) {
|
|
|
9965
10130
|
};
|
|
9966
10131
|
if ((response_format || "text") === "json") {
|
|
9967
10132
|
return {
|
|
9968
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
10133
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope11(payload), null, 2) }]
|
|
9969
10134
|
};
|
|
9970
10135
|
}
|
|
9971
10136
|
return {
|
|
@@ -9989,7 +10154,7 @@ Next Action: ${payload.recommendedNextAction}`
|
|
|
9989
10154
|
init_edge_function();
|
|
9990
10155
|
init_version();
|
|
9991
10156
|
import { z as z15 } from "zod";
|
|
9992
|
-
function
|
|
10157
|
+
function asEnvelope12(data) {
|
|
9993
10158
|
return {
|
|
9994
10159
|
_meta: {
|
|
9995
10160
|
version: MCP_VERSION,
|
|
@@ -10022,7 +10187,7 @@ function registerUsageTools(server2) {
|
|
|
10022
10187
|
content: [
|
|
10023
10188
|
{
|
|
10024
10189
|
type: "text",
|
|
10025
|
-
text: JSON.stringify(
|
|
10190
|
+
text: JSON.stringify(asEnvelope12({ tools: rows, totalCalls, totalCredits }), null, 2)
|
|
10026
10191
|
}
|
|
10027
10192
|
]
|
|
10028
10193
|
};
|
|
@@ -10063,7 +10228,7 @@ ${"=".repeat(40)}
|
|
|
10063
10228
|
init_edge_function();
|
|
10064
10229
|
init_version();
|
|
10065
10230
|
import { z as z16 } from "zod";
|
|
10066
|
-
function
|
|
10231
|
+
function asEnvelope13(data) {
|
|
10067
10232
|
return {
|
|
10068
10233
|
_meta: {
|
|
10069
10234
|
version: MCP_VERSION,
|
|
@@ -10103,7 +10268,7 @@ function registerAutopilotTools(server2) {
|
|
|
10103
10268
|
content: [
|
|
10104
10269
|
{
|
|
10105
10270
|
type: "text",
|
|
10106
|
-
text: JSON.stringify(
|
|
10271
|
+
text: JSON.stringify(asEnvelope13(configs), null, 2)
|
|
10107
10272
|
}
|
|
10108
10273
|
]
|
|
10109
10274
|
};
|
|
@@ -10244,7 +10409,7 @@ Schedule: ${JSON.stringify(updated.schedule_config)}`
|
|
|
10244
10409
|
content: [
|
|
10245
10410
|
{
|
|
10246
10411
|
type: "text",
|
|
10247
|
-
text: JSON.stringify(
|
|
10412
|
+
text: JSON.stringify(asEnvelope13(statusData), null, 2)
|
|
10248
10413
|
}
|
|
10249
10414
|
]
|
|
10250
10415
|
};
|
|
@@ -10328,7 +10493,7 @@ ${"=".repeat(40)}
|
|
|
10328
10493
|
}
|
|
10329
10494
|
if (format === "json") {
|
|
10330
10495
|
return {
|
|
10331
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
10496
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope13(created), null, 2) }]
|
|
10332
10497
|
};
|
|
10333
10498
|
}
|
|
10334
10499
|
return {
|
|
@@ -10351,7 +10516,7 @@ Active: ${is_active}`
|
|
|
10351
10516
|
init_edge_function();
|
|
10352
10517
|
init_version();
|
|
10353
10518
|
import { z as z17 } from "zod";
|
|
10354
|
-
function
|
|
10519
|
+
function asEnvelope14(data) {
|
|
10355
10520
|
return {
|
|
10356
10521
|
_meta: {
|
|
10357
10522
|
version: MCP_VERSION,
|
|
@@ -10400,7 +10565,7 @@ function registerRecipeTools(server2) {
|
|
|
10400
10565
|
content: [
|
|
10401
10566
|
{
|
|
10402
10567
|
type: "text",
|
|
10403
|
-
text: JSON.stringify(
|
|
10568
|
+
text: JSON.stringify(asEnvelope14(recipes))
|
|
10404
10569
|
}
|
|
10405
10570
|
]
|
|
10406
10571
|
};
|
|
@@ -10469,7 +10634,7 @@ ${lines.join("\n\n")}`
|
|
|
10469
10634
|
content: [
|
|
10470
10635
|
{
|
|
10471
10636
|
type: "text",
|
|
10472
|
-
text: JSON.stringify(
|
|
10637
|
+
text: JSON.stringify(asEnvelope14(recipe))
|
|
10473
10638
|
}
|
|
10474
10639
|
]
|
|
10475
10640
|
};
|
|
@@ -10506,7 +10671,7 @@ ${lines.join("\n\n")}`
|
|
|
10506
10671
|
"Execute a recipe template with the provided inputs. This creates a recipe run that processes each step sequentially. Long-running recipes will return a run_id you can check with get_recipe_run_status.",
|
|
10507
10672
|
{
|
|
10508
10673
|
slug: z17.string().describe('Recipe slug (e.g., "weekly-instagram-calendar")'),
|
|
10509
|
-
inputs: z17.record(z17.unknown()).describe(
|
|
10674
|
+
inputs: z17.record(z17.string(), z17.unknown()).describe(
|
|
10510
10675
|
"Input values matching the recipe input schema. Use get_recipe_details to see required inputs."
|
|
10511
10676
|
),
|
|
10512
10677
|
response_format: z17.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
@@ -10529,7 +10694,7 @@ ${lines.join("\n\n")}`
|
|
|
10529
10694
|
content: [
|
|
10530
10695
|
{
|
|
10531
10696
|
type: "text",
|
|
10532
|
-
text: JSON.stringify(
|
|
10697
|
+
text: JSON.stringify(asEnvelope14(result))
|
|
10533
10698
|
}
|
|
10534
10699
|
]
|
|
10535
10700
|
};
|
|
@@ -10585,7 +10750,7 @@ ${result?.message || "Use get_recipe_run_status to check progress."}`
|
|
|
10585
10750
|
content: [
|
|
10586
10751
|
{
|
|
10587
10752
|
type: "text",
|
|
10588
|
-
text: JSON.stringify(
|
|
10753
|
+
text: JSON.stringify(asEnvelope14(run))
|
|
10589
10754
|
}
|
|
10590
10755
|
]
|
|
10591
10756
|
};
|
|
@@ -10616,17 +10781,114 @@ ${JSON.stringify(run.outputs, null, 2)}
|
|
|
10616
10781
|
}
|
|
10617
10782
|
|
|
10618
10783
|
// src/tools/extraction.ts
|
|
10619
|
-
init_edge_function();
|
|
10620
10784
|
import { z as z18 } from "zod";
|
|
10621
10785
|
init_version();
|
|
10622
|
-
|
|
10623
|
-
|
|
10624
|
-
|
|
10625
|
-
function
|
|
10786
|
+
|
|
10787
|
+
// src/lib/urlExtraction.ts
|
|
10788
|
+
init_edge_function();
|
|
10789
|
+
function classifyYouTubeUrl(url) {
|
|
10626
10790
|
if (/youtube\.com\/watch|youtu\.be\//.test(url)) return "video";
|
|
10627
10791
|
if (/youtube\.com\/@/.test(url)) return "channel";
|
|
10628
10792
|
return false;
|
|
10629
10793
|
}
|
|
10794
|
+
function unwrapEf(result, label) {
|
|
10795
|
+
if (result.error) return { error: result.error };
|
|
10796
|
+
const env = result.data;
|
|
10797
|
+
if (!env || env.success === false) {
|
|
10798
|
+
return { error: env?.error ?? `No data returned from ${label}` };
|
|
10799
|
+
}
|
|
10800
|
+
return { payload: env.data ?? void 0 };
|
|
10801
|
+
}
|
|
10802
|
+
function segmentsToTranscript(segments) {
|
|
10803
|
+
if (!Array.isArray(segments)) return "";
|
|
10804
|
+
return segments.map((s) => (s?.text ?? "").trim()).filter(Boolean).join(" ");
|
|
10805
|
+
}
|
|
10806
|
+
async function extractUrlContent(url, opts = {}) {
|
|
10807
|
+
const extractType = opts.extractType ?? "auto";
|
|
10808
|
+
const youtubeType = classifyYouTubeUrl(url);
|
|
10809
|
+
if (youtubeType === "video") {
|
|
10810
|
+
const [txRes, metaRes] = await Promise.all([
|
|
10811
|
+
callEdgeFunction(
|
|
10812
|
+
"scrape-youtube",
|
|
10813
|
+
{ action: "transcript", videoUrl: url },
|
|
10814
|
+
{ timeoutMs: 3e4 }
|
|
10815
|
+
),
|
|
10816
|
+
callEdgeFunction(
|
|
10817
|
+
"scrape-youtube",
|
|
10818
|
+
{ action: "metadata", videoUrl: url },
|
|
10819
|
+
{ timeoutMs: 3e4 }
|
|
10820
|
+
)
|
|
10821
|
+
]);
|
|
10822
|
+
const tx = unwrapEf(txRes, "scrape-youtube transcript");
|
|
10823
|
+
const meta = unwrapEf(metaRes, "scrape-youtube metadata");
|
|
10824
|
+
if (tx.error && meta.error) {
|
|
10825
|
+
return { error: `Failed to extract YouTube video: ${meta.error ?? tx.error}` };
|
|
10826
|
+
}
|
|
10827
|
+
const m = meta.payload;
|
|
10828
|
+
return {
|
|
10829
|
+
content: {
|
|
10830
|
+
source_type: "youtube_video",
|
|
10831
|
+
url,
|
|
10832
|
+
title: m?.title ?? "",
|
|
10833
|
+
description: m?.description ?? "",
|
|
10834
|
+
transcript: segmentsToTranscript(tx.payload?.segments),
|
|
10835
|
+
video_metadata: m ? {
|
|
10836
|
+
views: typeof m.viewCount === "number" ? m.viewCount : 0,
|
|
10837
|
+
likes: typeof m.likes === "number" ? m.likes : 0,
|
|
10838
|
+
duration: typeof m.duration === "number" ? m.duration : 0,
|
|
10839
|
+
tags: Array.isArray(m.tags) ? m.tags : [],
|
|
10840
|
+
channel_name: m.channelName ?? ""
|
|
10841
|
+
} : void 0
|
|
10842
|
+
}
|
|
10843
|
+
};
|
|
10844
|
+
}
|
|
10845
|
+
if (youtubeType === "channel") {
|
|
10846
|
+
const res2 = await callEdgeFunction(
|
|
10847
|
+
"scrape-youtube",
|
|
10848
|
+
{ action: "channel_videos", videoUrl: url },
|
|
10849
|
+
{ timeoutMs: 3e4 }
|
|
10850
|
+
);
|
|
10851
|
+
const ch = unwrapEf(res2, "scrape-youtube channel");
|
|
10852
|
+
if (ch.error) return { error: `Failed to extract YouTube channel: ${ch.error}` };
|
|
10853
|
+
const videos = ch.payload?.videos ?? [];
|
|
10854
|
+
return {
|
|
10855
|
+
content: {
|
|
10856
|
+
source_type: "youtube_channel",
|
|
10857
|
+
url,
|
|
10858
|
+
title: url,
|
|
10859
|
+
description: videos.length ? `${videos.length} recent videos:
|
|
10860
|
+
${videos.map((v) => `- ${v.title ?? "Untitled"}`).join("\n")}` : "No videos found for this channel."
|
|
10861
|
+
}
|
|
10862
|
+
};
|
|
10863
|
+
}
|
|
10864
|
+
const res = await callEdgeFunction(
|
|
10865
|
+
"fetch-url-content",
|
|
10866
|
+
{ url, extractType: extractType === "auto" ? "product" : extractType },
|
|
10867
|
+
{ timeoutMs: 3e4 }
|
|
10868
|
+
);
|
|
10869
|
+
const result = unwrapEf(res, "fetch-url-content");
|
|
10870
|
+
if (result.error || !result.payload) {
|
|
10871
|
+
return { error: `Failed to extract URL content: ${result.error ?? "No data returned"}` };
|
|
10872
|
+
}
|
|
10873
|
+
const info = result.payload;
|
|
10874
|
+
return {
|
|
10875
|
+
content: {
|
|
10876
|
+
source_type: extractType === "product" ? "product" : "article",
|
|
10877
|
+
url,
|
|
10878
|
+
title: info.name ?? "",
|
|
10879
|
+
description: info.description ?? "",
|
|
10880
|
+
features: info.features,
|
|
10881
|
+
benefits: info.benefits,
|
|
10882
|
+
usp: info.usp,
|
|
10883
|
+
suggested_hooks: info.suggestedHookAngles
|
|
10884
|
+
}
|
|
10885
|
+
};
|
|
10886
|
+
}
|
|
10887
|
+
|
|
10888
|
+
// src/tools/extraction.ts
|
|
10889
|
+
function asEnvelope15(data) {
|
|
10890
|
+
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
10891
|
+
}
|
|
10630
10892
|
function formatExtractedContentAsText(content) {
|
|
10631
10893
|
const lines = [];
|
|
10632
10894
|
lines.push(`Source: ${content.source_type} (${content.url})`);
|
|
@@ -10671,15 +10933,15 @@ ${content.suggested_hooks.map((h) => ` - ${h}`).join("\n")}`
|
|
|
10671
10933
|
function registerExtractionTools(server2) {
|
|
10672
10934
|
server2.tool(
|
|
10673
10935
|
"extract_url_content",
|
|
10674
|
-
"Extract text content from any URL \u2014 YouTube video
|
|
10936
|
+
"Extract text content from any URL \u2014 YouTube video transcript + metadata (title/views/channel), or article text, or product page features/benefits/USP. YouTube URLs auto-route to transcript+metadata extraction; channel URLs return a recent-video list. Use before generate_content to repurpose existing content, or before plan_content_week to base a content plan on a source URL.",
|
|
10675
10937
|
{
|
|
10676
10938
|
url: z18.string().url().describe("URL to extract content from"),
|
|
10677
|
-
extract_type: z18.enum(["auto", "transcript", "article", "product"]).default("auto").describe(
|
|
10678
|
-
|
|
10679
|
-
|
|
10939
|
+
extract_type: z18.enum(["auto", "transcript", "article", "product"]).default("auto").describe(
|
|
10940
|
+
"auto = product-style extraction; transcript = YouTube (auto-detected by URL); article = blog/news main text + key points; product = e-commerce features/benefits/USP."
|
|
10941
|
+
),
|
|
10680
10942
|
response_format: z18.enum(["text", "json"]).default("text")
|
|
10681
10943
|
},
|
|
10682
|
-
async ({ url, extract_type,
|
|
10944
|
+
async ({ url, extract_type, response_format }) => {
|
|
10683
10945
|
const ssrfCheck = await validateUrlForSSRF(url);
|
|
10684
10946
|
if (!ssrfCheck.isValid) {
|
|
10685
10947
|
return {
|
|
@@ -10687,106 +10949,20 @@ function registerExtractionTools(server2) {
|
|
|
10687
10949
|
isError: true
|
|
10688
10950
|
};
|
|
10689
10951
|
}
|
|
10690
|
-
const youtubeType = isYouTubeUrl(url);
|
|
10691
10952
|
try {
|
|
10692
|
-
|
|
10693
|
-
|
|
10694
|
-
|
|
10695
|
-
|
|
10696
|
-
|
|
10697
|
-
|
|
10698
|
-
|
|
10699
|
-
maxComments: max_results
|
|
10700
|
-
},
|
|
10701
|
-
{ timeoutMs: 3e4 }
|
|
10702
|
-
);
|
|
10703
|
-
if (error || !data) {
|
|
10704
|
-
return {
|
|
10705
|
-
content: [
|
|
10706
|
-
{
|
|
10707
|
-
type: "text",
|
|
10708
|
-
text: `Failed to extract YouTube video: ${error ?? "No data returned"}`
|
|
10709
|
-
}
|
|
10710
|
-
],
|
|
10711
|
-
isError: true
|
|
10712
|
-
};
|
|
10713
|
-
}
|
|
10714
|
-
extracted = {
|
|
10715
|
-
source_type: "youtube_video",
|
|
10716
|
-
url,
|
|
10717
|
-
title: data.title ?? "",
|
|
10718
|
-
description: data.description ?? "",
|
|
10719
|
-
transcript: data.transcript,
|
|
10720
|
-
video_metadata: data.metadata ? {
|
|
10721
|
-
views: data.metadata.views ?? 0,
|
|
10722
|
-
likes: data.metadata.likes ?? 0,
|
|
10723
|
-
duration: data.metadata.duration ?? 0,
|
|
10724
|
-
tags: data.metadata.tags ?? [],
|
|
10725
|
-
channel_name: data.metadata.channelName ?? ""
|
|
10726
|
-
} : void 0
|
|
10727
|
-
};
|
|
10728
|
-
} else if (youtubeType === "channel") {
|
|
10729
|
-
const { data, error } = await callEdgeFunction(
|
|
10730
|
-
"scrape-youtube",
|
|
10731
|
-
{
|
|
10732
|
-
url,
|
|
10733
|
-
type: "channel"
|
|
10734
|
-
},
|
|
10735
|
-
{ timeoutMs: 3e4 }
|
|
10736
|
-
);
|
|
10737
|
-
if (error || !data) {
|
|
10738
|
-
return {
|
|
10739
|
-
content: [
|
|
10740
|
-
{
|
|
10741
|
-
type: "text",
|
|
10742
|
-
text: `Failed to extract YouTube channel: ${error ?? "No data returned"}`
|
|
10743
|
-
}
|
|
10744
|
-
],
|
|
10745
|
-
isError: true
|
|
10746
|
-
};
|
|
10747
|
-
}
|
|
10748
|
-
extracted = {
|
|
10749
|
-
source_type: "youtube_channel",
|
|
10750
|
-
url,
|
|
10751
|
-
title: data.title ?? "",
|
|
10752
|
-
description: data.description ?? ""
|
|
10753
|
-
};
|
|
10754
|
-
} else {
|
|
10755
|
-
const body = { url };
|
|
10756
|
-
if (extract_type !== "auto") body.type = extract_type;
|
|
10757
|
-
const { data, error } = await callEdgeFunction(
|
|
10758
|
-
"fetch-url-content",
|
|
10759
|
-
body,
|
|
10760
|
-
{ timeoutMs: 3e4 }
|
|
10761
|
-
);
|
|
10762
|
-
if (error || !data) {
|
|
10763
|
-
return {
|
|
10764
|
-
content: [
|
|
10765
|
-
{
|
|
10766
|
-
type: "text",
|
|
10767
|
-
text: `Failed to extract URL content: ${error ?? "No data returned"}`
|
|
10768
|
-
}
|
|
10769
|
-
],
|
|
10770
|
-
isError: true
|
|
10771
|
-
};
|
|
10772
|
-
}
|
|
10773
|
-
const sourceType = extract_type === "product" ? "product" : data.type === "product" ? "product" : "article";
|
|
10774
|
-
extracted = {
|
|
10775
|
-
source_type: sourceType,
|
|
10776
|
-
url,
|
|
10777
|
-
title: data.title ?? "",
|
|
10778
|
-
description: data.description ?? "",
|
|
10779
|
-
transcript: data.content,
|
|
10780
|
-
features: data.features,
|
|
10781
|
-
benefits: data.benefits,
|
|
10782
|
-
usp: data.usp,
|
|
10783
|
-
suggested_hooks: data.suggestedHooks
|
|
10953
|
+
const { content: extracted, error } = await extractUrlContent(url, {
|
|
10954
|
+
extractType: extract_type
|
|
10955
|
+
});
|
|
10956
|
+
if (error || !extracted) {
|
|
10957
|
+
return {
|
|
10958
|
+
content: [{ type: "text", text: error ?? "No data returned" }],
|
|
10959
|
+
isError: true
|
|
10784
10960
|
};
|
|
10785
10961
|
}
|
|
10786
10962
|
if (response_format === "json") {
|
|
10787
10963
|
return {
|
|
10788
10964
|
content: [
|
|
10789
|
-
{ type: "text", text: JSON.stringify(
|
|
10965
|
+
{ type: "text", text: JSON.stringify(asEnvelope15(extracted), null, 2) }
|
|
10790
10966
|
],
|
|
10791
10967
|
isError: false
|
|
10792
10968
|
};
|
|
@@ -10810,7 +10986,7 @@ function registerExtractionTools(server2) {
|
|
|
10810
10986
|
init_quality();
|
|
10811
10987
|
init_version();
|
|
10812
10988
|
import { z as z19 } from "zod";
|
|
10813
|
-
function
|
|
10989
|
+
function asEnvelope16(data) {
|
|
10814
10990
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
10815
10991
|
}
|
|
10816
10992
|
function registerQualityTools(server2) {
|
|
@@ -10863,7 +11039,7 @@ function registerQualityTools(server2) {
|
|
|
10863
11039
|
});
|
|
10864
11040
|
if (response_format === "json") {
|
|
10865
11041
|
return {
|
|
10866
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
11042
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope16(result), null, 2) }],
|
|
10867
11043
|
isError: false
|
|
10868
11044
|
};
|
|
10869
11045
|
}
|
|
@@ -10938,7 +11114,7 @@ function registerQualityTools(server2) {
|
|
|
10938
11114
|
content: [
|
|
10939
11115
|
{
|
|
10940
11116
|
type: "text",
|
|
10941
|
-
text: JSON.stringify(
|
|
11117
|
+
text: JSON.stringify(asEnvelope16({ posts: postsWithQuality, summary }), null, 2)
|
|
10942
11118
|
}
|
|
10943
11119
|
],
|
|
10944
11120
|
isError: false
|
|
@@ -11273,7 +11449,7 @@ function buildCorrectiveHint(overflowIssues, spellingIssues) {
|
|
|
11273
11449
|
|
|
11274
11450
|
// src/tools/visualQuality.ts
|
|
11275
11451
|
init_version();
|
|
11276
|
-
function
|
|
11452
|
+
function asEnvelope17(data) {
|
|
11277
11453
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
11278
11454
|
}
|
|
11279
11455
|
var VALID_STYLES = ["dark-cinematic", "clean-editorial", "bold-authority"];
|
|
@@ -11398,7 +11574,7 @@ function registerVisualQualityTools(server2) {
|
|
|
11398
11574
|
{
|
|
11399
11575
|
type: "text",
|
|
11400
11576
|
text: JSON.stringify(
|
|
11401
|
-
|
|
11577
|
+
asEnvelope17({ ...result, correctiveHint: hint || null }),
|
|
11402
11578
|
null,
|
|
11403
11579
|
2
|
|
11404
11580
|
)
|
|
@@ -11468,7 +11644,7 @@ function registerVisualQualityTools(server2) {
|
|
|
11468
11644
|
return { content: [{ type: "text", text: lines.join("\n") }], isError: false };
|
|
11469
11645
|
}
|
|
11470
11646
|
return {
|
|
11471
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
11647
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope17(data), null, 2) }],
|
|
11472
11648
|
isError: false
|
|
11473
11649
|
};
|
|
11474
11650
|
}
|
|
@@ -11506,7 +11682,7 @@ function extractJsonArray(text) {
|
|
|
11506
11682
|
function toRecord(value) {
|
|
11507
11683
|
return value && typeof value === "object" ? value : void 0;
|
|
11508
11684
|
}
|
|
11509
|
-
function
|
|
11685
|
+
function asEnvelope18(data) {
|
|
11510
11686
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
11511
11687
|
}
|
|
11512
11688
|
function tomorrowIsoDate() {
|
|
@@ -11519,9 +11695,6 @@ function addDaysToIsoDate(dateStr, days) {
|
|
|
11519
11695
|
d.setDate(d.getDate() + days);
|
|
11520
11696
|
return d.toISOString().split("T")[0];
|
|
11521
11697
|
}
|
|
11522
|
-
function isYouTubeUrl2(url) {
|
|
11523
|
-
return /youtube\.com\/watch|youtu\.be\/|youtube\.com\/@/.test(url);
|
|
11524
|
-
}
|
|
11525
11698
|
function formatPlanAsText(plan) {
|
|
11526
11699
|
const lines = [];
|
|
11527
11700
|
lines.push(`WEEKLY CONTENT PLAN: "${plan.topic}"`);
|
|
@@ -11619,20 +11792,22 @@ function registerPlanningTools(server2) {
|
|
|
11619
11792
|
let sourceContext = "";
|
|
11620
11793
|
if (source_url) {
|
|
11621
11794
|
try {
|
|
11622
|
-
const
|
|
11623
|
-
|
|
11624
|
-
|
|
11625
|
-
|
|
11626
|
-
|
|
11627
|
-
|
|
11628
|
-
|
|
11629
|
-
|
|
11630
|
-
|
|
11631
|
-
|
|
11632
|
-
|
|
11633
|
-
|
|
11634
|
-
|
|
11635
|
-
|
|
11795
|
+
const ssrf = await validateUrlForSSRF(source_url);
|
|
11796
|
+
if (ssrf.isValid) {
|
|
11797
|
+
const { content } = await extractUrlContent(source_url);
|
|
11798
|
+
if (content) {
|
|
11799
|
+
const parts = [
|
|
11800
|
+
content.title,
|
|
11801
|
+
content.description,
|
|
11802
|
+
content.transcript ? content.transcript.slice(0, 2e3) : "",
|
|
11803
|
+
content.features?.length ? `Features:
|
|
11804
|
+
${content.features.join("\n")}` : "",
|
|
11805
|
+
content.benefits?.length ? `Benefits:
|
|
11806
|
+
${content.benefits.join("\n")}` : "",
|
|
11807
|
+
content.usp ?? ""
|
|
11808
|
+
].filter(Boolean);
|
|
11809
|
+
sourceContext = parts.join("\n\n");
|
|
11810
|
+
}
|
|
11636
11811
|
}
|
|
11637
11812
|
} catch {
|
|
11638
11813
|
}
|
|
@@ -11838,13 +12013,16 @@ ${rawText.slice(0, 1e3)}`
|
|
|
11838
12013
|
};
|
|
11839
12014
|
}
|
|
11840
12015
|
}
|
|
12016
|
+
const structuredContent = asEnvelope18(plan);
|
|
11841
12017
|
if (response_format === "json") {
|
|
11842
12018
|
return {
|
|
11843
|
-
|
|
12019
|
+
structuredContent,
|
|
12020
|
+
content: [{ type: "text", text: JSON.stringify(structuredContent, null, 2) }],
|
|
11844
12021
|
isError: false
|
|
11845
12022
|
};
|
|
11846
12023
|
}
|
|
11847
12024
|
return {
|
|
12025
|
+
structuredContent,
|
|
11848
12026
|
content: [{ type: "text", text: formatPlanAsText(plan) }],
|
|
11849
12027
|
isError: false
|
|
11850
12028
|
};
|
|
@@ -11915,7 +12093,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
11915
12093
|
};
|
|
11916
12094
|
if (response_format === "json") {
|
|
11917
12095
|
return {
|
|
11918
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
12096
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope18(result), null, 2) }],
|
|
11919
12097
|
isError: false
|
|
11920
12098
|
};
|
|
11921
12099
|
}
|
|
@@ -11974,7 +12152,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
11974
12152
|
};
|
|
11975
12153
|
if (response_format === "json") {
|
|
11976
12154
|
return {
|
|
11977
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
12155
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope18(payload), null, 2) }],
|
|
11978
12156
|
isError: false
|
|
11979
12157
|
};
|
|
11980
12158
|
}
|
|
@@ -12046,7 +12224,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
12046
12224
|
};
|
|
12047
12225
|
if (response_format === "json") {
|
|
12048
12226
|
return {
|
|
12049
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
12227
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope18(payload), null, 2) }],
|
|
12050
12228
|
isError: false
|
|
12051
12229
|
};
|
|
12052
12230
|
}
|
|
@@ -12099,7 +12277,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
12099
12277
|
};
|
|
12100
12278
|
if (response_format === "json") {
|
|
12101
12279
|
return {
|
|
12102
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
12280
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope18(payload), null, 2) }],
|
|
12103
12281
|
isError: false
|
|
12104
12282
|
};
|
|
12105
12283
|
}
|
|
@@ -12121,7 +12299,7 @@ init_edge_function();
|
|
|
12121
12299
|
init_supabase();
|
|
12122
12300
|
init_version();
|
|
12123
12301
|
import { z as z22 } from "zod";
|
|
12124
|
-
function
|
|
12302
|
+
function asEnvelope19(data) {
|
|
12125
12303
|
return {
|
|
12126
12304
|
_meta: {
|
|
12127
12305
|
version: MCP_VERSION,
|
|
@@ -12196,7 +12374,7 @@ function registerPlanApprovalTools(server2) {
|
|
|
12196
12374
|
};
|
|
12197
12375
|
if ((response_format || "text") === "json") {
|
|
12198
12376
|
return {
|
|
12199
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
12377
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope19(payload), null, 2) }],
|
|
12200
12378
|
isError: false
|
|
12201
12379
|
};
|
|
12202
12380
|
}
|
|
@@ -12248,7 +12426,7 @@ function registerPlanApprovalTools(server2) {
|
|
|
12248
12426
|
};
|
|
12249
12427
|
if ((response_format || "text") === "json") {
|
|
12250
12428
|
return {
|
|
12251
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
12429
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope19(payload), null, 2) }],
|
|
12252
12430
|
isError: false
|
|
12253
12431
|
};
|
|
12254
12432
|
}
|
|
@@ -12326,7 +12504,7 @@ function registerPlanApprovalTools(server2) {
|
|
|
12326
12504
|
}
|
|
12327
12505
|
if ((response_format || "text") === "json") {
|
|
12328
12506
|
return {
|
|
12329
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
12507
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope19(data), null, 2) }],
|
|
12330
12508
|
isError: false
|
|
12331
12509
|
};
|
|
12332
12510
|
}
|
|
@@ -12346,7 +12524,191 @@ function registerPlanApprovalTools(server2) {
|
|
|
12346
12524
|
// src/tools/discovery.ts
|
|
12347
12525
|
init_tool_catalog();
|
|
12348
12526
|
import { z as z23 } from "zod";
|
|
12527
|
+
init_supabase();
|
|
12528
|
+
init_request_context();
|
|
12529
|
+
var KNOWLEDGE_BASE_URL = "https://socialneuron.com/for-developers";
|
|
12530
|
+
var KNOWLEDGE_SEARCH_LIMIT = 10;
|
|
12531
|
+
var SearchOutputSchema = {
|
|
12532
|
+
results: z23.array(
|
|
12533
|
+
z23.object({
|
|
12534
|
+
id: z23.string(),
|
|
12535
|
+
title: z23.string(),
|
|
12536
|
+
url: z23.string().url()
|
|
12537
|
+
})
|
|
12538
|
+
)
|
|
12539
|
+
};
|
|
12540
|
+
var FetchOutputSchema = {
|
|
12541
|
+
id: z23.string(),
|
|
12542
|
+
title: z23.string(),
|
|
12543
|
+
text: z23.string(),
|
|
12544
|
+
url: z23.string().url(),
|
|
12545
|
+
metadata: z23.record(z23.string(), z23.string()).optional()
|
|
12546
|
+
};
|
|
12547
|
+
var STATIC_KNOWLEDGE_DOCUMENTS = [
|
|
12548
|
+
{
|
|
12549
|
+
id: "overview",
|
|
12550
|
+
title: "Social Neuron MCP Overview",
|
|
12551
|
+
url: `${KNOWLEDGE_BASE_URL}#mcp`,
|
|
12552
|
+
text: [
|
|
12553
|
+
"Social Neuron exposes an MCP server for creating, scheduling, and optimizing social content.",
|
|
12554
|
+
"Use the hosted streamable HTTP endpoint at https://mcp.socialneuron.com/mcp for ChatGPT, Claude, and other remote MCP clients.",
|
|
12555
|
+
"The npm package provides stdio transport for local tools and Codex-style workflows."
|
|
12556
|
+
].join("\n"),
|
|
12557
|
+
metadata: { source: "public-developer-docs", category: "overview" }
|
|
12558
|
+
},
|
|
12559
|
+
{
|
|
12560
|
+
id: "integrations",
|
|
12561
|
+
title: "Supported Social Integrations",
|
|
12562
|
+
url: "https://socialneuron.com/integrations",
|
|
12563
|
+
text: [
|
|
12564
|
+
"Social Neuron tracks platform availability on the integrations page.",
|
|
12565
|
+
"YouTube, TikTok, Instagram, LinkedIn, X, and Facebook are live for supported posting workflows.",
|
|
12566
|
+
"Threads and Bluesky are supported surfaces where live availability depends on the current integration status."
|
|
12567
|
+
].join("\n"),
|
|
12568
|
+
metadata: { source: "public-integrations-page", category: "integrations" }
|
|
12569
|
+
},
|
|
12570
|
+
{
|
|
12571
|
+
id: "chatgpt-connector",
|
|
12572
|
+
title: "ChatGPT Connector Setup",
|
|
12573
|
+
url: `${KNOWLEDGE_BASE_URL}#chatgpt`,
|
|
12574
|
+
text: [
|
|
12575
|
+
"In ChatGPT Developer Mode, create an MCP app using https://mcp.socialneuron.com/mcp as the MCP URL.",
|
|
12576
|
+
"The connector uses OAuth for account linking and tool scopes for read, write, distribution, analytics, comments, and autopilot access.",
|
|
12577
|
+
"Publishing tools should be treated as externally visible actions and require the distribute scope."
|
|
12578
|
+
].join("\n"),
|
|
12579
|
+
metadata: { source: "public-developer-docs", category: "chatgpt" }
|
|
12580
|
+
},
|
|
12581
|
+
{
|
|
12582
|
+
id: "privacy-security",
|
|
12583
|
+
title: "Connector Security and Data Minimization",
|
|
12584
|
+
url: `${KNOWLEDGE_BASE_URL}#security`,
|
|
12585
|
+
text: [
|
|
12586
|
+
"Social Neuron MCP tools enforce OAuth or API-key scopes before tool execution.",
|
|
12587
|
+
"Read-only discovery tools expose public product and tool metadata, not private account content.",
|
|
12588
|
+
"User-owned content and analytics require authenticated scopes and organization or project membership checks in backend functions."
|
|
12589
|
+
].join("\n"),
|
|
12590
|
+
metadata: { source: "public-developer-docs", category: "security" }
|
|
12591
|
+
}
|
|
12592
|
+
];
|
|
12593
|
+
function toolKnowledgeDocument(tool) {
|
|
12594
|
+
const lines = [
|
|
12595
|
+
`Tool: ${tool.name}`,
|
|
12596
|
+
`Description: ${tool.description}`,
|
|
12597
|
+
`Module: ${tool.module}`,
|
|
12598
|
+
`Required scope: ${tool.scope}`
|
|
12599
|
+
];
|
|
12600
|
+
if (tool.task_intent) lines.push(`Task intent: ${tool.task_intent}`);
|
|
12601
|
+
if (tool.use_when) lines.push(`Use when: ${tool.use_when}`);
|
|
12602
|
+
if (tool.avoid_when) lines.push(`Avoid when: ${tool.avoid_when}`);
|
|
12603
|
+
if (tool.next_tools?.length) lines.push(`Common next tools: ${tool.next_tools.join(", ")}`);
|
|
12604
|
+
return {
|
|
12605
|
+
id: `tool:${tool.name}`,
|
|
12606
|
+
title: `MCP tool: ${tool.name}`,
|
|
12607
|
+
url: `${KNOWLEDGE_BASE_URL}#tool-${tool.name}`,
|
|
12608
|
+
text: lines.join("\n"),
|
|
12609
|
+
metadata: {
|
|
12610
|
+
source: "mcp-tool-catalog",
|
|
12611
|
+
category: "tool",
|
|
12612
|
+
module: tool.module,
|
|
12613
|
+
scope: tool.scope
|
|
12614
|
+
}
|
|
12615
|
+
};
|
|
12616
|
+
}
|
|
12617
|
+
function getKnowledgeDocuments() {
|
|
12618
|
+
return [...STATIC_KNOWLEDGE_DOCUMENTS, ...TOOL_CATALOG.map(toolKnowledgeDocument)];
|
|
12619
|
+
}
|
|
12620
|
+
function tokenize(input) {
|
|
12621
|
+
return input.toLowerCase().split(/[^a-z0-9:_-]+/).map((token) => token.trim()).filter(Boolean);
|
|
12622
|
+
}
|
|
12623
|
+
function scoreDocument(queryTokens, doc) {
|
|
12624
|
+
const title = doc.title.toLowerCase();
|
|
12625
|
+
const text = doc.text.toLowerCase();
|
|
12626
|
+
const metadata = JSON.stringify(doc.metadata ?? {}).toLowerCase();
|
|
12627
|
+
return queryTokens.reduce((score, token) => {
|
|
12628
|
+
if (doc.id.toLowerCase() === token) return score + 12;
|
|
12629
|
+
if (doc.id.toLowerCase().includes(token)) score += 8;
|
|
12630
|
+
if (title.includes(token)) score += 5;
|
|
12631
|
+
if (text.includes(token)) score += 2;
|
|
12632
|
+
if (metadata.includes(token)) score += 1;
|
|
12633
|
+
return score;
|
|
12634
|
+
}, 0);
|
|
12635
|
+
}
|
|
12636
|
+
function searchKnowledge(query) {
|
|
12637
|
+
const docs = getKnowledgeDocuments();
|
|
12638
|
+
const tokens = tokenize(query);
|
|
12639
|
+
if (tokens.length === 0) {
|
|
12640
|
+
return docs.slice(0, KNOWLEDGE_SEARCH_LIMIT);
|
|
12641
|
+
}
|
|
12642
|
+
return docs.map((doc) => ({ doc, score: scoreDocument(tokens, doc) })).filter(({ score }) => score > 0).sort((a, b) => b.score - a.score || a.doc.title.localeCompare(b.doc.title)).slice(0, KNOWLEDGE_SEARCH_LIMIT).map(({ doc }) => doc);
|
|
12643
|
+
}
|
|
12349
12644
|
function registerDiscoveryTools(server2) {
|
|
12645
|
+
server2.registerTool(
|
|
12646
|
+
"search",
|
|
12647
|
+
{
|
|
12648
|
+
title: "Search Social Neuron Knowledge",
|
|
12649
|
+
description: "Search public Social Neuron product, integration, developer, and MCP tool knowledge. Uses the standard ChatGPT MCP search schema and never returns private account content.",
|
|
12650
|
+
inputSchema: {
|
|
12651
|
+
query: z23.string().describe("Search query.")
|
|
12652
|
+
},
|
|
12653
|
+
outputSchema: SearchOutputSchema,
|
|
12654
|
+
annotations: {
|
|
12655
|
+
readOnlyHint: true,
|
|
12656
|
+
destructiveHint: false,
|
|
12657
|
+
idempotentHint: true,
|
|
12658
|
+
openWorldHint: false
|
|
12659
|
+
}
|
|
12660
|
+
},
|
|
12661
|
+
async ({ query }) => {
|
|
12662
|
+
const structuredContent = {
|
|
12663
|
+
results: searchKnowledge(query).map((doc) => ({
|
|
12664
|
+
id: doc.id,
|
|
12665
|
+
title: doc.title,
|
|
12666
|
+
url: doc.url
|
|
12667
|
+
}))
|
|
12668
|
+
};
|
|
12669
|
+
return {
|
|
12670
|
+
structuredContent,
|
|
12671
|
+
content: [{ type: "text", text: JSON.stringify(structuredContent) }]
|
|
12672
|
+
};
|
|
12673
|
+
}
|
|
12674
|
+
);
|
|
12675
|
+
server2.registerTool(
|
|
12676
|
+
"fetch",
|
|
12677
|
+
{
|
|
12678
|
+
title: "Fetch Social Neuron Knowledge",
|
|
12679
|
+
description: "Fetch a public Social Neuron knowledge document by ID. Use IDs returned by the search tool.",
|
|
12680
|
+
inputSchema: {
|
|
12681
|
+
id: z23.string().describe("Document ID returned by search.")
|
|
12682
|
+
},
|
|
12683
|
+
outputSchema: FetchOutputSchema,
|
|
12684
|
+
annotations: {
|
|
12685
|
+
readOnlyHint: true,
|
|
12686
|
+
destructiveHint: false,
|
|
12687
|
+
idempotentHint: true,
|
|
12688
|
+
openWorldHint: false
|
|
12689
|
+
}
|
|
12690
|
+
},
|
|
12691
|
+
async ({ id }) => {
|
|
12692
|
+
const doc = getKnowledgeDocuments().find((candidate) => candidate.id === id);
|
|
12693
|
+
if (!doc) {
|
|
12694
|
+
return {
|
|
12695
|
+
content: [{ type: "text", text: `Document not found: ${id}` }],
|
|
12696
|
+
isError: true
|
|
12697
|
+
};
|
|
12698
|
+
}
|
|
12699
|
+
const structuredContent = {
|
|
12700
|
+
id: doc.id,
|
|
12701
|
+
title: doc.title,
|
|
12702
|
+
text: doc.text,
|
|
12703
|
+
url: doc.url,
|
|
12704
|
+
...doc.metadata ? { metadata: doc.metadata } : {}
|
|
12705
|
+
};
|
|
12706
|
+
return {
|
|
12707
|
+
structuredContent,
|
|
12708
|
+
content: [{ type: "text", text: JSON.stringify(structuredContent) }]
|
|
12709
|
+
};
|
|
12710
|
+
}
|
|
12711
|
+
);
|
|
12350
12712
|
server2.tool(
|
|
12351
12713
|
"search_tools",
|
|
12352
12714
|
'Search available tools by name, description, module, or scope. Use "name" detail (~50 tokens) for quick lookup, "summary" (~500 tokens) for descriptions, "full" for complete input schemas. Start here if unsure which tool to call \u2014 filter by module (e.g. "planning", "content", "analytics") to narrow results.',
|
|
@@ -12356,9 +12718,15 @@ function registerDiscoveryTools(server2) {
|
|
|
12356
12718
|
scope: z23.string().optional().describe('Filter by required scope (e.g. "mcp:read", "mcp:write")'),
|
|
12357
12719
|
detail: z23.enum(["name", "summary", "full"]).default("summary").describe(
|
|
12358
12720
|
'Detail level: "name" for just tool names, "summary" for names + descriptions, "full" for complete info including scope and module'
|
|
12721
|
+
),
|
|
12722
|
+
available_only: z23.boolean().default(false).describe(
|
|
12723
|
+
"When true, only return tools allowed by the current API key/OAuth scopes. Use this after a permission_denied error."
|
|
12359
12724
|
)
|
|
12360
12725
|
},
|
|
12361
|
-
async ({ query, module, scope, detail }) => {
|
|
12726
|
+
async ({ query, module, scope, detail, available_only }) => {
|
|
12727
|
+
const currentScopes = getRequestScopes() ?? getAuthenticatedScopes();
|
|
12728
|
+
const hasKnownScopes = currentScopes.length > 0;
|
|
12729
|
+
const isAvailable = (tool) => !hasKnownScopes || hasScope(currentScopes, tool.scope);
|
|
12362
12730
|
let results = [...TOOL_CATALOG];
|
|
12363
12731
|
if (query) {
|
|
12364
12732
|
results = searchTools(query);
|
|
@@ -12371,24 +12739,48 @@ function registerDiscoveryTools(server2) {
|
|
|
12371
12739
|
const scopeTools = getToolsByScope(scope);
|
|
12372
12740
|
results = results.filter((t) => scopeTools.some((st) => st.name === t.name));
|
|
12373
12741
|
}
|
|
12742
|
+
if (available_only) {
|
|
12743
|
+
results = results.filter(isAvailable);
|
|
12744
|
+
}
|
|
12745
|
+
const unavailableCount = hasKnownScopes ? results.filter((t) => !isAvailable(t)).length : 0;
|
|
12374
12746
|
let output;
|
|
12375
12747
|
switch (detail) {
|
|
12376
12748
|
case "name":
|
|
12377
12749
|
output = results.map((t) => t.name);
|
|
12378
12750
|
break;
|
|
12379
12751
|
case "summary":
|
|
12380
|
-
output = results.map((t) => ({
|
|
12752
|
+
output = results.map((t) => ({
|
|
12753
|
+
name: t.name,
|
|
12754
|
+
description: t.description,
|
|
12755
|
+
...hasKnownScopes || scope ? { required_scope: t.scope } : {},
|
|
12756
|
+
...hasKnownScopes ? { available: isAvailable(t) } : {},
|
|
12757
|
+
...t.task_intent ? { task_intent: t.task_intent } : {},
|
|
12758
|
+
...t.use_when ? { use_when: t.use_when } : {},
|
|
12759
|
+
...t.avoid_when ? { avoid_when: t.avoid_when } : {}
|
|
12760
|
+
}));
|
|
12381
12761
|
break;
|
|
12382
12762
|
case "full":
|
|
12383
12763
|
default:
|
|
12384
|
-
output = results
|
|
12764
|
+
output = results.map((tool) => ({
|
|
12765
|
+
...tool,
|
|
12766
|
+
required_scope: tool.scope,
|
|
12767
|
+
...hasKnownScopes ? { available: isAvailable(tool) } : {}
|
|
12768
|
+
}));
|
|
12385
12769
|
break;
|
|
12386
12770
|
}
|
|
12387
12771
|
return {
|
|
12388
12772
|
content: [
|
|
12389
12773
|
{
|
|
12390
12774
|
type: "text",
|
|
12391
|
-
text: JSON.stringify(
|
|
12775
|
+
text: JSON.stringify(
|
|
12776
|
+
{
|
|
12777
|
+
toolCount: results.length,
|
|
12778
|
+
...hasKnownScopes ? { scopes: { available: currentScopes, unavailable_matches: unavailableCount } } : {},
|
|
12779
|
+
tools: output
|
|
12780
|
+
},
|
|
12781
|
+
null,
|
|
12782
|
+
detail === "full" ? 2 : 0
|
|
12783
|
+
)
|
|
12392
12784
|
}
|
|
12393
12785
|
]
|
|
12394
12786
|
};
|
|
@@ -12403,7 +12795,7 @@ import { randomUUID as randomUUID3 } from "node:crypto";
|
|
|
12403
12795
|
init_supabase();
|
|
12404
12796
|
init_quality();
|
|
12405
12797
|
init_version();
|
|
12406
|
-
function
|
|
12798
|
+
function asEnvelope20(data) {
|
|
12407
12799
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
12408
12800
|
}
|
|
12409
12801
|
var PLATFORM_ENUM2 = z24.enum([
|
|
@@ -12447,6 +12839,7 @@ function registerPipelineTools(server2) {
|
|
|
12447
12839
|
throw new Error(readinessError ?? "No response from mcp-data");
|
|
12448
12840
|
}
|
|
12449
12841
|
const credits = readiness.credits;
|
|
12842
|
+
const isUnlimited = readiness.is_unlimited === true;
|
|
12450
12843
|
const connectedPlatforms = readiness.connected_platforms;
|
|
12451
12844
|
const missingPlatforms = readiness.missing_platforms;
|
|
12452
12845
|
const hasBrand = readiness.has_brand;
|
|
@@ -12455,7 +12848,7 @@ function registerPipelineTools(server2) {
|
|
|
12455
12848
|
const insightsFresh = readiness.insights_fresh;
|
|
12456
12849
|
const blockers = [];
|
|
12457
12850
|
const warnings = [];
|
|
12458
|
-
if (credits < estimatedCost) {
|
|
12851
|
+
if (!isUnlimited && credits < estimatedCost) {
|
|
12459
12852
|
blockers.push(`Insufficient credits: ${credits} available, ~${estimatedCost} needed`);
|
|
12460
12853
|
}
|
|
12461
12854
|
if (missingPlatforms.length > 0) {
|
|
@@ -12494,7 +12887,7 @@ function registerPipelineTools(server2) {
|
|
|
12494
12887
|
};
|
|
12495
12888
|
if (format === "json") {
|
|
12496
12889
|
return {
|
|
12497
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
12890
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope20(result), null, 2) }]
|
|
12498
12891
|
};
|
|
12499
12892
|
}
|
|
12500
12893
|
const lines = [];
|
|
@@ -12591,8 +12984,9 @@ function registerPipelineTools(server2) {
|
|
|
12591
12984
|
{ timeoutMs: 1e4 }
|
|
12592
12985
|
);
|
|
12593
12986
|
const availableCredits = budgetData?.credits ?? 0;
|
|
12987
|
+
const isUnlimited = budgetData?.is_unlimited === true;
|
|
12594
12988
|
const creditLimit = max_credits ?? availableCredits;
|
|
12595
|
-
if (availableCredits < estimatedCost) {
|
|
12989
|
+
if (!isUnlimited && availableCredits < estimatedCost) {
|
|
12596
12990
|
return {
|
|
12597
12991
|
content: [
|
|
12598
12992
|
{
|
|
@@ -12818,21 +13212,27 @@ function registerPipelineTools(server2) {
|
|
|
12818
13212
|
let postsScheduled = 0;
|
|
12819
13213
|
if (!dry_run && !skipSet.has("schedule") && postsApproved > 0) {
|
|
12820
13214
|
const approvedPosts = posts.filter((p) => p.status === "approved");
|
|
13215
|
+
const scheduleBase = /* @__PURE__ */ new Date();
|
|
13216
|
+
scheduleBase.setDate(scheduleBase.getDate() + 1);
|
|
13217
|
+
const scheduleBaseMs = scheduleBase.getTime();
|
|
12821
13218
|
for (const post of approvedPosts) {
|
|
12822
13219
|
if (creditsUsed >= creditLimit) {
|
|
12823
13220
|
errors.push({ stage: "schedule", message: "Credit limit reached" });
|
|
12824
13221
|
break;
|
|
12825
13222
|
}
|
|
13223
|
+
const scheduledAt = post.schedule_at ?? new Date(scheduleBaseMs + (Math.max(1, post.day) - 1) * 864e5).toISOString();
|
|
12826
13224
|
try {
|
|
12827
13225
|
const { error: schedError } = await callEdgeFunction(
|
|
12828
13226
|
"schedule-post",
|
|
12829
13227
|
{
|
|
12830
|
-
|
|
13228
|
+
platforms: [post.platform],
|
|
12831
13229
|
caption: post.caption,
|
|
12832
13230
|
title: post.title,
|
|
12833
13231
|
hashtags: post.hashtags,
|
|
12834
|
-
|
|
12835
|
-
|
|
13232
|
+
mediaUrl: post.media_url,
|
|
13233
|
+
scheduledAt,
|
|
13234
|
+
planId,
|
|
13235
|
+
idempotencyKey: `pipeline-${planId}-${post.id}`,
|
|
12836
13236
|
...resolvedProjectId ? { projectId: resolvedProjectId, project_id: resolvedProjectId } : {}
|
|
12837
13237
|
},
|
|
12838
13238
|
{ timeoutMs: 15e3 }
|
|
@@ -12898,7 +13298,7 @@ function registerPipelineTools(server2) {
|
|
|
12898
13298
|
if (response_format === "json") {
|
|
12899
13299
|
return {
|
|
12900
13300
|
content: [
|
|
12901
|
-
{ type: "text", text: JSON.stringify(
|
|
13301
|
+
{ type: "text", text: JSON.stringify(asEnvelope20(resultPayload), null, 2) }
|
|
12902
13302
|
]
|
|
12903
13303
|
};
|
|
12904
13304
|
}
|
|
@@ -12979,7 +13379,7 @@ function registerPipelineTools(server2) {
|
|
|
12979
13379
|
}
|
|
12980
13380
|
if (format === "json") {
|
|
12981
13381
|
return {
|
|
12982
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
13382
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope20(data), null, 2) }]
|
|
12983
13383
|
};
|
|
12984
13384
|
}
|
|
12985
13385
|
const lines = [];
|
|
@@ -13113,7 +13513,7 @@ function registerPipelineTools(server2) {
|
|
|
13113
13513
|
if (response_format === "json") {
|
|
13114
13514
|
return {
|
|
13115
13515
|
content: [
|
|
13116
|
-
{ type: "text", text: JSON.stringify(
|
|
13516
|
+
{ type: "text", text: JSON.stringify(asEnvelope20(resultPayload), null, 2) }
|
|
13117
13517
|
]
|
|
13118
13518
|
};
|
|
13119
13519
|
}
|
|
@@ -13163,7 +13563,7 @@ function buildPlanPrompt(topic, platforms, days, postsPerDay, sourceUrl) {
|
|
|
13163
13563
|
init_edge_function();
|
|
13164
13564
|
import { z as z25 } from "zod";
|
|
13165
13565
|
init_version();
|
|
13166
|
-
function
|
|
13566
|
+
function asEnvelope21(data) {
|
|
13167
13567
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
13168
13568
|
}
|
|
13169
13569
|
function registerSuggestTools(server2) {
|
|
@@ -13271,7 +13671,7 @@ function registerSuggestTools(server2) {
|
|
|
13271
13671
|
if (format === "json") {
|
|
13272
13672
|
return {
|
|
13273
13673
|
content: [
|
|
13274
|
-
{ type: "text", text: JSON.stringify(
|
|
13674
|
+
{ type: "text", text: JSON.stringify(asEnvelope21(resultPayload), null, 2) }
|
|
13275
13675
|
]
|
|
13276
13676
|
};
|
|
13277
13677
|
}
|
|
@@ -13408,7 +13808,7 @@ function detectAnomalies(currentData, previousData, sensitivity = "medium", aver
|
|
|
13408
13808
|
|
|
13409
13809
|
// src/tools/digest.ts
|
|
13410
13810
|
init_version();
|
|
13411
|
-
function
|
|
13811
|
+
function asEnvelope22(data) {
|
|
13412
13812
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
13413
13813
|
}
|
|
13414
13814
|
var PLATFORM_ENUM3 = z26.enum([
|
|
@@ -13577,7 +13977,7 @@ function registerDigestTools(server2) {
|
|
|
13577
13977
|
};
|
|
13578
13978
|
if (format === "json") {
|
|
13579
13979
|
return {
|
|
13580
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
13980
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope22(digest), null, 2) }]
|
|
13581
13981
|
};
|
|
13582
13982
|
}
|
|
13583
13983
|
const lines = [];
|
|
@@ -13676,7 +14076,7 @@ Best: ${best.id.slice(0, 8)}... (${best.platform}) \u2014 ${best.views.toLocaleS
|
|
|
13676
14076
|
if (format === "json") {
|
|
13677
14077
|
return {
|
|
13678
14078
|
content: [
|
|
13679
|
-
{ type: "text", text: JSON.stringify(
|
|
14079
|
+
{ type: "text", text: JSON.stringify(asEnvelope22(resultPayload), null, 2) }
|
|
13680
14080
|
]
|
|
13681
14081
|
};
|
|
13682
14082
|
}
|
|
@@ -13725,18 +14125,129 @@ var WEIGHTS = {
|
|
|
13725
14125
|
structuralPatterns: 0.05
|
|
13726
14126
|
};
|
|
13727
14127
|
function norm(content) {
|
|
13728
|
-
return content.toLowerCase().replace(/[^a-z0-9\s]/g, " ");
|
|
14128
|
+
return content.toLowerCase().replace(/[^a-z0-9\s]/g, " ").replace(/\s+/g, " ").trim();
|
|
13729
14129
|
}
|
|
13730
14130
|
function findMatches(content, terms) {
|
|
13731
14131
|
const n = norm(content);
|
|
13732
|
-
return terms.filter((t) =>
|
|
14132
|
+
return terms.filter((t) => {
|
|
14133
|
+
const nt = norm(t);
|
|
14134
|
+
return nt.length > 0 && n.includes(nt);
|
|
14135
|
+
});
|
|
13733
14136
|
}
|
|
13734
14137
|
function findMissing(content, terms) {
|
|
13735
14138
|
const n = norm(content);
|
|
13736
|
-
return terms.filter((t) =>
|
|
14139
|
+
return terms.filter((t) => {
|
|
14140
|
+
const nt = norm(t);
|
|
14141
|
+
return nt.length > 0 && !n.includes(nt);
|
|
14142
|
+
});
|
|
14143
|
+
}
|
|
14144
|
+
function asStringArray(v) {
|
|
14145
|
+
if (Array.isArray(v)) return v.map((x) => String(x ?? "").trim()).filter(Boolean);
|
|
14146
|
+
if (typeof v === "string") {
|
|
14147
|
+
return v.split(/[,;\n]/).map((s) => s.trim()).filter(Boolean);
|
|
14148
|
+
}
|
|
14149
|
+
return [];
|
|
14150
|
+
}
|
|
14151
|
+
function extractAtomicTerms(entries) {
|
|
14152
|
+
const out = [];
|
|
14153
|
+
for (const raw of entries) {
|
|
14154
|
+
const e = (raw || "").trim();
|
|
14155
|
+
if (!e) continue;
|
|
14156
|
+
const quoted = e.match(/['"“”‘’]([^'"“”‘’]{2,40})['"“”‘’]/g);
|
|
14157
|
+
if (quoted) {
|
|
14158
|
+
for (const q of quoted) out.push(q.replace(/['"“”‘’]/g, "").trim());
|
|
14159
|
+
continue;
|
|
14160
|
+
}
|
|
14161
|
+
const words = e.split(/\s+/);
|
|
14162
|
+
const isGuidancePhrase = words.length > 2 && /\b(tone|voice|style|approach|language)\b/i.test(e);
|
|
14163
|
+
if (!e.includes("(") && words.length <= 3 && !isGuidancePhrase) out.push(e);
|
|
14164
|
+
}
|
|
14165
|
+
return Array.from(new Set(out.map((t) => t.trim()).filter(Boolean)));
|
|
14166
|
+
}
|
|
14167
|
+
function hasField(obj, key) {
|
|
14168
|
+
return Object.prototype.hasOwnProperty.call(obj, key) && obj[key] != null;
|
|
14169
|
+
}
|
|
14170
|
+
function unionTerms(existing, incoming) {
|
|
14171
|
+
const out = [];
|
|
14172
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14173
|
+
for (const t of [...existing, ...incoming]) {
|
|
14174
|
+
if (t && !seen.has(t)) {
|
|
14175
|
+
seen.add(t);
|
|
14176
|
+
out.push(t);
|
|
14177
|
+
}
|
|
14178
|
+
}
|
|
14179
|
+
return out;
|
|
14180
|
+
}
|
|
14181
|
+
function mergeVoiceVocabulary(resolved, raw) {
|
|
14182
|
+
const voice = raw.voice;
|
|
14183
|
+
if (!voice || typeof voice !== "object") return resolved;
|
|
14184
|
+
const vp = asStringArray(voice.preferredTerms);
|
|
14185
|
+
const vb = asStringArray(voice.bannedTerms);
|
|
14186
|
+
if (!vp.length && !vb.length) return resolved;
|
|
14187
|
+
const existing = resolved.vocabularyRules ?? {};
|
|
14188
|
+
return {
|
|
14189
|
+
...resolved,
|
|
14190
|
+
vocabularyRules: {
|
|
14191
|
+
preferredTerms: unionTerms(asStringArray(existing.preferredTerms), vp),
|
|
14192
|
+
bannedTerms: unionTerms(asStringArray(existing.bannedTerms), vb)
|
|
14193
|
+
}
|
|
14194
|
+
};
|
|
14195
|
+
}
|
|
14196
|
+
function resolveBrandProfile(raw) {
|
|
14197
|
+
if (!raw || typeof raw !== "object") return {};
|
|
14198
|
+
const r = raw;
|
|
14199
|
+
const name = typeof r.name === "string" ? r.name : void 0;
|
|
14200
|
+
const voiceProfile = r.voiceProfile;
|
|
14201
|
+
const vocabularyRules = r.vocabularyRules;
|
|
14202
|
+
if (voiceProfile && vocabularyRules) {
|
|
14203
|
+
return mergeVoiceVocabulary(raw, r);
|
|
14204
|
+
}
|
|
14205
|
+
if (voiceProfile) {
|
|
14206
|
+
return mergeVoiceVocabulary(
|
|
14207
|
+
{
|
|
14208
|
+
name,
|
|
14209
|
+
voiceProfile,
|
|
14210
|
+
vocabularyRules: {
|
|
14211
|
+
bannedTerms: extractAtomicTerms(asStringArray(voiceProfile.avoidPatterns)),
|
|
14212
|
+
preferredTerms: asStringArray(voiceProfile.languagePatterns)
|
|
14213
|
+
},
|
|
14214
|
+
targetAudience: r.targetAudience,
|
|
14215
|
+
writingStyleRules: r.writingStyleRules
|
|
14216
|
+
},
|
|
14217
|
+
r
|
|
14218
|
+
);
|
|
14219
|
+
}
|
|
14220
|
+
const isFlat = hasField(r, "voiceTone") || hasField(r, "voiceTags") || hasField(r, "discouragedTerms") || hasField(r, "preferredTerms");
|
|
14221
|
+
if (isFlat) {
|
|
14222
|
+
const ta = r.targetAudience;
|
|
14223
|
+
const psy = ta?.psychographics;
|
|
14224
|
+
const painPoints = ta ? asStringArray(psy?.painPoints ?? ta.painPoints) : [];
|
|
14225
|
+
const interests = ta ? asStringArray(psy?.interests ?? ta.interests) : [];
|
|
14226
|
+
return mergeVoiceVocabulary(
|
|
14227
|
+
{
|
|
14228
|
+
name,
|
|
14229
|
+
voiceProfile: {
|
|
14230
|
+
tone: [...asStringArray(r.voiceTags), ...asStringArray(r.voiceTone)],
|
|
14231
|
+
style: asStringArray(r.voiceStyle),
|
|
14232
|
+
languagePatterns: [],
|
|
14233
|
+
avoidPatterns: []
|
|
14234
|
+
},
|
|
14235
|
+
vocabularyRules: {
|
|
14236
|
+
bannedTerms: asStringArray(r.discouragedTerms),
|
|
14237
|
+
preferredTerms: asStringArray(r.preferredTerms)
|
|
14238
|
+
},
|
|
14239
|
+
targetAudience: painPoints.length || interests.length ? { psychographics: { painPoints, interests } } : void 0
|
|
14240
|
+
},
|
|
14241
|
+
r
|
|
14242
|
+
);
|
|
14243
|
+
}
|
|
14244
|
+
const stub = { name };
|
|
14245
|
+
if (vocabularyRules)
|
|
14246
|
+
stub.vocabularyRules = vocabularyRules;
|
|
14247
|
+
return mergeVoiceVocabulary(stub, r);
|
|
13737
14248
|
}
|
|
13738
14249
|
var FABRICATION_PATTERNS = [
|
|
13739
|
-
{ regex: /\b\d+[,.]?\
|
|
14250
|
+
{ regex: /\b\d+(?:[,.]\d+)?\s*(?:%|percent\b)/gi, label: "unverified percentage" },
|
|
13740
14251
|
{ regex: /\b(award[- ]?winning|best[- ]selling|#\s*1)\b/gi, label: "unverified ranking" },
|
|
13741
14252
|
{
|
|
13742
14253
|
regex: /\b(guaranteed|proven to|studies show|scientifically proven)\b/gi,
|
|
@@ -13952,24 +14463,25 @@ function computeBrandConsistency(content, profile, threshold = 60) {
|
|
|
13952
14463
|
fabricationWarnings: []
|
|
13953
14464
|
};
|
|
13954
14465
|
}
|
|
14466
|
+
const resolved = resolveBrandProfile(profile);
|
|
13955
14467
|
const dimensions = {
|
|
13956
|
-
toneAlignment: scoreTone(content,
|
|
13957
|
-
vocabularyAdherence: scoreVocab(content,
|
|
13958
|
-
avoidCompliance: scoreAvoid(content,
|
|
13959
|
-
audienceRelevance: scoreAudience(content,
|
|
13960
|
-
brandMentions: scoreBrand(content,
|
|
13961
|
-
structuralPatterns: scoreStructure(content,
|
|
14468
|
+
toneAlignment: scoreTone(content, resolved),
|
|
14469
|
+
vocabularyAdherence: scoreVocab(content, resolved),
|
|
14470
|
+
avoidCompliance: scoreAvoid(content, resolved),
|
|
14471
|
+
audienceRelevance: scoreAudience(content, resolved),
|
|
14472
|
+
brandMentions: scoreBrand(content, resolved),
|
|
14473
|
+
structuralPatterns: scoreStructure(content, resolved)
|
|
13962
14474
|
};
|
|
13963
14475
|
const overall = Math.round(
|
|
13964
14476
|
Object.values(dimensions).reduce((sum, d) => sum + d.score * d.weight, 0)
|
|
13965
14477
|
);
|
|
13966
14478
|
const preferred = [
|
|
13967
|
-
...
|
|
13968
|
-
...
|
|
14479
|
+
...resolved.voiceProfile?.languagePatterns || [],
|
|
14480
|
+
...resolved.vocabularyRules?.preferredTerms || []
|
|
13969
14481
|
];
|
|
13970
14482
|
const banned = [
|
|
13971
|
-
...
|
|
13972
|
-
...
|
|
14483
|
+
...resolved.voiceProfile?.avoidPatterns || [],
|
|
14484
|
+
...resolved.vocabularyRules?.bannedTerms || []
|
|
13973
14485
|
];
|
|
13974
14486
|
const fabrications = detectFabricationPatterns(content);
|
|
13975
14487
|
return {
|
|
@@ -14000,11 +14512,11 @@ function hexToLab(hex) {
|
|
|
14000
14512
|
const lb = srgbToLinear(b);
|
|
14001
14513
|
const x = lr * 0.4124564 + lg * 0.3575761 + lb * 0.1804375;
|
|
14002
14514
|
const y = lr * 0.2126729 + lg * 0.7151522 + lb * 0.072175;
|
|
14003
|
-
const
|
|
14515
|
+
const z39 = lr * 0.0193339 + lg * 0.119192 + lb * 0.9503041;
|
|
14004
14516
|
const f = (t) => t > 8856e-6 ? Math.cbrt(t) : 7.787 * t + 16 / 116;
|
|
14005
14517
|
const fx = f(x / 0.95047);
|
|
14006
14518
|
const fy = f(y / 1);
|
|
14007
|
-
const fz = f(
|
|
14519
|
+
const fz = f(z39 / 1.08883);
|
|
14008
14520
|
return { L: 116 * fy - 16, a: 500 * (fx - fy), b: 200 * (fy - fz) };
|
|
14009
14521
|
}
|
|
14010
14522
|
function deltaE2000(lab1, lab2) {
|
|
@@ -14156,7 +14668,7 @@ function exportFigma(palette, typography) {
|
|
|
14156
14668
|
}
|
|
14157
14669
|
|
|
14158
14670
|
// src/tools/brandRuntime.ts
|
|
14159
|
-
function
|
|
14671
|
+
function asEnvelope23(data) {
|
|
14160
14672
|
return {
|
|
14161
14673
|
_meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() },
|
|
14162
14674
|
data
|
|
@@ -14237,8 +14749,9 @@ function registerBrandRuntimeTools(server2) {
|
|
|
14237
14749
|
pagesScraped: meta.pagesScraped || 0
|
|
14238
14750
|
}
|
|
14239
14751
|
};
|
|
14240
|
-
const envelope =
|
|
14752
|
+
const envelope = asEnvelope23(runtime);
|
|
14241
14753
|
return {
|
|
14754
|
+
structuredContent: envelope,
|
|
14242
14755
|
content: [{ type: "text", text: JSON.stringify(envelope, null, 2) }]
|
|
14243
14756
|
};
|
|
14244
14757
|
}
|
|
@@ -14379,7 +14892,7 @@ function registerBrandRuntimeTools(server2) {
|
|
|
14379
14892
|
}
|
|
14380
14893
|
const profile = row.profile_data;
|
|
14381
14894
|
const checkResult = computeBrandConsistency(content, profile);
|
|
14382
|
-
const envelope =
|
|
14895
|
+
const envelope = asEnvelope23(checkResult);
|
|
14383
14896
|
return {
|
|
14384
14897
|
content: [{ type: "text", text: JSON.stringify(envelope, null, 2) }]
|
|
14385
14898
|
};
|
|
@@ -14413,7 +14926,7 @@ function registerBrandRuntimeTools(server2) {
|
|
|
14413
14926
|
content_colors,
|
|
14414
14927
|
threshold ?? 10
|
|
14415
14928
|
);
|
|
14416
|
-
const envelope =
|
|
14929
|
+
const envelope = asEnvelope23(auditResult);
|
|
14417
14930
|
return {
|
|
14418
14931
|
content: [{ type: "text", text: JSON.stringify(envelope, null, 2) }]
|
|
14419
14932
|
};
|
|
@@ -14448,7 +14961,7 @@ function registerBrandRuntimeTools(server2) {
|
|
|
14448
14961
|
row.profile_data.typography,
|
|
14449
14962
|
format
|
|
14450
14963
|
);
|
|
14451
|
-
const envelope =
|
|
14964
|
+
const envelope = asEnvelope23({ format, tokens: output });
|
|
14452
14965
|
return {
|
|
14453
14966
|
content: [{ type: "text", text: JSON.stringify(envelope, null, 2) }]
|
|
14454
14967
|
};
|
|
@@ -14460,58 +14973,7 @@ function registerBrandRuntimeTools(server2) {
|
|
|
14460
14973
|
init_edge_function();
|
|
14461
14974
|
import { z as z28 } from "zod";
|
|
14462
14975
|
init_supabase();
|
|
14463
|
-
init_request_context();
|
|
14464
14976
|
init_version();
|
|
14465
|
-
var MAX_CREDITS_PER_RUN2 = Math.max(0, Number(process.env.SOCIALNEURON_MAX_CREDITS_PER_RUN || 0));
|
|
14466
|
-
var MAX_ASSETS_PER_RUN2 = Math.max(0, Number(process.env.SOCIALNEURON_MAX_ASSETS_PER_RUN || 0));
|
|
14467
|
-
var _globalCreditsUsed2 = 0;
|
|
14468
|
-
var _globalAssetsGenerated2 = 0;
|
|
14469
|
-
function getCreditsUsed2() {
|
|
14470
|
-
const ctx = requestContext.getStore();
|
|
14471
|
-
return ctx ? ctx.creditsUsed : _globalCreditsUsed2;
|
|
14472
|
-
}
|
|
14473
|
-
function addCreditsUsed2(amount) {
|
|
14474
|
-
const ctx = requestContext.getStore();
|
|
14475
|
-
if (ctx) {
|
|
14476
|
-
ctx.creditsUsed += amount;
|
|
14477
|
-
} else {
|
|
14478
|
-
_globalCreditsUsed2 += amount;
|
|
14479
|
-
}
|
|
14480
|
-
}
|
|
14481
|
-
function getAssetsGenerated2() {
|
|
14482
|
-
const ctx = requestContext.getStore();
|
|
14483
|
-
return ctx ? ctx.assetsGenerated : _globalAssetsGenerated2;
|
|
14484
|
-
}
|
|
14485
|
-
function addAssetsGenerated2(count) {
|
|
14486
|
-
const ctx = requestContext.getStore();
|
|
14487
|
-
if (ctx) {
|
|
14488
|
-
ctx.assetsGenerated += count;
|
|
14489
|
-
} else {
|
|
14490
|
-
_globalAssetsGenerated2 += count;
|
|
14491
|
-
}
|
|
14492
|
-
}
|
|
14493
|
-
function checkCreditBudget2(estimatedCost) {
|
|
14494
|
-
if (MAX_CREDITS_PER_RUN2 <= 0) return { ok: true };
|
|
14495
|
-
const used = getCreditsUsed2();
|
|
14496
|
-
if (used + estimatedCost > MAX_CREDITS_PER_RUN2) {
|
|
14497
|
-
return {
|
|
14498
|
-
ok: false,
|
|
14499
|
-
message: `Credit budget exceeded: ${used} used + ${estimatedCost} estimated > ${MAX_CREDITS_PER_RUN2} limit. Use a smaller slide count or cheaper image model.`
|
|
14500
|
-
};
|
|
14501
|
-
}
|
|
14502
|
-
return { ok: true };
|
|
14503
|
-
}
|
|
14504
|
-
function checkAssetBudget2() {
|
|
14505
|
-
if (MAX_ASSETS_PER_RUN2 <= 0) return { ok: true };
|
|
14506
|
-
const gen = getAssetsGenerated2();
|
|
14507
|
-
if (gen >= MAX_ASSETS_PER_RUN2) {
|
|
14508
|
-
return {
|
|
14509
|
-
ok: false,
|
|
14510
|
-
message: `Asset limit reached: ${gen}/${MAX_ASSETS_PER_RUN2} assets generated this run.`
|
|
14511
|
-
};
|
|
14512
|
-
}
|
|
14513
|
-
return { ok: true };
|
|
14514
|
-
}
|
|
14515
14977
|
var IMAGE_CREDIT_ESTIMATES2 = {
|
|
14516
14978
|
midjourney: 20,
|
|
14517
14979
|
"nano-banana": 15,
|
|
@@ -14520,7 +14982,7 @@ var IMAGE_CREDIT_ESTIMATES2 = {
|
|
|
14520
14982
|
"flux-max": 50,
|
|
14521
14983
|
"gpt4o-image": 40,
|
|
14522
14984
|
imagen4: 35,
|
|
14523
|
-
"imagen4-fast":
|
|
14985
|
+
"imagen4-fast": 35,
|
|
14524
14986
|
seedream: 20
|
|
14525
14987
|
};
|
|
14526
14988
|
async function fetchBrandVisualContext(projectId) {
|
|
@@ -14644,14 +15106,14 @@ function registerCarouselTools(server2) {
|
|
|
14644
15106
|
const carouselTextCost = 10 + slideCount * 2;
|
|
14645
15107
|
const perImageCost = IMAGE_CREDIT_ESTIMATES2[image_model] ?? 30;
|
|
14646
15108
|
const totalEstimatedCost = carouselTextCost + slideCount * perImageCost;
|
|
14647
|
-
const budgetCheck =
|
|
15109
|
+
const budgetCheck = checkCreditBudget(totalEstimatedCost);
|
|
14648
15110
|
if (!budgetCheck.ok) {
|
|
14649
15111
|
return {
|
|
14650
15112
|
content: [{ type: "text", text: budgetCheck.message }],
|
|
14651
15113
|
isError: true
|
|
14652
15114
|
};
|
|
14653
15115
|
}
|
|
14654
|
-
const assetBudget =
|
|
15116
|
+
const assetBudget = checkAssetBudget(slideCount);
|
|
14655
15117
|
if (!assetBudget.ok) {
|
|
14656
15118
|
return {
|
|
14657
15119
|
content: [{ type: "text", text: assetBudget.message }],
|
|
@@ -14698,8 +15160,15 @@ function registerCarouselTools(server2) {
|
|
|
14698
15160
|
};
|
|
14699
15161
|
}
|
|
14700
15162
|
const carousel = carouselData.carousel;
|
|
15163
|
+
const imageAssetBudget = checkAssetBudget(carousel.slides.length);
|
|
15164
|
+
if (!imageAssetBudget.ok) {
|
|
15165
|
+
return {
|
|
15166
|
+
content: [{ type: "text", text: imageAssetBudget.message }],
|
|
15167
|
+
isError: true
|
|
15168
|
+
};
|
|
15169
|
+
}
|
|
14701
15170
|
const textCredits = carousel.credits?.used ?? carouselTextCost;
|
|
14702
|
-
|
|
15171
|
+
addCreditsUsed(textCredits);
|
|
14703
15172
|
const imageJobs = await Promise.all(
|
|
14704
15173
|
carousel.slides.map(async (slide) => {
|
|
14705
15174
|
const promptParts = [];
|
|
@@ -14729,8 +15198,8 @@ function registerCarouselTools(server2) {
|
|
|
14729
15198
|
}
|
|
14730
15199
|
const jobId = data.asyncJobId ?? data.taskId ?? null;
|
|
14731
15200
|
if (jobId) {
|
|
14732
|
-
|
|
14733
|
-
|
|
15201
|
+
addCreditsUsed(perImageCost);
|
|
15202
|
+
addAssetsGenerated(1);
|
|
14734
15203
|
}
|
|
14735
15204
|
return {
|
|
14736
15205
|
slideNumber: slide.slideNumber,
|
|
@@ -14838,7 +15307,7 @@ function registerCarouselTools(server2) {
|
|
|
14838
15307
|
init_edge_function();
|
|
14839
15308
|
import { z as z29 } from "zod";
|
|
14840
15309
|
init_version();
|
|
14841
|
-
function
|
|
15310
|
+
function asEnvelope24(data) {
|
|
14842
15311
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
14843
15312
|
}
|
|
14844
15313
|
function registerNicheResearchTools(server2) {
|
|
@@ -14872,7 +15341,7 @@ function registerNicheResearchTools(server2) {
|
|
|
14872
15341
|
{
|
|
14873
15342
|
type: "text",
|
|
14874
15343
|
text: JSON.stringify(
|
|
14875
|
-
|
|
15344
|
+
asEnvelope24({
|
|
14876
15345
|
winners,
|
|
14877
15346
|
count: winners.length,
|
|
14878
15347
|
filters: result?.filters ?? {}
|
|
@@ -15171,6 +15640,16 @@ import { z as z31 } from "zod";
|
|
|
15171
15640
|
import fs from "node:fs/promises";
|
|
15172
15641
|
import path from "node:path";
|
|
15173
15642
|
var CALENDAR_URI = "ui://content-calendar/mcp-app.html";
|
|
15643
|
+
var RecentPostOutputSchema = z31.object({
|
|
15644
|
+
id: z31.string(),
|
|
15645
|
+
platform: z31.string(),
|
|
15646
|
+
status: z31.string(),
|
|
15647
|
+
title: z31.string().nullable(),
|
|
15648
|
+
external_post_id: z31.string().nullable(),
|
|
15649
|
+
published_at: z31.string().nullable(),
|
|
15650
|
+
scheduled_at: z31.string().nullable(),
|
|
15651
|
+
created_at: z31.string()
|
|
15652
|
+
});
|
|
15174
15653
|
function startOfCurrentWeekMonday() {
|
|
15175
15654
|
const now = /* @__PURE__ */ new Date();
|
|
15176
15655
|
const day = now.getDay();
|
|
@@ -15190,6 +15669,11 @@ function registerContentCalendarApp(server2) {
|
|
|
15190
15669
|
"ISO date for the week start (YYYY-MM-DD); defaults to the current week's Monday."
|
|
15191
15670
|
)
|
|
15192
15671
|
},
|
|
15672
|
+
outputSchema: {
|
|
15673
|
+
start_date: z31.string(),
|
|
15674
|
+
posts: z31.array(RecentPostOutputSchema),
|
|
15675
|
+
scopes: z31.array(z31.string())
|
|
15676
|
+
},
|
|
15193
15677
|
_meta: {
|
|
15194
15678
|
ui: {
|
|
15195
15679
|
resourceUri: CALENDAR_URI,
|
|
@@ -15228,11 +15712,17 @@ function registerContentCalendarApp(server2) {
|
|
|
15228
15712
|
if (!ts) return false;
|
|
15229
15713
|
return ts.split("T")[0] >= fromDate;
|
|
15230
15714
|
});
|
|
15715
|
+
const structuredContent = {
|
|
15716
|
+
start_date: fromDate,
|
|
15717
|
+
posts,
|
|
15718
|
+
scopes: userScopes
|
|
15719
|
+
};
|
|
15231
15720
|
return {
|
|
15721
|
+
structuredContent,
|
|
15232
15722
|
content: [
|
|
15233
15723
|
{
|
|
15234
15724
|
type: "text",
|
|
15235
|
-
text:
|
|
15725
|
+
text: `Loaded ${posts.length} calendar post${posts.length === 1 ? "" : "s"} from ${fromDate}.`
|
|
15236
15726
|
}
|
|
15237
15727
|
]
|
|
15238
15728
|
};
|
|
@@ -15611,7 +16101,7 @@ function registerHarnessTools(server2, _ctx) {
|
|
|
15611
16101
|
init_edge_function();
|
|
15612
16102
|
init_version();
|
|
15613
16103
|
import { z as z34 } from "zod";
|
|
15614
|
-
function
|
|
16104
|
+
function asEnvelope25(data) {
|
|
15615
16105
|
return {
|
|
15616
16106
|
_meta: {
|
|
15617
16107
|
version: MCP_VERSION,
|
|
@@ -15662,7 +16152,7 @@ function registerHermesTools(server2) {
|
|
|
15662
16152
|
const payload = { content_id: data?.content_id ?? null };
|
|
15663
16153
|
if (format === "json") {
|
|
15664
16154
|
return {
|
|
15665
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
16155
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope25(payload), null, 2) }]
|
|
15666
16156
|
};
|
|
15667
16157
|
}
|
|
15668
16158
|
return {
|
|
@@ -15706,7 +16196,7 @@ function registerHermesTools(server2) {
|
|
|
15706
16196
|
};
|
|
15707
16197
|
if (response_format === "json") {
|
|
15708
16198
|
return {
|
|
15709
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
16199
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope25(payload), null, 2) }]
|
|
15710
16200
|
};
|
|
15711
16201
|
}
|
|
15712
16202
|
return {
|
|
@@ -15736,7 +16226,7 @@ function registerHermesTools(server2) {
|
|
|
15736
16226
|
const payload = { observation_id: data?.observation_id ?? null };
|
|
15737
16227
|
if (response_format === "json") {
|
|
15738
16228
|
return {
|
|
15739
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
16229
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope25(payload), null, 2) }]
|
|
15740
16230
|
};
|
|
15741
16231
|
}
|
|
15742
16232
|
return {
|
|
@@ -15780,7 +16270,7 @@ function registerHermesTools(server2) {
|
|
|
15780
16270
|
};
|
|
15781
16271
|
if (response_format === "json") {
|
|
15782
16272
|
return {
|
|
15783
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
16273
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope25(payload), null, 2) }]
|
|
15784
16274
|
};
|
|
15785
16275
|
}
|
|
15786
16276
|
return {
|
|
@@ -15824,7 +16314,7 @@ function registerHermesTools(server2) {
|
|
|
15824
16314
|
};
|
|
15825
16315
|
if (response_format === "json") {
|
|
15826
16316
|
return {
|
|
15827
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
16317
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope25(payload), null, 2) }]
|
|
15828
16318
|
};
|
|
15829
16319
|
}
|
|
15830
16320
|
return {
|
|
@@ -15854,7 +16344,7 @@ function registerHermesTools(server2) {
|
|
|
15854
16344
|
content: [
|
|
15855
16345
|
{
|
|
15856
16346
|
type: "text",
|
|
15857
|
-
text: JSON.stringify(
|
|
16347
|
+
text: JSON.stringify(asEnvelope25({ campaigns }), null, 2)
|
|
15858
16348
|
}
|
|
15859
16349
|
]
|
|
15860
16350
|
};
|
|
@@ -15885,6 +16375,345 @@ ${"=".repeat(40)}
|
|
|
15885
16375
|
);
|
|
15886
16376
|
}
|
|
15887
16377
|
|
|
16378
|
+
// src/tools/skills.ts
|
|
16379
|
+
init_version();
|
|
16380
|
+
import { z as z35 } from "zod";
|
|
16381
|
+
|
|
16382
|
+
// src/lib/skills-manifest.ts
|
|
16383
|
+
var SKILLS_MANIFEST = [
|
|
16384
|
+
{
|
|
16385
|
+
id: "skill-brand-locked-viral-hook-reel",
|
|
16386
|
+
name: "Brand-locked viral hook reel",
|
|
16387
|
+
studio: "video",
|
|
16388
|
+
category: "hook",
|
|
16389
|
+
shortDescription: "Pulls your brand voice + the hook pattern that actually worked last cycle, then renders a 12s reel with stacked camera moves and brand-cloned voice.",
|
|
16390
|
+
hookFormula: "Pattern interrupt in first 0.5s + escalating stakes every 2s + brand-locked voice + cloned-from-winning-past-post structure.",
|
|
16391
|
+
estimatedCredits: 580,
|
|
16392
|
+
estimatedSeconds: 158,
|
|
16393
|
+
featured: true,
|
|
16394
|
+
stepCount: 9,
|
|
16395
|
+
inspiredBy: ["MrBeast", "Alex Hormozi"]
|
|
16396
|
+
}
|
|
16397
|
+
];
|
|
16398
|
+
function getSkill(id) {
|
|
16399
|
+
return SKILLS_MANIFEST.find((s) => s.id === id);
|
|
16400
|
+
}
|
|
16401
|
+
function listSkills(opts) {
|
|
16402
|
+
let entries = SKILLS_MANIFEST;
|
|
16403
|
+
if (opts?.studio) entries = entries.filter((s) => s.studio === opts.studio);
|
|
16404
|
+
if (opts?.featuredOnly) entries = entries.filter((s) => s.featured);
|
|
16405
|
+
return entries;
|
|
16406
|
+
}
|
|
16407
|
+
|
|
16408
|
+
// src/tools/skills.ts
|
|
16409
|
+
function asEnvelope26(data) {
|
|
16410
|
+
return {
|
|
16411
|
+
_meta: {
|
|
16412
|
+
version: MCP_VERSION,
|
|
16413
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
16414
|
+
},
|
|
16415
|
+
data
|
|
16416
|
+
};
|
|
16417
|
+
}
|
|
16418
|
+
var STUDIO_VALUES = ["video", "avatar", "carousel", "voice", "caption", "edit"];
|
|
16419
|
+
function renderSkillSummary(skill) {
|
|
16420
|
+
const lines = [];
|
|
16421
|
+
lines.push(`${skill.name} (${skill.id})`);
|
|
16422
|
+
lines.push(` Studio: ${skill.studio} \xB7 Category: ${skill.category}`);
|
|
16423
|
+
if (skill.featured) lines.push(" \u2B50 Featured");
|
|
16424
|
+
lines.push(` ${skill.shortDescription}`);
|
|
16425
|
+
lines.push(` Hook: ${skill.hookFormula}`);
|
|
16426
|
+
lines.push(
|
|
16427
|
+
` Cost: ~${skill.estimatedCredits} credits \xB7 ~${skill.estimatedSeconds}s \xB7 ${skill.stepCount} steps`
|
|
16428
|
+
);
|
|
16429
|
+
lines.push(` Inspired by: ${skill.inspiredBy.join(", ")}`);
|
|
16430
|
+
return lines.join("\n");
|
|
16431
|
+
}
|
|
16432
|
+
function registerSkillsTools(server2) {
|
|
16433
|
+
server2.tool(
|
|
16434
|
+
"list_skills",
|
|
16435
|
+
'List Social Neuron content workflow skills available to the authenticated user. A skill is a brand-locked multi-step pipeline (research \u2192 hook \u2192 script \u2192 visuals \u2192 voice \u2192 captions \u2192 assembly \u2192 quality gate \u2192 schedule) inspired by documented viral patterns (MrBeast 3-second hook, Hormozi pattern interrupt, etc.). Use this tool when the user asks "what can SN do", "what skills are available", "show me viral templates", or before calling run_skill so you can pick the right one.',
|
|
16436
|
+
{
|
|
16437
|
+
studio: z35.enum(STUDIO_VALUES).optional().describe("Filter to one studio (video, avatar, carousel, voice, caption, edit)."),
|
|
16438
|
+
featured_only: z35.boolean().optional().describe("Return only featured (recommended) skills. Defaults to false."),
|
|
16439
|
+
response_format: z35.enum(["text", "json"]).optional().describe("Response format. Defaults to text \u2014 human-readable summary.")
|
|
16440
|
+
},
|
|
16441
|
+
async ({ studio, featured_only, response_format }) => {
|
|
16442
|
+
const skills = listSkills({ studio, featuredOnly: featured_only });
|
|
16443
|
+
const format = response_format ?? "text";
|
|
16444
|
+
if (format === "json") {
|
|
16445
|
+
return {
|
|
16446
|
+
content: [
|
|
16447
|
+
{
|
|
16448
|
+
type: "text",
|
|
16449
|
+
text: JSON.stringify(asEnvelope26({ count: skills.length, skills }), null, 2)
|
|
16450
|
+
}
|
|
16451
|
+
]
|
|
16452
|
+
};
|
|
16453
|
+
}
|
|
16454
|
+
if (skills.length === 0) {
|
|
16455
|
+
return {
|
|
16456
|
+
content: [
|
|
16457
|
+
{
|
|
16458
|
+
type: "text",
|
|
16459
|
+
text: "No skills match the filter." + (studio ? ` Studio "${studio}" has no skills yet.` : "") + "\n\nAvailable studios with skills: " + Array.from(new Set(SKILLS_MANIFEST.map((s) => s.studio))).join(", ")
|
|
16460
|
+
}
|
|
16461
|
+
]
|
|
16462
|
+
};
|
|
16463
|
+
}
|
|
16464
|
+
const blocks = skills.map(renderSkillSummary).join("\n\n");
|
|
16465
|
+
const header = `${skills.length} skill${skills.length === 1 ? "" : "s"} available
|
|
16466
|
+
${"=".repeat(40)}`;
|
|
16467
|
+
return {
|
|
16468
|
+
content: [{ type: "text", text: `${header}
|
|
16469
|
+
|
|
16470
|
+
${blocks}` }]
|
|
16471
|
+
};
|
|
16472
|
+
}
|
|
16473
|
+
);
|
|
16474
|
+
server2.tool(
|
|
16475
|
+
"run_skill",
|
|
16476
|
+
"Run a Social Neuron workflow skill end-to-end (brand-locked content production). PR #4.4 v1: returns a structured run preview with the exact step plan, credit cost, and a deep-link to launch the run in the SN dashboard. PR #4.5 (next release) executes in-process so you can stream step-by-step progress back to the user. Call list_skills first to discover available skill ids.",
|
|
16477
|
+
{
|
|
16478
|
+
skill_id: z35.string().describe(
|
|
16479
|
+
'The id of the skill to run (e.g. "skill-brand-locked-viral-hook-reel"). Use list_skills to discover available ids.'
|
|
16480
|
+
),
|
|
16481
|
+
topic: z35.string().min(1).describe('What the content is about (e.g. "why we built Social Neuron").'),
|
|
16482
|
+
audience: z35.string().optional().describe(
|
|
16483
|
+
'Who the content is for (e.g. "first-time founders"). Defaults to brand persona.'
|
|
16484
|
+
),
|
|
16485
|
+
hook: z35.string().optional().describe(
|
|
16486
|
+
"Optional explicit hook. If omitted, the skill drafts and scores 3-5 candidates."
|
|
16487
|
+
),
|
|
16488
|
+
cta: z35.string().optional().describe("Optional call-to-action override. Defaults to brand standard CTA."),
|
|
16489
|
+
response_format: z35.enum(["text", "json"]).optional().describe("Response format. Defaults to text.")
|
|
16490
|
+
},
|
|
16491
|
+
async ({ skill_id, topic, audience, hook, cta, response_format }) => {
|
|
16492
|
+
const skill = getSkill(skill_id);
|
|
16493
|
+
const format = response_format ?? "text";
|
|
16494
|
+
if (!skill) {
|
|
16495
|
+
const knownIds = SKILLS_MANIFEST.map((s) => s.id).join(", ");
|
|
16496
|
+
return {
|
|
16497
|
+
content: [
|
|
16498
|
+
{
|
|
16499
|
+
type: "text",
|
|
16500
|
+
text: `Unknown skill_id "${skill_id}". Available skills: ${knownIds}. Call list_skills for descriptions.`
|
|
16501
|
+
}
|
|
16502
|
+
],
|
|
16503
|
+
isError: true
|
|
16504
|
+
};
|
|
16505
|
+
}
|
|
16506
|
+
const runUrl = "https://socialneuron.com/dashboard/creation?skill=" + encodeURIComponent(skill.id);
|
|
16507
|
+
const inputs = { topic, audience, hook, cta };
|
|
16508
|
+
const previewedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
16509
|
+
if (format === "json") {
|
|
16510
|
+
return {
|
|
16511
|
+
content: [
|
|
16512
|
+
{
|
|
16513
|
+
type: "text",
|
|
16514
|
+
text: JSON.stringify(
|
|
16515
|
+
asEnvelope26({
|
|
16516
|
+
status: "preview",
|
|
16517
|
+
skill,
|
|
16518
|
+
inputs,
|
|
16519
|
+
runUrl,
|
|
16520
|
+
previewedAt,
|
|
16521
|
+
note: "PR #4.4 v1 returns a preview. PR #4.5 will execute in-process via the run-skill EF."
|
|
16522
|
+
}),
|
|
16523
|
+
null,
|
|
16524
|
+
2
|
|
16525
|
+
)
|
|
16526
|
+
}
|
|
16527
|
+
]
|
|
16528
|
+
};
|
|
16529
|
+
}
|
|
16530
|
+
const lines = [
|
|
16531
|
+
`Ready to run: ${skill.name}`,
|
|
16532
|
+
"=".repeat(40),
|
|
16533
|
+
"",
|
|
16534
|
+
skill.shortDescription,
|
|
16535
|
+
"",
|
|
16536
|
+
`Hook formula: ${skill.hookFormula}`,
|
|
16537
|
+
`Inspired by: ${skill.inspiredBy.join(", ")}`,
|
|
16538
|
+
"",
|
|
16539
|
+
"Inputs",
|
|
16540
|
+
` topic : ${topic}`,
|
|
16541
|
+
` audience : ${audience ?? "(brand persona)"}`,
|
|
16542
|
+
` hook : ${hook ?? "(auto-drafted from 3-5 candidates, scored)"}`,
|
|
16543
|
+
` cta : ${cta ?? "(brand default)"}`,
|
|
16544
|
+
"",
|
|
16545
|
+
"Plan",
|
|
16546
|
+
` ${skill.stepCount} steps \xB7 ~${skill.estimatedCredits} credits \xB7 ~${skill.estimatedSeconds}s wall time`,
|
|
16547
|
+
"",
|
|
16548
|
+
`Launch in dashboard: ${runUrl}`,
|
|
16549
|
+
"",
|
|
16550
|
+
"\u26A0\uFE0F PR #4.4 v1: preview only. End-to-end MCP execution arrives in the next release."
|
|
16551
|
+
];
|
|
16552
|
+
return {
|
|
16553
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
16554
|
+
};
|
|
16555
|
+
}
|
|
16556
|
+
);
|
|
16557
|
+
}
|
|
16558
|
+
|
|
16559
|
+
// src/tools/loopPulse.ts
|
|
16560
|
+
init_edge_function();
|
|
16561
|
+
init_version();
|
|
16562
|
+
import { z as z36 } from "zod";
|
|
16563
|
+
function asEnvelope27(data) {
|
|
16564
|
+
return {
|
|
16565
|
+
_meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() },
|
|
16566
|
+
data
|
|
16567
|
+
};
|
|
16568
|
+
}
|
|
16569
|
+
function registerLoopPulseTools(server2) {
|
|
16570
|
+
server2.tool(
|
|
16571
|
+
"get_loop_pulse",
|
|
16572
|
+
'Read the dynamic loop-health KPIs for the Social Neuron growth loop over the last 7 days. Returns reflection coverage, decision coverage, visual gate pass rate, bandit-update application rate, per-platform bandit uptake, autopilot lag, and pattern aggregation counts \u2014 each with a status ("ok" / "warn" / "bad") and a why-line explaining what the metric measures. Use this to decide whether the loop is closing or where it is stuck before recommending next moves.',
|
|
16573
|
+
{
|
|
16574
|
+
response_format: z36.enum(["text", "json"]).optional().describe("Output format. Defaults to text.")
|
|
16575
|
+
},
|
|
16576
|
+
async ({ response_format }) => {
|
|
16577
|
+
const format = response_format ?? "text";
|
|
16578
|
+
const { data, error } = await callEdgeFunction("mc-loop-pulse", {});
|
|
16579
|
+
if (error || !data) {
|
|
16580
|
+
return {
|
|
16581
|
+
content: [
|
|
16582
|
+
{
|
|
16583
|
+
type: "text",
|
|
16584
|
+
text: `Failed to read loop pulse: ${error || "no data"}`
|
|
16585
|
+
}
|
|
16586
|
+
],
|
|
16587
|
+
isError: true
|
|
16588
|
+
};
|
|
16589
|
+
}
|
|
16590
|
+
if (format === "json") {
|
|
16591
|
+
return {
|
|
16592
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope27(data), null, 2) }]
|
|
16593
|
+
};
|
|
16594
|
+
}
|
|
16595
|
+
const rank = { bad: 0, warn: 1, unknown: 2, ok: 3 };
|
|
16596
|
+
const sorted = [...data.kpis].sort((a, b) => rank[a.status] - rank[b.status]);
|
|
16597
|
+
const lines = [
|
|
16598
|
+
`LOOP PULSE \u2014 overall: ${data.overall.toUpperCase()}`,
|
|
16599
|
+
"",
|
|
16600
|
+
...sorted.map((k) => {
|
|
16601
|
+
const unit = k.unit ?? "";
|
|
16602
|
+
const val = k.value == null ? "\u2014" : `${k.value}${unit}`;
|
|
16603
|
+
return `[${k.status.toUpperCase().padEnd(4)}] ${k.label}: ${val}
|
|
16604
|
+
${k.why}`;
|
|
16605
|
+
}),
|
|
16606
|
+
"",
|
|
16607
|
+
`Generated ${data.generated_at}`
|
|
16608
|
+
];
|
|
16609
|
+
return {
|
|
16610
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
16611
|
+
};
|
|
16612
|
+
}
|
|
16613
|
+
);
|
|
16614
|
+
}
|
|
16615
|
+
|
|
16616
|
+
// src/tools/banditState.ts
|
|
16617
|
+
init_edge_function();
|
|
16618
|
+
init_supabase();
|
|
16619
|
+
init_version();
|
|
16620
|
+
import { z as z37 } from "zod";
|
|
16621
|
+
function asEnvelope28(data) {
|
|
16622
|
+
return {
|
|
16623
|
+
_meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() },
|
|
16624
|
+
data
|
|
16625
|
+
};
|
|
16626
|
+
}
|
|
16627
|
+
function registerBanditStateTools(server2) {
|
|
16628
|
+
server2.tool(
|
|
16629
|
+
"get_bandit_state",
|
|
16630
|
+
"Read the current Thompson Sampling bandit posteriors for a project. Returns top-K arms per (arm_type, platform) with Beta(alpha, beta) posterior mean and uncertainty. Use this to reason about which hook family / format / timing slot the bandit currently prefers on each platform before recommending next moves. SN real arm types: hook_family (6 fallback families), length_bucket (xs/s/m/l/xl by platform), posting_time_bucket (morning/midday/evening/late), content_format (video/carousel/image/caption/text/avatar/storyboard). Legacy/dead-taxonomy types also present: hook_type, format, timing_slot, topic_cluster, caption_style, platform, story_type, emoji_type.",
|
|
16631
|
+
{
|
|
16632
|
+
project_id: z37.string().uuid().optional().describe("Project UUID. Defaults to the authenticated user default project."),
|
|
16633
|
+
platform: z37.enum([
|
|
16634
|
+
"instagram",
|
|
16635
|
+
"tiktok",
|
|
16636
|
+
"youtube",
|
|
16637
|
+
"linkedin",
|
|
16638
|
+
"twitter",
|
|
16639
|
+
"facebook",
|
|
16640
|
+
"threads",
|
|
16641
|
+
"bluesky"
|
|
16642
|
+
]).optional().describe(
|
|
16643
|
+
"Lowercase platform name. Omit to return both platform-scoped and legacy platform-agnostic arms."
|
|
16644
|
+
),
|
|
16645
|
+
arm_type: z37.enum([
|
|
16646
|
+
"hook_family",
|
|
16647
|
+
"hook_type",
|
|
16648
|
+
"format",
|
|
16649
|
+
"timing_slot",
|
|
16650
|
+
"topic_cluster",
|
|
16651
|
+
"caption_style",
|
|
16652
|
+
"platform",
|
|
16653
|
+
"story_type",
|
|
16654
|
+
"emoji_type",
|
|
16655
|
+
"length_bucket",
|
|
16656
|
+
"posting_time_bucket",
|
|
16657
|
+
"content_format"
|
|
16658
|
+
]).optional().describe("Arm dimension. Omit to return all types grouped."),
|
|
16659
|
+
top_k: z37.number().min(1).max(50).optional().describe("Max arms per (arm_type) group. Default 5."),
|
|
16660
|
+
response_format: z37.enum(["text", "json"]).optional().describe("Output format. Defaults to text.")
|
|
16661
|
+
},
|
|
16662
|
+
async ({ project_id, platform: platform3, arm_type, top_k, response_format }) => {
|
|
16663
|
+
const format = response_format ?? "text";
|
|
16664
|
+
const projectId = project_id ?? await getDefaultProjectId();
|
|
16665
|
+
if (!projectId) {
|
|
16666
|
+
return {
|
|
16667
|
+
content: [
|
|
16668
|
+
{ type: "text", text: "No project_id provided and no default project available." }
|
|
16669
|
+
],
|
|
16670
|
+
isError: true
|
|
16671
|
+
};
|
|
16672
|
+
}
|
|
16673
|
+
const { data, error } = await callEdgeFunction("mc-bandit-state", {
|
|
16674
|
+
project_id: projectId,
|
|
16675
|
+
platform: platform3,
|
|
16676
|
+
arm_type,
|
|
16677
|
+
top_k
|
|
16678
|
+
});
|
|
16679
|
+
if (error || !data) {
|
|
16680
|
+
return {
|
|
16681
|
+
content: [
|
|
16682
|
+
{ type: "text", text: `Failed to read bandit state: ${error || "no data"}` }
|
|
16683
|
+
],
|
|
16684
|
+
isError: true
|
|
16685
|
+
};
|
|
16686
|
+
}
|
|
16687
|
+
if (format === "json") {
|
|
16688
|
+
return { content: [{ type: "text", text: JSON.stringify(asEnvelope28(data), null, 2) }] };
|
|
16689
|
+
}
|
|
16690
|
+
const lines = [
|
|
16691
|
+
`BANDIT STATE \u2014 project ${data.project_id.slice(0, 8)}${data.platform_filter ? ` \xB7 ${data.platform_filter}` : ""}`,
|
|
16692
|
+
`${data.total_arms} arms across ${data.groups.length} arm types`,
|
|
16693
|
+
""
|
|
16694
|
+
];
|
|
16695
|
+
for (const group of data.groups) {
|
|
16696
|
+
lines.push(`[${group.arm_type}] ${group.summary}`);
|
|
16697
|
+
const renderArm = (a) => {
|
|
16698
|
+
const conf = a.posterior_stdev < 0.05 ? "high" : a.posterior_stdev < 0.15 ? "med" : "low";
|
|
16699
|
+
return ` ${a.arm_name.padEnd(18)} mean=${a.posterior_mean.toFixed(3)} \xB1${a.posterior_stdev.toFixed(3)} (${conf} conf \xB7 ${a.total_pulls} pulls)`;
|
|
16700
|
+
};
|
|
16701
|
+
if (group.platform_scoped.length > 0) {
|
|
16702
|
+
lines.push(` Per-platform top arms:`);
|
|
16703
|
+
group.platform_scoped.forEach((a) => lines.push(renderArm(a)));
|
|
16704
|
+
}
|
|
16705
|
+
if (group.platform_agnostic.length > 0) {
|
|
16706
|
+
lines.push(` Platform-agnostic (legacy) top arms:`);
|
|
16707
|
+
group.platform_agnostic.forEach((a) => lines.push(renderArm(a)));
|
|
16708
|
+
}
|
|
16709
|
+
lines.push("");
|
|
16710
|
+
}
|
|
16711
|
+
lines.push(`Generated ${data.generated_at}`);
|
|
16712
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
16713
|
+
}
|
|
16714
|
+
);
|
|
16715
|
+
}
|
|
16716
|
+
|
|
15888
16717
|
// src/lib/register-tools.ts
|
|
15889
16718
|
function wrapToolWithScanner(toolName, handler) {
|
|
15890
16719
|
return async function scannerWrappedHandler(...handlerArgs) {
|
|
@@ -15937,7 +16766,8 @@ function wrapToolWithScanner(toolName, handler) {
|
|
|
15937
16766
|
}
|
|
15938
16767
|
function applyScopeEnforcement(server2, scopeResolver) {
|
|
15939
16768
|
const originalTool = server2.tool.bind(server2);
|
|
15940
|
-
|
|
16769
|
+
const originalRegisterTool = server2.registerTool?.bind(server2);
|
|
16770
|
+
const wrapRegistration = (...args) => {
|
|
15941
16771
|
const name = args[0];
|
|
15942
16772
|
const requiredScope = TOOL_SCOPES[name];
|
|
15943
16773
|
const handlerIndex = args.findIndex(
|
|
@@ -15948,27 +16778,11 @@ function applyScopeEnforcement(server2, scopeResolver) {
|
|
|
15948
16778
|
const scannerWrappedHandler = wrapToolWithScanner(name, originalHandler);
|
|
15949
16779
|
args[handlerIndex] = async function scopeEnforcedHandler(...handlerArgs) {
|
|
15950
16780
|
if (!requiredScope) {
|
|
15951
|
-
return
|
|
15952
|
-
content: [
|
|
15953
|
-
{
|
|
15954
|
-
type: "text",
|
|
15955
|
-
text: `Permission denied: '${name}' has no scope defined. Contact support.`
|
|
15956
|
-
}
|
|
15957
|
-
],
|
|
15958
|
-
isError: true
|
|
15959
|
-
};
|
|
16781
|
+
return scopeDeniedResult(name, void 0, scopeResolver());
|
|
15960
16782
|
}
|
|
15961
16783
|
const userScopes = scopeResolver();
|
|
15962
16784
|
if (!hasScope(userScopes, requiredScope)) {
|
|
15963
|
-
return
|
|
15964
|
-
content: [
|
|
15965
|
-
{
|
|
15966
|
-
type: "text",
|
|
15967
|
-
text: `Permission denied: '${name}' requires scope '${requiredScope}'. Generate a new key with the required scope at https://socialneuron.com/settings/developer`
|
|
15968
|
-
}
|
|
15969
|
-
],
|
|
15970
|
-
isError: true
|
|
15971
|
-
};
|
|
16785
|
+
return scopeDeniedResult(name, requiredScope, userScopes);
|
|
15972
16786
|
}
|
|
15973
16787
|
const startedAt = Date.now();
|
|
15974
16788
|
try {
|
|
@@ -15995,9 +16809,59 @@ function applyScopeEnforcement(server2, scopeResolver) {
|
|
|
15995
16809
|
}
|
|
15996
16810
|
};
|
|
15997
16811
|
}
|
|
15998
|
-
return
|
|
16812
|
+
return args;
|
|
16813
|
+
};
|
|
16814
|
+
server2.tool = function wrappedTool(...args) {
|
|
16815
|
+
return originalTool(...wrapRegistration(...args));
|
|
16816
|
+
};
|
|
16817
|
+
if (originalRegisterTool) {
|
|
16818
|
+
server2.registerTool = function wrappedRegisterTool(...args) {
|
|
16819
|
+
return originalRegisterTool(...wrapRegistration(...args));
|
|
16820
|
+
};
|
|
16821
|
+
}
|
|
16822
|
+
}
|
|
16823
|
+
function scopeDeniedResult(name, requiredScope, userScopes) {
|
|
16824
|
+
const error = requiredScope ? {
|
|
16825
|
+
error: "permission_denied",
|
|
16826
|
+
tool: name,
|
|
16827
|
+
required_scope: requiredScope,
|
|
16828
|
+
available_scopes: userScopes,
|
|
16829
|
+
recover_with: [
|
|
16830
|
+
"Call search_tools with available_only=true to find tools this key can use.",
|
|
16831
|
+
"Use a read-only alternative if one is available for the task.",
|
|
16832
|
+
"Regenerate the API key with the required scope or upgrade the plan tier."
|
|
16833
|
+
],
|
|
16834
|
+
developer_url: "https://socialneuron.com/settings/developer"
|
|
16835
|
+
} : {
|
|
16836
|
+
error: "tool_scope_missing",
|
|
16837
|
+
tool: name,
|
|
16838
|
+
available_scopes: userScopes,
|
|
16839
|
+
recover_with: ["Contact support; this tool is not mapped to a required scope."]
|
|
16840
|
+
};
|
|
16841
|
+
const challenge = requiredScope ? buildWwwAuthenticateHeader({
|
|
16842
|
+
issuerUrl: getChallengeIssuerUrl(),
|
|
16843
|
+
error: "insufficient_scope",
|
|
16844
|
+
errorDescription: `Tool ${name} requires scope ${requiredScope}.`,
|
|
16845
|
+
scope: requiredScope
|
|
16846
|
+
}) : void 0;
|
|
16847
|
+
return {
|
|
16848
|
+
content: [{ type: "text", text: JSON.stringify(error, null, 2) }],
|
|
16849
|
+
...challenge ? { _meta: { "mcp/www_authenticate": [challenge] } } : {},
|
|
16850
|
+
isError: true
|
|
15999
16851
|
};
|
|
16000
16852
|
}
|
|
16853
|
+
function getChallengeIssuerUrl() {
|
|
16854
|
+
if (process.env.OAUTH_ISSUER_URL) return process.env.OAUTH_ISSUER_URL;
|
|
16855
|
+
const mcpServerUrl = process.env.MCP_SERVER_URL;
|
|
16856
|
+
if (mcpServerUrl) {
|
|
16857
|
+
try {
|
|
16858
|
+
const parsed = new URL(mcpServerUrl);
|
|
16859
|
+
return `${parsed.protocol}//${parsed.host}`;
|
|
16860
|
+
} catch {
|
|
16861
|
+
}
|
|
16862
|
+
}
|
|
16863
|
+
return "https://mcp.socialneuron.com";
|
|
16864
|
+
}
|
|
16001
16865
|
var RESPONSE_CHAR_LIMIT = 1e5;
|
|
16002
16866
|
function truncateResponse(result) {
|
|
16003
16867
|
if (!result?.content || !Array.isArray(result.content)) return result;
|
|
@@ -16067,6 +16931,9 @@ function registerAllTools(server2, options) {
|
|
|
16067
16931
|
registerConnectionTools(server2);
|
|
16068
16932
|
registerHarnessTools(server2, void 0);
|
|
16069
16933
|
registerHermesTools(server2);
|
|
16934
|
+
registerSkillsTools(server2);
|
|
16935
|
+
registerLoopPulseTools(server2);
|
|
16936
|
+
registerBanditStateTools(server2);
|
|
16070
16937
|
if (!options?.skipApps) {
|
|
16071
16938
|
registerContentCalendarApp(server2);
|
|
16072
16939
|
}
|
|
@@ -16074,17 +16941,17 @@ function registerAllTools(server2, options) {
|
|
|
16074
16941
|
}
|
|
16075
16942
|
|
|
16076
16943
|
// src/prompts.ts
|
|
16077
|
-
import { z as
|
|
16944
|
+
import { z as z38 } from "zod";
|
|
16078
16945
|
function registerPrompts(server2) {
|
|
16079
16946
|
server2.prompt(
|
|
16080
16947
|
"create_weekly_content_plan",
|
|
16081
16948
|
"Generate a full week of social media content (7 days, multiple platforms). Returns a structured plan with topics, formats, and posting times.",
|
|
16082
16949
|
{
|
|
16083
|
-
niche:
|
|
16084
|
-
platforms:
|
|
16950
|
+
niche: z38.string().describe('Your content niche or industry (e.g., "fitness coaching", "SaaS marketing")'),
|
|
16951
|
+
platforms: z38.string().optional().describe(
|
|
16085
16952
|
'Comma-separated platforms to target (default: "YouTube, Instagram, TikTok, LinkedIn")'
|
|
16086
16953
|
),
|
|
16087
|
-
tone:
|
|
16954
|
+
tone: z38.string().optional().describe('Brand tone of voice (e.g., "professional", "casual", "bold and edgy")')
|
|
16088
16955
|
},
|
|
16089
16956
|
({ niche, platforms, tone }) => {
|
|
16090
16957
|
const targetPlatforms = platforms || "YouTube, Instagram, TikTok, LinkedIn";
|
|
@@ -16126,8 +16993,8 @@ After building the plan, use \`save_content_plan\` to save it.`
|
|
|
16126
16993
|
"analyze_top_content",
|
|
16127
16994
|
"Analyze your best-performing posts to identify patterns and replicate success. Returns insights on hooks, formats, timing, and topics that resonate.",
|
|
16128
16995
|
{
|
|
16129
|
-
timeframe:
|
|
16130
|
-
platform:
|
|
16996
|
+
timeframe: z38.string().optional().describe('Analysis period (default: "30 days"). E.g., "7 days", "90 days"'),
|
|
16997
|
+
platform: z38.string().optional().describe('Filter to a specific platform (e.g., "youtube", "instagram")')
|
|
16131
16998
|
},
|
|
16132
16999
|
({ timeframe, platform: platform3 }) => {
|
|
16133
17000
|
const period = timeframe || "30 days";
|
|
@@ -16164,10 +17031,10 @@ Format as a clear, actionable performance report.`
|
|
|
16164
17031
|
"repurpose_content",
|
|
16165
17032
|
"Take one piece of content and transform it into 8-10 pieces across multiple platforms and formats.",
|
|
16166
17033
|
{
|
|
16167
|
-
source:
|
|
17034
|
+
source: z38.string().describe(
|
|
16168
17035
|
"The source content to repurpose \u2014 a URL, transcript, or the content text itself"
|
|
16169
17036
|
),
|
|
16170
|
-
target_platforms:
|
|
17037
|
+
target_platforms: z38.string().optional().describe(
|
|
16171
17038
|
'Comma-separated target platforms (default: "Twitter, LinkedIn, Instagram, TikTok, YouTube")'
|
|
16172
17039
|
)
|
|
16173
17040
|
},
|
|
@@ -16209,9 +17076,9 @@ For each piece, include the platform, format, character count, and suggested pos
|
|
|
16209
17076
|
"setup_brand_voice",
|
|
16210
17077
|
"Define or refine your brand voice profile so all generated content stays on-brand. Walks through tone, audience, values, and style.",
|
|
16211
17078
|
{
|
|
16212
|
-
brand_name:
|
|
16213
|
-
industry:
|
|
16214
|
-
website:
|
|
17079
|
+
brand_name: z38.string().describe("Your brand or business name"),
|
|
17080
|
+
industry: z38.string().optional().describe('Your industry or niche (e.g., "B2B SaaS", "fitness coaching")'),
|
|
17081
|
+
website: z38.string().optional().describe("Your website URL for context")
|
|
16215
17082
|
},
|
|
16216
17083
|
({ brand_name, industry, website }) => {
|
|
16217
17084
|
const industryContext = industry ? ` in the ${industry} space` : "";
|
|
@@ -16454,13 +17321,13 @@ function registerResources(server2) {
|
|
|
16454
17321
|
pro: {
|
|
16455
17322
|
price: "$49/mo",
|
|
16456
17323
|
credits: 1500,
|
|
16457
|
-
mcp_access: "Read + Analytics",
|
|
17324
|
+
mcp_access: "Read + Analytics + Write + Distribute",
|
|
16458
17325
|
features: [
|
|
16459
17326
|
"All Starter features",
|
|
16460
17327
|
"Closed-loop optimization",
|
|
16461
|
-
"
|
|
17328
|
+
"Full agent engine via MCP (generate, gate, publish)",
|
|
16462
17329
|
"All 10 platforms",
|
|
16463
|
-
"MCP read + analytics"
|
|
17330
|
+
"MCP read + analytics + write + distribute"
|
|
16464
17331
|
]
|
|
16465
17332
|
},
|
|
16466
17333
|
team: {
|
|
@@ -16565,6 +17432,7 @@ function registerResources(server2) {
|
|
|
16565
17432
|
init_posthog();
|
|
16566
17433
|
init_supabase();
|
|
16567
17434
|
init_sn();
|
|
17435
|
+
process.env.MCP_TRANSPORT ??= "stdio";
|
|
16568
17436
|
function flushAndExit(code) {
|
|
16569
17437
|
const done = { out: false, err: false };
|
|
16570
17438
|
const tryExit = () => {
|
|
@@ -16726,7 +17594,7 @@ if (command === "sn") {
|
|
|
16726
17594
|
process.exit(snSubcommand ? 0 : 1);
|
|
16727
17595
|
}
|
|
16728
17596
|
await runSnCli(process.argv.slice(3));
|
|
16729
|
-
await new Promise((resolve3) => process.stdout.write("", resolve3));
|
|
17597
|
+
await new Promise((resolve3) => process.stdout.write("", () => resolve3()));
|
|
16730
17598
|
process.exit(0);
|
|
16731
17599
|
}
|
|
16732
17600
|
if (command && !["setup", "login", "logout", "whoami", "health", "sn", "repl"].includes(command)) {
|