@socialneuron/mcp-server 1.7.12 → 1.7.14

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