@socialneuron/mcp-server 1.7.12 → 1.7.15
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/CHANGELOG.md +15 -0
- package/README.md +8 -6
- package/dist/http.js +1934 -707
- package/dist/index.js +1624 -695
- package/dist/sn.js +4211 -0
- package/package.json +46 -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.15";
|
|
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
|
{
|
|
@@ -1267,7 +1235,7 @@ var init_tool_catalog = __esm({
|
|
|
1267
1235
|
// niche research
|
|
1268
1236
|
{
|
|
1269
1237
|
name: "find_winning_content",
|
|
1270
|
-
description: "Find QA-gated high-performing short-form videos in the project's niche. Returns extracted hook patterns, content structures, and pre-compiled replication prompts
|
|
1238
|
+
description: "Find QA-gated high-performing short-form videos in the project's niche. Returns extracted hook patterns, content structures, and pre-compiled replication prompts.",
|
|
1271
1239
|
module: "research",
|
|
1272
1240
|
scope: "mcp:read"
|
|
1273
1241
|
},
|
|
@@ -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",
|
|
@@ -1406,58 +1386,93 @@ var init_tool_catalog = __esm({
|
|
|
1406
1386
|
// agentic-harness — learning loop write-back
|
|
1407
1387
|
{
|
|
1408
1388
|
name: "write_agent_reflection",
|
|
1409
|
-
description: "Persist a verbal reflection for an agent loop. Provenance keys are restricted
|
|
1389
|
+
description: "Persist a verbal reflection for an agent loop. Provenance keys are restricted to an allowlist: only content_history_id, outcome_event_id, prm_score_ids, and handoff_ids are accepted.",
|
|
1410
1390
|
module: "harness",
|
|
1411
|
-
scope: "mcp:write"
|
|
1391
|
+
scope: "mcp:write",
|
|
1392
|
+
internal: true
|
|
1412
1393
|
},
|
|
1413
1394
|
{
|
|
1414
1395
|
name: "record_outcome",
|
|
1415
|
-
description: "Record an outcome for a published decision event. Idempotent on (decision_event_id, horizon). Only horizon=24h triggers a
|
|
1396
|
+
description: "Record an outcome for a published decision event. Idempotent on (decision_event_id, horizon). Only horizon=24h triggers a learning-loop update.",
|
|
1416
1397
|
module: "harness",
|
|
1417
|
-
scope: "mcp:write"
|
|
1398
|
+
scope: "mcp:write",
|
|
1399
|
+
internal: true
|
|
1418
1400
|
},
|
|
1419
1401
|
// agentic-harness — learning loop read-back
|
|
1420
1402
|
{
|
|
1421
1403
|
name: "read_agent_reflection",
|
|
1422
1404
|
description: "Read past agent reflections for a brand. Ordered by created_at DESC, id ASC (deterministic tiebreak). Only active reflections returned (superseded_by IS NULL). Optional generated_by_agent filter.",
|
|
1423
1405
|
module: "harness",
|
|
1424
|
-
scope: "mcp:read"
|
|
1406
|
+
scope: "mcp:read",
|
|
1407
|
+
internal: true
|
|
1425
1408
|
},
|
|
1426
1409
|
// hermes — autonomous agent integration (closed-loop content)
|
|
1427
1410
|
{
|
|
1428
1411
|
name: "save_draft_to_library",
|
|
1429
|
-
description: "Save a draft post to the SN content library
|
|
1412
|
+
description: "Save a draft post to the SN content library for review before publishing. Drafts land in the content library pending approval.",
|
|
1430
1413
|
module: "hermes",
|
|
1431
|
-
scope: "mcp:write"
|
|
1414
|
+
scope: "mcp:write",
|
|
1415
|
+
internal: true
|
|
1432
1416
|
},
|
|
1433
1417
|
{
|
|
1434
1418
|
name: "record_voice_lesson",
|
|
1435
|
-
description: "Persist a learned voice lesson to
|
|
1419
|
+
description: "Persist a learned voice lesson to the brand voice profile.",
|
|
1436
1420
|
module: "hermes",
|
|
1437
|
-
scope: "mcp:write"
|
|
1421
|
+
scope: "mcp:write",
|
|
1422
|
+
internal: true
|
|
1438
1423
|
},
|
|
1439
1424
|
{
|
|
1440
1425
|
name: "record_observation",
|
|
1441
|
-
description: 'Record an agent observation (e.g. "topic X engagement up 23%") for the
|
|
1426
|
+
description: 'Record an agent observation (e.g. "topic X engagement up 23%") for the analytics playbook.',
|
|
1442
1427
|
module: "hermes",
|
|
1443
|
-
scope: "mcp:write"
|
|
1428
|
+
scope: "mcp:write",
|
|
1429
|
+
internal: true
|
|
1444
1430
|
},
|
|
1445
1431
|
{
|
|
1446
1432
|
name: "record_intel_signal",
|
|
1447
|
-
description: "Record a research/trend signal
|
|
1433
|
+
description: "Record a research/trend signal (news, competitor, community sources) for niche intelligence. Dedupes by URL.",
|
|
1448
1434
|
module: "hermes",
|
|
1449
|
-
scope: "mcp:write"
|
|
1435
|
+
scope: "mcp:write",
|
|
1436
|
+
internal: true
|
|
1450
1437
|
},
|
|
1451
1438
|
{
|
|
1452
1439
|
name: "record_campaign_spend",
|
|
1453
|
-
description: "Log a campaign cost line
|
|
1440
|
+
description: "Log a campaign cost line item. Ownership-checked.",
|
|
1454
1441
|
module: "hermes",
|
|
1455
|
-
scope: "mcp:write"
|
|
1442
|
+
scope: "mcp:write",
|
|
1443
|
+
internal: true
|
|
1456
1444
|
},
|
|
1457
1445
|
{
|
|
1458
1446
|
name: "get_active_campaigns",
|
|
1459
|
-
description: "List currently-running campaigns with thesis, budget, hero format, and current spend.
|
|
1447
|
+
description: "List currently-running campaigns with thesis, budget, hero format, and current spend.",
|
|
1460
1448
|
module: "hermes",
|
|
1449
|
+
scope: "mcp:read",
|
|
1450
|
+
internal: true
|
|
1451
|
+
},
|
|
1452
|
+
// skills (workflow skills — multi-step brand-locked content pipelines)
|
|
1453
|
+
{
|
|
1454
|
+
name: "list_skills",
|
|
1455
|
+
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.).",
|
|
1456
|
+
module: "skills",
|
|
1457
|
+
scope: "mcp:read"
|
|
1458
|
+
},
|
|
1459
|
+
{
|
|
1460
|
+
name: "run_skill",
|
|
1461
|
+
description: "Run a Social Neuron workflow skill end-to-end (brand-locked content production). Returns a structured run preview with the step plan, credit cost, and a deep-link to launch in the SN dashboard.",
|
|
1462
|
+
module: "skills",
|
|
1463
|
+
scope: "mcp:write"
|
|
1464
|
+
},
|
|
1465
|
+
// loop observability (growth-loop KPIs + content learning state)
|
|
1466
|
+
{
|
|
1467
|
+
name: "get_loop_pulse",
|
|
1468
|
+
description: "Read dynamic loop-health KPIs for the growth loop over the last 7 days (reflection/decision coverage, visual gate pass rate, learning-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.",
|
|
1469
|
+
module: "loop",
|
|
1470
|
+
scope: "mcp:read"
|
|
1471
|
+
},
|
|
1472
|
+
{
|
|
1473
|
+
name: "get_bandit_state",
|
|
1474
|
+
description: "Read the current content learning state for a project \u2014 top-K arms per (arm_type, platform) with expected performance and uncertainty. Use to reason about which hook family / format / timing slot currently performs best per platform.",
|
|
1475
|
+
module: "loop",
|
|
1461
1476
|
scope: "mcp:read"
|
|
1462
1477
|
}
|
|
1463
1478
|
];
|
|
@@ -1602,7 +1617,7 @@ function classifyError(err) {
|
|
|
1602
1617
|
hint: "Run: npx @socialneuron/mcp-server login \u2014 Requires a paid plan (Starter+). See https://socialneuron.com/pricing"
|
|
1603
1618
|
};
|
|
1604
1619
|
}
|
|
1605
|
-
if (lower.includes("econnrefused") || lower.includes("etimedout") || lower.includes("fetch failed") || lower.includes("aborterror") || lower.includes("network") || lower.includes("dns")) {
|
|
1620
|
+
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
1621
|
return {
|
|
1607
1622
|
message,
|
|
1608
1623
|
errorType: "NETWORK",
|
|
@@ -2631,6 +2646,7 @@ async function handleTools(args, asJson) {
|
|
|
2631
2646
|
if (typeof module === "string") {
|
|
2632
2647
|
tools = getToolsByModule(module);
|
|
2633
2648
|
}
|
|
2649
|
+
tools = tools.filter((t) => !t.internal);
|
|
2634
2650
|
if (asJson) {
|
|
2635
2651
|
emitSnResult({ ok: true, command: "tools", toolCount: tools.length, tools }, true);
|
|
2636
2652
|
return;
|
|
@@ -2661,7 +2677,7 @@ Module: ${moduleName} (${moduleTools.length} tools)`);
|
|
|
2661
2677
|
async function handleInfo(args, asJson) {
|
|
2662
2678
|
const info = {
|
|
2663
2679
|
version: MCP_VERSION,
|
|
2664
|
-
toolCount: TOOL_CATALOG.length,
|
|
2680
|
+
toolCount: TOOL_CATALOG.filter((t) => !t.internal).length,
|
|
2665
2681
|
modules: getModules()
|
|
2666
2682
|
};
|
|
2667
2683
|
try {
|
|
@@ -2872,13 +2888,13 @@ __export(presets_exports, {
|
|
|
2872
2888
|
handlePreset: () => handlePreset,
|
|
2873
2889
|
resolvePreset: () => resolvePreset
|
|
2874
2890
|
});
|
|
2875
|
-
import { readFileSync as
|
|
2876
|
-
import { join as
|
|
2877
|
-
import { homedir as
|
|
2891
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "node:fs";
|
|
2892
|
+
import { join as join2 } from "node:path";
|
|
2893
|
+
import { homedir as homedir2 } from "node:os";
|
|
2878
2894
|
function loadUserPresets() {
|
|
2879
2895
|
if (!existsSync2(PRESETS_FILE)) return [];
|
|
2880
2896
|
try {
|
|
2881
|
-
const raw =
|
|
2897
|
+
const raw = readFileSync2(PRESETS_FILE, "utf-8");
|
|
2882
2898
|
const parsed = JSON.parse(raw);
|
|
2883
2899
|
if (!Array.isArray(parsed)) return [];
|
|
2884
2900
|
return parsed;
|
|
@@ -2888,9 +2904,9 @@ function loadUserPresets() {
|
|
|
2888
2904
|
}
|
|
2889
2905
|
function saveUserPresets(presets) {
|
|
2890
2906
|
if (!existsSync2(PRESETS_DIR)) {
|
|
2891
|
-
|
|
2907
|
+
mkdirSync2(PRESETS_DIR, { recursive: true });
|
|
2892
2908
|
}
|
|
2893
|
-
|
|
2909
|
+
writeFileSync2(PRESETS_FILE, JSON.stringify(presets, null, 2) + "\n", "utf-8");
|
|
2894
2910
|
}
|
|
2895
2911
|
function resolvePreset(name) {
|
|
2896
2912
|
const lower = name.toLowerCase();
|
|
@@ -3055,8 +3071,8 @@ var init_presets = __esm({
|
|
|
3055
3071
|
{ name: "linkedin-post", platform: "LinkedIn", maxLength: 3e3, builtin: true },
|
|
3056
3072
|
{ name: "twitter-post", platform: "Twitter", maxLength: 280, builtin: true }
|
|
3057
3073
|
];
|
|
3058
|
-
PRESETS_DIR =
|
|
3059
|
-
PRESETS_FILE =
|
|
3074
|
+
PRESETS_DIR = join2(homedir2(), ".config", "socialneuron");
|
|
3075
|
+
PRESETS_FILE = join2(PRESETS_DIR, "presets.json");
|
|
3060
3076
|
USAGE = `Usage: sn preset <sub-command> [options]
|
|
3061
3077
|
|
|
3062
3078
|
Sub-commands:
|
|
@@ -3259,9 +3275,9 @@ __export(setup_exports, {
|
|
|
3259
3275
|
});
|
|
3260
3276
|
import { createHash as createHash4, randomBytes, randomUUID as randomUUID4 } from "node:crypto";
|
|
3261
3277
|
import { createServer } from "node:http";
|
|
3262
|
-
import { existsSync as existsSync3, mkdirSync as
|
|
3263
|
-
import { homedir as
|
|
3264
|
-
import { join as
|
|
3278
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
3279
|
+
import { homedir as homedir3, platform as platform2 } from "node:os";
|
|
3280
|
+
import { join as join3 } from "node:path";
|
|
3265
3281
|
function base64url(buffer) {
|
|
3266
3282
|
return buffer.toString("base64url");
|
|
3267
3283
|
}
|
|
@@ -3283,8 +3299,8 @@ function getConfigPaths() {
|
|
|
3283
3299
|
const os = platform2();
|
|
3284
3300
|
if (os === "darwin") {
|
|
3285
3301
|
paths.push({
|
|
3286
|
-
path:
|
|
3287
|
-
|
|
3302
|
+
path: join3(
|
|
3303
|
+
homedir3(),
|
|
3288
3304
|
"Library",
|
|
3289
3305
|
"Application Support",
|
|
3290
3306
|
"Claude",
|
|
@@ -3294,15 +3310,15 @@ function getConfigPaths() {
|
|
|
3294
3310
|
});
|
|
3295
3311
|
} else if (os === "linux") {
|
|
3296
3312
|
paths.push({
|
|
3297
|
-
path:
|
|
3313
|
+
path: join3(homedir3(), ".config", "claude", "claude_desktop_config.json"),
|
|
3298
3314
|
name: "Claude Desktop"
|
|
3299
3315
|
});
|
|
3300
3316
|
}
|
|
3301
|
-
const claudeCodeGlobal =
|
|
3317
|
+
const claudeCodeGlobal = join3(homedir3(), ".claude", ".mcp.json");
|
|
3302
3318
|
if (existsSync3(claudeCodeGlobal)) {
|
|
3303
3319
|
paths.push({ path: claudeCodeGlobal, name: "Claude Code (global)" });
|
|
3304
3320
|
}
|
|
3305
|
-
const projectConfig =
|
|
3321
|
+
const projectConfig = join3(process.cwd(), ".mcp.json");
|
|
3306
3322
|
if (existsSync3(projectConfig)) {
|
|
3307
3323
|
paths.push({ path: projectConfig, name: "Claude Code (project)" });
|
|
3308
3324
|
}
|
|
@@ -3312,12 +3328,12 @@ function configureMcpClient(configPath) {
|
|
|
3312
3328
|
try {
|
|
3313
3329
|
let config = {};
|
|
3314
3330
|
if (existsSync3(configPath)) {
|
|
3315
|
-
const raw =
|
|
3331
|
+
const raw = readFileSync3(configPath, "utf-8");
|
|
3316
3332
|
config = JSON.parse(raw);
|
|
3317
3333
|
} else {
|
|
3318
|
-
const dir =
|
|
3334
|
+
const dir = join3(configPath, "..");
|
|
3319
3335
|
if (!existsSync3(dir)) {
|
|
3320
|
-
|
|
3336
|
+
mkdirSync3(dir, { recursive: true });
|
|
3321
3337
|
}
|
|
3322
3338
|
}
|
|
3323
3339
|
if (!config.mcpServers) {
|
|
@@ -3327,7 +3343,7 @@ function configureMcpClient(configPath) {
|
|
|
3327
3343
|
command: "npx",
|
|
3328
3344
|
args: ["-y", "@socialneuron/mcp-server"]
|
|
3329
3345
|
};
|
|
3330
|
-
|
|
3346
|
+
writeFileSync3(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
3331
3347
|
return true;
|
|
3332
3348
|
} catch {
|
|
3333
3349
|
return false;
|
|
@@ -3504,38 +3520,110 @@ var init_setup = __esm({
|
|
|
3504
3520
|
}
|
|
3505
3521
|
});
|
|
3506
3522
|
|
|
3507
|
-
// src/
|
|
3508
|
-
var
|
|
3509
|
-
__export(
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3523
|
+
// src/lib/validation-cache.ts
|
|
3524
|
+
var validation_cache_exports = {};
|
|
3525
|
+
__export(validation_cache_exports, {
|
|
3526
|
+
clearValidationCache: () => clearValidationCache,
|
|
3527
|
+
keyFingerprint: () => keyFingerprint,
|
|
3528
|
+
readValidationCache: () => readValidationCache,
|
|
3529
|
+
writeValidationCache: () => writeValidationCache
|
|
3514
3530
|
});
|
|
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;
|
|
3531
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
3532
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, chmodSync } from "node:fs";
|
|
3533
|
+
import { join as join4 } from "node:path";
|
|
3534
|
+
import { homedir as homedir4 } from "node:os";
|
|
3535
|
+
function keyFingerprint(apiKey) {
|
|
3536
|
+
return `sha256:${createHash5("sha256").update(apiKey, "utf8").digest("hex")}`;
|
|
3527
3537
|
}
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3538
|
+
function readValidationCache(apiKey) {
|
|
3539
|
+
try {
|
|
3540
|
+
const raw = readFileSync4(CACHE_FILE, "utf-8");
|
|
3541
|
+
const cached = JSON.parse(raw);
|
|
3542
|
+
if (cached.keyFingerprint !== keyFingerprint(apiKey)) {
|
|
3543
|
+
return null;
|
|
3544
|
+
}
|
|
3545
|
+
if (Date.now() - cached.cachedAt > CACHE_TTL_MS) {
|
|
3546
|
+
return null;
|
|
3547
|
+
}
|
|
3548
|
+
if (!cached.result.valid) {
|
|
3549
|
+
return null;
|
|
3550
|
+
}
|
|
3551
|
+
if (cached.result.expiresAt) {
|
|
3552
|
+
const expiresMs = new Date(cached.result.expiresAt).getTime();
|
|
3553
|
+
if (expiresMs <= Date.now()) {
|
|
3554
|
+
return null;
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
return cached.result;
|
|
3558
|
+
} catch {
|
|
3559
|
+
return null;
|
|
3560
|
+
}
|
|
3561
|
+
}
|
|
3562
|
+
function writeValidationCache(apiKey, result) {
|
|
3563
|
+
if (!result.valid) return;
|
|
3564
|
+
try {
|
|
3565
|
+
mkdirSync4(CONFIG_DIR2, { recursive: true });
|
|
3566
|
+
const entry = {
|
|
3567
|
+
keyFingerprint: keyFingerprint(apiKey),
|
|
3568
|
+
result,
|
|
3569
|
+
cachedAt: Date.now()
|
|
3570
|
+
};
|
|
3571
|
+
writeFileSync4(CACHE_FILE, JSON.stringify(entry, null, 2), { mode: 384 });
|
|
3572
|
+
try {
|
|
3573
|
+
chmodSync(CACHE_FILE, 384);
|
|
3574
|
+
} catch {
|
|
3575
|
+
}
|
|
3576
|
+
} catch {
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
function clearValidationCache() {
|
|
3580
|
+
try {
|
|
3581
|
+
writeFileSync4(CACHE_FILE, "{}", { mode: 384 });
|
|
3582
|
+
} catch {
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3585
|
+
var CONFIG_DIR2, CACHE_FILE, CACHE_TTL_MS;
|
|
3586
|
+
var init_validation_cache = __esm({
|
|
3587
|
+
"src/lib/validation-cache.ts"() {
|
|
3588
|
+
"use strict";
|
|
3589
|
+
CONFIG_DIR2 = join4(homedir4(), ".config", "socialneuron");
|
|
3590
|
+
CACHE_FILE = join4(CONFIG_DIR2, "validation-cache.json");
|
|
3591
|
+
CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
3592
|
+
}
|
|
3593
|
+
});
|
|
3594
|
+
|
|
3595
|
+
// src/cli/commands.ts
|
|
3596
|
+
var commands_exports = {};
|
|
3597
|
+
__export(commands_exports, {
|
|
3598
|
+
runHealthCheck: () => runHealthCheck,
|
|
3599
|
+
runLogin: () => runLogin,
|
|
3600
|
+
runLogoutCommand: () => runLogoutCommand,
|
|
3601
|
+
runWhoami: () => runWhoami
|
|
3602
|
+
});
|
|
3603
|
+
import { createInterface } from "node:readline";
|
|
3604
|
+
function prompt(question) {
|
|
3605
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
3606
|
+
return new Promise((resolve3) => {
|
|
3607
|
+
rl.question(question, (answer) => {
|
|
3608
|
+
rl.close();
|
|
3609
|
+
resolve3(answer.trim());
|
|
3610
|
+
});
|
|
3611
|
+
});
|
|
3612
|
+
}
|
|
3613
|
+
function getDefaultSupabaseUrl2() {
|
|
3614
|
+
return process.env.SOCIALNEURON_SUPABASE_URL || process.env.SUPABASE_URL || CLOUD_SUPABASE_URL;
|
|
3615
|
+
}
|
|
3616
|
+
async function runLogin(method) {
|
|
3617
|
+
if (method === "browser") {
|
|
3618
|
+
await runSetup();
|
|
3619
|
+
return;
|
|
3620
|
+
}
|
|
3621
|
+
if (method === "paste") {
|
|
3622
|
+
await runLoginPaste();
|
|
3623
|
+
return;
|
|
3624
|
+
}
|
|
3625
|
+
if (method === "device") {
|
|
3626
|
+
await runLoginDevice();
|
|
3539
3627
|
return;
|
|
3540
3628
|
}
|
|
3541
3629
|
}
|
|
@@ -3626,20 +3714,27 @@ async function runLoginDevice() {
|
|
|
3626
3714
|
if (pollData.api_key) {
|
|
3627
3715
|
try {
|
|
3628
3716
|
await saveApiKey(pollData.api_key);
|
|
3629
|
-
await saveSupabaseUrl(supabaseUrl);
|
|
3630
3717
|
} catch (saveErr) {
|
|
3631
3718
|
console.error("");
|
|
3632
|
-
console.error(" Warning: Could not save key
|
|
3719
|
+
console.error(" Warning: Could not save API key securely.");
|
|
3633
3720
|
console.error(` Error: ${saveErr instanceof Error ? saveErr.message : String(saveErr)}`);
|
|
3634
3721
|
console.error("");
|
|
3635
|
-
console.error("
|
|
3636
|
-
console.error(
|
|
3637
|
-
console.error("");
|
|
3638
|
-
console.error(" To authenticate manually, run:");
|
|
3722
|
+
console.error(" For security, the API key was not printed to the terminal.");
|
|
3723
|
+
console.error(" Create or copy an API key from your Social Neuron dashboard, then run:");
|
|
3639
3724
|
console.error(" npx @socialneuron/mcp-server login --paste");
|
|
3640
3725
|
console.error("");
|
|
3641
|
-
|
|
3642
|
-
|
|
3726
|
+
return;
|
|
3727
|
+
}
|
|
3728
|
+
try {
|
|
3729
|
+
await saveSupabaseUrl(supabaseUrl);
|
|
3730
|
+
} catch (saveErr) {
|
|
3731
|
+
console.error("");
|
|
3732
|
+
console.error(" Authorized!");
|
|
3733
|
+
console.error(` Key prefix: ${pollData.api_key.substring(0, 12)}...`);
|
|
3734
|
+
console.error("");
|
|
3735
|
+
console.error(" Warning: API key saved, but could not save the Supabase URL.");
|
|
3736
|
+
console.error(` Error: ${saveErr instanceof Error ? saveErr.message : String(saveErr)}`);
|
|
3737
|
+
console.error(" If needed, set SOCIALNEURON_SUPABASE_URL to your Supabase URL.");
|
|
3643
3738
|
console.error("");
|
|
3644
3739
|
return;
|
|
3645
3740
|
}
|
|
@@ -4155,6 +4250,8 @@ var TOOL_SCOPES = {
|
|
|
4155
4250
|
get_mcp_usage: "mcp:read",
|
|
4156
4251
|
list_plan_approvals: "mcp:read",
|
|
4157
4252
|
search_tools: "mcp:read",
|
|
4253
|
+
search: "mcp:read",
|
|
4254
|
+
fetch: "mcp:read",
|
|
4158
4255
|
// mcp:read (pipeline readiness + status are read-only)
|
|
4159
4256
|
check_pipeline_readiness: "mcp:read",
|
|
4160
4257
|
get_pipeline_status: "mcp:read",
|
|
@@ -4182,7 +4279,13 @@ var TOOL_SCOPES = {
|
|
|
4182
4279
|
record_intel_signal: "mcp:write",
|
|
4183
4280
|
record_campaign_spend: "mcp:write",
|
|
4184
4281
|
// mcp:read
|
|
4185
|
-
get_active_campaigns: "mcp:read"
|
|
4282
|
+
get_active_campaigns: "mcp:read",
|
|
4283
|
+
// mcp:read / mcp:write (Skills)
|
|
4284
|
+
list_skills: "mcp:read",
|
|
4285
|
+
run_skill: "mcp:write",
|
|
4286
|
+
// mcp:read (Loop observability — growth-loop KPIs + content learning state)
|
|
4287
|
+
get_loop_pulse: "mcp:read",
|
|
4288
|
+
get_bandit_state: "mcp:read"
|
|
4186
4289
|
};
|
|
4187
4290
|
function hasScope(userScopes, required) {
|
|
4188
4291
|
if (userScopes.includes(required)) return true;
|
|
@@ -4282,6 +4385,10 @@ var OVERRIDES = {
|
|
|
4282
4385
|
generate_performance_digest: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
4283
4386
|
detect_anomalies: { readOnlyHint: true, destructiveHint: false, idempotentHint: true }
|
|
4284
4387
|
};
|
|
4388
|
+
function buildToolSecuritySchemes(toolName) {
|
|
4389
|
+
const scope = TOOL_SCOPES[toolName];
|
|
4390
|
+
return scope ? [{ type: "oauth2", scopes: [scope] }] : [];
|
|
4391
|
+
}
|
|
4285
4392
|
function buildAnnotationsMap() {
|
|
4286
4393
|
const map = /* @__PURE__ */ new Map();
|
|
4287
4394
|
for (const [toolName, scope] of Object.entries(TOOL_SCOPES)) {
|
|
@@ -4326,17 +4433,37 @@ function applyAnnotations(server2) {
|
|
|
4326
4433
|
destructiveHint: ann.destructiveHint,
|
|
4327
4434
|
idempotentHint: ann.idempotentHint,
|
|
4328
4435
|
openWorldHint: ann.openWorldHint
|
|
4329
|
-
}
|
|
4436
|
+
},
|
|
4437
|
+
securitySchemes: buildToolSecuritySchemes(toolName)
|
|
4330
4438
|
});
|
|
4331
4439
|
applied++;
|
|
4332
4440
|
}
|
|
4333
4441
|
}
|
|
4334
|
-
console.
|
|
4442
|
+
console.error(`[annotations] Applied annotations to ${applied}/${entries.length} tools`);
|
|
4335
4443
|
}
|
|
4336
4444
|
|
|
4337
4445
|
// src/lib/register-tools.ts
|
|
4338
4446
|
init_supabase();
|
|
4339
4447
|
|
|
4448
|
+
// src/lib/www-authenticate.ts
|
|
4449
|
+
function buildWwwAuthenticateHeader(opts) {
|
|
4450
|
+
const SANITIZE_CONTROL = /[\x00-\x1f\x7f]/g;
|
|
4451
|
+
const sanitize = (s) => s.replace(/[\\"]/g, "_").replace(SANITIZE_CONTROL, " ");
|
|
4452
|
+
const params = ['realm="socialneuron"'];
|
|
4453
|
+
if (opts.error) {
|
|
4454
|
+
params.push(`error="${sanitize(opts.error)}"`);
|
|
4455
|
+
}
|
|
4456
|
+
if (opts.errorDescription) {
|
|
4457
|
+
params.push(`error_description="${sanitize(opts.errorDescription)}"`);
|
|
4458
|
+
}
|
|
4459
|
+
if (opts.scope) {
|
|
4460
|
+
params.push(`scope="${sanitize(opts.scope)}"`);
|
|
4461
|
+
}
|
|
4462
|
+
const issuer = opts.issuerUrl.replace(/\/+$/, "");
|
|
4463
|
+
params.push(`resource_metadata="${issuer}/.well-known/oauth-protected-resource"`);
|
|
4464
|
+
return `Bearer ${params.join(", ")}`;
|
|
4465
|
+
}
|
|
4466
|
+
|
|
4340
4467
|
// src/lib/agent-harness/constants.json
|
|
4341
4468
|
var constants_default = {
|
|
4342
4469
|
ZERO_WIDTH_CHARS: "[\\u200B\\u200C\\u200D\\u2060\\uFEFF\\u{E0020}-\\u{E007F}]",
|
|
@@ -4538,6 +4665,16 @@ function checkRateLimit(category, key) {
|
|
|
4538
4665
|
|
|
4539
4666
|
// src/tools/ideation.ts
|
|
4540
4667
|
init_supabase();
|
|
4668
|
+
init_version();
|
|
4669
|
+
function asEnvelope(data) {
|
|
4670
|
+
return {
|
|
4671
|
+
_meta: {
|
|
4672
|
+
version: MCP_VERSION,
|
|
4673
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4674
|
+
},
|
|
4675
|
+
data
|
|
4676
|
+
};
|
|
4677
|
+
}
|
|
4541
4678
|
function registerIdeationTools(server2) {
|
|
4542
4679
|
server2.tool(
|
|
4543
4680
|
"generate_content",
|
|
@@ -4699,7 +4836,14 @@ Content Type: ${content_type}`;
|
|
|
4699
4836
|
};
|
|
4700
4837
|
}
|
|
4701
4838
|
const text = data?.text ?? "(empty response)";
|
|
4839
|
+
const structuredContent = asEnvelope({
|
|
4840
|
+
text,
|
|
4841
|
+
content_type,
|
|
4842
|
+
platform: platform3 ?? null,
|
|
4843
|
+
model: model ?? "gemini-2.5-flash"
|
|
4844
|
+
});
|
|
4702
4845
|
return {
|
|
4846
|
+
structuredContent,
|
|
4703
4847
|
content: [{ type: "text", text }]
|
|
4704
4848
|
};
|
|
4705
4849
|
}
|
|
@@ -4733,7 +4877,10 @@ Content Type: ${content_type}`;
|
|
|
4733
4877
|
const { data, error } = await callEdgeFunction(
|
|
4734
4878
|
"fetch-trends",
|
|
4735
4879
|
{
|
|
4736
|
-
source
|
|
4880
|
+
// Forward as `trend_source`: the mcp-gateway overwrites top-level `source`
|
|
4881
|
+
// with 'mcp' (attribution) before reaching the EF, which collided with the
|
|
4882
|
+
// routing param. The EF reads `trend_source ?? source`.
|
|
4883
|
+
trend_source: source,
|
|
4737
4884
|
category: category ?? "general",
|
|
4738
4885
|
niche,
|
|
4739
4886
|
url,
|
|
@@ -4902,7 +5049,14 @@ ${content}`,
|
|
|
4902
5049
|
const header = `Adapted for ${target_platform}${source_platform ? ` (from ${source_platform})` : ""}:
|
|
4903
5050
|
|
|
4904
5051
|
`;
|
|
5052
|
+
const structuredContent = asEnvelope({
|
|
5053
|
+
text,
|
|
5054
|
+
source_platform: source_platform ?? null,
|
|
5055
|
+
target_platform,
|
|
5056
|
+
model: "gemini-2.5-flash"
|
|
5057
|
+
});
|
|
4905
5058
|
return {
|
|
5059
|
+
structuredContent,
|
|
4906
5060
|
content: [{ type: "text", text: header + text }]
|
|
4907
5061
|
};
|
|
4908
5062
|
}
|
|
@@ -4913,8 +5067,10 @@ ${content}`,
|
|
|
4913
5067
|
init_edge_function();
|
|
4914
5068
|
import { z as z2 } from "zod";
|
|
4915
5069
|
init_supabase();
|
|
4916
|
-
init_request_context();
|
|
4917
5070
|
init_version();
|
|
5071
|
+
|
|
5072
|
+
// src/lib/budget.ts
|
|
5073
|
+
init_request_context();
|
|
4918
5074
|
var MAX_CREDITS_PER_RUN = Math.max(0, Number(process.env.SOCIALNEURON_MAX_CREDITS_PER_RUN || 0));
|
|
4919
5075
|
var MAX_ASSETS_PER_RUN = Math.max(0, Number(process.env.SOCIALNEURON_MAX_ASSETS_PER_RUN || 0));
|
|
4920
5076
|
var _globalCreditsUsed = 0;
|
|
@@ -4955,7 +5111,35 @@ function getCurrentBudgetStatus() {
|
|
|
4955
5111
|
remainingAssets: MAX_ASSETS_PER_RUN > 0 ? Math.max(0, MAX_ASSETS_PER_RUN - assetsGen) : null
|
|
4956
5112
|
};
|
|
4957
5113
|
}
|
|
4958
|
-
function
|
|
5114
|
+
function checkCreditBudget(estimatedCost) {
|
|
5115
|
+
if (MAX_CREDITS_PER_RUN <= 0) {
|
|
5116
|
+
return { ok: true };
|
|
5117
|
+
}
|
|
5118
|
+
const used = getCreditsUsed();
|
|
5119
|
+
if (used + estimatedCost > MAX_CREDITS_PER_RUN) {
|
|
5120
|
+
return {
|
|
5121
|
+
ok: false,
|
|
5122
|
+
message: `Credit budget exceeded for this MCP run. Used=${used}, next~=${estimatedCost}, limit=${MAX_CREDITS_PER_RUN}.`
|
|
5123
|
+
};
|
|
5124
|
+
}
|
|
5125
|
+
return { ok: true };
|
|
5126
|
+
}
|
|
5127
|
+
function checkAssetBudget(requestedCount = 1) {
|
|
5128
|
+
if (MAX_ASSETS_PER_RUN <= 0) {
|
|
5129
|
+
return { ok: true };
|
|
5130
|
+
}
|
|
5131
|
+
const generated = getAssetsGenerated();
|
|
5132
|
+
if (generated + requestedCount > MAX_ASSETS_PER_RUN) {
|
|
5133
|
+
return {
|
|
5134
|
+
ok: false,
|
|
5135
|
+
message: `Asset budget exceeded for this MCP run. Generated=${generated}, next=${requestedCount}, limit=${MAX_ASSETS_PER_RUN}.`
|
|
5136
|
+
};
|
|
5137
|
+
}
|
|
5138
|
+
return { ok: true };
|
|
5139
|
+
}
|
|
5140
|
+
|
|
5141
|
+
// src/tools/content.ts
|
|
5142
|
+
function asEnvelope2(data) {
|
|
4959
5143
|
return {
|
|
4960
5144
|
_meta: {
|
|
4961
5145
|
version: MCP_VERSION,
|
|
@@ -4982,35 +5166,9 @@ var IMAGE_CREDIT_ESTIMATES = {
|
|
|
4982
5166
|
"flux-max": 50,
|
|
4983
5167
|
"gpt4o-image": 40,
|
|
4984
5168
|
imagen4: 35,
|
|
4985
|
-
"imagen4-fast":
|
|
5169
|
+
"imagen4-fast": 35,
|
|
4986
5170
|
seedream: 20
|
|
4987
5171
|
};
|
|
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
5172
|
function registerContentTools(server2) {
|
|
5015
5173
|
server2.tool(
|
|
5016
5174
|
"generate_video",
|
|
@@ -5129,7 +5287,7 @@ function registerContentTools(server2) {
|
|
|
5129
5287
|
{
|
|
5130
5288
|
type: "text",
|
|
5131
5289
|
text: JSON.stringify(
|
|
5132
|
-
|
|
5290
|
+
asEnvelope2({
|
|
5133
5291
|
jobId,
|
|
5134
5292
|
taskId: data.taskId,
|
|
5135
5293
|
asyncJobId: data.asyncJobId,
|
|
@@ -5259,7 +5417,7 @@ function registerContentTools(server2) {
|
|
|
5259
5417
|
{
|
|
5260
5418
|
type: "text",
|
|
5261
5419
|
text: JSON.stringify(
|
|
5262
|
-
|
|
5420
|
+
asEnvelope2({
|
|
5263
5421
|
jobId,
|
|
5264
5422
|
taskId: data.taskId,
|
|
5265
5423
|
asyncJobId: data.asyncJobId,
|
|
@@ -5362,7 +5520,7 @@ function registerContentTools(server2) {
|
|
|
5362
5520
|
{
|
|
5363
5521
|
type: "text",
|
|
5364
5522
|
text: JSON.stringify(
|
|
5365
|
-
|
|
5523
|
+
asEnvelope2({
|
|
5366
5524
|
jobId: job.id,
|
|
5367
5525
|
jobType: job.job_type,
|
|
5368
5526
|
model: job.model,
|
|
@@ -5423,7 +5581,7 @@ function registerContentTools(server2) {
|
|
|
5423
5581
|
all_urls: allUrls ?? null
|
|
5424
5582
|
};
|
|
5425
5583
|
return {
|
|
5426
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
5584
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope2(enriched), null, 2) }]
|
|
5427
5585
|
};
|
|
5428
5586
|
}
|
|
5429
5587
|
return {
|
|
@@ -5552,7 +5710,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
5552
5710
|
try {
|
|
5553
5711
|
const parsed = JSON.parse(rawContent);
|
|
5554
5712
|
return {
|
|
5555
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
5713
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope2(parsed), null, 2) }]
|
|
5556
5714
|
};
|
|
5557
5715
|
} catch {
|
|
5558
5716
|
return {
|
|
@@ -5580,20 +5738,8 @@ Return ONLY valid JSON in this exact format:
|
|
|
5580
5738
|
"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
5739
|
{
|
|
5582
5740
|
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."
|
|
5741
|
+
voice: z2.enum(["rachel", "domi"]).optional().describe(
|
|
5742
|
+
"Voice selection. rachel=warm female, domi=confident female. Defaults to rachel."
|
|
5597
5743
|
),
|
|
5598
5744
|
speed: z2.number().min(0.5).max(2).optional().describe("Speech speed multiplier. 1.0 is normal. Defaults to 1.0."),
|
|
5599
5745
|
response_format: z2.enum(["text", "json"]).optional().describe("Response format. Defaults to text.")
|
|
@@ -5621,11 +5767,15 @@ Return ONLY valid JSON in this exact format:
|
|
|
5621
5767
|
isError: true
|
|
5622
5768
|
};
|
|
5623
5769
|
}
|
|
5770
|
+
const ELEVENLABS_VOICE_IDS = {
|
|
5771
|
+
rachel: "21m00Tcm4TlvDq8ikWAM",
|
|
5772
|
+
domi: "AZnzlk1XvdvUeBnXmlld"
|
|
5773
|
+
};
|
|
5624
5774
|
const { data, error } = await callEdgeFunction(
|
|
5625
5775
|
"elevenlabs-tts",
|
|
5626
5776
|
{
|
|
5627
5777
|
text,
|
|
5628
|
-
|
|
5778
|
+
voiceId: ELEVENLABS_VOICE_IDS[voice ?? "rachel"],
|
|
5629
5779
|
speed: speed ?? 1
|
|
5630
5780
|
},
|
|
5631
5781
|
{ timeoutMs: 6e4 }
|
|
@@ -5651,7 +5801,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
5651
5801
|
{
|
|
5652
5802
|
type: "text",
|
|
5653
5803
|
text: JSON.stringify(
|
|
5654
|
-
|
|
5804
|
+
asEnvelope2({
|
|
5655
5805
|
audioUrl: data.audioUrl,
|
|
5656
5806
|
durationSeconds: data.durationSeconds,
|
|
5657
5807
|
voice: voice ?? "rachel"
|
|
@@ -5800,7 +5950,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
5800
5950
|
{
|
|
5801
5951
|
type: "text",
|
|
5802
5952
|
text: JSON.stringify(
|
|
5803
|
-
|
|
5953
|
+
asEnvelope2({
|
|
5804
5954
|
carouselId: data.carousel.id,
|
|
5805
5955
|
templateId,
|
|
5806
5956
|
style: resolvedStyle,
|
|
@@ -6069,7 +6219,7 @@ var PLATFORM_CASE_MAP = {
|
|
|
6069
6219
|
};
|
|
6070
6220
|
var TIKTOK_AUDIT_APPROVED = false;
|
|
6071
6221
|
var MCP_NOT_LIVE_FOR_POSTING = /* @__PURE__ */ new Set(["LinkedIn", "Pinterest", "Reddit"]);
|
|
6072
|
-
function
|
|
6222
|
+
function asEnvelope3(data) {
|
|
6073
6223
|
return {
|
|
6074
6224
|
_meta: {
|
|
6075
6225
|
version: MCP_VERSION,
|
|
@@ -6167,7 +6317,24 @@ function registerDistributionTools(server2) {
|
|
|
6167
6317
|
linkedin: z3.object({
|
|
6168
6318
|
article_url: z3.string().optional()
|
|
6169
6319
|
}).optional(),
|
|
6170
|
-
twitter: z3.object({
|
|
6320
|
+
twitter: z3.object({
|
|
6321
|
+
paid_partnership: z3.boolean().optional().describe(
|
|
6322
|
+
"Set true for sponsored, affiliate, or branded campaign content. Forwarded to X as paid_partnership and persisted on posts.metadata.paid_partnership."
|
|
6323
|
+
),
|
|
6324
|
+
disclosure: z3.union([
|
|
6325
|
+
z3.string(),
|
|
6326
|
+
z3.object({
|
|
6327
|
+
label: z3.string().optional(),
|
|
6328
|
+
text: z3.string().optional(),
|
|
6329
|
+
source: z3.string().optional(),
|
|
6330
|
+
required: z3.boolean().optional()
|
|
6331
|
+
}).passthrough()
|
|
6332
|
+
]).optional().describe(
|
|
6333
|
+
"Campaign disclosure metadata for X posts. String values become disclosure.text; object values persist to posts.metadata.disclosure."
|
|
6334
|
+
),
|
|
6335
|
+
disclosure_text: z3.string().optional(),
|
|
6336
|
+
disclosure_label: z3.string().optional()
|
|
6337
|
+
}).passthrough().optional()
|
|
6171
6338
|
}).optional().describe(
|
|
6172
6339
|
'Platform-specific metadata. Example: {"tiktok":{"privacy_status":"PUBLIC_TO_EVERYONE"}, "youtube":{"title":"My Video"}}'
|
|
6173
6340
|
),
|
|
@@ -6211,11 +6378,11 @@ function registerDistributionTools(server2) {
|
|
|
6211
6378
|
visual_gate_result: z3.object({ passed: z3.boolean() }).passthrough().optional().describe(
|
|
6212
6379
|
"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
6380
|
),
|
|
6214
|
-
origin: z3.
|
|
6215
|
-
"
|
|
6381
|
+
origin: z3.enum(["human", "hermes", "user"]).optional().describe(
|
|
6382
|
+
"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
6383
|
),
|
|
6217
6384
|
hermes_run_id: z3.string().optional().describe(
|
|
6218
|
-
"Hermes cron run identifier
|
|
6385
|
+
"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
6386
|
)
|
|
6220
6387
|
},
|
|
6221
6388
|
async ({
|
|
@@ -6498,11 +6665,11 @@ Created with Social Neuron`;
|
|
|
6498
6665
|
// Forward visual gate verdict + source so the EF can enforce on media posts.
|
|
6499
6666
|
...visual_gate_result ? { visualGateResult: visual_gate_result } : {},
|
|
6500
6667
|
visualGateSource: "mcp",
|
|
6501
|
-
//
|
|
6502
|
-
//
|
|
6503
|
-
//
|
|
6668
|
+
// Originator lineage (Hermes integration, 2026-05-22). EF validates origin
|
|
6669
|
+
// against the posts.origin CHECK constraint and persists hermesRunId when
|
|
6670
|
+
// origin === 'hermes'.
|
|
6504
6671
|
...origin ? { origin } : {},
|
|
6505
|
-
...hermes_run_id ? { hermes_run_id } : {}
|
|
6672
|
+
...hermes_run_id ? { hermesRunId: hermes_run_id } : {}
|
|
6506
6673
|
},
|
|
6507
6674
|
{ timeoutMs: 3e4 }
|
|
6508
6675
|
);
|
|
@@ -6547,12 +6714,15 @@ Created with Social Neuron`;
|
|
|
6547
6714
|
}
|
|
6548
6715
|
}
|
|
6549
6716
|
if (format === "json") {
|
|
6717
|
+
const structuredContent = asEnvelope3(data);
|
|
6550
6718
|
return {
|
|
6551
|
-
|
|
6719
|
+
structuredContent,
|
|
6720
|
+
content: [{ type: "text", text: JSON.stringify(structuredContent, null, 2) }],
|
|
6552
6721
|
isError: !data.success
|
|
6553
6722
|
};
|
|
6554
6723
|
}
|
|
6555
6724
|
return {
|
|
6725
|
+
structuredContent: asEnvelope3(data),
|
|
6556
6726
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
6557
6727
|
isError: !data.success
|
|
6558
6728
|
};
|
|
@@ -6585,7 +6755,7 @@ Created with Social Neuron`;
|
|
|
6585
6755
|
content: [
|
|
6586
6756
|
{
|
|
6587
6757
|
type: "text",
|
|
6588
|
-
text: JSON.stringify(
|
|
6758
|
+
text: JSON.stringify(asEnvelope3({ accounts: [] }), null, 2)
|
|
6589
6759
|
}
|
|
6590
6760
|
]
|
|
6591
6761
|
};
|
|
@@ -6608,7 +6778,7 @@ Created with Social Neuron`;
|
|
|
6608
6778
|
if (format === "json") {
|
|
6609
6779
|
return {
|
|
6610
6780
|
content: [
|
|
6611
|
-
{ type: "text", text: JSON.stringify(
|
|
6781
|
+
{ type: "text", text: JSON.stringify(asEnvelope3({ accounts }), null, 2) }
|
|
6612
6782
|
]
|
|
6613
6783
|
};
|
|
6614
6784
|
}
|
|
@@ -6660,10 +6830,10 @@ Created with Social Neuron`;
|
|
|
6660
6830
|
const rows = result.posts ?? [];
|
|
6661
6831
|
if (rows.length === 0) {
|
|
6662
6832
|
if (format === "json") {
|
|
6833
|
+
const structuredContent2 = asEnvelope3({ posts: [] });
|
|
6663
6834
|
return {
|
|
6664
|
-
|
|
6665
|
-
|
|
6666
|
-
]
|
|
6835
|
+
structuredContent: structuredContent2,
|
|
6836
|
+
content: [{ type: "text", text: JSON.stringify(structuredContent2, null, 2) }]
|
|
6667
6837
|
};
|
|
6668
6838
|
}
|
|
6669
6839
|
return {
|
|
@@ -6676,11 +6846,11 @@ Created with Social Neuron`;
|
|
|
6676
6846
|
};
|
|
6677
6847
|
}
|
|
6678
6848
|
const posts = rows;
|
|
6849
|
+
const structuredContent = asEnvelope3({ posts });
|
|
6679
6850
|
if (format === "json") {
|
|
6680
6851
|
return {
|
|
6681
|
-
|
|
6682
|
-
|
|
6683
|
-
]
|
|
6852
|
+
structuredContent,
|
|
6853
|
+
content: [{ type: "text", text: JSON.stringify(structuredContent, null, 2) }]
|
|
6684
6854
|
};
|
|
6685
6855
|
}
|
|
6686
6856
|
const statusIcon = {
|
|
@@ -6705,6 +6875,7 @@ Created with Social Neuron`;
|
|
|
6705
6875
|
lines.push(line);
|
|
6706
6876
|
}
|
|
6707
6877
|
return {
|
|
6878
|
+
structuredContent,
|
|
6708
6879
|
content: [{ type: "text", text: lines.join("\n") }]
|
|
6709
6880
|
};
|
|
6710
6881
|
}
|
|
@@ -6788,7 +6959,7 @@ Created with Social Neuron`;
|
|
|
6788
6959
|
{
|
|
6789
6960
|
type: "text",
|
|
6790
6961
|
text: JSON.stringify(
|
|
6791
|
-
|
|
6962
|
+
asEnvelope3({
|
|
6792
6963
|
slots,
|
|
6793
6964
|
total_candidates: candidates.length,
|
|
6794
6965
|
conflicts_avoided: conflictsAvoided
|
|
@@ -7077,7 +7248,7 @@ Created with Social Neuron`;
|
|
|
7077
7248
|
{
|
|
7078
7249
|
type: "text",
|
|
7079
7250
|
text: JSON.stringify(
|
|
7080
|
-
|
|
7251
|
+
asEnvelope3({
|
|
7081
7252
|
dry_run: true,
|
|
7082
7253
|
plan_id: effectivePlanId,
|
|
7083
7254
|
approvals: approvalSummary,
|
|
@@ -7265,7 +7436,7 @@ Created with Social Neuron`;
|
|
|
7265
7436
|
{
|
|
7266
7437
|
type: "text",
|
|
7267
7438
|
text: JSON.stringify(
|
|
7268
|
-
|
|
7439
|
+
asEnvelope3({
|
|
7269
7440
|
plan_id: effectivePlanId,
|
|
7270
7441
|
approvals: approvalSummary,
|
|
7271
7442
|
posts: results,
|
|
@@ -7540,6 +7711,17 @@ function registerMediaTools(server2) {
|
|
|
7540
7711
|
projectId: project_id
|
|
7541
7712
|
};
|
|
7542
7713
|
} else {
|
|
7714
|
+
if (process.env.MCP_TRANSPORT !== "stdio") {
|
|
7715
|
+
return {
|
|
7716
|
+
content: [
|
|
7717
|
+
{
|
|
7718
|
+
type: "text",
|
|
7719
|
+
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\`.`
|
|
7720
|
+
}
|
|
7721
|
+
],
|
|
7722
|
+
isError: true
|
|
7723
|
+
};
|
|
7724
|
+
}
|
|
7543
7725
|
let fileBuffer;
|
|
7544
7726
|
try {
|
|
7545
7727
|
fileBuffer = await readFile(src);
|
|
@@ -7582,7 +7764,8 @@ function registerMediaTools(server2) {
|
|
|
7582
7764
|
const putResp = await fetch(putUrl, {
|
|
7583
7765
|
method: "PUT",
|
|
7584
7766
|
headers: { "Content-Type": ct },
|
|
7585
|
-
|
|
7767
|
+
// Uint8Array: Buffer no longer satisfies BodyInit under @types/node 26 fetch types
|
|
7768
|
+
body: new Uint8Array(fileBuffer)
|
|
7586
7769
|
});
|
|
7587
7770
|
if (!putResp.ok) {
|
|
7588
7771
|
return {
|
|
@@ -7766,7 +7949,7 @@ init_supabase();
|
|
|
7766
7949
|
init_edge_function();
|
|
7767
7950
|
import { z as z5 } from "zod";
|
|
7768
7951
|
init_version();
|
|
7769
|
-
function
|
|
7952
|
+
function asEnvelope4(data) {
|
|
7770
7953
|
return {
|
|
7771
7954
|
_meta: {
|
|
7772
7955
|
version: MCP_VERSION,
|
|
@@ -7819,22 +8002,20 @@ function registerAnalyticsTools(server2) {
|
|
|
7819
8002
|
const rows = result?.rows ?? [];
|
|
7820
8003
|
if (rows.length === 0) {
|
|
7821
8004
|
if (format === "json") {
|
|
8005
|
+
const structuredContent = asEnvelope4({
|
|
8006
|
+
platform: platform3 ?? null,
|
|
8007
|
+
days: lookbackDays,
|
|
8008
|
+
totalViews: 0,
|
|
8009
|
+
totalEngagement: 0,
|
|
8010
|
+
postCount: 0,
|
|
8011
|
+
posts: []
|
|
8012
|
+
});
|
|
7822
8013
|
return {
|
|
8014
|
+
structuredContent,
|
|
7823
8015
|
content: [
|
|
7824
8016
|
{
|
|
7825
8017
|
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
|
-
)
|
|
8018
|
+
text: JSON.stringify(structuredContent, null, 2)
|
|
7838
8019
|
}
|
|
7839
8020
|
]
|
|
7840
8021
|
};
|
|
@@ -7924,20 +8105,18 @@ function registerAnalyticsTools(server2) {
|
|
|
7924
8105
|
lines.push(` Errors: ${errored}`);
|
|
7925
8106
|
}
|
|
7926
8107
|
if (format === "json") {
|
|
8108
|
+
const structuredContent = asEnvelope4({
|
|
8109
|
+
success: true,
|
|
8110
|
+
postsProcessed: result.postsProcessed,
|
|
8111
|
+
queued,
|
|
8112
|
+
errored
|
|
8113
|
+
});
|
|
7927
8114
|
return {
|
|
8115
|
+
structuredContent,
|
|
7928
8116
|
content: [
|
|
7929
8117
|
{
|
|
7930
8118
|
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
|
-
)
|
|
8119
|
+
text: JSON.stringify(structuredContent, null, 2)
|
|
7941
8120
|
}
|
|
7942
8121
|
]
|
|
7943
8122
|
};
|
|
@@ -7947,11 +8126,11 @@ function registerAnalyticsTools(server2) {
|
|
|
7947
8126
|
);
|
|
7948
8127
|
}
|
|
7949
8128
|
function formatAnalytics(summary, days, format) {
|
|
8129
|
+
const structuredContent = asEnvelope4({ ...summary, days });
|
|
7950
8130
|
if (format === "json") {
|
|
7951
8131
|
return {
|
|
7952
|
-
|
|
7953
|
-
|
|
7954
|
-
]
|
|
8132
|
+
structuredContent,
|
|
8133
|
+
content: [{ type: "text", text: JSON.stringify(structuredContent, null, 2) }]
|
|
7955
8134
|
};
|
|
7956
8135
|
}
|
|
7957
8136
|
const lines = [
|
|
@@ -7976,6 +8155,7 @@ function formatAnalytics(summary, days, format) {
|
|
|
7976
8155
|
}
|
|
7977
8156
|
}
|
|
7978
8157
|
return {
|
|
8158
|
+
structuredContent,
|
|
7979
8159
|
content: [{ type: "text", text: lines.join("\n") }]
|
|
7980
8160
|
};
|
|
7981
8161
|
}
|
|
@@ -7985,7 +8165,7 @@ init_edge_function();
|
|
|
7985
8165
|
init_supabase();
|
|
7986
8166
|
import { z as z6 } from "zod";
|
|
7987
8167
|
init_version();
|
|
7988
|
-
function
|
|
8168
|
+
function asEnvelope5(data) {
|
|
7989
8169
|
return {
|
|
7990
8170
|
_meta: {
|
|
7991
8171
|
version: MCP_VERSION,
|
|
@@ -8040,8 +8220,10 @@ function registerBrandTools(server2) {
|
|
|
8040
8220
|
};
|
|
8041
8221
|
}
|
|
8042
8222
|
if ((response_format || "text") === "json") {
|
|
8223
|
+
const structuredContent = asEnvelope5(data);
|
|
8043
8224
|
return {
|
|
8044
|
-
|
|
8225
|
+
structuredContent,
|
|
8226
|
+
content: [{ type: "text", text: JSON.stringify(structuredContent, null, 2) }]
|
|
8045
8227
|
};
|
|
8046
8228
|
}
|
|
8047
8229
|
const lines = [
|
|
@@ -8082,18 +8264,10 @@ function registerBrandTools(server2) {
|
|
|
8082
8264
|
},
|
|
8083
8265
|
async ({ project_id, response_format }) => {
|
|
8084
8266
|
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 });
|
|
8267
|
+
const { data: result, error: efError } = await callEdgeFunction("mcp-data", {
|
|
8268
|
+
action: "brand-profile",
|
|
8269
|
+
...projectId ? { projectId } : {}
|
|
8270
|
+
});
|
|
8097
8271
|
if (efError || result && !result.success) {
|
|
8098
8272
|
return {
|
|
8099
8273
|
content: [
|
|
@@ -8113,20 +8287,24 @@ function registerBrandTools(server2) {
|
|
|
8113
8287
|
]
|
|
8114
8288
|
};
|
|
8115
8289
|
}
|
|
8290
|
+
const structuredContent = asEnvelope5(data);
|
|
8116
8291
|
if ((response_format || "text") === "json") {
|
|
8117
8292
|
return {
|
|
8118
|
-
|
|
8293
|
+
structuredContent,
|
|
8294
|
+
content: [{ type: "text", text: JSON.stringify(structuredContent, null, 2) }]
|
|
8119
8295
|
};
|
|
8120
8296
|
}
|
|
8297
|
+
const brandContext = data.brand_context;
|
|
8121
8298
|
const lines = [
|
|
8122
8299
|
`Active Brand Profile`,
|
|
8123
|
-
`Project: ${projectId}`,
|
|
8124
|
-
`Brand Name: ${data.brand_name ||
|
|
8300
|
+
`Project: ${data.project_id || projectId || "default"}`,
|
|
8301
|
+
`Brand Name: ${data.brand_name || brandContext?.name || "N/A"}`,
|
|
8125
8302
|
`Version: ${data.version ?? "N/A"}`,
|
|
8126
8303
|
`Updated: ${data.updated_at || "N/A"}`,
|
|
8127
8304
|
`Extraction Method: ${data.extraction_method || "manual"}`
|
|
8128
8305
|
];
|
|
8129
8306
|
return {
|
|
8307
|
+
structuredContent,
|
|
8130
8308
|
content: [{ type: "text", text: lines.join("\n") }]
|
|
8131
8309
|
};
|
|
8132
8310
|
}
|
|
@@ -8201,7 +8379,7 @@ function registerBrandTools(server2) {
|
|
|
8201
8379
|
};
|
|
8202
8380
|
if ((response_format || "text") === "json") {
|
|
8203
8381
|
return {
|
|
8204
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
8382
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope5(payload), null, 2) }]
|
|
8205
8383
|
};
|
|
8206
8384
|
}
|
|
8207
8385
|
return {
|
|
@@ -8291,7 +8469,7 @@ Version: ${payload.version ?? "N/A"}`
|
|
|
8291
8469
|
};
|
|
8292
8470
|
if ((response_format || "text") === "json") {
|
|
8293
8471
|
return {
|
|
8294
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
8472
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope5(payload), null, 2) }],
|
|
8295
8473
|
isError: false
|
|
8296
8474
|
};
|
|
8297
8475
|
}
|
|
@@ -9000,7 +9178,7 @@ var PLATFORM_ENUM = [
|
|
|
9000
9178
|
"bluesky"
|
|
9001
9179
|
];
|
|
9002
9180
|
var DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
9003
|
-
function
|
|
9181
|
+
function asEnvelope6(data) {
|
|
9004
9182
|
return {
|
|
9005
9183
|
_meta: {
|
|
9006
9184
|
version: MCP_VERSION,
|
|
@@ -9046,19 +9224,17 @@ function registerInsightsTools(server2) {
|
|
|
9046
9224
|
}
|
|
9047
9225
|
if (rows.length === 0) {
|
|
9048
9226
|
if (format === "json") {
|
|
9227
|
+
const structuredContent2 = asEnvelope6({
|
|
9228
|
+
insights: [],
|
|
9229
|
+
days: lookbackDays,
|
|
9230
|
+
insightType: insight_type ?? null
|
|
9231
|
+
});
|
|
9049
9232
|
return {
|
|
9233
|
+
structuredContent: structuredContent2,
|
|
9050
9234
|
content: [
|
|
9051
9235
|
{
|
|
9052
9236
|
type: "text",
|
|
9053
|
-
text: JSON.stringify(
|
|
9054
|
-
asEnvelope5({
|
|
9055
|
-
insights: [],
|
|
9056
|
-
days: lookbackDays,
|
|
9057
|
-
insightType: insight_type ?? null
|
|
9058
|
-
}),
|
|
9059
|
-
null,
|
|
9060
|
-
2
|
|
9061
|
-
)
|
|
9237
|
+
text: JSON.stringify(structuredContent2, null, 2)
|
|
9062
9238
|
}
|
|
9063
9239
|
]
|
|
9064
9240
|
};
|
|
@@ -9073,20 +9249,18 @@ function registerInsightsTools(server2) {
|
|
|
9073
9249
|
};
|
|
9074
9250
|
}
|
|
9075
9251
|
const insights = rows;
|
|
9252
|
+
const structuredContent = asEnvelope6({
|
|
9253
|
+
insights,
|
|
9254
|
+
days: lookbackDays,
|
|
9255
|
+
insightType: insight_type ?? null
|
|
9256
|
+
});
|
|
9076
9257
|
if (format === "json") {
|
|
9077
9258
|
return {
|
|
9259
|
+
structuredContent,
|
|
9078
9260
|
content: [
|
|
9079
9261
|
{
|
|
9080
9262
|
type: "text",
|
|
9081
|
-
text: JSON.stringify(
|
|
9082
|
-
asEnvelope5({
|
|
9083
|
-
insights,
|
|
9084
|
-
days: lookbackDays,
|
|
9085
|
-
insightType: insight_type ?? null
|
|
9086
|
-
}),
|
|
9087
|
-
null,
|
|
9088
|
-
2
|
|
9089
|
-
)
|
|
9263
|
+
text: JSON.stringify(structuredContent, null, 2)
|
|
9090
9264
|
}
|
|
9091
9265
|
]
|
|
9092
9266
|
};
|
|
@@ -9111,6 +9285,7 @@ function registerInsightsTools(server2) {
|
|
|
9111
9285
|
lines.push(line);
|
|
9112
9286
|
}
|
|
9113
9287
|
return {
|
|
9288
|
+
structuredContent,
|
|
9114
9289
|
content: [{ type: "text", text: lines.join("\n") }]
|
|
9115
9290
|
};
|
|
9116
9291
|
}
|
|
@@ -9204,7 +9379,7 @@ function registerInsightsTools(server2) {
|
|
|
9204
9379
|
{
|
|
9205
9380
|
type: "text",
|
|
9206
9381
|
text: JSON.stringify(
|
|
9207
|
-
|
|
9382
|
+
asEnvelope6({
|
|
9208
9383
|
platform: platform3 ?? null,
|
|
9209
9384
|
days: lookbackDays,
|
|
9210
9385
|
recordsAnalyzed: rows.length,
|
|
@@ -9243,7 +9418,7 @@ function registerInsightsTools(server2) {
|
|
|
9243
9418
|
init_edge_function();
|
|
9244
9419
|
init_version();
|
|
9245
9420
|
import { z as z10 } from "zod";
|
|
9246
|
-
function
|
|
9421
|
+
function asEnvelope7(data) {
|
|
9247
9422
|
return {
|
|
9248
9423
|
_meta: {
|
|
9249
9424
|
version: MCP_VERSION,
|
|
@@ -9298,7 +9473,7 @@ function registerYouTubeAnalyticsTools(server2) {
|
|
|
9298
9473
|
{
|
|
9299
9474
|
type: "text",
|
|
9300
9475
|
text: JSON.stringify(
|
|
9301
|
-
|
|
9476
|
+
asEnvelope7({ action, startDate: start_date, endDate: end_date, analytics: a }),
|
|
9302
9477
|
null,
|
|
9303
9478
|
2
|
|
9304
9479
|
)
|
|
@@ -9335,7 +9510,7 @@ function registerYouTubeAnalyticsTools(server2) {
|
|
|
9335
9510
|
{
|
|
9336
9511
|
type: "text",
|
|
9337
9512
|
text: JSON.stringify(
|
|
9338
|
-
|
|
9513
|
+
asEnvelope7({
|
|
9339
9514
|
action,
|
|
9340
9515
|
startDate: start_date,
|
|
9341
9516
|
endDate: end_date,
|
|
@@ -9364,7 +9539,7 @@ function registerYouTubeAnalyticsTools(server2) {
|
|
|
9364
9539
|
{
|
|
9365
9540
|
type: "text",
|
|
9366
9541
|
text: JSON.stringify(
|
|
9367
|
-
|
|
9542
|
+
asEnvelope7({
|
|
9368
9543
|
action,
|
|
9369
9544
|
videoId: video_id,
|
|
9370
9545
|
startDate: start_date,
|
|
@@ -9404,7 +9579,7 @@ function registerYouTubeAnalyticsTools(server2) {
|
|
|
9404
9579
|
{
|
|
9405
9580
|
type: "text",
|
|
9406
9581
|
text: JSON.stringify(
|
|
9407
|
-
|
|
9582
|
+
asEnvelope7({
|
|
9408
9583
|
action,
|
|
9409
9584
|
startDate: start_date,
|
|
9410
9585
|
endDate: end_date,
|
|
@@ -9430,7 +9605,7 @@ function registerYouTubeAnalyticsTools(server2) {
|
|
|
9430
9605
|
}
|
|
9431
9606
|
if (format === "json") {
|
|
9432
9607
|
return {
|
|
9433
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
9608
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope7(result), null, 2) }]
|
|
9434
9609
|
};
|
|
9435
9610
|
}
|
|
9436
9611
|
return {
|
|
@@ -9445,7 +9620,7 @@ init_edge_function();
|
|
|
9445
9620
|
import { z as z11 } from "zod";
|
|
9446
9621
|
init_supabase();
|
|
9447
9622
|
init_version();
|
|
9448
|
-
function
|
|
9623
|
+
function asEnvelope8(data) {
|
|
9449
9624
|
return {
|
|
9450
9625
|
_meta: {
|
|
9451
9626
|
version: MCP_VERSION,
|
|
@@ -9490,7 +9665,7 @@ function registerCommentsTools(server2) {
|
|
|
9490
9665
|
{
|
|
9491
9666
|
type: "text",
|
|
9492
9667
|
text: JSON.stringify(
|
|
9493
|
-
|
|
9668
|
+
asEnvelope8({ comments, nextPageToken: result.nextPageToken ?? null }),
|
|
9494
9669
|
null,
|
|
9495
9670
|
2
|
|
9496
9671
|
)
|
|
@@ -9560,7 +9735,7 @@ function registerCommentsTools(server2) {
|
|
|
9560
9735
|
const result = data;
|
|
9561
9736
|
if (format === "json") {
|
|
9562
9737
|
return {
|
|
9563
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
9738
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope8(result), null, 2) }]
|
|
9564
9739
|
};
|
|
9565
9740
|
}
|
|
9566
9741
|
return {
|
|
@@ -9612,7 +9787,7 @@ function registerCommentsTools(server2) {
|
|
|
9612
9787
|
const result = data;
|
|
9613
9788
|
if (format === "json") {
|
|
9614
9789
|
return {
|
|
9615
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
9790
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope8(result), null, 2) }]
|
|
9616
9791
|
};
|
|
9617
9792
|
}
|
|
9618
9793
|
return {
|
|
@@ -9667,7 +9842,7 @@ function registerCommentsTools(server2) {
|
|
|
9667
9842
|
{
|
|
9668
9843
|
type: "text",
|
|
9669
9844
|
text: JSON.stringify(
|
|
9670
|
-
|
|
9845
|
+
asEnvelope8({
|
|
9671
9846
|
success: true,
|
|
9672
9847
|
commentId: comment_id,
|
|
9673
9848
|
moderationStatus: moderation_status
|
|
@@ -9726,7 +9901,7 @@ function registerCommentsTools(server2) {
|
|
|
9726
9901
|
content: [
|
|
9727
9902
|
{
|
|
9728
9903
|
type: "text",
|
|
9729
|
-
text: JSON.stringify(
|
|
9904
|
+
text: JSON.stringify(asEnvelope8({ success: true, commentId: comment_id }), null, 2)
|
|
9730
9905
|
}
|
|
9731
9906
|
]
|
|
9732
9907
|
};
|
|
@@ -9743,7 +9918,7 @@ init_supabase();
|
|
|
9743
9918
|
init_edge_function();
|
|
9744
9919
|
init_version();
|
|
9745
9920
|
import { z as z12 } from "zod";
|
|
9746
|
-
function
|
|
9921
|
+
function asEnvelope9(data) {
|
|
9747
9922
|
return {
|
|
9748
9923
|
_meta: {
|
|
9749
9924
|
version: MCP_VERSION,
|
|
@@ -9795,7 +9970,7 @@ function registerIdeationContextTools(server2) {
|
|
|
9795
9970
|
const context = result.context;
|
|
9796
9971
|
if (format === "json") {
|
|
9797
9972
|
return {
|
|
9798
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
9973
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope9(context), null, 2) }]
|
|
9799
9974
|
};
|
|
9800
9975
|
}
|
|
9801
9976
|
const lines = [
|
|
@@ -9817,7 +9992,7 @@ function registerIdeationContextTools(server2) {
|
|
|
9817
9992
|
init_edge_function();
|
|
9818
9993
|
import { z as z13 } from "zod";
|
|
9819
9994
|
init_version();
|
|
9820
|
-
function
|
|
9995
|
+
function asEnvelope10(data) {
|
|
9821
9996
|
return {
|
|
9822
9997
|
_meta: {
|
|
9823
9998
|
version: MCP_VERSION,
|
|
@@ -9854,7 +10029,7 @@ function registerCreditsTools(server2) {
|
|
|
9854
10029
|
};
|
|
9855
10030
|
if ((response_format || "text") === "json") {
|
|
9856
10031
|
return {
|
|
9857
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
10032
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope10(payload), null, 2) }]
|
|
9858
10033
|
};
|
|
9859
10034
|
}
|
|
9860
10035
|
return {
|
|
@@ -9888,7 +10063,7 @@ Monthly used: ${payload.monthlyUsed}` + (payload.monthlyLimit ? ` / ${payload.mo
|
|
|
9888
10063
|
};
|
|
9889
10064
|
if ((response_format || "text") === "json") {
|
|
9890
10065
|
return {
|
|
9891
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
10066
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope10(payload), null, 2) }]
|
|
9892
10067
|
};
|
|
9893
10068
|
}
|
|
9894
10069
|
return {
|
|
@@ -9914,7 +10089,7 @@ init_supabase();
|
|
|
9914
10089
|
init_edge_function();
|
|
9915
10090
|
init_version();
|
|
9916
10091
|
import { z as z14 } from "zod";
|
|
9917
|
-
function
|
|
10092
|
+
function asEnvelope11(data) {
|
|
9918
10093
|
return {
|
|
9919
10094
|
_meta: {
|
|
9920
10095
|
version: MCP_VERSION,
|
|
@@ -9965,7 +10140,7 @@ function registerLoopSummaryTools(server2) {
|
|
|
9965
10140
|
};
|
|
9966
10141
|
if ((response_format || "text") === "json") {
|
|
9967
10142
|
return {
|
|
9968
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
10143
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope11(payload), null, 2) }]
|
|
9969
10144
|
};
|
|
9970
10145
|
}
|
|
9971
10146
|
return {
|
|
@@ -9989,7 +10164,7 @@ Next Action: ${payload.recommendedNextAction}`
|
|
|
9989
10164
|
init_edge_function();
|
|
9990
10165
|
init_version();
|
|
9991
10166
|
import { z as z15 } from "zod";
|
|
9992
|
-
function
|
|
10167
|
+
function asEnvelope12(data) {
|
|
9993
10168
|
return {
|
|
9994
10169
|
_meta: {
|
|
9995
10170
|
version: MCP_VERSION,
|
|
@@ -10022,7 +10197,7 @@ function registerUsageTools(server2) {
|
|
|
10022
10197
|
content: [
|
|
10023
10198
|
{
|
|
10024
10199
|
type: "text",
|
|
10025
|
-
text: JSON.stringify(
|
|
10200
|
+
text: JSON.stringify(asEnvelope12({ tools: rows, totalCalls, totalCredits }), null, 2)
|
|
10026
10201
|
}
|
|
10027
10202
|
]
|
|
10028
10203
|
};
|
|
@@ -10063,7 +10238,7 @@ ${"=".repeat(40)}
|
|
|
10063
10238
|
init_edge_function();
|
|
10064
10239
|
init_version();
|
|
10065
10240
|
import { z as z16 } from "zod";
|
|
10066
|
-
function
|
|
10241
|
+
function asEnvelope13(data) {
|
|
10067
10242
|
return {
|
|
10068
10243
|
_meta: {
|
|
10069
10244
|
version: MCP_VERSION,
|
|
@@ -10103,7 +10278,7 @@ function registerAutopilotTools(server2) {
|
|
|
10103
10278
|
content: [
|
|
10104
10279
|
{
|
|
10105
10280
|
type: "text",
|
|
10106
|
-
text: JSON.stringify(
|
|
10281
|
+
text: JSON.stringify(asEnvelope13(configs), null, 2)
|
|
10107
10282
|
}
|
|
10108
10283
|
]
|
|
10109
10284
|
};
|
|
@@ -10244,7 +10419,7 @@ Schedule: ${JSON.stringify(updated.schedule_config)}`
|
|
|
10244
10419
|
content: [
|
|
10245
10420
|
{
|
|
10246
10421
|
type: "text",
|
|
10247
|
-
text: JSON.stringify(
|
|
10422
|
+
text: JSON.stringify(asEnvelope13(statusData), null, 2)
|
|
10248
10423
|
}
|
|
10249
10424
|
]
|
|
10250
10425
|
};
|
|
@@ -10328,7 +10503,7 @@ ${"=".repeat(40)}
|
|
|
10328
10503
|
}
|
|
10329
10504
|
if (format === "json") {
|
|
10330
10505
|
return {
|
|
10331
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
10506
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope13(created), null, 2) }]
|
|
10332
10507
|
};
|
|
10333
10508
|
}
|
|
10334
10509
|
return {
|
|
@@ -10351,7 +10526,7 @@ Active: ${is_active}`
|
|
|
10351
10526
|
init_edge_function();
|
|
10352
10527
|
init_version();
|
|
10353
10528
|
import { z as z17 } from "zod";
|
|
10354
|
-
function
|
|
10529
|
+
function asEnvelope14(data) {
|
|
10355
10530
|
return {
|
|
10356
10531
|
_meta: {
|
|
10357
10532
|
version: MCP_VERSION,
|
|
@@ -10400,7 +10575,7 @@ function registerRecipeTools(server2) {
|
|
|
10400
10575
|
content: [
|
|
10401
10576
|
{
|
|
10402
10577
|
type: "text",
|
|
10403
|
-
text: JSON.stringify(
|
|
10578
|
+
text: JSON.stringify(asEnvelope14(recipes))
|
|
10404
10579
|
}
|
|
10405
10580
|
]
|
|
10406
10581
|
};
|
|
@@ -10469,7 +10644,7 @@ ${lines.join("\n\n")}`
|
|
|
10469
10644
|
content: [
|
|
10470
10645
|
{
|
|
10471
10646
|
type: "text",
|
|
10472
|
-
text: JSON.stringify(
|
|
10647
|
+
text: JSON.stringify(asEnvelope14(recipe))
|
|
10473
10648
|
}
|
|
10474
10649
|
]
|
|
10475
10650
|
};
|
|
@@ -10506,7 +10681,7 @@ ${lines.join("\n\n")}`
|
|
|
10506
10681
|
"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
10682
|
{
|
|
10508
10683
|
slug: z17.string().describe('Recipe slug (e.g., "weekly-instagram-calendar")'),
|
|
10509
|
-
inputs: z17.record(z17.unknown()).describe(
|
|
10684
|
+
inputs: z17.record(z17.string(), z17.unknown()).describe(
|
|
10510
10685
|
"Input values matching the recipe input schema. Use get_recipe_details to see required inputs."
|
|
10511
10686
|
),
|
|
10512
10687
|
response_format: z17.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
@@ -10529,7 +10704,7 @@ ${lines.join("\n\n")}`
|
|
|
10529
10704
|
content: [
|
|
10530
10705
|
{
|
|
10531
10706
|
type: "text",
|
|
10532
|
-
text: JSON.stringify(
|
|
10707
|
+
text: JSON.stringify(asEnvelope14(result))
|
|
10533
10708
|
}
|
|
10534
10709
|
]
|
|
10535
10710
|
};
|
|
@@ -10585,7 +10760,7 @@ ${result?.message || "Use get_recipe_run_status to check progress."}`
|
|
|
10585
10760
|
content: [
|
|
10586
10761
|
{
|
|
10587
10762
|
type: "text",
|
|
10588
|
-
text: JSON.stringify(
|
|
10763
|
+
text: JSON.stringify(asEnvelope14(run))
|
|
10589
10764
|
}
|
|
10590
10765
|
]
|
|
10591
10766
|
};
|
|
@@ -10616,17 +10791,114 @@ ${JSON.stringify(run.outputs, null, 2)}
|
|
|
10616
10791
|
}
|
|
10617
10792
|
|
|
10618
10793
|
// src/tools/extraction.ts
|
|
10619
|
-
init_edge_function();
|
|
10620
10794
|
import { z as z18 } from "zod";
|
|
10621
10795
|
init_version();
|
|
10622
|
-
|
|
10623
|
-
|
|
10624
|
-
|
|
10625
|
-
function
|
|
10796
|
+
|
|
10797
|
+
// src/lib/urlExtraction.ts
|
|
10798
|
+
init_edge_function();
|
|
10799
|
+
function classifyYouTubeUrl(url) {
|
|
10626
10800
|
if (/youtube\.com\/watch|youtu\.be\//.test(url)) return "video";
|
|
10627
10801
|
if (/youtube\.com\/@/.test(url)) return "channel";
|
|
10628
10802
|
return false;
|
|
10629
10803
|
}
|
|
10804
|
+
function unwrapEf(result, label) {
|
|
10805
|
+
if (result.error) return { error: result.error };
|
|
10806
|
+
const env = result.data;
|
|
10807
|
+
if (!env || env.success === false) {
|
|
10808
|
+
return { error: env?.error ?? `No data returned from ${label}` };
|
|
10809
|
+
}
|
|
10810
|
+
return { payload: env.data ?? void 0 };
|
|
10811
|
+
}
|
|
10812
|
+
function segmentsToTranscript(segments) {
|
|
10813
|
+
if (!Array.isArray(segments)) return "";
|
|
10814
|
+
return segments.map((s) => (s?.text ?? "").trim()).filter(Boolean).join(" ");
|
|
10815
|
+
}
|
|
10816
|
+
async function extractUrlContent(url, opts = {}) {
|
|
10817
|
+
const extractType = opts.extractType ?? "auto";
|
|
10818
|
+
const youtubeType = classifyYouTubeUrl(url);
|
|
10819
|
+
if (youtubeType === "video") {
|
|
10820
|
+
const [txRes, metaRes] = await Promise.all([
|
|
10821
|
+
callEdgeFunction(
|
|
10822
|
+
"scrape-youtube",
|
|
10823
|
+
{ action: "transcript", videoUrl: url },
|
|
10824
|
+
{ timeoutMs: 3e4 }
|
|
10825
|
+
),
|
|
10826
|
+
callEdgeFunction(
|
|
10827
|
+
"scrape-youtube",
|
|
10828
|
+
{ action: "metadata", videoUrl: url },
|
|
10829
|
+
{ timeoutMs: 3e4 }
|
|
10830
|
+
)
|
|
10831
|
+
]);
|
|
10832
|
+
const tx = unwrapEf(txRes, "scrape-youtube transcript");
|
|
10833
|
+
const meta = unwrapEf(metaRes, "scrape-youtube metadata");
|
|
10834
|
+
if (tx.error && meta.error) {
|
|
10835
|
+
return { error: `Failed to extract YouTube video: ${meta.error ?? tx.error}` };
|
|
10836
|
+
}
|
|
10837
|
+
const m = meta.payload;
|
|
10838
|
+
return {
|
|
10839
|
+
content: {
|
|
10840
|
+
source_type: "youtube_video",
|
|
10841
|
+
url,
|
|
10842
|
+
title: m?.title ?? "",
|
|
10843
|
+
description: m?.description ?? "",
|
|
10844
|
+
transcript: segmentsToTranscript(tx.payload?.segments),
|
|
10845
|
+
video_metadata: m ? {
|
|
10846
|
+
views: typeof m.viewCount === "number" ? m.viewCount : 0,
|
|
10847
|
+
likes: typeof m.likes === "number" ? m.likes : 0,
|
|
10848
|
+
duration: typeof m.duration === "number" ? m.duration : 0,
|
|
10849
|
+
tags: Array.isArray(m.tags) ? m.tags : [],
|
|
10850
|
+
channel_name: m.channelName ?? ""
|
|
10851
|
+
} : void 0
|
|
10852
|
+
}
|
|
10853
|
+
};
|
|
10854
|
+
}
|
|
10855
|
+
if (youtubeType === "channel") {
|
|
10856
|
+
const res2 = await callEdgeFunction(
|
|
10857
|
+
"scrape-youtube",
|
|
10858
|
+
{ action: "channel_videos", videoUrl: url },
|
|
10859
|
+
{ timeoutMs: 3e4 }
|
|
10860
|
+
);
|
|
10861
|
+
const ch = unwrapEf(res2, "scrape-youtube channel");
|
|
10862
|
+
if (ch.error) return { error: `Failed to extract YouTube channel: ${ch.error}` };
|
|
10863
|
+
const videos = ch.payload?.videos ?? [];
|
|
10864
|
+
return {
|
|
10865
|
+
content: {
|
|
10866
|
+
source_type: "youtube_channel",
|
|
10867
|
+
url,
|
|
10868
|
+
title: url,
|
|
10869
|
+
description: videos.length ? `${videos.length} recent videos:
|
|
10870
|
+
${videos.map((v) => `- ${v.title ?? "Untitled"}`).join("\n")}` : "No videos found for this channel."
|
|
10871
|
+
}
|
|
10872
|
+
};
|
|
10873
|
+
}
|
|
10874
|
+
const res = await callEdgeFunction(
|
|
10875
|
+
"fetch-url-content",
|
|
10876
|
+
{ url, extractType: extractType === "auto" ? "product" : extractType },
|
|
10877
|
+
{ timeoutMs: 3e4 }
|
|
10878
|
+
);
|
|
10879
|
+
const result = unwrapEf(res, "fetch-url-content");
|
|
10880
|
+
if (result.error || !result.payload) {
|
|
10881
|
+
return { error: `Failed to extract URL content: ${result.error ?? "No data returned"}` };
|
|
10882
|
+
}
|
|
10883
|
+
const info = result.payload;
|
|
10884
|
+
return {
|
|
10885
|
+
content: {
|
|
10886
|
+
source_type: extractType === "product" ? "product" : "article",
|
|
10887
|
+
url,
|
|
10888
|
+
title: info.name ?? "",
|
|
10889
|
+
description: info.description ?? "",
|
|
10890
|
+
features: info.features,
|
|
10891
|
+
benefits: info.benefits,
|
|
10892
|
+
usp: info.usp,
|
|
10893
|
+
suggested_hooks: info.suggestedHookAngles
|
|
10894
|
+
}
|
|
10895
|
+
};
|
|
10896
|
+
}
|
|
10897
|
+
|
|
10898
|
+
// src/tools/extraction.ts
|
|
10899
|
+
function asEnvelope15(data) {
|
|
10900
|
+
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
10901
|
+
}
|
|
10630
10902
|
function formatExtractedContentAsText(content) {
|
|
10631
10903
|
const lines = [];
|
|
10632
10904
|
lines.push(`Source: ${content.source_type} (${content.url})`);
|
|
@@ -10671,15 +10943,15 @@ ${content.suggested_hooks.map((h) => ` - ${h}`).join("\n")}`
|
|
|
10671
10943
|
function registerExtractionTools(server2) {
|
|
10672
10944
|
server2.tool(
|
|
10673
10945
|
"extract_url_content",
|
|
10674
|
-
"Extract text content from any URL \u2014 YouTube video
|
|
10946
|
+
"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
10947
|
{
|
|
10676
10948
|
url: z18.string().url().describe("URL to extract content from"),
|
|
10677
|
-
extract_type: z18.enum(["auto", "transcript", "article", "product"]).default("auto").describe(
|
|
10678
|
-
|
|
10679
|
-
|
|
10949
|
+
extract_type: z18.enum(["auto", "transcript", "article", "product"]).default("auto").describe(
|
|
10950
|
+
"auto = product-style extraction; transcript = YouTube (auto-detected by URL); article = blog/news main text + key points; product = e-commerce features/benefits/USP."
|
|
10951
|
+
),
|
|
10680
10952
|
response_format: z18.enum(["text", "json"]).default("text")
|
|
10681
10953
|
},
|
|
10682
|
-
async ({ url, extract_type,
|
|
10954
|
+
async ({ url, extract_type, response_format }) => {
|
|
10683
10955
|
const ssrfCheck = await validateUrlForSSRF(url);
|
|
10684
10956
|
if (!ssrfCheck.isValid) {
|
|
10685
10957
|
return {
|
|
@@ -10687,106 +10959,20 @@ function registerExtractionTools(server2) {
|
|
|
10687
10959
|
isError: true
|
|
10688
10960
|
};
|
|
10689
10961
|
}
|
|
10690
|
-
const youtubeType = isYouTubeUrl(url);
|
|
10691
10962
|
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
|
|
10963
|
+
const { content: extracted, error } = await extractUrlContent(url, {
|
|
10964
|
+
extractType: extract_type
|
|
10965
|
+
});
|
|
10966
|
+
if (error || !extracted) {
|
|
10967
|
+
return {
|
|
10968
|
+
content: [{ type: "text", text: error ?? "No data returned" }],
|
|
10969
|
+
isError: true
|
|
10784
10970
|
};
|
|
10785
10971
|
}
|
|
10786
10972
|
if (response_format === "json") {
|
|
10787
10973
|
return {
|
|
10788
10974
|
content: [
|
|
10789
|
-
{ type: "text", text: JSON.stringify(
|
|
10975
|
+
{ type: "text", text: JSON.stringify(asEnvelope15(extracted), null, 2) }
|
|
10790
10976
|
],
|
|
10791
10977
|
isError: false
|
|
10792
10978
|
};
|
|
@@ -10810,7 +10996,7 @@ function registerExtractionTools(server2) {
|
|
|
10810
10996
|
init_quality();
|
|
10811
10997
|
init_version();
|
|
10812
10998
|
import { z as z19 } from "zod";
|
|
10813
|
-
function
|
|
10999
|
+
function asEnvelope16(data) {
|
|
10814
11000
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
10815
11001
|
}
|
|
10816
11002
|
function registerQualityTools(server2) {
|
|
@@ -10863,7 +11049,7 @@ function registerQualityTools(server2) {
|
|
|
10863
11049
|
});
|
|
10864
11050
|
if (response_format === "json") {
|
|
10865
11051
|
return {
|
|
10866
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
11052
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope16(result), null, 2) }],
|
|
10867
11053
|
isError: false
|
|
10868
11054
|
};
|
|
10869
11055
|
}
|
|
@@ -10938,7 +11124,7 @@ function registerQualityTools(server2) {
|
|
|
10938
11124
|
content: [
|
|
10939
11125
|
{
|
|
10940
11126
|
type: "text",
|
|
10941
|
-
text: JSON.stringify(
|
|
11127
|
+
text: JSON.stringify(asEnvelope16({ posts: postsWithQuality, summary }), null, 2)
|
|
10942
11128
|
}
|
|
10943
11129
|
],
|
|
10944
11130
|
isError: false
|
|
@@ -11273,7 +11459,7 @@ function buildCorrectiveHint(overflowIssues, spellingIssues) {
|
|
|
11273
11459
|
|
|
11274
11460
|
// src/tools/visualQuality.ts
|
|
11275
11461
|
init_version();
|
|
11276
|
-
function
|
|
11462
|
+
function asEnvelope17(data) {
|
|
11277
11463
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
11278
11464
|
}
|
|
11279
11465
|
var VALID_STYLES = ["dark-cinematic", "clean-editorial", "bold-authority"];
|
|
@@ -11398,7 +11584,7 @@ function registerVisualQualityTools(server2) {
|
|
|
11398
11584
|
{
|
|
11399
11585
|
type: "text",
|
|
11400
11586
|
text: JSON.stringify(
|
|
11401
|
-
|
|
11587
|
+
asEnvelope17({ ...result, correctiveHint: hint || null }),
|
|
11402
11588
|
null,
|
|
11403
11589
|
2
|
|
11404
11590
|
)
|
|
@@ -11468,7 +11654,7 @@ function registerVisualQualityTools(server2) {
|
|
|
11468
11654
|
return { content: [{ type: "text", text: lines.join("\n") }], isError: false };
|
|
11469
11655
|
}
|
|
11470
11656
|
return {
|
|
11471
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
11657
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope17(data), null, 2) }],
|
|
11472
11658
|
isError: false
|
|
11473
11659
|
};
|
|
11474
11660
|
}
|
|
@@ -11506,7 +11692,7 @@ function extractJsonArray(text) {
|
|
|
11506
11692
|
function toRecord(value) {
|
|
11507
11693
|
return value && typeof value === "object" ? value : void 0;
|
|
11508
11694
|
}
|
|
11509
|
-
function
|
|
11695
|
+
function asEnvelope18(data) {
|
|
11510
11696
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
11511
11697
|
}
|
|
11512
11698
|
function tomorrowIsoDate() {
|
|
@@ -11519,9 +11705,6 @@ function addDaysToIsoDate(dateStr, days) {
|
|
|
11519
11705
|
d.setDate(d.getDate() + days);
|
|
11520
11706
|
return d.toISOString().split("T")[0];
|
|
11521
11707
|
}
|
|
11522
|
-
function isYouTubeUrl2(url) {
|
|
11523
|
-
return /youtube\.com\/watch|youtu\.be\/|youtube\.com\/@/.test(url);
|
|
11524
|
-
}
|
|
11525
11708
|
function formatPlanAsText(plan) {
|
|
11526
11709
|
const lines = [];
|
|
11527
11710
|
lines.push(`WEEKLY CONTENT PLAN: "${plan.topic}"`);
|
|
@@ -11619,20 +11802,22 @@ function registerPlanningTools(server2) {
|
|
|
11619
11802
|
let sourceContext = "";
|
|
11620
11803
|
if (source_url) {
|
|
11621
11804
|
try {
|
|
11622
|
-
const
|
|
11623
|
-
|
|
11624
|
-
|
|
11625
|
-
|
|
11626
|
-
|
|
11627
|
-
|
|
11628
|
-
|
|
11629
|
-
|
|
11630
|
-
|
|
11631
|
-
|
|
11632
|
-
|
|
11633
|
-
|
|
11634
|
-
|
|
11635
|
-
|
|
11805
|
+
const ssrf = await validateUrlForSSRF(source_url);
|
|
11806
|
+
if (ssrf.isValid) {
|
|
11807
|
+
const { content } = await extractUrlContent(source_url);
|
|
11808
|
+
if (content) {
|
|
11809
|
+
const parts = [
|
|
11810
|
+
content.title,
|
|
11811
|
+
content.description,
|
|
11812
|
+
content.transcript ? content.transcript.slice(0, 2e3) : "",
|
|
11813
|
+
content.features?.length ? `Features:
|
|
11814
|
+
${content.features.join("\n")}` : "",
|
|
11815
|
+
content.benefits?.length ? `Benefits:
|
|
11816
|
+
${content.benefits.join("\n")}` : "",
|
|
11817
|
+
content.usp ?? ""
|
|
11818
|
+
].filter(Boolean);
|
|
11819
|
+
sourceContext = parts.join("\n\n");
|
|
11820
|
+
}
|
|
11636
11821
|
}
|
|
11637
11822
|
} catch {
|
|
11638
11823
|
}
|
|
@@ -11838,13 +12023,16 @@ ${rawText.slice(0, 1e3)}`
|
|
|
11838
12023
|
};
|
|
11839
12024
|
}
|
|
11840
12025
|
}
|
|
12026
|
+
const structuredContent = asEnvelope18(plan);
|
|
11841
12027
|
if (response_format === "json") {
|
|
11842
12028
|
return {
|
|
11843
|
-
|
|
12029
|
+
structuredContent,
|
|
12030
|
+
content: [{ type: "text", text: JSON.stringify(structuredContent, null, 2) }],
|
|
11844
12031
|
isError: false
|
|
11845
12032
|
};
|
|
11846
12033
|
}
|
|
11847
12034
|
return {
|
|
12035
|
+
structuredContent,
|
|
11848
12036
|
content: [{ type: "text", text: formatPlanAsText(plan) }],
|
|
11849
12037
|
isError: false
|
|
11850
12038
|
};
|
|
@@ -11915,7 +12103,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
11915
12103
|
};
|
|
11916
12104
|
if (response_format === "json") {
|
|
11917
12105
|
return {
|
|
11918
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
12106
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope18(result), null, 2) }],
|
|
11919
12107
|
isError: false
|
|
11920
12108
|
};
|
|
11921
12109
|
}
|
|
@@ -11974,7 +12162,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
11974
12162
|
};
|
|
11975
12163
|
if (response_format === "json") {
|
|
11976
12164
|
return {
|
|
11977
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
12165
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope18(payload), null, 2) }],
|
|
11978
12166
|
isError: false
|
|
11979
12167
|
};
|
|
11980
12168
|
}
|
|
@@ -12046,7 +12234,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
12046
12234
|
};
|
|
12047
12235
|
if (response_format === "json") {
|
|
12048
12236
|
return {
|
|
12049
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
12237
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope18(payload), null, 2) }],
|
|
12050
12238
|
isError: false
|
|
12051
12239
|
};
|
|
12052
12240
|
}
|
|
@@ -12099,7 +12287,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
12099
12287
|
};
|
|
12100
12288
|
if (response_format === "json") {
|
|
12101
12289
|
return {
|
|
12102
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
12290
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope18(payload), null, 2) }],
|
|
12103
12291
|
isError: false
|
|
12104
12292
|
};
|
|
12105
12293
|
}
|
|
@@ -12121,7 +12309,7 @@ init_edge_function();
|
|
|
12121
12309
|
init_supabase();
|
|
12122
12310
|
init_version();
|
|
12123
12311
|
import { z as z22 } from "zod";
|
|
12124
|
-
function
|
|
12312
|
+
function asEnvelope19(data) {
|
|
12125
12313
|
return {
|
|
12126
12314
|
_meta: {
|
|
12127
12315
|
version: MCP_VERSION,
|
|
@@ -12196,7 +12384,7 @@ function registerPlanApprovalTools(server2) {
|
|
|
12196
12384
|
};
|
|
12197
12385
|
if ((response_format || "text") === "json") {
|
|
12198
12386
|
return {
|
|
12199
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
12387
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope19(payload), null, 2) }],
|
|
12200
12388
|
isError: false
|
|
12201
12389
|
};
|
|
12202
12390
|
}
|
|
@@ -12248,7 +12436,7 @@ function registerPlanApprovalTools(server2) {
|
|
|
12248
12436
|
};
|
|
12249
12437
|
if ((response_format || "text") === "json") {
|
|
12250
12438
|
return {
|
|
12251
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
12439
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope19(payload), null, 2) }],
|
|
12252
12440
|
isError: false
|
|
12253
12441
|
};
|
|
12254
12442
|
}
|
|
@@ -12326,7 +12514,7 @@ function registerPlanApprovalTools(server2) {
|
|
|
12326
12514
|
}
|
|
12327
12515
|
if ((response_format || "text") === "json") {
|
|
12328
12516
|
return {
|
|
12329
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
12517
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope19(data), null, 2) }],
|
|
12330
12518
|
isError: false
|
|
12331
12519
|
};
|
|
12332
12520
|
}
|
|
@@ -12346,7 +12534,194 @@ function registerPlanApprovalTools(server2) {
|
|
|
12346
12534
|
// src/tools/discovery.ts
|
|
12347
12535
|
init_tool_catalog();
|
|
12348
12536
|
import { z as z23 } from "zod";
|
|
12537
|
+
init_supabase();
|
|
12538
|
+
init_request_context();
|
|
12539
|
+
var KNOWLEDGE_BASE_URL = "https://socialneuron.com/for-developers";
|
|
12540
|
+
var KNOWLEDGE_SEARCH_LIMIT = 10;
|
|
12541
|
+
var SearchOutputSchema = {
|
|
12542
|
+
results: z23.array(
|
|
12543
|
+
z23.object({
|
|
12544
|
+
id: z23.string(),
|
|
12545
|
+
title: z23.string(),
|
|
12546
|
+
url: z23.string().url()
|
|
12547
|
+
})
|
|
12548
|
+
)
|
|
12549
|
+
};
|
|
12550
|
+
var FetchOutputSchema = {
|
|
12551
|
+
id: z23.string(),
|
|
12552
|
+
title: z23.string(),
|
|
12553
|
+
text: z23.string(),
|
|
12554
|
+
url: z23.string().url(),
|
|
12555
|
+
metadata: z23.record(z23.string(), z23.string()).optional()
|
|
12556
|
+
};
|
|
12557
|
+
var STATIC_KNOWLEDGE_DOCUMENTS = [
|
|
12558
|
+
{
|
|
12559
|
+
id: "overview",
|
|
12560
|
+
title: "Social Neuron MCP Overview",
|
|
12561
|
+
url: `${KNOWLEDGE_BASE_URL}#mcp`,
|
|
12562
|
+
text: [
|
|
12563
|
+
"Social Neuron exposes an MCP server for creating, scheduling, and optimizing social content.",
|
|
12564
|
+
"Use the hosted streamable HTTP endpoint at https://mcp.socialneuron.com/mcp for ChatGPT, Claude, and other remote MCP clients.",
|
|
12565
|
+
"The npm package provides stdio transport for local tools and Codex-style workflows."
|
|
12566
|
+
].join("\n"),
|
|
12567
|
+
metadata: { source: "public-developer-docs", category: "overview" }
|
|
12568
|
+
},
|
|
12569
|
+
{
|
|
12570
|
+
id: "integrations",
|
|
12571
|
+
title: "Supported Social Integrations",
|
|
12572
|
+
url: "https://socialneuron.com/integrations",
|
|
12573
|
+
text: [
|
|
12574
|
+
"Social Neuron tracks platform availability on the integrations page.",
|
|
12575
|
+
"YouTube, TikTok, Instagram, LinkedIn, X, and Facebook are live for supported posting workflows.",
|
|
12576
|
+
"Threads and Bluesky are supported surfaces where live availability depends on the current integration status."
|
|
12577
|
+
].join("\n"),
|
|
12578
|
+
metadata: { source: "public-integrations-page", category: "integrations" }
|
|
12579
|
+
},
|
|
12580
|
+
{
|
|
12581
|
+
id: "chatgpt-connector",
|
|
12582
|
+
title: "ChatGPT Connector Setup",
|
|
12583
|
+
url: `${KNOWLEDGE_BASE_URL}#chatgpt`,
|
|
12584
|
+
text: [
|
|
12585
|
+
"In ChatGPT Developer Mode, create an MCP app using https://mcp.socialneuron.com/mcp as the MCP URL.",
|
|
12586
|
+
"The connector uses OAuth for account linking and tool scopes for read, write, distribution, analytics, comments, and autopilot access.",
|
|
12587
|
+
"Publishing tools should be treated as externally visible actions and require the distribute scope."
|
|
12588
|
+
].join("\n"),
|
|
12589
|
+
metadata: { source: "public-developer-docs", category: "chatgpt" }
|
|
12590
|
+
},
|
|
12591
|
+
{
|
|
12592
|
+
id: "privacy-security",
|
|
12593
|
+
title: "Connector Security and Data Minimization",
|
|
12594
|
+
url: `${KNOWLEDGE_BASE_URL}#security`,
|
|
12595
|
+
text: [
|
|
12596
|
+
"Social Neuron MCP tools enforce OAuth or API-key scopes before tool execution.",
|
|
12597
|
+
"Read-only discovery tools expose public product and tool metadata, not private account content.",
|
|
12598
|
+
"User-owned content and analytics require authenticated scopes and organization or project membership checks in backend functions."
|
|
12599
|
+
].join("\n"),
|
|
12600
|
+
metadata: { source: "public-developer-docs", category: "security" }
|
|
12601
|
+
}
|
|
12602
|
+
];
|
|
12603
|
+
function toolKnowledgeDocument(tool) {
|
|
12604
|
+
const lines = [
|
|
12605
|
+
`Tool: ${tool.name}`,
|
|
12606
|
+
`Description: ${tool.description}`,
|
|
12607
|
+
`Module: ${tool.module}`,
|
|
12608
|
+
`Required scope: ${tool.scope}`
|
|
12609
|
+
];
|
|
12610
|
+
if (tool.task_intent) lines.push(`Task intent: ${tool.task_intent}`);
|
|
12611
|
+
if (tool.use_when) lines.push(`Use when: ${tool.use_when}`);
|
|
12612
|
+
if (tool.avoid_when) lines.push(`Avoid when: ${tool.avoid_when}`);
|
|
12613
|
+
if (tool.next_tools?.length) lines.push(`Common next tools: ${tool.next_tools.join(", ")}`);
|
|
12614
|
+
return {
|
|
12615
|
+
id: `tool:${tool.name}`,
|
|
12616
|
+
title: `MCP tool: ${tool.name}`,
|
|
12617
|
+
url: `${KNOWLEDGE_BASE_URL}#tool-${tool.name}`,
|
|
12618
|
+
text: lines.join("\n"),
|
|
12619
|
+
metadata: {
|
|
12620
|
+
source: "mcp-tool-catalog",
|
|
12621
|
+
category: "tool",
|
|
12622
|
+
module: tool.module,
|
|
12623
|
+
scope: tool.scope
|
|
12624
|
+
}
|
|
12625
|
+
};
|
|
12626
|
+
}
|
|
12627
|
+
function getKnowledgeDocuments() {
|
|
12628
|
+
return [
|
|
12629
|
+
...STATIC_KNOWLEDGE_DOCUMENTS,
|
|
12630
|
+
...TOOL_CATALOG.filter((t) => !t.internal).map(toolKnowledgeDocument)
|
|
12631
|
+
];
|
|
12632
|
+
}
|
|
12633
|
+
function tokenize(input) {
|
|
12634
|
+
return input.toLowerCase().split(/[^a-z0-9:_-]+/).map((token) => token.trim()).filter(Boolean);
|
|
12635
|
+
}
|
|
12636
|
+
function scoreDocument(queryTokens, doc) {
|
|
12637
|
+
const title = doc.title.toLowerCase();
|
|
12638
|
+
const text = doc.text.toLowerCase();
|
|
12639
|
+
const metadata = JSON.stringify(doc.metadata ?? {}).toLowerCase();
|
|
12640
|
+
return queryTokens.reduce((score, token) => {
|
|
12641
|
+
if (doc.id.toLowerCase() === token) return score + 12;
|
|
12642
|
+
if (doc.id.toLowerCase().includes(token)) score += 8;
|
|
12643
|
+
if (title.includes(token)) score += 5;
|
|
12644
|
+
if (text.includes(token)) score += 2;
|
|
12645
|
+
if (metadata.includes(token)) score += 1;
|
|
12646
|
+
return score;
|
|
12647
|
+
}, 0);
|
|
12648
|
+
}
|
|
12649
|
+
function searchKnowledge(query) {
|
|
12650
|
+
const docs = getKnowledgeDocuments();
|
|
12651
|
+
const tokens = tokenize(query);
|
|
12652
|
+
if (tokens.length === 0) {
|
|
12653
|
+
return docs.slice(0, KNOWLEDGE_SEARCH_LIMIT);
|
|
12654
|
+
}
|
|
12655
|
+
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);
|
|
12656
|
+
}
|
|
12349
12657
|
function registerDiscoveryTools(server2) {
|
|
12658
|
+
server2.registerTool(
|
|
12659
|
+
"search",
|
|
12660
|
+
{
|
|
12661
|
+
title: "Search Social Neuron Knowledge",
|
|
12662
|
+
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.",
|
|
12663
|
+
inputSchema: {
|
|
12664
|
+
query: z23.string().describe("Search query.")
|
|
12665
|
+
},
|
|
12666
|
+
outputSchema: SearchOutputSchema,
|
|
12667
|
+
annotations: {
|
|
12668
|
+
readOnlyHint: true,
|
|
12669
|
+
destructiveHint: false,
|
|
12670
|
+
idempotentHint: true,
|
|
12671
|
+
openWorldHint: false
|
|
12672
|
+
}
|
|
12673
|
+
},
|
|
12674
|
+
async ({ query }) => {
|
|
12675
|
+
const structuredContent = {
|
|
12676
|
+
results: searchKnowledge(query).map((doc) => ({
|
|
12677
|
+
id: doc.id,
|
|
12678
|
+
title: doc.title,
|
|
12679
|
+
url: doc.url
|
|
12680
|
+
}))
|
|
12681
|
+
};
|
|
12682
|
+
return {
|
|
12683
|
+
structuredContent,
|
|
12684
|
+
content: [{ type: "text", text: JSON.stringify(structuredContent) }]
|
|
12685
|
+
};
|
|
12686
|
+
}
|
|
12687
|
+
);
|
|
12688
|
+
server2.registerTool(
|
|
12689
|
+
"fetch",
|
|
12690
|
+
{
|
|
12691
|
+
title: "Fetch Social Neuron Knowledge",
|
|
12692
|
+
description: "Fetch a public Social Neuron knowledge document by ID. Use IDs returned by the search tool.",
|
|
12693
|
+
inputSchema: {
|
|
12694
|
+
id: z23.string().describe("Document ID returned by search.")
|
|
12695
|
+
},
|
|
12696
|
+
outputSchema: FetchOutputSchema,
|
|
12697
|
+
annotations: {
|
|
12698
|
+
readOnlyHint: true,
|
|
12699
|
+
destructiveHint: false,
|
|
12700
|
+
idempotentHint: true,
|
|
12701
|
+
openWorldHint: false
|
|
12702
|
+
}
|
|
12703
|
+
},
|
|
12704
|
+
async ({ id }) => {
|
|
12705
|
+
const doc = getKnowledgeDocuments().find((candidate) => candidate.id === id);
|
|
12706
|
+
if (!doc) {
|
|
12707
|
+
return {
|
|
12708
|
+
content: [{ type: "text", text: `Document not found: ${id}` }],
|
|
12709
|
+
isError: true
|
|
12710
|
+
};
|
|
12711
|
+
}
|
|
12712
|
+
const structuredContent = {
|
|
12713
|
+
id: doc.id,
|
|
12714
|
+
title: doc.title,
|
|
12715
|
+
text: doc.text,
|
|
12716
|
+
url: doc.url,
|
|
12717
|
+
...doc.metadata ? { metadata: doc.metadata } : {}
|
|
12718
|
+
};
|
|
12719
|
+
return {
|
|
12720
|
+
structuredContent,
|
|
12721
|
+
content: [{ type: "text", text: JSON.stringify(structuredContent) }]
|
|
12722
|
+
};
|
|
12723
|
+
}
|
|
12724
|
+
);
|
|
12350
12725
|
server2.tool(
|
|
12351
12726
|
"search_tools",
|
|
12352
12727
|
'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,13 +12731,20 @@ function registerDiscoveryTools(server2) {
|
|
|
12356
12731
|
scope: z23.string().optional().describe('Filter by required scope (e.g. "mcp:read", "mcp:write")'),
|
|
12357
12732
|
detail: z23.enum(["name", "summary", "full"]).default("summary").describe(
|
|
12358
12733
|
'Detail level: "name" for just tool names, "summary" for names + descriptions, "full" for complete info including scope and module'
|
|
12734
|
+
),
|
|
12735
|
+
available_only: z23.boolean().default(false).describe(
|
|
12736
|
+
"When true, only return tools allowed by the current API key/OAuth scopes. Use this after a permission_denied error."
|
|
12359
12737
|
)
|
|
12360
12738
|
},
|
|
12361
|
-
async ({ query, module, scope, detail }) => {
|
|
12739
|
+
async ({ query, module, scope, detail, available_only }) => {
|
|
12740
|
+
const currentScopes = getRequestScopes() ?? getAuthenticatedScopes();
|
|
12741
|
+
const hasKnownScopes = currentScopes.length > 0;
|
|
12742
|
+
const isAvailable = (tool) => !hasKnownScopes || hasScope(currentScopes, tool.scope);
|
|
12362
12743
|
let results = [...TOOL_CATALOG];
|
|
12363
12744
|
if (query) {
|
|
12364
12745
|
results = searchTools(query);
|
|
12365
12746
|
}
|
|
12747
|
+
results = results.filter((t) => !t.internal);
|
|
12366
12748
|
if (module) {
|
|
12367
12749
|
const moduleTools = getToolsByModule(module);
|
|
12368
12750
|
results = results.filter((t) => moduleTools.some((mt) => mt.name === t.name));
|
|
@@ -12371,24 +12753,48 @@ function registerDiscoveryTools(server2) {
|
|
|
12371
12753
|
const scopeTools = getToolsByScope(scope);
|
|
12372
12754
|
results = results.filter((t) => scopeTools.some((st) => st.name === t.name));
|
|
12373
12755
|
}
|
|
12756
|
+
if (available_only) {
|
|
12757
|
+
results = results.filter(isAvailable);
|
|
12758
|
+
}
|
|
12759
|
+
const unavailableCount = hasKnownScopes ? results.filter((t) => !isAvailable(t)).length : 0;
|
|
12374
12760
|
let output;
|
|
12375
12761
|
switch (detail) {
|
|
12376
12762
|
case "name":
|
|
12377
12763
|
output = results.map((t) => t.name);
|
|
12378
12764
|
break;
|
|
12379
12765
|
case "summary":
|
|
12380
|
-
output = results.map((t) => ({
|
|
12766
|
+
output = results.map((t) => ({
|
|
12767
|
+
name: t.name,
|
|
12768
|
+
description: t.description,
|
|
12769
|
+
...hasKnownScopes || scope ? { required_scope: t.scope } : {},
|
|
12770
|
+
...hasKnownScopes ? { available: isAvailable(t) } : {},
|
|
12771
|
+
...t.task_intent ? { task_intent: t.task_intent } : {},
|
|
12772
|
+
...t.use_when ? { use_when: t.use_when } : {},
|
|
12773
|
+
...t.avoid_when ? { avoid_when: t.avoid_when } : {}
|
|
12774
|
+
}));
|
|
12381
12775
|
break;
|
|
12382
12776
|
case "full":
|
|
12383
12777
|
default:
|
|
12384
|
-
output = results
|
|
12778
|
+
output = results.map((tool) => ({
|
|
12779
|
+
...tool,
|
|
12780
|
+
required_scope: tool.scope,
|
|
12781
|
+
...hasKnownScopes ? { available: isAvailable(tool) } : {}
|
|
12782
|
+
}));
|
|
12385
12783
|
break;
|
|
12386
12784
|
}
|
|
12387
12785
|
return {
|
|
12388
12786
|
content: [
|
|
12389
12787
|
{
|
|
12390
12788
|
type: "text",
|
|
12391
|
-
text: JSON.stringify(
|
|
12789
|
+
text: JSON.stringify(
|
|
12790
|
+
{
|
|
12791
|
+
toolCount: results.length,
|
|
12792
|
+
...hasKnownScopes ? { scopes: { available: currentScopes, unavailable_matches: unavailableCount } } : {},
|
|
12793
|
+
tools: output
|
|
12794
|
+
},
|
|
12795
|
+
null,
|
|
12796
|
+
detail === "full" ? 2 : 0
|
|
12797
|
+
)
|
|
12392
12798
|
}
|
|
12393
12799
|
]
|
|
12394
12800
|
};
|
|
@@ -12403,7 +12809,7 @@ import { randomUUID as randomUUID3 } from "node:crypto";
|
|
|
12403
12809
|
init_supabase();
|
|
12404
12810
|
init_quality();
|
|
12405
12811
|
init_version();
|
|
12406
|
-
function
|
|
12812
|
+
function asEnvelope20(data) {
|
|
12407
12813
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
12408
12814
|
}
|
|
12409
12815
|
var PLATFORM_ENUM2 = z24.enum([
|
|
@@ -12418,6 +12824,7 @@ var PLATFORM_ENUM2 = z24.enum([
|
|
|
12418
12824
|
]);
|
|
12419
12825
|
var BASE_PLAN_CREDITS = 15;
|
|
12420
12826
|
var SOURCE_EXTRACTION_CREDITS = 5;
|
|
12827
|
+
var SCHEDULE_POST_CREDITS = 1;
|
|
12421
12828
|
function registerPipelineTools(server2) {
|
|
12422
12829
|
server2.tool(
|
|
12423
12830
|
"check_pipeline_readiness",
|
|
@@ -12447,6 +12854,7 @@ function registerPipelineTools(server2) {
|
|
|
12447
12854
|
throw new Error(readinessError ?? "No response from mcp-data");
|
|
12448
12855
|
}
|
|
12449
12856
|
const credits = readiness.credits;
|
|
12857
|
+
const isUnlimited = readiness.is_unlimited === true;
|
|
12450
12858
|
const connectedPlatforms = readiness.connected_platforms;
|
|
12451
12859
|
const missingPlatforms = readiness.missing_platforms;
|
|
12452
12860
|
const hasBrand = readiness.has_brand;
|
|
@@ -12455,7 +12863,7 @@ function registerPipelineTools(server2) {
|
|
|
12455
12863
|
const insightsFresh = readiness.insights_fresh;
|
|
12456
12864
|
const blockers = [];
|
|
12457
12865
|
const warnings = [];
|
|
12458
|
-
if (credits < estimatedCost) {
|
|
12866
|
+
if (!isUnlimited && credits < estimatedCost) {
|
|
12459
12867
|
blockers.push(`Insufficient credits: ${credits} available, ~${estimatedCost} needed`);
|
|
12460
12868
|
}
|
|
12461
12869
|
if (missingPlatforms.length > 0) {
|
|
@@ -12494,7 +12902,7 @@ function registerPipelineTools(server2) {
|
|
|
12494
12902
|
};
|
|
12495
12903
|
if (format === "json") {
|
|
12496
12904
|
return {
|
|
12497
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
12905
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope20(result), null, 2) }]
|
|
12498
12906
|
};
|
|
12499
12907
|
}
|
|
12500
12908
|
const lines = [];
|
|
@@ -12533,7 +12941,7 @@ function registerPipelineTools(server2) {
|
|
|
12533
12941
|
);
|
|
12534
12942
|
server2.tool(
|
|
12535
12943
|
"run_content_pipeline",
|
|
12536
|
-
"Run the full content pipeline: research trends \u2192 generate plan \u2192 quality check \u2192 auto-approve \u2192 schedule posts. Chains all stages in one call for maximum efficiency. Set dry_run=true to preview the plan without publishing. Check check_pipeline_readiness first to verify credits, OAuth, and brand profile are ready.",
|
|
12944
|
+
"Run the full content pipeline: research trends \u2192 generate plan \u2192 quality check \u2192 auto-approve \u2192 schedule posts. Chains all stages in one call for maximum efficiency. Set dry_run=true to preview the plan without publishing. To schedule posts, set schedule_confirmed=true after the user explicitly approves publishing. Check check_pipeline_readiness first to verify credits, OAuth, and brand profile are ready.",
|
|
12537
12945
|
{
|
|
12538
12946
|
project_id: z24.string().uuid().optional().describe("Project ID (auto-detected if omitted)"),
|
|
12539
12947
|
topic: z24.string().optional().describe("Content topic (required if no source_url)"),
|
|
@@ -12549,6 +12957,9 @@ function registerPipelineTools(server2) {
|
|
|
12549
12957
|
),
|
|
12550
12958
|
max_credits: z24.number().optional().describe("Credit budget cap"),
|
|
12551
12959
|
dry_run: z24.boolean().default(false).describe("If true, skip scheduling and return plan only"),
|
|
12960
|
+
schedule_confirmed: z24.boolean().default(false).describe(
|
|
12961
|
+
"Required to schedule posts. Set true only after explicit user confirmation to publish/schedule."
|
|
12962
|
+
),
|
|
12552
12963
|
skip_stages: z24.array(z24.enum(["research", "quality", "schedule"])).optional().describe("Stages to skip"),
|
|
12553
12964
|
response_format: z24.enum(["text", "json"]).default("json")
|
|
12554
12965
|
},
|
|
@@ -12563,6 +12974,7 @@ function registerPipelineTools(server2) {
|
|
|
12563
12974
|
auto_approve_threshold,
|
|
12564
12975
|
max_credits,
|
|
12565
12976
|
dry_run,
|
|
12977
|
+
schedule_confirmed,
|
|
12566
12978
|
skip_stages,
|
|
12567
12979
|
response_format
|
|
12568
12980
|
}) => {
|
|
@@ -12578,6 +12990,29 @@ function registerPipelineTools(server2) {
|
|
|
12578
12990
|
};
|
|
12579
12991
|
}
|
|
12580
12992
|
const skipSet = new Set(skip_stages ?? []);
|
|
12993
|
+
const schedulingRequested = !dry_run && !skipSet.has("schedule");
|
|
12994
|
+
if (schedulingRequested && !schedule_confirmed) {
|
|
12995
|
+
return {
|
|
12996
|
+
content: [
|
|
12997
|
+
{
|
|
12998
|
+
type: "text",
|
|
12999
|
+
text: 'Scheduling requires explicit confirmation. Re-run with schedule_confirmed=true after the user approves publishing, or set dry_run=true / skip_stages=["schedule"].'
|
|
13000
|
+
}
|
|
13001
|
+
],
|
|
13002
|
+
isError: true
|
|
13003
|
+
};
|
|
13004
|
+
}
|
|
13005
|
+
if (schedulingRequested && skipSet.has("quality")) {
|
|
13006
|
+
return {
|
|
13007
|
+
content: [
|
|
13008
|
+
{
|
|
13009
|
+
type: "text",
|
|
13010
|
+
text: "Scheduling cannot run when the quality stage is skipped."
|
|
13011
|
+
}
|
|
13012
|
+
],
|
|
13013
|
+
isError: true
|
|
13014
|
+
};
|
|
13015
|
+
}
|
|
12581
13016
|
try {
|
|
12582
13017
|
const resolvedProjectId = project_id ?? await getDefaultProjectId() ?? void 0;
|
|
12583
13018
|
const estimatedCost = BASE_PLAN_CREDITS + (source_url ? SOURCE_EXTRACTION_CREDITS : 0);
|
|
@@ -12591,8 +13026,9 @@ function registerPipelineTools(server2) {
|
|
|
12591
13026
|
{ timeoutMs: 1e4 }
|
|
12592
13027
|
);
|
|
12593
13028
|
const availableCredits = budgetData?.credits ?? 0;
|
|
13029
|
+
const isUnlimited = budgetData?.is_unlimited === true;
|
|
12594
13030
|
const creditLimit = max_credits ?? availableCredits;
|
|
12595
|
-
if (availableCredits < estimatedCost) {
|
|
13031
|
+
if (!isUnlimited && availableCredits < estimatedCost) {
|
|
12596
13032
|
return {
|
|
12597
13033
|
content: [
|
|
12598
13034
|
{
|
|
@@ -12619,6 +13055,7 @@ function registerPipelineTools(server2) {
|
|
|
12619
13055
|
approval_mode,
|
|
12620
13056
|
auto_approve_threshold,
|
|
12621
13057
|
dry_run,
|
|
13058
|
+
schedule_confirmed,
|
|
12622
13059
|
skip_stages: skip_stages ?? []
|
|
12623
13060
|
},
|
|
12624
13061
|
current_stage: "planning",
|
|
@@ -12684,7 +13121,9 @@ function registerPipelineTools(server2) {
|
|
|
12684
13121
|
stagesCompleted.push("planning");
|
|
12685
13122
|
const rawText = String(planData.text ?? planData.content ?? "");
|
|
12686
13123
|
const postsArray = extractJsonArray(rawText);
|
|
12687
|
-
const
|
|
13124
|
+
const requestedPlatformSet = new Set(platforms);
|
|
13125
|
+
const maxPosts = platforms.length * days * posts_per_day;
|
|
13126
|
+
const parsedPosts = (postsArray ?? []).map((p) => ({
|
|
12688
13127
|
id: String(p.id ?? randomUUID3().slice(0, 8)),
|
|
12689
13128
|
day: Number(p.day ?? 1),
|
|
12690
13129
|
date: String(p.date ?? ""),
|
|
@@ -12698,6 +13137,23 @@ function registerPipelineTools(server2) {
|
|
|
12698
13137
|
visual_direction: p.visual_direction ? String(p.visual_direction) : void 0,
|
|
12699
13138
|
media_type: p.media_type ? String(p.media_type) : void 0
|
|
12700
13139
|
}));
|
|
13140
|
+
const platformFilteredPosts = parsedPosts.filter(
|
|
13141
|
+
(post) => requestedPlatformSet.has(post.platform)
|
|
13142
|
+
);
|
|
13143
|
+
const posts = platformFilteredPosts.slice(0, maxPosts);
|
|
13144
|
+
if (parsedPosts.length > maxPosts) {
|
|
13145
|
+
errors.push({
|
|
13146
|
+
stage: "planning",
|
|
13147
|
+
message: `AI returned ${parsedPosts.length} posts; truncated to ${maxPosts}.`
|
|
13148
|
+
});
|
|
13149
|
+
}
|
|
13150
|
+
const invalidPlatformCount = parsedPosts.length - platformFilteredPosts.length;
|
|
13151
|
+
if (invalidPlatformCount > 0) {
|
|
13152
|
+
errors.push({
|
|
13153
|
+
stage: "planning",
|
|
13154
|
+
message: `Dropped ${invalidPlatformCount} post(s) with unrequested or invalid platform.`
|
|
13155
|
+
});
|
|
13156
|
+
}
|
|
12701
13157
|
let postsApproved = 0;
|
|
12702
13158
|
let postsFlagged = 0;
|
|
12703
13159
|
if (!skipSet.has("quality")) {
|
|
@@ -12818,21 +13274,27 @@ function registerPipelineTools(server2) {
|
|
|
12818
13274
|
let postsScheduled = 0;
|
|
12819
13275
|
if (!dry_run && !skipSet.has("schedule") && postsApproved > 0) {
|
|
12820
13276
|
const approvedPosts = posts.filter((p) => p.status === "approved");
|
|
13277
|
+
const scheduleBase = /* @__PURE__ */ new Date();
|
|
13278
|
+
scheduleBase.setDate(scheduleBase.getDate() + 1);
|
|
13279
|
+
const scheduleBaseMs = scheduleBase.getTime();
|
|
12821
13280
|
for (const post of approvedPosts) {
|
|
12822
13281
|
if (creditsUsed >= creditLimit) {
|
|
12823
13282
|
errors.push({ stage: "schedule", message: "Credit limit reached" });
|
|
12824
13283
|
break;
|
|
12825
13284
|
}
|
|
13285
|
+
const scheduledAt = post.schedule_at ?? new Date(scheduleBaseMs + (Math.max(1, post.day) - 1) * 864e5).toISOString();
|
|
12826
13286
|
try {
|
|
12827
13287
|
const { error: schedError } = await callEdgeFunction(
|
|
12828
13288
|
"schedule-post",
|
|
12829
13289
|
{
|
|
12830
|
-
|
|
13290
|
+
platforms: [post.platform],
|
|
12831
13291
|
caption: post.caption,
|
|
12832
13292
|
title: post.title,
|
|
12833
13293
|
hashtags: post.hashtags,
|
|
12834
|
-
|
|
12835
|
-
|
|
13294
|
+
mediaUrl: post.media_url,
|
|
13295
|
+
scheduledAt,
|
|
13296
|
+
planId,
|
|
13297
|
+
idempotencyKey: `pipeline-${planId}-${post.id}`,
|
|
12836
13298
|
...resolvedProjectId ? { projectId: resolvedProjectId, project_id: resolvedProjectId } : {}
|
|
12837
13299
|
},
|
|
12838
13300
|
{ timeoutMs: 15e3 }
|
|
@@ -12844,6 +13306,7 @@ function registerPipelineTools(server2) {
|
|
|
12844
13306
|
});
|
|
12845
13307
|
} else {
|
|
12846
13308
|
postsScheduled++;
|
|
13309
|
+
creditsUsed += SCHEDULE_POST_CREDITS;
|
|
12847
13310
|
}
|
|
12848
13311
|
} catch (schedErr) {
|
|
12849
13312
|
errors.push({
|
|
@@ -12898,7 +13361,7 @@ function registerPipelineTools(server2) {
|
|
|
12898
13361
|
if (response_format === "json") {
|
|
12899
13362
|
return {
|
|
12900
13363
|
content: [
|
|
12901
|
-
{ type: "text", text: JSON.stringify(
|
|
13364
|
+
{ type: "text", text: JSON.stringify(asEnvelope20(resultPayload), null, 2) }
|
|
12902
13365
|
]
|
|
12903
13366
|
};
|
|
12904
13367
|
}
|
|
@@ -12979,7 +13442,7 @@ function registerPipelineTools(server2) {
|
|
|
12979
13442
|
}
|
|
12980
13443
|
if (format === "json") {
|
|
12981
13444
|
return {
|
|
12982
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
13445
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope20(data), null, 2) }]
|
|
12983
13446
|
};
|
|
12984
13447
|
}
|
|
12985
13448
|
const lines = [];
|
|
@@ -13113,7 +13576,7 @@ function registerPipelineTools(server2) {
|
|
|
13113
13576
|
if (response_format === "json") {
|
|
13114
13577
|
return {
|
|
13115
13578
|
content: [
|
|
13116
|
-
{ type: "text", text: JSON.stringify(
|
|
13579
|
+
{ type: "text", text: JSON.stringify(asEnvelope20(resultPayload), null, 2) }
|
|
13117
13580
|
]
|
|
13118
13581
|
};
|
|
13119
13582
|
}
|
|
@@ -13163,7 +13626,7 @@ function buildPlanPrompt(topic, platforms, days, postsPerDay, sourceUrl) {
|
|
|
13163
13626
|
init_edge_function();
|
|
13164
13627
|
import { z as z25 } from "zod";
|
|
13165
13628
|
init_version();
|
|
13166
|
-
function
|
|
13629
|
+
function asEnvelope21(data) {
|
|
13167
13630
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
13168
13631
|
}
|
|
13169
13632
|
function registerSuggestTools(server2) {
|
|
@@ -13271,7 +13734,7 @@ function registerSuggestTools(server2) {
|
|
|
13271
13734
|
if (format === "json") {
|
|
13272
13735
|
return {
|
|
13273
13736
|
content: [
|
|
13274
|
-
{ type: "text", text: JSON.stringify(
|
|
13737
|
+
{ type: "text", text: JSON.stringify(asEnvelope21(resultPayload), null, 2) }
|
|
13275
13738
|
]
|
|
13276
13739
|
};
|
|
13277
13740
|
}
|
|
@@ -13408,7 +13871,7 @@ function detectAnomalies(currentData, previousData, sensitivity = "medium", aver
|
|
|
13408
13871
|
|
|
13409
13872
|
// src/tools/digest.ts
|
|
13410
13873
|
init_version();
|
|
13411
|
-
function
|
|
13874
|
+
function asEnvelope22(data) {
|
|
13412
13875
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
13413
13876
|
}
|
|
13414
13877
|
var PLATFORM_ENUM3 = z26.enum([
|
|
@@ -13577,7 +14040,7 @@ function registerDigestTools(server2) {
|
|
|
13577
14040
|
};
|
|
13578
14041
|
if (format === "json") {
|
|
13579
14042
|
return {
|
|
13580
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
14043
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope22(digest), null, 2) }]
|
|
13581
14044
|
};
|
|
13582
14045
|
}
|
|
13583
14046
|
const lines = [];
|
|
@@ -13676,7 +14139,7 @@ Best: ${best.id.slice(0, 8)}... (${best.platform}) \u2014 ${best.views.toLocaleS
|
|
|
13676
14139
|
if (format === "json") {
|
|
13677
14140
|
return {
|
|
13678
14141
|
content: [
|
|
13679
|
-
{ type: "text", text: JSON.stringify(
|
|
14142
|
+
{ type: "text", text: JSON.stringify(asEnvelope22(resultPayload), null, 2) }
|
|
13680
14143
|
]
|
|
13681
14144
|
};
|
|
13682
14145
|
}
|
|
@@ -13725,18 +14188,129 @@ var WEIGHTS = {
|
|
|
13725
14188
|
structuralPatterns: 0.05
|
|
13726
14189
|
};
|
|
13727
14190
|
function norm(content) {
|
|
13728
|
-
return content.toLowerCase().replace(/[^a-z0-9\s]/g, " ");
|
|
14191
|
+
return content.toLowerCase().replace(/[^a-z0-9\s]/g, " ").replace(/\s+/g, " ").trim();
|
|
13729
14192
|
}
|
|
13730
14193
|
function findMatches(content, terms) {
|
|
13731
14194
|
const n = norm(content);
|
|
13732
|
-
return terms.filter((t) =>
|
|
14195
|
+
return terms.filter((t) => {
|
|
14196
|
+
const nt = norm(t);
|
|
14197
|
+
return nt.length > 0 && n.includes(nt);
|
|
14198
|
+
});
|
|
13733
14199
|
}
|
|
13734
14200
|
function findMissing(content, terms) {
|
|
13735
14201
|
const n = norm(content);
|
|
13736
|
-
return terms.filter((t) =>
|
|
14202
|
+
return terms.filter((t) => {
|
|
14203
|
+
const nt = norm(t);
|
|
14204
|
+
return nt.length > 0 && !n.includes(nt);
|
|
14205
|
+
});
|
|
14206
|
+
}
|
|
14207
|
+
function asStringArray(v) {
|
|
14208
|
+
if (Array.isArray(v)) return v.map((x) => String(x ?? "").trim()).filter(Boolean);
|
|
14209
|
+
if (typeof v === "string") {
|
|
14210
|
+
return v.split(/[,;\n]/).map((s) => s.trim()).filter(Boolean);
|
|
14211
|
+
}
|
|
14212
|
+
return [];
|
|
14213
|
+
}
|
|
14214
|
+
function extractAtomicTerms(entries) {
|
|
14215
|
+
const out = [];
|
|
14216
|
+
for (const raw of entries) {
|
|
14217
|
+
const e = (raw || "").trim();
|
|
14218
|
+
if (!e) continue;
|
|
14219
|
+
const quoted = e.match(/['"“”‘’]([^'"“”‘’]{2,40})['"“”‘’]/g);
|
|
14220
|
+
if (quoted) {
|
|
14221
|
+
for (const q of quoted) out.push(q.replace(/['"“”‘’]/g, "").trim());
|
|
14222
|
+
continue;
|
|
14223
|
+
}
|
|
14224
|
+
const words = e.split(/\s+/);
|
|
14225
|
+
const isGuidancePhrase = words.length > 2 && /\b(tone|voice|style|approach|language)\b/i.test(e);
|
|
14226
|
+
if (!e.includes("(") && words.length <= 3 && !isGuidancePhrase) out.push(e);
|
|
14227
|
+
}
|
|
14228
|
+
return Array.from(new Set(out.map((t) => t.trim()).filter(Boolean)));
|
|
14229
|
+
}
|
|
14230
|
+
function hasField(obj, key) {
|
|
14231
|
+
return Object.prototype.hasOwnProperty.call(obj, key) && obj[key] != null;
|
|
14232
|
+
}
|
|
14233
|
+
function unionTerms(existing, incoming) {
|
|
14234
|
+
const out = [];
|
|
14235
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14236
|
+
for (const t of [...existing, ...incoming]) {
|
|
14237
|
+
if (t && !seen.has(t)) {
|
|
14238
|
+
seen.add(t);
|
|
14239
|
+
out.push(t);
|
|
14240
|
+
}
|
|
14241
|
+
}
|
|
14242
|
+
return out;
|
|
14243
|
+
}
|
|
14244
|
+
function mergeVoiceVocabulary(resolved, raw) {
|
|
14245
|
+
const voice = raw.voice;
|
|
14246
|
+
if (!voice || typeof voice !== "object") return resolved;
|
|
14247
|
+
const vp = asStringArray(voice.preferredTerms);
|
|
14248
|
+
const vb = asStringArray(voice.bannedTerms);
|
|
14249
|
+
if (!vp.length && !vb.length) return resolved;
|
|
14250
|
+
const existing = resolved.vocabularyRules ?? {};
|
|
14251
|
+
return {
|
|
14252
|
+
...resolved,
|
|
14253
|
+
vocabularyRules: {
|
|
14254
|
+
preferredTerms: unionTerms(asStringArray(existing.preferredTerms), vp),
|
|
14255
|
+
bannedTerms: unionTerms(asStringArray(existing.bannedTerms), vb)
|
|
14256
|
+
}
|
|
14257
|
+
};
|
|
14258
|
+
}
|
|
14259
|
+
function resolveBrandProfile(raw) {
|
|
14260
|
+
if (!raw || typeof raw !== "object") return {};
|
|
14261
|
+
const r = raw;
|
|
14262
|
+
const name = typeof r.name === "string" ? r.name : void 0;
|
|
14263
|
+
const voiceProfile = r.voiceProfile;
|
|
14264
|
+
const vocabularyRules = r.vocabularyRules;
|
|
14265
|
+
if (voiceProfile && vocabularyRules) {
|
|
14266
|
+
return mergeVoiceVocabulary(raw, r);
|
|
14267
|
+
}
|
|
14268
|
+
if (voiceProfile) {
|
|
14269
|
+
return mergeVoiceVocabulary(
|
|
14270
|
+
{
|
|
14271
|
+
name,
|
|
14272
|
+
voiceProfile,
|
|
14273
|
+
vocabularyRules: {
|
|
14274
|
+
bannedTerms: extractAtomicTerms(asStringArray(voiceProfile.avoidPatterns)),
|
|
14275
|
+
preferredTerms: asStringArray(voiceProfile.languagePatterns)
|
|
14276
|
+
},
|
|
14277
|
+
targetAudience: r.targetAudience,
|
|
14278
|
+
writingStyleRules: r.writingStyleRules
|
|
14279
|
+
},
|
|
14280
|
+
r
|
|
14281
|
+
);
|
|
14282
|
+
}
|
|
14283
|
+
const isFlat = hasField(r, "voiceTone") || hasField(r, "voiceTags") || hasField(r, "discouragedTerms") || hasField(r, "preferredTerms");
|
|
14284
|
+
if (isFlat) {
|
|
14285
|
+
const ta = r.targetAudience;
|
|
14286
|
+
const psy = ta?.psychographics;
|
|
14287
|
+
const painPoints = ta ? asStringArray(psy?.painPoints ?? ta.painPoints) : [];
|
|
14288
|
+
const interests = ta ? asStringArray(psy?.interests ?? ta.interests) : [];
|
|
14289
|
+
return mergeVoiceVocabulary(
|
|
14290
|
+
{
|
|
14291
|
+
name,
|
|
14292
|
+
voiceProfile: {
|
|
14293
|
+
tone: [...asStringArray(r.voiceTags), ...asStringArray(r.voiceTone)],
|
|
14294
|
+
style: asStringArray(r.voiceStyle),
|
|
14295
|
+
languagePatterns: [],
|
|
14296
|
+
avoidPatterns: []
|
|
14297
|
+
},
|
|
14298
|
+
vocabularyRules: {
|
|
14299
|
+
bannedTerms: asStringArray(r.discouragedTerms),
|
|
14300
|
+
preferredTerms: asStringArray(r.preferredTerms)
|
|
14301
|
+
},
|
|
14302
|
+
targetAudience: painPoints.length || interests.length ? { psychographics: { painPoints, interests } } : void 0
|
|
14303
|
+
},
|
|
14304
|
+
r
|
|
14305
|
+
);
|
|
14306
|
+
}
|
|
14307
|
+
const stub = { name };
|
|
14308
|
+
if (vocabularyRules)
|
|
14309
|
+
stub.vocabularyRules = vocabularyRules;
|
|
14310
|
+
return mergeVoiceVocabulary(stub, r);
|
|
13737
14311
|
}
|
|
13738
14312
|
var FABRICATION_PATTERNS = [
|
|
13739
|
-
{ regex: /\b\d+[,.]?\
|
|
14313
|
+
{ regex: /\b\d+(?:[,.]\d+)?\s*(?:%|percent\b)/gi, label: "unverified percentage" },
|
|
13740
14314
|
{ regex: /\b(award[- ]?winning|best[- ]selling|#\s*1)\b/gi, label: "unverified ranking" },
|
|
13741
14315
|
{
|
|
13742
14316
|
regex: /\b(guaranteed|proven to|studies show|scientifically proven)\b/gi,
|
|
@@ -13952,24 +14526,25 @@ function computeBrandConsistency(content, profile, threshold = 60) {
|
|
|
13952
14526
|
fabricationWarnings: []
|
|
13953
14527
|
};
|
|
13954
14528
|
}
|
|
14529
|
+
const resolved = resolveBrandProfile(profile);
|
|
13955
14530
|
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,
|
|
14531
|
+
toneAlignment: scoreTone(content, resolved),
|
|
14532
|
+
vocabularyAdherence: scoreVocab(content, resolved),
|
|
14533
|
+
avoidCompliance: scoreAvoid(content, resolved),
|
|
14534
|
+
audienceRelevance: scoreAudience(content, resolved),
|
|
14535
|
+
brandMentions: scoreBrand(content, resolved),
|
|
14536
|
+
structuralPatterns: scoreStructure(content, resolved)
|
|
13962
14537
|
};
|
|
13963
14538
|
const overall = Math.round(
|
|
13964
14539
|
Object.values(dimensions).reduce((sum, d) => sum + d.score * d.weight, 0)
|
|
13965
14540
|
);
|
|
13966
14541
|
const preferred = [
|
|
13967
|
-
...
|
|
13968
|
-
...
|
|
14542
|
+
...resolved.voiceProfile?.languagePatterns || [],
|
|
14543
|
+
...resolved.vocabularyRules?.preferredTerms || []
|
|
13969
14544
|
];
|
|
13970
14545
|
const banned = [
|
|
13971
|
-
...
|
|
13972
|
-
...
|
|
14546
|
+
...resolved.voiceProfile?.avoidPatterns || [],
|
|
14547
|
+
...resolved.vocabularyRules?.bannedTerms || []
|
|
13973
14548
|
];
|
|
13974
14549
|
const fabrications = detectFabricationPatterns(content);
|
|
13975
14550
|
return {
|
|
@@ -14000,11 +14575,11 @@ function hexToLab(hex) {
|
|
|
14000
14575
|
const lb = srgbToLinear(b);
|
|
14001
14576
|
const x = lr * 0.4124564 + lg * 0.3575761 + lb * 0.1804375;
|
|
14002
14577
|
const y = lr * 0.2126729 + lg * 0.7151522 + lb * 0.072175;
|
|
14003
|
-
const
|
|
14578
|
+
const z39 = lr * 0.0193339 + lg * 0.119192 + lb * 0.9503041;
|
|
14004
14579
|
const f = (t) => t > 8856e-6 ? Math.cbrt(t) : 7.787 * t + 16 / 116;
|
|
14005
14580
|
const fx = f(x / 0.95047);
|
|
14006
14581
|
const fy = f(y / 1);
|
|
14007
|
-
const fz = f(
|
|
14582
|
+
const fz = f(z39 / 1.08883);
|
|
14008
14583
|
return { L: 116 * fy - 16, a: 500 * (fx - fy), b: 200 * (fy - fz) };
|
|
14009
14584
|
}
|
|
14010
14585
|
function deltaE2000(lab1, lab2) {
|
|
@@ -14156,7 +14731,7 @@ function exportFigma(palette, typography) {
|
|
|
14156
14731
|
}
|
|
14157
14732
|
|
|
14158
14733
|
// src/tools/brandRuntime.ts
|
|
14159
|
-
function
|
|
14734
|
+
function asEnvelope23(data) {
|
|
14160
14735
|
return {
|
|
14161
14736
|
_meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() },
|
|
14162
14737
|
data
|
|
@@ -14237,8 +14812,9 @@ function registerBrandRuntimeTools(server2) {
|
|
|
14237
14812
|
pagesScraped: meta.pagesScraped || 0
|
|
14238
14813
|
}
|
|
14239
14814
|
};
|
|
14240
|
-
const envelope =
|
|
14815
|
+
const envelope = asEnvelope23(runtime);
|
|
14241
14816
|
return {
|
|
14817
|
+
structuredContent: envelope,
|
|
14242
14818
|
content: [{ type: "text", text: JSON.stringify(envelope, null, 2) }]
|
|
14243
14819
|
};
|
|
14244
14820
|
}
|
|
@@ -14379,7 +14955,7 @@ function registerBrandRuntimeTools(server2) {
|
|
|
14379
14955
|
}
|
|
14380
14956
|
const profile = row.profile_data;
|
|
14381
14957
|
const checkResult = computeBrandConsistency(content, profile);
|
|
14382
|
-
const envelope =
|
|
14958
|
+
const envelope = asEnvelope23(checkResult);
|
|
14383
14959
|
return {
|
|
14384
14960
|
content: [{ type: "text", text: JSON.stringify(envelope, null, 2) }]
|
|
14385
14961
|
};
|
|
@@ -14413,7 +14989,7 @@ function registerBrandRuntimeTools(server2) {
|
|
|
14413
14989
|
content_colors,
|
|
14414
14990
|
threshold ?? 10
|
|
14415
14991
|
);
|
|
14416
|
-
const envelope =
|
|
14992
|
+
const envelope = asEnvelope23(auditResult);
|
|
14417
14993
|
return {
|
|
14418
14994
|
content: [{ type: "text", text: JSON.stringify(envelope, null, 2) }]
|
|
14419
14995
|
};
|
|
@@ -14448,7 +15024,7 @@ function registerBrandRuntimeTools(server2) {
|
|
|
14448
15024
|
row.profile_data.typography,
|
|
14449
15025
|
format
|
|
14450
15026
|
);
|
|
14451
|
-
const envelope =
|
|
15027
|
+
const envelope = asEnvelope23({ format, tokens: output });
|
|
14452
15028
|
return {
|
|
14453
15029
|
content: [{ type: "text", text: JSON.stringify(envelope, null, 2) }]
|
|
14454
15030
|
};
|
|
@@ -14460,58 +15036,7 @@ function registerBrandRuntimeTools(server2) {
|
|
|
14460
15036
|
init_edge_function();
|
|
14461
15037
|
import { z as z28 } from "zod";
|
|
14462
15038
|
init_supabase();
|
|
14463
|
-
init_request_context();
|
|
14464
15039
|
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
15040
|
var IMAGE_CREDIT_ESTIMATES2 = {
|
|
14516
15041
|
midjourney: 20,
|
|
14517
15042
|
"nano-banana": 15,
|
|
@@ -14520,7 +15045,7 @@ var IMAGE_CREDIT_ESTIMATES2 = {
|
|
|
14520
15045
|
"flux-max": 50,
|
|
14521
15046
|
"gpt4o-image": 40,
|
|
14522
15047
|
imagen4: 35,
|
|
14523
|
-
"imagen4-fast":
|
|
15048
|
+
"imagen4-fast": 35,
|
|
14524
15049
|
seedream: 20
|
|
14525
15050
|
};
|
|
14526
15051
|
async function fetchBrandVisualContext(projectId) {
|
|
@@ -14644,14 +15169,14 @@ function registerCarouselTools(server2) {
|
|
|
14644
15169
|
const carouselTextCost = 10 + slideCount * 2;
|
|
14645
15170
|
const perImageCost = IMAGE_CREDIT_ESTIMATES2[image_model] ?? 30;
|
|
14646
15171
|
const totalEstimatedCost = carouselTextCost + slideCount * perImageCost;
|
|
14647
|
-
const budgetCheck =
|
|
15172
|
+
const budgetCheck = checkCreditBudget(totalEstimatedCost);
|
|
14648
15173
|
if (!budgetCheck.ok) {
|
|
14649
15174
|
return {
|
|
14650
15175
|
content: [{ type: "text", text: budgetCheck.message }],
|
|
14651
15176
|
isError: true
|
|
14652
15177
|
};
|
|
14653
15178
|
}
|
|
14654
|
-
const assetBudget =
|
|
15179
|
+
const assetBudget = checkAssetBudget(slideCount);
|
|
14655
15180
|
if (!assetBudget.ok) {
|
|
14656
15181
|
return {
|
|
14657
15182
|
content: [{ type: "text", text: assetBudget.message }],
|
|
@@ -14698,8 +15223,15 @@ function registerCarouselTools(server2) {
|
|
|
14698
15223
|
};
|
|
14699
15224
|
}
|
|
14700
15225
|
const carousel = carouselData.carousel;
|
|
15226
|
+
const imageAssetBudget = checkAssetBudget(carousel.slides.length);
|
|
15227
|
+
if (!imageAssetBudget.ok) {
|
|
15228
|
+
return {
|
|
15229
|
+
content: [{ type: "text", text: imageAssetBudget.message }],
|
|
15230
|
+
isError: true
|
|
15231
|
+
};
|
|
15232
|
+
}
|
|
14701
15233
|
const textCredits = carousel.credits?.used ?? carouselTextCost;
|
|
14702
|
-
|
|
15234
|
+
addCreditsUsed(textCredits);
|
|
14703
15235
|
const imageJobs = await Promise.all(
|
|
14704
15236
|
carousel.slides.map(async (slide) => {
|
|
14705
15237
|
const promptParts = [];
|
|
@@ -14729,8 +15261,8 @@ function registerCarouselTools(server2) {
|
|
|
14729
15261
|
}
|
|
14730
15262
|
const jobId = data.asyncJobId ?? data.taskId ?? null;
|
|
14731
15263
|
if (jobId) {
|
|
14732
|
-
|
|
14733
|
-
|
|
15264
|
+
addCreditsUsed(perImageCost);
|
|
15265
|
+
addAssetsGenerated(1);
|
|
14734
15266
|
}
|
|
14735
15267
|
return {
|
|
14736
15268
|
slideNumber: slide.slideNumber,
|
|
@@ -14838,19 +15370,19 @@ function registerCarouselTools(server2) {
|
|
|
14838
15370
|
init_edge_function();
|
|
14839
15371
|
import { z as z29 } from "zod";
|
|
14840
15372
|
init_version();
|
|
14841
|
-
function
|
|
15373
|
+
function asEnvelope24(data) {
|
|
14842
15374
|
return { _meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, data };
|
|
14843
15375
|
}
|
|
14844
15376
|
function registerNicheResearchTools(server2) {
|
|
14845
15377
|
server2.tool(
|
|
14846
15378
|
"find_winning_content",
|
|
14847
|
-
"Find QA-gated high-performing short-form videos in the project's niche. Returns extracted hook patterns, content structures, and pre-compiled replication prompts you can use to generate new content on a different topic.
|
|
15379
|
+
"Find QA-gated high-performing short-form videos in the project's niche. Returns extracted hook patterns, content structures, and pre-compiled replication prompts you can use to generate new content on a different topic. Only QA-gated winners with a populated replication prompt are returned.",
|
|
14848
15380
|
{
|
|
14849
15381
|
project_id: z29.string().uuid().optional().describe("Project ID (auto-detected if omitted)"),
|
|
14850
15382
|
platform: z29.enum(["tiktok", "instagram", "youtube", "reddit", "twitter"]).optional().describe("Filter to one platform. Omit for all platforms."),
|
|
14851
15383
|
days: z29.number().int().min(1).max(365).default(30).describe("Window: only return winners scanned within this many days."),
|
|
14852
15384
|
limit: z29.number().int().min(1).max(50).default(10).describe("Number of winners to return (1-50)."),
|
|
14853
|
-
min_qa_score: z29.number().min(0).max(1).default(0.5).describe("Minimum QA score (0..1). Default 0.5
|
|
15385
|
+
min_qa_score: z29.number().min(0).max(1).default(0.5).describe("Minimum QA score (0..1). Default 0.5."),
|
|
14854
15386
|
response_format: z29.enum(["text", "json"]).optional()
|
|
14855
15387
|
},
|
|
14856
15388
|
async ({ project_id, platform: platform3, days, limit, min_qa_score, response_format }) => {
|
|
@@ -14872,7 +15404,7 @@ function registerNicheResearchTools(server2) {
|
|
|
14872
15404
|
{
|
|
14873
15405
|
type: "text",
|
|
14874
15406
|
text: JSON.stringify(
|
|
14875
|
-
|
|
15407
|
+
asEnvelope24({
|
|
14876
15408
|
winners,
|
|
14877
15409
|
count: winners.length,
|
|
14878
15410
|
filters: result?.filters ?? {}
|
|
@@ -15171,6 +15703,16 @@ import { z as z31 } from "zod";
|
|
|
15171
15703
|
import fs from "node:fs/promises";
|
|
15172
15704
|
import path from "node:path";
|
|
15173
15705
|
var CALENDAR_URI = "ui://content-calendar/mcp-app.html";
|
|
15706
|
+
var RecentPostOutputSchema = z31.object({
|
|
15707
|
+
id: z31.string(),
|
|
15708
|
+
platform: z31.string(),
|
|
15709
|
+
status: z31.string(),
|
|
15710
|
+
title: z31.string().nullable(),
|
|
15711
|
+
external_post_id: z31.string().nullable(),
|
|
15712
|
+
published_at: z31.string().nullable(),
|
|
15713
|
+
scheduled_at: z31.string().nullable(),
|
|
15714
|
+
created_at: z31.string()
|
|
15715
|
+
});
|
|
15174
15716
|
function startOfCurrentWeekMonday() {
|
|
15175
15717
|
const now = /* @__PURE__ */ new Date();
|
|
15176
15718
|
const day = now.getDay();
|
|
@@ -15190,6 +15732,11 @@ function registerContentCalendarApp(server2) {
|
|
|
15190
15732
|
"ISO date for the week start (YYYY-MM-DD); defaults to the current week's Monday."
|
|
15191
15733
|
)
|
|
15192
15734
|
},
|
|
15735
|
+
outputSchema: {
|
|
15736
|
+
start_date: z31.string(),
|
|
15737
|
+
posts: z31.array(RecentPostOutputSchema),
|
|
15738
|
+
scopes: z31.array(z31.string())
|
|
15739
|
+
},
|
|
15193
15740
|
_meta: {
|
|
15194
15741
|
ui: {
|
|
15195
15742
|
resourceUri: CALENDAR_URI,
|
|
@@ -15228,11 +15775,17 @@ function registerContentCalendarApp(server2) {
|
|
|
15228
15775
|
if (!ts) return false;
|
|
15229
15776
|
return ts.split("T")[0] >= fromDate;
|
|
15230
15777
|
});
|
|
15778
|
+
const structuredContent = {
|
|
15779
|
+
start_date: fromDate,
|
|
15780
|
+
posts,
|
|
15781
|
+
scopes: userScopes
|
|
15782
|
+
};
|
|
15231
15783
|
return {
|
|
15784
|
+
structuredContent,
|
|
15232
15785
|
content: [
|
|
15233
15786
|
{
|
|
15234
15787
|
type: "text",
|
|
15235
|
-
text:
|
|
15788
|
+
text: `Loaded ${posts.length} calendar post${posts.length === 1 ? "" : "s"} from ${fromDate}.`
|
|
15236
15789
|
}
|
|
15237
15790
|
]
|
|
15238
15791
|
};
|
|
@@ -15491,7 +16044,7 @@ var writeReflectionSchema = {
|
|
|
15491
16044
|
prm_score_ids: z33.array(z33.string()).optional().describe("PRM score IDs that informed this reflection."),
|
|
15492
16045
|
handoff_ids: z33.array(z33.string()).optional().describe("Handoff event IDs that triggered this reflection.")
|
|
15493
16046
|
}).strict().describe(
|
|
15494
|
-
"Source evidence for this reflection. Only these four keys are accepted
|
|
16047
|
+
"Source evidence for this reflection. Only these four keys are accepted \u2014 unknown keys are rejected to prevent spurious provenance claims."
|
|
15495
16048
|
),
|
|
15496
16049
|
brand_id: z33.string().describe("Brand profile UUID this reflection belongs to."),
|
|
15497
16050
|
pipeline_id: z33.string().optional().describe("Optional: pipeline run UUID."),
|
|
@@ -15509,7 +16062,7 @@ var readReflectionSchema = {
|
|
|
15509
16062
|
var recordOutcomeSchema = {
|
|
15510
16063
|
decision_event_id: z33.string().describe("UUID of the decision_events row to record an outcome for."),
|
|
15511
16064
|
horizon: z33.enum(["1h", "6h", "24h"]).describe(
|
|
15512
|
-
"Observation horizon. Only horizon=24h with a non-null reward triggers a
|
|
16065
|
+
"Observation horizon. Only horizon=24h with a non-null reward triggers a learning-loop update; 1h/6h are stored but inert for learning."
|
|
15513
16066
|
),
|
|
15514
16067
|
reward: z33.number().min(0).max(1).describe("Normalised reward signal in [0, 1]. Higher = better outcome."),
|
|
15515
16068
|
outcome_metrics: z33.record(z33.string(), z33.number()).optional().describe(
|
|
@@ -15519,7 +16072,7 @@ var recordOutcomeSchema = {
|
|
|
15519
16072
|
function registerHarnessTools(server2, _ctx) {
|
|
15520
16073
|
server2.tool(
|
|
15521
16074
|
"write_agent_reflection",
|
|
15522
|
-
"Persist a verbal reflection for an agent loop. Provenance keys are restricted
|
|
16075
|
+
"Persist a verbal reflection for an agent loop. Provenance keys are restricted to an allowlist: only content_history_id, outcome_event_id, prm_score_ids, and handoff_ids are accepted \u2014 unknown keys are rejected at the input layer. Requires mcp:write scope. Returns the created reflection UUID on success.",
|
|
15523
16076
|
writeReflectionSchema,
|
|
15524
16077
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
15525
16078
|
async (args) => {
|
|
@@ -15547,7 +16100,7 @@ function registerHarnessTools(server2, _ctx) {
|
|
|
15547
16100
|
);
|
|
15548
16101
|
server2.tool(
|
|
15549
16102
|
"record_outcome",
|
|
15550
|
-
"Record an outcome for a published decision event. Idempotent on (decision_event_id, horizon) \u2014 safe to call multiple times. Returns idempotent:true when the row already existed (UPDATE), idempotent:false on fresh INSERT. Note: only horizon=24h with reward != null triggers a
|
|
16103
|
+
"Record an outcome for a published decision event. Idempotent on (decision_event_id, horizon) \u2014 safe to call multiple times. Returns idempotent:true when the row already existed (UPDATE), idempotent:false on fresh INSERT. Note: only horizon=24h with reward != null triggers a learning-loop update; 1h/6h outcomes are stored but are inert for the learning loop. Requires mcp:write scope.",
|
|
15551
16104
|
recordOutcomeSchema,
|
|
15552
16105
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
15553
16106
|
async (args) => {
|
|
@@ -15611,7 +16164,7 @@ function registerHarnessTools(server2, _ctx) {
|
|
|
15611
16164
|
init_edge_function();
|
|
15612
16165
|
init_version();
|
|
15613
16166
|
import { z as z34 } from "zod";
|
|
15614
|
-
function
|
|
16167
|
+
function asEnvelope25(data) {
|
|
15615
16168
|
return {
|
|
15616
16169
|
_meta: {
|
|
15617
16170
|
version: MCP_VERSION,
|
|
@@ -15633,13 +16186,13 @@ var PLATFORM = z34.enum([
|
|
|
15633
16186
|
function registerHermesTools(server2) {
|
|
15634
16187
|
server2.tool(
|
|
15635
16188
|
"save_draft_to_library",
|
|
15636
|
-
"Save a draft post to the SN content library. Use when an autonomous agent
|
|
16189
|
+
"Save a draft post to the SN content library. Use when an autonomous agent wants to persist a draft for review before publishing. Lands in the content library with status='draft'. The draft can then be approved/edited in the SN UI.",
|
|
15637
16190
|
{
|
|
15638
16191
|
platform: PLATFORM.describe("Target platform for the draft."),
|
|
15639
16192
|
copy: z34.string().min(1).max(8e3).describe("The draft post body."),
|
|
15640
16193
|
project_id: z34.string().optional().describe("SN project UUID. Optional but recommended."),
|
|
15641
16194
|
media_url: z34.string().url().optional().describe("Optional cover/media URL (R2 signed URL)."),
|
|
15642
|
-
hermes_run_id: z34.string().optional().describe("
|
|
16195
|
+
hermes_run_id: z34.string().optional().describe("Agent run id, for traceability."),
|
|
15643
16196
|
source_intel_ids: z34.array(z34.string()).optional().describe("Optional list of intel_signals.id rows that informed this draft."),
|
|
15644
16197
|
response_format: z34.enum(["text", "json"]).optional()
|
|
15645
16198
|
},
|
|
@@ -15662,7 +16215,7 @@ function registerHermesTools(server2) {
|
|
|
15662
16215
|
const payload = { content_id: data?.content_id ?? null };
|
|
15663
16216
|
if (format === "json") {
|
|
15664
16217
|
return {
|
|
15665
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
16218
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope25(payload), null, 2) }]
|
|
15666
16219
|
};
|
|
15667
16220
|
}
|
|
15668
16221
|
return {
|
|
@@ -15706,7 +16259,7 @@ function registerHermesTools(server2) {
|
|
|
15706
16259
|
};
|
|
15707
16260
|
if (response_format === "json") {
|
|
15708
16261
|
return {
|
|
15709
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
16262
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope25(payload), null, 2) }]
|
|
15710
16263
|
};
|
|
15711
16264
|
}
|
|
15712
16265
|
return {
|
|
@@ -15721,11 +16274,11 @@ function registerHermesTools(server2) {
|
|
|
15721
16274
|
);
|
|
15722
16275
|
server2.tool(
|
|
15723
16276
|
"record_observation",
|
|
15724
|
-
'Record an agent observation ("
|
|
16277
|
+
'Record an agent observation (e.g. "topic X engagement up 23% this week"). Surfaces in the analytics Playbook. Use for weekly reflection digests and mid-campaign pulse summaries.',
|
|
15725
16278
|
{
|
|
15726
|
-
summary: z34.string().min(1).max(2e3).describe("One-paragraph summary,
|
|
16279
|
+
summary: z34.string().min(1).max(2e3).describe("One-paragraph summary, shown to the account owner."),
|
|
15727
16280
|
deltas: z34.record(z34.string(), z34.union([z34.number(), z34.string(), z34.boolean()])).optional().describe("Optional structured key/value payload (e.g. {topic_x_er_pct: 23})."),
|
|
15728
|
-
run_id: z34.string().optional().describe("
|
|
16281
|
+
run_id: z34.string().optional().describe("Agent run id for traceability."),
|
|
15729
16282
|
response_format: z34.enum(["text", "json"]).optional()
|
|
15730
16283
|
},
|
|
15731
16284
|
async ({ summary, deltas, run_id, response_format }) => {
|
|
@@ -15736,7 +16289,7 @@ function registerHermesTools(server2) {
|
|
|
15736
16289
|
const payload = { observation_id: data?.observation_id ?? null };
|
|
15737
16290
|
if (response_format === "json") {
|
|
15738
16291
|
return {
|
|
15739
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
16292
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope25(payload), null, 2) }]
|
|
15740
16293
|
};
|
|
15741
16294
|
}
|
|
15742
16295
|
return {
|
|
@@ -15780,7 +16333,7 @@ function registerHermesTools(server2) {
|
|
|
15780
16333
|
};
|
|
15781
16334
|
if (response_format === "json") {
|
|
15782
16335
|
return {
|
|
15783
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
16336
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope25(payload), null, 2) }]
|
|
15784
16337
|
};
|
|
15785
16338
|
}
|
|
15786
16339
|
return {
|
|
@@ -15795,7 +16348,7 @@ function registerHermesTools(server2) {
|
|
|
15795
16348
|
);
|
|
15796
16349
|
server2.tool(
|
|
15797
16350
|
"record_campaign_spend",
|
|
15798
|
-
"Log a campaign cost line. Use when
|
|
16351
|
+
"Log a campaign cost line. Use when an autonomous agent incurs spend that should be attributed to a campaign \u2014 drafts, renders, paid amp. Read aggregate via get_active_campaigns.",
|
|
15799
16352
|
{
|
|
15800
16353
|
campaign_id: z34.string().min(1).max(200).describe("Campaign identifier (slug)."),
|
|
15801
16354
|
category: z34.enum([
|
|
@@ -15824,7 +16377,7 @@ function registerHermesTools(server2) {
|
|
|
15824
16377
|
};
|
|
15825
16378
|
if (response_format === "json") {
|
|
15826
16379
|
return {
|
|
15827
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
16380
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope25(payload), null, 2) }]
|
|
15828
16381
|
};
|
|
15829
16382
|
}
|
|
15830
16383
|
return {
|
|
@@ -15839,7 +16392,7 @@ function registerHermesTools(server2) {
|
|
|
15839
16392
|
);
|
|
15840
16393
|
server2.tool(
|
|
15841
16394
|
"get_active_campaigns",
|
|
15842
|
-
"List currently-running campaigns with thesis, budget, hero format, and current spend.
|
|
16395
|
+
"List currently-running campaigns with thesis, budget, hero format, and current spend. Use to bias drafts toward active campaign themes.",
|
|
15843
16396
|
{
|
|
15844
16397
|
response_format: z34.enum(["text", "json"]).optional()
|
|
15845
16398
|
},
|
|
@@ -15854,7 +16407,7 @@ function registerHermesTools(server2) {
|
|
|
15854
16407
|
content: [
|
|
15855
16408
|
{
|
|
15856
16409
|
type: "text",
|
|
15857
|
-
text: JSON.stringify(
|
|
16410
|
+
text: JSON.stringify(asEnvelope25({ campaigns }), null, 2)
|
|
15858
16411
|
}
|
|
15859
16412
|
]
|
|
15860
16413
|
};
|
|
@@ -15885,6 +16438,343 @@ ${"=".repeat(40)}
|
|
|
15885
16438
|
);
|
|
15886
16439
|
}
|
|
15887
16440
|
|
|
16441
|
+
// src/tools/skills.ts
|
|
16442
|
+
init_version();
|
|
16443
|
+
import { z as z35 } from "zod";
|
|
16444
|
+
|
|
16445
|
+
// src/lib/skills-manifest.ts
|
|
16446
|
+
var SKILLS_MANIFEST = [
|
|
16447
|
+
{
|
|
16448
|
+
id: "skill-brand-locked-viral-hook-reel",
|
|
16449
|
+
name: "Brand-locked viral hook reel",
|
|
16450
|
+
studio: "video",
|
|
16451
|
+
category: "hook",
|
|
16452
|
+
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.",
|
|
16453
|
+
hookFormula: "Pattern interrupt in first 0.5s + escalating stakes every 2s + brand-locked voice + cloned-from-winning-past-post structure.",
|
|
16454
|
+
estimatedCredits: 580,
|
|
16455
|
+
estimatedSeconds: 158,
|
|
16456
|
+
featured: true,
|
|
16457
|
+
stepCount: 9,
|
|
16458
|
+
inspiredBy: ["MrBeast", "Alex Hormozi"]
|
|
16459
|
+
}
|
|
16460
|
+
];
|
|
16461
|
+
function getSkill(id) {
|
|
16462
|
+
return SKILLS_MANIFEST.find((s) => s.id === id);
|
|
16463
|
+
}
|
|
16464
|
+
function listSkills(opts) {
|
|
16465
|
+
let entries = SKILLS_MANIFEST;
|
|
16466
|
+
if (opts?.studio) entries = entries.filter((s) => s.studio === opts.studio);
|
|
16467
|
+
if (opts?.featuredOnly) entries = entries.filter((s) => s.featured);
|
|
16468
|
+
return entries;
|
|
16469
|
+
}
|
|
16470
|
+
|
|
16471
|
+
// src/tools/skills.ts
|
|
16472
|
+
function asEnvelope26(data) {
|
|
16473
|
+
return {
|
|
16474
|
+
_meta: {
|
|
16475
|
+
version: MCP_VERSION,
|
|
16476
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
16477
|
+
},
|
|
16478
|
+
data
|
|
16479
|
+
};
|
|
16480
|
+
}
|
|
16481
|
+
var STUDIO_VALUES = ["video", "avatar", "carousel", "voice", "caption", "edit"];
|
|
16482
|
+
function renderSkillSummary(skill) {
|
|
16483
|
+
const lines = [];
|
|
16484
|
+
lines.push(`${skill.name} (${skill.id})`);
|
|
16485
|
+
lines.push(` Studio: ${skill.studio} \xB7 Category: ${skill.category}`);
|
|
16486
|
+
if (skill.featured) lines.push(" \u2B50 Featured");
|
|
16487
|
+
lines.push(` ${skill.shortDescription}`);
|
|
16488
|
+
lines.push(` Hook: ${skill.hookFormula}`);
|
|
16489
|
+
lines.push(
|
|
16490
|
+
` Cost: ~${skill.estimatedCredits} credits \xB7 ~${skill.estimatedSeconds}s \xB7 ${skill.stepCount} steps`
|
|
16491
|
+
);
|
|
16492
|
+
lines.push(` Inspired by: ${skill.inspiredBy.join(", ")}`);
|
|
16493
|
+
return lines.join("\n");
|
|
16494
|
+
}
|
|
16495
|
+
function registerSkillsTools(server2) {
|
|
16496
|
+
server2.tool(
|
|
16497
|
+
"list_skills",
|
|
16498
|
+
'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.',
|
|
16499
|
+
{
|
|
16500
|
+
studio: z35.enum(STUDIO_VALUES).optional().describe("Filter to one studio (video, avatar, carousel, voice, caption, edit)."),
|
|
16501
|
+
featured_only: z35.boolean().optional().describe("Return only featured (recommended) skills. Defaults to false."),
|
|
16502
|
+
response_format: z35.enum(["text", "json"]).optional().describe("Response format. Defaults to text \u2014 human-readable summary.")
|
|
16503
|
+
},
|
|
16504
|
+
async ({ studio, featured_only, response_format }) => {
|
|
16505
|
+
const skills = listSkills({ studio, featuredOnly: featured_only });
|
|
16506
|
+
const format = response_format ?? "text";
|
|
16507
|
+
if (format === "json") {
|
|
16508
|
+
return {
|
|
16509
|
+
content: [
|
|
16510
|
+
{
|
|
16511
|
+
type: "text",
|
|
16512
|
+
text: JSON.stringify(asEnvelope26({ count: skills.length, skills }), null, 2)
|
|
16513
|
+
}
|
|
16514
|
+
]
|
|
16515
|
+
};
|
|
16516
|
+
}
|
|
16517
|
+
if (skills.length === 0) {
|
|
16518
|
+
return {
|
|
16519
|
+
content: [
|
|
16520
|
+
{
|
|
16521
|
+
type: "text",
|
|
16522
|
+
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(", ")
|
|
16523
|
+
}
|
|
16524
|
+
]
|
|
16525
|
+
};
|
|
16526
|
+
}
|
|
16527
|
+
const blocks = skills.map(renderSkillSummary).join("\n\n");
|
|
16528
|
+
const header = `${skills.length} skill${skills.length === 1 ? "" : "s"} available
|
|
16529
|
+
${"=".repeat(40)}`;
|
|
16530
|
+
return {
|
|
16531
|
+
content: [{ type: "text", text: `${header}
|
|
16532
|
+
|
|
16533
|
+
${blocks}` }]
|
|
16534
|
+
};
|
|
16535
|
+
}
|
|
16536
|
+
);
|
|
16537
|
+
server2.tool(
|
|
16538
|
+
"run_skill",
|
|
16539
|
+
"Run a Social Neuron workflow skill end-to-end (brand-locked content production). Returns a structured run preview with the exact step plan, credit cost, and a deep-link to launch the run in the SN dashboard. A future 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.",
|
|
16540
|
+
{
|
|
16541
|
+
skill_id: z35.string().describe(
|
|
16542
|
+
'The id of the skill to run (e.g. "skill-brand-locked-viral-hook-reel"). Use list_skills to discover available ids.'
|
|
16543
|
+
),
|
|
16544
|
+
topic: z35.string().min(1).describe('What the content is about (e.g. "why we built Social Neuron").'),
|
|
16545
|
+
audience: z35.string().optional().describe(
|
|
16546
|
+
'Who the content is for (e.g. "first-time founders"). Defaults to brand persona.'
|
|
16547
|
+
),
|
|
16548
|
+
hook: z35.string().optional().describe(
|
|
16549
|
+
"Optional explicit hook. If omitted, the skill drafts and scores 3-5 candidates."
|
|
16550
|
+
),
|
|
16551
|
+
cta: z35.string().optional().describe("Optional call-to-action override. Defaults to brand standard CTA."),
|
|
16552
|
+
response_format: z35.enum(["text", "json"]).optional().describe("Response format. Defaults to text.")
|
|
16553
|
+
},
|
|
16554
|
+
async ({ skill_id, topic, audience, hook, cta, response_format }) => {
|
|
16555
|
+
const skill = getSkill(skill_id);
|
|
16556
|
+
const format = response_format ?? "text";
|
|
16557
|
+
if (!skill) {
|
|
16558
|
+
const knownIds = SKILLS_MANIFEST.map((s) => s.id).join(", ");
|
|
16559
|
+
return {
|
|
16560
|
+
content: [
|
|
16561
|
+
{
|
|
16562
|
+
type: "text",
|
|
16563
|
+
text: `Unknown skill_id "${skill_id}". Available skills: ${knownIds}. Call list_skills for descriptions.`
|
|
16564
|
+
}
|
|
16565
|
+
],
|
|
16566
|
+
isError: true
|
|
16567
|
+
};
|
|
16568
|
+
}
|
|
16569
|
+
const runUrl = "https://socialneuron.com/dashboard/creation?skill=" + encodeURIComponent(skill.id);
|
|
16570
|
+
const inputs = { topic, audience, hook, cta };
|
|
16571
|
+
const previewedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
16572
|
+
if (format === "json") {
|
|
16573
|
+
return {
|
|
16574
|
+
content: [
|
|
16575
|
+
{
|
|
16576
|
+
type: "text",
|
|
16577
|
+
text: JSON.stringify(
|
|
16578
|
+
asEnvelope26({
|
|
16579
|
+
status: "preview",
|
|
16580
|
+
skill,
|
|
16581
|
+
inputs,
|
|
16582
|
+
runUrl,
|
|
16583
|
+
previewedAt,
|
|
16584
|
+
note: "This release returns a preview. A future release will execute in-process."
|
|
16585
|
+
}),
|
|
16586
|
+
null,
|
|
16587
|
+
2
|
|
16588
|
+
)
|
|
16589
|
+
}
|
|
16590
|
+
]
|
|
16591
|
+
};
|
|
16592
|
+
}
|
|
16593
|
+
const lines = [
|
|
16594
|
+
`Ready to run: ${skill.name}`,
|
|
16595
|
+
"=".repeat(40),
|
|
16596
|
+
"",
|
|
16597
|
+
skill.shortDescription,
|
|
16598
|
+
"",
|
|
16599
|
+
`Hook formula: ${skill.hookFormula}`,
|
|
16600
|
+
`Inspired by: ${skill.inspiredBy.join(", ")}`,
|
|
16601
|
+
"",
|
|
16602
|
+
"Inputs",
|
|
16603
|
+
` topic : ${topic}`,
|
|
16604
|
+
` audience : ${audience ?? "(brand persona)"}`,
|
|
16605
|
+
` hook : ${hook ?? "(auto-drafted from 3-5 candidates, scored)"}`,
|
|
16606
|
+
` cta : ${cta ?? "(brand default)"}`,
|
|
16607
|
+
"",
|
|
16608
|
+
"Plan",
|
|
16609
|
+
` ${skill.stepCount} steps \xB7 ~${skill.estimatedCredits} credits \xB7 ~${skill.estimatedSeconds}s wall time`,
|
|
16610
|
+
"",
|
|
16611
|
+
`Launch in dashboard: ${runUrl}`,
|
|
16612
|
+
"",
|
|
16613
|
+
"\u26A0\uFE0F Preview only. End-to-end MCP execution arrives in a future release."
|
|
16614
|
+
];
|
|
16615
|
+
return {
|
|
16616
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
16617
|
+
};
|
|
16618
|
+
}
|
|
16619
|
+
);
|
|
16620
|
+
}
|
|
16621
|
+
|
|
16622
|
+
// src/tools/loopPulse.ts
|
|
16623
|
+
init_edge_function();
|
|
16624
|
+
init_version();
|
|
16625
|
+
import { z as z36 } from "zod";
|
|
16626
|
+
function asEnvelope27(data) {
|
|
16627
|
+
return {
|
|
16628
|
+
_meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() },
|
|
16629
|
+
data
|
|
16630
|
+
};
|
|
16631
|
+
}
|
|
16632
|
+
function registerLoopPulseTools(server2) {
|
|
16633
|
+
server2.tool(
|
|
16634
|
+
"get_loop_pulse",
|
|
16635
|
+
'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, learning-update application rate, per-platform learning 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.',
|
|
16636
|
+
{
|
|
16637
|
+
response_format: z36.enum(["text", "json"]).optional().describe("Output format. Defaults to text.")
|
|
16638
|
+
},
|
|
16639
|
+
async ({ response_format }) => {
|
|
16640
|
+
const format = response_format ?? "text";
|
|
16641
|
+
const { data, error } = await callEdgeFunction("mc-loop-pulse", {});
|
|
16642
|
+
if (error || !data) {
|
|
16643
|
+
return {
|
|
16644
|
+
content: [
|
|
16645
|
+
{
|
|
16646
|
+
type: "text",
|
|
16647
|
+
text: `Failed to read loop pulse: ${error || "no data"}`
|
|
16648
|
+
}
|
|
16649
|
+
],
|
|
16650
|
+
isError: true
|
|
16651
|
+
};
|
|
16652
|
+
}
|
|
16653
|
+
if (format === "json") {
|
|
16654
|
+
return {
|
|
16655
|
+
content: [{ type: "text", text: JSON.stringify(asEnvelope27(data), null, 2) }]
|
|
16656
|
+
};
|
|
16657
|
+
}
|
|
16658
|
+
const rank = { bad: 0, warn: 1, unknown: 2, ok: 3 };
|
|
16659
|
+
const sorted = [...data.kpis].sort((a, b) => rank[a.status] - rank[b.status]);
|
|
16660
|
+
const lines = [
|
|
16661
|
+
`LOOP PULSE \u2014 overall: ${data.overall.toUpperCase()}`,
|
|
16662
|
+
"",
|
|
16663
|
+
...sorted.map((k) => {
|
|
16664
|
+
const unit = k.unit ?? "";
|
|
16665
|
+
const val = k.value == null ? "\u2014" : `${k.value}${unit}`;
|
|
16666
|
+
return `[${k.status.toUpperCase().padEnd(4)}] ${k.label}: ${val}
|
|
16667
|
+
${k.why}`;
|
|
16668
|
+
}),
|
|
16669
|
+
"",
|
|
16670
|
+
`Generated ${data.generated_at}`
|
|
16671
|
+
];
|
|
16672
|
+
return {
|
|
16673
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
16674
|
+
};
|
|
16675
|
+
}
|
|
16676
|
+
);
|
|
16677
|
+
}
|
|
16678
|
+
|
|
16679
|
+
// src/tools/banditState.ts
|
|
16680
|
+
init_edge_function();
|
|
16681
|
+
init_supabase();
|
|
16682
|
+
init_version();
|
|
16683
|
+
import { z as z37 } from "zod";
|
|
16684
|
+
function asEnvelope28(data) {
|
|
16685
|
+
return {
|
|
16686
|
+
_meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() },
|
|
16687
|
+
data
|
|
16688
|
+
};
|
|
16689
|
+
}
|
|
16690
|
+
function registerBanditStateTools(server2) {
|
|
16691
|
+
server2.tool(
|
|
16692
|
+
"get_bandit_state",
|
|
16693
|
+
"Read the current content learning state for a project. Returns top-K arms per (arm_type, platform) with expected performance and uncertainty. Use this to reason about which hook family / format / timing slot currently performs best on each platform before recommending next moves. Arm types: hook_family, 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).",
|
|
16694
|
+
{
|
|
16695
|
+
project_id: z37.string().uuid().optional().describe("Project UUID. Defaults to the authenticated user default project."),
|
|
16696
|
+
platform: z37.enum([
|
|
16697
|
+
"instagram",
|
|
16698
|
+
"tiktok",
|
|
16699
|
+
"youtube",
|
|
16700
|
+
"linkedin",
|
|
16701
|
+
"twitter",
|
|
16702
|
+
"facebook",
|
|
16703
|
+
"threads",
|
|
16704
|
+
"bluesky"
|
|
16705
|
+
]).optional().describe(
|
|
16706
|
+
"Lowercase platform name. Omit to return both platform-scoped and legacy platform-agnostic arms."
|
|
16707
|
+
),
|
|
16708
|
+
arm_type: z37.enum([
|
|
16709
|
+
"hook_family",
|
|
16710
|
+
"hook_type",
|
|
16711
|
+
"format",
|
|
16712
|
+
"timing_slot",
|
|
16713
|
+
"topic_cluster",
|
|
16714
|
+
"caption_style",
|
|
16715
|
+
"platform",
|
|
16716
|
+
"story_type",
|
|
16717
|
+
"emoji_type",
|
|
16718
|
+
"length_bucket",
|
|
16719
|
+
"posting_time_bucket",
|
|
16720
|
+
"content_format"
|
|
16721
|
+
]).optional().describe("Arm dimension. Omit to return all types grouped."),
|
|
16722
|
+
top_k: z37.number().min(1).max(50).optional().describe("Max arms per (arm_type) group. Default 5."),
|
|
16723
|
+
response_format: z37.enum(["text", "json"]).optional().describe("Output format. Defaults to text.")
|
|
16724
|
+
},
|
|
16725
|
+
async ({ project_id, platform: platform3, arm_type, top_k, response_format }) => {
|
|
16726
|
+
const format = response_format ?? "text";
|
|
16727
|
+
const projectId = project_id ?? await getDefaultProjectId();
|
|
16728
|
+
if (!projectId) {
|
|
16729
|
+
return {
|
|
16730
|
+
content: [
|
|
16731
|
+
{ type: "text", text: "No project_id provided and no default project available." }
|
|
16732
|
+
],
|
|
16733
|
+
isError: true
|
|
16734
|
+
};
|
|
16735
|
+
}
|
|
16736
|
+
const { data, error } = await callEdgeFunction("mc-bandit-state", {
|
|
16737
|
+
project_id: projectId,
|
|
16738
|
+
platform: platform3,
|
|
16739
|
+
arm_type,
|
|
16740
|
+
top_k
|
|
16741
|
+
});
|
|
16742
|
+
if (error || !data) {
|
|
16743
|
+
return {
|
|
16744
|
+
content: [{ type: "text", text: `Failed to read bandit state: ${error || "no data"}` }],
|
|
16745
|
+
isError: true
|
|
16746
|
+
};
|
|
16747
|
+
}
|
|
16748
|
+
if (format === "json") {
|
|
16749
|
+
return { content: [{ type: "text", text: JSON.stringify(asEnvelope28(data), null, 2) }] };
|
|
16750
|
+
}
|
|
16751
|
+
const lines = [
|
|
16752
|
+
`BANDIT STATE \u2014 project ${data.project_id.slice(0, 8)}${data.platform_filter ? ` \xB7 ${data.platform_filter}` : ""}`,
|
|
16753
|
+
`${data.total_arms} arms across ${data.groups.length} arm types`,
|
|
16754
|
+
""
|
|
16755
|
+
];
|
|
16756
|
+
for (const group of data.groups) {
|
|
16757
|
+
lines.push(`[${group.arm_type}] ${group.summary}`);
|
|
16758
|
+
const renderArm = (a) => {
|
|
16759
|
+
const conf = a.posterior_stdev < 0.05 ? "high" : a.posterior_stdev < 0.15 ? "med" : "low";
|
|
16760
|
+
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)`;
|
|
16761
|
+
};
|
|
16762
|
+
if (group.platform_scoped.length > 0) {
|
|
16763
|
+
lines.push(` Per-platform top arms:`);
|
|
16764
|
+
group.platform_scoped.forEach((a) => lines.push(renderArm(a)));
|
|
16765
|
+
}
|
|
16766
|
+
if (group.platform_agnostic.length > 0) {
|
|
16767
|
+
lines.push(` Platform-agnostic (legacy) top arms:`);
|
|
16768
|
+
group.platform_agnostic.forEach((a) => lines.push(renderArm(a)));
|
|
16769
|
+
}
|
|
16770
|
+
lines.push("");
|
|
16771
|
+
}
|
|
16772
|
+
lines.push(`Generated ${data.generated_at}`);
|
|
16773
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
16774
|
+
}
|
|
16775
|
+
);
|
|
16776
|
+
}
|
|
16777
|
+
|
|
15888
16778
|
// src/lib/register-tools.ts
|
|
15889
16779
|
function wrapToolWithScanner(toolName, handler) {
|
|
15890
16780
|
return async function scannerWrappedHandler(...handlerArgs) {
|
|
@@ -15937,7 +16827,8 @@ function wrapToolWithScanner(toolName, handler) {
|
|
|
15937
16827
|
}
|
|
15938
16828
|
function applyScopeEnforcement(server2, scopeResolver) {
|
|
15939
16829
|
const originalTool = server2.tool.bind(server2);
|
|
15940
|
-
|
|
16830
|
+
const originalRegisterTool = server2.registerTool?.bind(server2);
|
|
16831
|
+
const wrapRegistration = (...args) => {
|
|
15941
16832
|
const name = args[0];
|
|
15942
16833
|
const requiredScope = TOOL_SCOPES[name];
|
|
15943
16834
|
const handlerIndex = args.findIndex(
|
|
@@ -15948,27 +16839,11 @@ function applyScopeEnforcement(server2, scopeResolver) {
|
|
|
15948
16839
|
const scannerWrappedHandler = wrapToolWithScanner(name, originalHandler);
|
|
15949
16840
|
args[handlerIndex] = async function scopeEnforcedHandler(...handlerArgs) {
|
|
15950
16841
|
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
|
-
};
|
|
16842
|
+
return scopeDeniedResult(name, void 0, scopeResolver());
|
|
15960
16843
|
}
|
|
15961
16844
|
const userScopes = scopeResolver();
|
|
15962
16845
|
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
|
-
};
|
|
16846
|
+
return scopeDeniedResult(name, requiredScope, userScopes);
|
|
15972
16847
|
}
|
|
15973
16848
|
const startedAt = Date.now();
|
|
15974
16849
|
try {
|
|
@@ -15995,9 +16870,59 @@ function applyScopeEnforcement(server2, scopeResolver) {
|
|
|
15995
16870
|
}
|
|
15996
16871
|
};
|
|
15997
16872
|
}
|
|
15998
|
-
return
|
|
16873
|
+
return args;
|
|
16874
|
+
};
|
|
16875
|
+
server2.tool = function wrappedTool(...args) {
|
|
16876
|
+
return originalTool(...wrapRegistration(...args));
|
|
16877
|
+
};
|
|
16878
|
+
if (originalRegisterTool) {
|
|
16879
|
+
server2.registerTool = function wrappedRegisterTool(...args) {
|
|
16880
|
+
return originalRegisterTool(...wrapRegistration(...args));
|
|
16881
|
+
};
|
|
16882
|
+
}
|
|
16883
|
+
}
|
|
16884
|
+
function scopeDeniedResult(name, requiredScope, userScopes) {
|
|
16885
|
+
const error = requiredScope ? {
|
|
16886
|
+
error: "permission_denied",
|
|
16887
|
+
tool: name,
|
|
16888
|
+
required_scope: requiredScope,
|
|
16889
|
+
available_scopes: userScopes,
|
|
16890
|
+
recover_with: [
|
|
16891
|
+
"Call search_tools with available_only=true to find tools this key can use.",
|
|
16892
|
+
"Use a read-only alternative if one is available for the task.",
|
|
16893
|
+
"Regenerate the API key with the required scope or upgrade the plan tier."
|
|
16894
|
+
],
|
|
16895
|
+
developer_url: "https://socialneuron.com/settings/developer"
|
|
16896
|
+
} : {
|
|
16897
|
+
error: "tool_scope_missing",
|
|
16898
|
+
tool: name,
|
|
16899
|
+
available_scopes: userScopes,
|
|
16900
|
+
recover_with: ["Contact support; this tool is not mapped to a required scope."]
|
|
16901
|
+
};
|
|
16902
|
+
const challenge = requiredScope ? buildWwwAuthenticateHeader({
|
|
16903
|
+
issuerUrl: getChallengeIssuerUrl(),
|
|
16904
|
+
error: "insufficient_scope",
|
|
16905
|
+
errorDescription: `Tool ${name} requires scope ${requiredScope}.`,
|
|
16906
|
+
scope: requiredScope
|
|
16907
|
+
}) : void 0;
|
|
16908
|
+
return {
|
|
16909
|
+
content: [{ type: "text", text: JSON.stringify(error, null, 2) }],
|
|
16910
|
+
...challenge ? { _meta: { "mcp/www_authenticate": [challenge] } } : {},
|
|
16911
|
+
isError: true
|
|
15999
16912
|
};
|
|
16000
16913
|
}
|
|
16914
|
+
function getChallengeIssuerUrl() {
|
|
16915
|
+
if (process.env.OAUTH_ISSUER_URL) return process.env.OAUTH_ISSUER_URL;
|
|
16916
|
+
const mcpServerUrl = process.env.MCP_SERVER_URL;
|
|
16917
|
+
if (mcpServerUrl) {
|
|
16918
|
+
try {
|
|
16919
|
+
const parsed = new URL(mcpServerUrl);
|
|
16920
|
+
return `${parsed.protocol}//${parsed.host}`;
|
|
16921
|
+
} catch {
|
|
16922
|
+
}
|
|
16923
|
+
}
|
|
16924
|
+
return "https://mcp.socialneuron.com";
|
|
16925
|
+
}
|
|
16001
16926
|
var RESPONSE_CHAR_LIMIT = 1e5;
|
|
16002
16927
|
function truncateResponse(result) {
|
|
16003
16928
|
if (!result?.content || !Array.isArray(result.content)) return result;
|
|
@@ -16067,6 +16992,9 @@ function registerAllTools(server2, options) {
|
|
|
16067
16992
|
registerConnectionTools(server2);
|
|
16068
16993
|
registerHarnessTools(server2, void 0);
|
|
16069
16994
|
registerHermesTools(server2);
|
|
16995
|
+
registerSkillsTools(server2);
|
|
16996
|
+
registerLoopPulseTools(server2);
|
|
16997
|
+
registerBanditStateTools(server2);
|
|
16070
16998
|
if (!options?.skipApps) {
|
|
16071
16999
|
registerContentCalendarApp(server2);
|
|
16072
17000
|
}
|
|
@@ -16074,17 +17002,17 @@ function registerAllTools(server2, options) {
|
|
|
16074
17002
|
}
|
|
16075
17003
|
|
|
16076
17004
|
// src/prompts.ts
|
|
16077
|
-
import { z as
|
|
17005
|
+
import { z as z38 } from "zod";
|
|
16078
17006
|
function registerPrompts(server2) {
|
|
16079
17007
|
server2.prompt(
|
|
16080
17008
|
"create_weekly_content_plan",
|
|
16081
17009
|
"Generate a full week of social media content (7 days, multiple platforms). Returns a structured plan with topics, formats, and posting times.",
|
|
16082
17010
|
{
|
|
16083
|
-
niche:
|
|
16084
|
-
platforms:
|
|
17011
|
+
niche: z38.string().describe('Your content niche or industry (e.g., "fitness coaching", "SaaS marketing")'),
|
|
17012
|
+
platforms: z38.string().optional().describe(
|
|
16085
17013
|
'Comma-separated platforms to target (default: "YouTube, Instagram, TikTok, LinkedIn")'
|
|
16086
17014
|
),
|
|
16087
|
-
tone:
|
|
17015
|
+
tone: z38.string().optional().describe('Brand tone of voice (e.g., "professional", "casual", "bold and edgy")')
|
|
16088
17016
|
},
|
|
16089
17017
|
({ niche, platforms, tone }) => {
|
|
16090
17018
|
const targetPlatforms = platforms || "YouTube, Instagram, TikTok, LinkedIn";
|
|
@@ -16126,8 +17054,8 @@ After building the plan, use \`save_content_plan\` to save it.`
|
|
|
16126
17054
|
"analyze_top_content",
|
|
16127
17055
|
"Analyze your best-performing posts to identify patterns and replicate success. Returns insights on hooks, formats, timing, and topics that resonate.",
|
|
16128
17056
|
{
|
|
16129
|
-
timeframe:
|
|
16130
|
-
platform:
|
|
17057
|
+
timeframe: z38.string().optional().describe('Analysis period (default: "30 days"). E.g., "7 days", "90 days"'),
|
|
17058
|
+
platform: z38.string().optional().describe('Filter to a specific platform (e.g., "youtube", "instagram")')
|
|
16131
17059
|
},
|
|
16132
17060
|
({ timeframe, platform: platform3 }) => {
|
|
16133
17061
|
const period = timeframe || "30 days";
|
|
@@ -16164,10 +17092,10 @@ Format as a clear, actionable performance report.`
|
|
|
16164
17092
|
"repurpose_content",
|
|
16165
17093
|
"Take one piece of content and transform it into 8-10 pieces across multiple platforms and formats.",
|
|
16166
17094
|
{
|
|
16167
|
-
source:
|
|
17095
|
+
source: z38.string().describe(
|
|
16168
17096
|
"The source content to repurpose \u2014 a URL, transcript, or the content text itself"
|
|
16169
17097
|
),
|
|
16170
|
-
target_platforms:
|
|
17098
|
+
target_platforms: z38.string().optional().describe(
|
|
16171
17099
|
'Comma-separated target platforms (default: "Twitter, LinkedIn, Instagram, TikTok, YouTube")'
|
|
16172
17100
|
)
|
|
16173
17101
|
},
|
|
@@ -16209,9 +17137,9 @@ For each piece, include the platform, format, character count, and suggested pos
|
|
|
16209
17137
|
"setup_brand_voice",
|
|
16210
17138
|
"Define or refine your brand voice profile so all generated content stays on-brand. Walks through tone, audience, values, and style.",
|
|
16211
17139
|
{
|
|
16212
|
-
brand_name:
|
|
16213
|
-
industry:
|
|
16214
|
-
website:
|
|
17140
|
+
brand_name: z38.string().describe("Your brand or business name"),
|
|
17141
|
+
industry: z38.string().optional().describe('Your industry or niche (e.g., "B2B SaaS", "fitness coaching")'),
|
|
17142
|
+
website: z38.string().optional().describe("Your website URL for context")
|
|
16215
17143
|
},
|
|
16216
17144
|
({ brand_name, industry, website }) => {
|
|
16217
17145
|
const industryContext = industry ? ` in the ${industry} space` : "";
|
|
@@ -16454,13 +17382,13 @@ function registerResources(server2) {
|
|
|
16454
17382
|
pro: {
|
|
16455
17383
|
price: "$49/mo",
|
|
16456
17384
|
credits: 1500,
|
|
16457
|
-
mcp_access: "Read + Analytics",
|
|
17385
|
+
mcp_access: "Read + Analytics + Write + Distribute",
|
|
16458
17386
|
features: [
|
|
16459
17387
|
"All Starter features",
|
|
16460
17388
|
"Closed-loop optimization",
|
|
16461
|
-
"
|
|
17389
|
+
"Full agent engine via MCP (generate, gate, publish)",
|
|
16462
17390
|
"All 10 platforms",
|
|
16463
|
-
"MCP read + analytics"
|
|
17391
|
+
"MCP read + analytics + write + distribute"
|
|
16464
17392
|
]
|
|
16465
17393
|
},
|
|
16466
17394
|
team: {
|
|
@@ -16565,6 +17493,7 @@ function registerResources(server2) {
|
|
|
16565
17493
|
init_posthog();
|
|
16566
17494
|
init_supabase();
|
|
16567
17495
|
init_sn();
|
|
17496
|
+
process.env.MCP_TRANSPORT ??= "stdio";
|
|
16568
17497
|
function flushAndExit(code) {
|
|
16569
17498
|
const done = { out: false, err: false };
|
|
16570
17499
|
const tryExit = () => {
|
|
@@ -16726,7 +17655,7 @@ if (command === "sn") {
|
|
|
16726
17655
|
process.exit(snSubcommand ? 0 : 1);
|
|
16727
17656
|
}
|
|
16728
17657
|
await runSnCli(process.argv.slice(3));
|
|
16729
|
-
await new Promise((resolve3) => process.stdout.write("", resolve3));
|
|
17658
|
+
await new Promise((resolve3) => process.stdout.write("", () => resolve3()));
|
|
16730
17659
|
process.exit(0);
|
|
16731
17660
|
}
|
|
16732
17661
|
if (command && !["setup", "login", "logout", "whoami", "health", "sn", "repl"].includes(command)) {
|