@socialneuron/mcp-server 1.7.14 → 1.7.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  All notable changes to `@socialneuron/mcp-server` will be documented in this file.
4
4
 
5
+ ## [1.7.15] - 2026-07-06
6
+
7
+ ### Changed
8
+
9
+ - **Public tool surface tightened.** Internal service tools used by Social Neuron's own automation are still registered and scope-gated at runtime, but are no longer advertised on the hosted server card, HTTP discovery, `search_tools`, knowledge documents, or the CLI tool listing. The hosted endpoint now advertises 85 tools; the npm stdio server exposes 87 discoverable tools (including 2 local screen-capture tools).
10
+ - **Tool descriptions cleaned up.** Internal project references and implementation jargon removed from tool descriptions across the catalog.
11
+ - **`run_content_pipeline` scheduling guard restored.** Scheduling again requires `schedule_confirmed=true` after explicit user approval, cannot run with the quality stage skipped, caps generated posts to the requested plan size, drops posts targeting unrequested platforms, and counts scheduled posts against the credit budget. (This hardening was unintentionally dropped in the 1.7.14 source sync.)
12
+ - **Metadata contract unified + CI-guarded.** `server.json` version/pricing/tool-count now match `package.json` and the canonical pricing (MCP requires Pro $49/mo or higher; free tier is 50 credits/mo with no MCP access). Added `mcpName` for MCP Registry ownership verification. New `npm run verify:metadata` gate (wired into CI and release) blocks stale counts, stale pricing, internal codenames, and dead endpoint links from re-entering the public surface.
13
+ - **Docs corrected.** REST API docs (tool count, response `version` example, plan limits), troubleshooting boot log line (95/95), integration methods (stdio vs hosted tool split), and auth docs (plan-scope matrix; removed internal implementation notes). Removed links to the not-yet-deployed `/v1/openapi.json` endpoint.
14
+ - **SDK release path hardened.** `packages/sdk` now ships a `package-lock.json`, the SDK release workflow uses `npm ci --ignore-scripts`, and Dependabot watches `/packages/sdk` and `/apps/content-calendar`.
15
+
16
+ ## [1.7.12–1.7.14] - 2026-06/07
17
+
18
+ Released without changelog entries (see git history): OAuth connector flow + DCR, MCP Apps content calendar, trial-key scopes and post-trial degrade, funnel instrumentation, 96-tool catalog sync, dependency cooldown pins.
19
+
5
20
  ## [1.7.11] - 2026-05-15
6
21
 
7
22
  ### Changed
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @socialneuron/mcp-server
2
2
 
3
- > 96 MCP tools for AI-powered social media management. Create content, schedule posts, track analytics, and optimize performance — all from Claude Code or any MCP client.
3
+ > 85+ MCP tools for AI-powered social media management. Create content, schedule posts, track analytics, and optimize performance — all from Claude Code or any MCP client.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@socialneuron/mcp-server)](https://www.npmjs.com/package/@socialneuron/mcp-server)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -92,7 +92,9 @@ Ask Claude things like:
92
92
  - "Check my analytics and suggest improvements"
93
93
  - "Set up autopilot to post 3 times per week"
94
94
 
95
- ## Tool Categories (96 tools)
95
+ ## Tool Categories
96
+
97
+ The hosted endpoint advertises **85 tools** (live count: [server card](https://mcp.socialneuron.com/.well-known/mcp/server-card.json)); the npm stdio server adds 2 local screen-capture tools (87 discoverable). A small set of internal service tools used by Social Neuron's own automation are registered but not part of the public surface.
96
98
 
97
99
  These tools are available to AI agents (Claude, Cursor, etc.) via the MCP protocol.
98
100
 
@@ -217,7 +219,7 @@ socialneuron-mcp sn loop
217
219
  socialneuron-mcp sn credits
218
220
 
219
221
  # Agent-native CLI v2
220
- socialneuron-mcp sn tools [--module ideation] [--scope mcp:write] # List/filter all 96 tools
222
+ socialneuron-mcp sn tools [--module ideation] [--scope mcp:write] # List/filter available tools
221
223
  socialneuron-mcp sn info # Version, auth, credits, tool count
222
224
  socialneuron-mcp sn plan list|view|approve # Content plan management
223
225
  socialneuron-mcp sn preset list|show|save|delete # Platform presets (6 built-in)
package/dist/http.js CHANGED
@@ -707,10 +707,10 @@ var TOOL_SCOPES = {
707
707
  record_campaign_spend: "mcp:write",
708
708
  // mcp:read
709
709
  get_active_campaigns: "mcp:read",
710
- // mcp:read / mcp:write (Skills — PR #4.4)
710
+ // mcp:read / mcp:write (Skills)
711
711
  list_skills: "mcp:read",
712
712
  run_skill: "mcp:write",
713
- // mcp:read (Loop observability — growth-loop KPIs + bandit posteriors)
713
+ // mcp:read (Loop observability — growth-loop KPIs + content learning state)
714
714
  get_loop_pulse: "mcp:read",
715
715
  get_bandit_state: "mcp:read"
716
716
  };
@@ -1213,7 +1213,7 @@ function checkRateLimit(category, key) {
1213
1213
  init_supabase();
1214
1214
 
1215
1215
  // src/lib/version.ts
1216
- var MCP_VERSION = "1.7.14";
1216
+ var MCP_VERSION = "1.7.15";
1217
1217
 
1218
1218
  // src/tools/ideation.ts
1219
1219
  function asEnvelope(data) {
@@ -9555,7 +9555,7 @@ var TOOL_CATALOG = [
9555
9555
  // niche research
9556
9556
  {
9557
9557
  name: "find_winning_content",
9558
- description: "Find QA-gated high-performing short-form videos in the project's niche. Returns extracted hook patterns, content structures, and pre-compiled replication prompts (backed by niche_winners view, qa_score >= 0.5).",
9558
+ description: "Find QA-gated high-performing short-form videos in the project's niche. Returns extracted hook patterns, content structures, and pre-compiled replication prompts.",
9559
9559
  module: "research",
9560
9560
  scope: "mcp:read"
9561
9561
  },
@@ -9706,59 +9706,68 @@ var TOOL_CATALOG = [
9706
9706
  // agentic-harness — learning loop write-back
9707
9707
  {
9708
9708
  name: "write_agent_reflection",
9709
- description: "Persist a verbal reflection for an agent loop. Provenance keys are restricted (Anti-Goodhart safety): only content_history_id, outcome_event_id, prm_score_ids, and handoff_ids are accepted.",
9709
+ description: "Persist a verbal reflection for an agent loop. Provenance keys are restricted to an allowlist: only content_history_id, outcome_event_id, prm_score_ids, and handoff_ids are accepted.",
9710
9710
  module: "harness",
9711
- scope: "mcp:write"
9711
+ scope: "mcp:write",
9712
+ internal: true
9712
9713
  },
9713
9714
  {
9714
9715
  name: "record_outcome",
9715
- description: "Record an outcome for a published decision event. Idempotent on (decision_event_id, horizon). Only horizon=24h triggers a content_bandits posterior update.",
9716
+ description: "Record an outcome for a published decision event. Idempotent on (decision_event_id, horizon). Only horizon=24h triggers a learning-loop update.",
9716
9717
  module: "harness",
9717
- scope: "mcp:write"
9718
+ scope: "mcp:write",
9719
+ internal: true
9718
9720
  },
9719
9721
  // agentic-harness — learning loop read-back
9720
9722
  {
9721
9723
  name: "read_agent_reflection",
9722
9724
  description: "Read past agent reflections for a brand. Ordered by created_at DESC, id ASC (deterministic tiebreak). Only active reflections returned (superseded_by IS NULL). Optional generated_by_agent filter.",
9723
9725
  module: "harness",
9724
- scope: "mcp:read"
9726
+ scope: "mcp:read",
9727
+ internal: true
9725
9728
  },
9726
9729
  // hermes — autonomous agent integration (closed-loop content)
9727
9730
  {
9728
9731
  name: "save_draft_to_library",
9729
- description: "Save a draft post to the SN content library with origin=hermes. Used by Hermes to persist drafts before the founder approves them.",
9732
+ description: "Save a draft post to the SN content library for review before publishing. Drafts land in the content library pending approval.",
9730
9733
  module: "hermes",
9731
- scope: "mcp:write"
9734
+ scope: "mcp:write",
9735
+ internal: true
9732
9736
  },
9733
9737
  {
9734
9738
  name: "record_voice_lesson",
9735
- description: "Persist a learned voice lesson to brand_profiles.brand_context.voiceProfile.voice_lessons via atomic RPC. Used by Hermes reflection cron.",
9739
+ description: "Persist a learned voice lesson to the brand voice profile.",
9736
9740
  module: "hermes",
9737
- scope: "mcp:write"
9741
+ scope: "mcp:write",
9742
+ internal: true
9738
9743
  },
9739
9744
  {
9740
9745
  name: "record_observation",
9741
- description: 'Record an agent observation (e.g. "topic X engagement up 23%") for the UnifiedAnalytics > Playbook surface.',
9746
+ description: 'Record an agent observation (e.g. "topic X engagement up 23%") for the analytics playbook.',
9742
9747
  module: "hermes",
9743
- scope: "mcp:write"
9748
+ scope: "mcp:write",
9749
+ internal: true
9744
9750
  },
9745
9751
  {
9746
9752
  name: "record_intel_signal",
9747
- description: "Record a research/trend signal from Hermes watchers (news, HN, competitor, etc.) for Niche Intelligence. Dedupes by URL.",
9753
+ description: "Record a research/trend signal (news, competitor, community sources) for niche intelligence. Dedupes by URL.",
9748
9754
  module: "hermes",
9749
- scope: "mcp:write"
9755
+ scope: "mcp:write",
9756
+ internal: true
9750
9757
  },
9751
9758
  {
9752
9759
  name: "record_campaign_spend",
9753
- description: "Log a campaign cost line (hermes_drafts, carousel_renders, analytics_pulls, paid_amplification, other). Ownership-checked.",
9760
+ description: "Log a campaign cost line item. Ownership-checked.",
9754
9761
  module: "hermes",
9755
- scope: "mcp:write"
9762
+ scope: "mcp:write",
9763
+ internal: true
9756
9764
  },
9757
9765
  {
9758
9766
  name: "get_active_campaigns",
9759
- description: "List currently-running campaigns with thesis, budget, hero format, and current spend. Used by Hermes pitch skill to bias drafts.",
9767
+ description: "List currently-running campaigns with thesis, budget, hero format, and current spend.",
9760
9768
  module: "hermes",
9761
- scope: "mcp:read"
9769
+ scope: "mcp:read",
9770
+ internal: true
9762
9771
  },
9763
9772
  // skills (workflow skills — multi-step brand-locked content pipelines)
9764
9773
  {
@@ -9769,20 +9778,20 @@ var TOOL_CATALOG = [
9769
9778
  },
9770
9779
  {
9771
9780
  name: "run_skill",
9772
- 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.",
9781
+ description: "Run a Social Neuron workflow skill end-to-end (brand-locked content production). Returns a structured run preview with the step plan, credit cost, and a deep-link to launch in the SN dashboard.",
9773
9782
  module: "skills",
9774
9783
  scope: "mcp:write"
9775
9784
  },
9776
- // loop observability (growth-loop KPIs + Thompson Sampling bandit posteriors)
9785
+ // loop observability (growth-loop KPIs + content learning state)
9777
9786
  {
9778
9787
  name: "get_loop_pulse",
9779
- 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.",
9788
+ description: "Read dynamic loop-health KPIs for the growth loop over the last 7 days (reflection/decision coverage, visual gate pass rate, learning-update application rate, per-platform uptake, autopilot lag) \u2014 each with an ok/warn/bad status. Use to decide whether the loop is closing or where it is stuck.",
9780
9789
  module: "loop",
9781
9790
  scope: "mcp:read"
9782
9791
  },
9783
9792
  {
9784
9793
  name: "get_bandit_state",
9785
- 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.",
9794
+ description: "Read the current content learning state for a project \u2014 top-K arms per (arm_type, platform) with expected performance and uncertainty. Use to reason about which hook family / format / timing slot currently performs best per platform.",
9786
9795
  module: "loop",
9787
9796
  scope: "mcp:read"
9788
9797
  }
@@ -9892,7 +9901,10 @@ function toolKnowledgeDocument(tool) {
9892
9901
  };
9893
9902
  }
9894
9903
  function getKnowledgeDocuments() {
9895
- return [...STATIC_KNOWLEDGE_DOCUMENTS, ...TOOL_CATALOG.map(toolKnowledgeDocument)];
9904
+ return [
9905
+ ...STATIC_KNOWLEDGE_DOCUMENTS,
9906
+ ...TOOL_CATALOG.filter((t) => !t.internal).map(toolKnowledgeDocument)
9907
+ ];
9896
9908
  }
9897
9909
  function tokenize(input) {
9898
9910
  return input.toLowerCase().split(/[^a-z0-9:_-]+/).map((token) => token.trim()).filter(Boolean);
@@ -10008,6 +10020,7 @@ function registerDiscoveryTools(server) {
10008
10020
  if (query) {
10009
10021
  results = searchTools(query);
10010
10022
  }
10023
+ results = results.filter((t) => !t.internal);
10011
10024
  if (module) {
10012
10025
  const moduleTools = getToolsByModule(module);
10013
10026
  results = results.filter((t) => moduleTools.some((mt) => mt.name === t.name));
@@ -10084,6 +10097,7 @@ var PLATFORM_ENUM2 = z24.enum([
10084
10097
  ]);
10085
10098
  var BASE_PLAN_CREDITS = 15;
10086
10099
  var SOURCE_EXTRACTION_CREDITS = 5;
10100
+ var SCHEDULE_POST_CREDITS = 1;
10087
10101
  function registerPipelineTools(server) {
10088
10102
  server.tool(
10089
10103
  "check_pipeline_readiness",
@@ -10200,7 +10214,7 @@ function registerPipelineTools(server) {
10200
10214
  );
10201
10215
  server.tool(
10202
10216
  "run_content_pipeline",
10203
- "Run the full content pipeline: research trends \u2192 generate plan \u2192 quality check \u2192 auto-approve \u2192 schedule posts. Chains all stages in one call for maximum efficiency. Set dry_run=true to preview the plan without publishing. Check check_pipeline_readiness first to verify credits, OAuth, and brand profile are ready.",
10217
+ "Run the full content pipeline: research trends \u2192 generate plan \u2192 quality check \u2192 auto-approve \u2192 schedule posts. Chains all stages in one call for maximum efficiency. Set dry_run=true to preview the plan without publishing. To schedule posts, set schedule_confirmed=true after the user explicitly approves publishing. Check check_pipeline_readiness first to verify credits, OAuth, and brand profile are ready.",
10204
10218
  {
10205
10219
  project_id: z24.string().uuid().optional().describe("Project ID (auto-detected if omitted)"),
10206
10220
  topic: z24.string().optional().describe("Content topic (required if no source_url)"),
@@ -10216,6 +10230,9 @@ function registerPipelineTools(server) {
10216
10230
  ),
10217
10231
  max_credits: z24.number().optional().describe("Credit budget cap"),
10218
10232
  dry_run: z24.boolean().default(false).describe("If true, skip scheduling and return plan only"),
10233
+ schedule_confirmed: z24.boolean().default(false).describe(
10234
+ "Required to schedule posts. Set true only after explicit user confirmation to publish/schedule."
10235
+ ),
10219
10236
  skip_stages: z24.array(z24.enum(["research", "quality", "schedule"])).optional().describe("Stages to skip"),
10220
10237
  response_format: z24.enum(["text", "json"]).default("json")
10221
10238
  },
@@ -10230,6 +10247,7 @@ function registerPipelineTools(server) {
10230
10247
  auto_approve_threshold,
10231
10248
  max_credits,
10232
10249
  dry_run,
10250
+ schedule_confirmed,
10233
10251
  skip_stages,
10234
10252
  response_format
10235
10253
  }) => {
@@ -10245,6 +10263,29 @@ function registerPipelineTools(server) {
10245
10263
  };
10246
10264
  }
10247
10265
  const skipSet = new Set(skip_stages ?? []);
10266
+ const schedulingRequested = !dry_run && !skipSet.has("schedule");
10267
+ if (schedulingRequested && !schedule_confirmed) {
10268
+ return {
10269
+ content: [
10270
+ {
10271
+ type: "text",
10272
+ text: 'Scheduling requires explicit confirmation. Re-run with schedule_confirmed=true after the user approves publishing, or set dry_run=true / skip_stages=["schedule"].'
10273
+ }
10274
+ ],
10275
+ isError: true
10276
+ };
10277
+ }
10278
+ if (schedulingRequested && skipSet.has("quality")) {
10279
+ return {
10280
+ content: [
10281
+ {
10282
+ type: "text",
10283
+ text: "Scheduling cannot run when the quality stage is skipped."
10284
+ }
10285
+ ],
10286
+ isError: true
10287
+ };
10288
+ }
10248
10289
  try {
10249
10290
  const resolvedProjectId = project_id ?? await getDefaultProjectId() ?? void 0;
10250
10291
  const estimatedCost = BASE_PLAN_CREDITS + (source_url ? SOURCE_EXTRACTION_CREDITS : 0);
@@ -10287,6 +10328,7 @@ function registerPipelineTools(server) {
10287
10328
  approval_mode,
10288
10329
  auto_approve_threshold,
10289
10330
  dry_run,
10331
+ schedule_confirmed,
10290
10332
  skip_stages: skip_stages ?? []
10291
10333
  },
10292
10334
  current_stage: "planning",
@@ -10352,7 +10394,9 @@ function registerPipelineTools(server) {
10352
10394
  stagesCompleted.push("planning");
10353
10395
  const rawText = String(planData.text ?? planData.content ?? "");
10354
10396
  const postsArray = extractJsonArray(rawText);
10355
- const posts = (postsArray ?? []).map((p) => ({
10397
+ const requestedPlatformSet = new Set(platforms);
10398
+ const maxPosts = platforms.length * days * posts_per_day;
10399
+ const parsedPosts = (postsArray ?? []).map((p) => ({
10356
10400
  id: String(p.id ?? randomUUID3().slice(0, 8)),
10357
10401
  day: Number(p.day ?? 1),
10358
10402
  date: String(p.date ?? ""),
@@ -10366,6 +10410,23 @@ function registerPipelineTools(server) {
10366
10410
  visual_direction: p.visual_direction ? String(p.visual_direction) : void 0,
10367
10411
  media_type: p.media_type ? String(p.media_type) : void 0
10368
10412
  }));
10413
+ const platformFilteredPosts = parsedPosts.filter(
10414
+ (post) => requestedPlatformSet.has(post.platform)
10415
+ );
10416
+ const posts = platformFilteredPosts.slice(0, maxPosts);
10417
+ if (parsedPosts.length > maxPosts) {
10418
+ errors.push({
10419
+ stage: "planning",
10420
+ message: `AI returned ${parsedPosts.length} posts; truncated to ${maxPosts}.`
10421
+ });
10422
+ }
10423
+ const invalidPlatformCount = parsedPosts.length - platformFilteredPosts.length;
10424
+ if (invalidPlatformCount > 0) {
10425
+ errors.push({
10426
+ stage: "planning",
10427
+ message: `Dropped ${invalidPlatformCount} post(s) with unrequested or invalid platform.`
10428
+ });
10429
+ }
10369
10430
  let postsApproved = 0;
10370
10431
  let postsFlagged = 0;
10371
10432
  if (!skipSet.has("quality")) {
@@ -10518,6 +10579,7 @@ function registerPipelineTools(server) {
10518
10579
  });
10519
10580
  } else {
10520
10581
  postsScheduled++;
10582
+ creditsUsed += SCHEDULE_POST_CREDITS;
10521
10583
  }
10522
10584
  } catch (schedErr) {
10523
10585
  errors.push({
@@ -12577,13 +12639,13 @@ function asEnvelope24(data) {
12577
12639
  function registerNicheResearchTools(server) {
12578
12640
  server.tool(
12579
12641
  "find_winning_content",
12580
- "Find QA-gated high-performing short-form videos in the project's niche. Returns extracted hook patterns, content structures, and pre-compiled replication prompts you can use to generate new content on a different topic. Backed by niche_winners view (qa_score >= 0.5 + replication_prompt populated).",
12642
+ "Find QA-gated high-performing short-form videos in the project's niche. Returns extracted hook patterns, content structures, and pre-compiled replication prompts you can use to generate new content on a different topic. Only QA-gated winners with a populated replication prompt are returned.",
12581
12643
  {
12582
12644
  project_id: z29.string().uuid().optional().describe("Project ID (auto-detected if omitted)"),
12583
12645
  platform: z29.enum(["tiktok", "instagram", "youtube", "reddit", "twitter"]).optional().describe("Filter to one platform. Omit for all platforms."),
12584
12646
  days: z29.number().int().min(1).max(365).default(30).describe("Window: only return winners scanned within this many days."),
12585
12647
  limit: z29.number().int().min(1).max(50).default(10).describe("Number of winners to return (1-50)."),
12586
- min_qa_score: z29.number().min(0).max(1).default(0.5).describe("Minimum QA score (0..1). Default 0.5 matches the niche_winners view floor."),
12648
+ min_qa_score: z29.number().min(0).max(1).default(0.5).describe("Minimum QA score (0..1). Default 0.5."),
12587
12649
  response_format: z29.enum(["text", "json"]).optional()
12588
12650
  },
12589
12651
  async ({ project_id, platform: platform2, days, limit, min_qa_score, response_format }) => {
@@ -13241,7 +13303,7 @@ var writeReflectionSchema = {
13241
13303
  prm_score_ids: z33.array(z33.string()).optional().describe("PRM score IDs that informed this reflection."),
13242
13304
  handoff_ids: z33.array(z33.string()).optional().describe("Handoff event IDs that triggered this reflection.")
13243
13305
  }).strict().describe(
13244
- "Source evidence for this reflection. Only these four keys are accepted (Anti-Goodhart guard \u2014 unknown keys are rejected to prevent spurious provenance claims)."
13306
+ "Source evidence for this reflection. Only these four keys are accepted \u2014 unknown keys are rejected to prevent spurious provenance claims."
13245
13307
  ),
13246
13308
  brand_id: z33.string().describe("Brand profile UUID this reflection belongs to."),
13247
13309
  pipeline_id: z33.string().optional().describe("Optional: pipeline run UUID."),
@@ -13259,7 +13321,7 @@ var readReflectionSchema = {
13259
13321
  var recordOutcomeSchema = {
13260
13322
  decision_event_id: z33.string().describe("UUID of the decision_events row to record an outcome for."),
13261
13323
  horizon: z33.enum(["1h", "6h", "24h"]).describe(
13262
- "Observation horizon. Only horizon=24h with a non-null reward triggers a content_bandits posterior update; 1h/6h are stored but inert for learning."
13324
+ "Observation horizon. Only horizon=24h with a non-null reward triggers a learning-loop update; 1h/6h are stored but inert for learning."
13263
13325
  ),
13264
13326
  reward: z33.number().min(0).max(1).describe("Normalised reward signal in [0, 1]. Higher = better outcome."),
13265
13327
  outcome_metrics: z33.record(z33.string(), z33.number()).optional().describe(
@@ -13269,7 +13331,7 @@ var recordOutcomeSchema = {
13269
13331
  function registerHarnessTools(server, _ctx) {
13270
13332
  server.tool(
13271
13333
  "write_agent_reflection",
13272
- "Persist a verbal reflection for an agent loop. Provenance keys are restricted (Anti-Goodhart safety): only content_history_id, outcome_event_id, prm_score_ids, and handoff_ids are accepted \u2014 unknown keys are rejected at the input layer. Requires mcp:write scope. Returns the created reflection UUID on success.",
13334
+ "Persist a verbal reflection for an agent loop. Provenance keys are restricted to an allowlist: only content_history_id, outcome_event_id, prm_score_ids, and handoff_ids are accepted \u2014 unknown keys are rejected at the input layer. Requires mcp:write scope. Returns the created reflection UUID on success.",
13273
13335
  writeReflectionSchema,
13274
13336
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
13275
13337
  async (args) => {
@@ -13297,7 +13359,7 @@ function registerHarnessTools(server, _ctx) {
13297
13359
  );
13298
13360
  server.tool(
13299
13361
  "record_outcome",
13300
- "Record an outcome for a published decision event. Idempotent on (decision_event_id, horizon) \u2014 safe to call multiple times. Returns idempotent:true when the row already existed (UPDATE), idempotent:false on fresh INSERT. Note: only horizon=24h with reward != null triggers a content_bandits posterior update; 1h/6h outcomes are stored but are inert for the learning loop. Requires mcp:write scope.",
13362
+ "Record an outcome for a published decision event. Idempotent on (decision_event_id, horizon) \u2014 safe to call multiple times. Returns idempotent:true when the row already existed (UPDATE), idempotent:false on fresh INSERT. Note: only horizon=24h with reward != null triggers a learning-loop update; 1h/6h outcomes are stored but are inert for the learning loop. Requires mcp:write scope.",
13301
13363
  recordOutcomeSchema,
13302
13364
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
13303
13365
  async (args) => {
@@ -13381,13 +13443,13 @@ var PLATFORM = z34.enum([
13381
13443
  function registerHermesTools(server) {
13382
13444
  server.tool(
13383
13445
  "save_draft_to_library",
13384
- "Save a draft post to the SN content library. Use when an autonomous agent (Hermes) wants to persist a draft before the founder approves it. Lands in ContentLibrary with status='draft', origin='hermes'. The draft can then be approved/edited in the SN UI.",
13446
+ "Save a draft post to the SN content library. Use when an autonomous agent wants to persist a draft for review before publishing. Lands in the content library with status='draft'. The draft can then be approved/edited in the SN UI.",
13385
13447
  {
13386
13448
  platform: PLATFORM.describe("Target platform for the draft."),
13387
13449
  copy: z34.string().min(1).max(8e3).describe("The draft post body."),
13388
13450
  project_id: z34.string().optional().describe("SN project UUID. Optional but recommended."),
13389
13451
  media_url: z34.string().url().optional().describe("Optional cover/media URL (R2 signed URL)."),
13390
- hermes_run_id: z34.string().optional().describe("Hermes cron run id, for traceability."),
13452
+ hermes_run_id: z34.string().optional().describe("Agent run id, for traceability."),
13391
13453
  source_intel_ids: z34.array(z34.string()).optional().describe("Optional list of intel_signals.id rows that informed this draft."),
13392
13454
  response_format: z34.enum(["text", "json"]).optional()
13393
13455
  },
@@ -13469,11 +13531,11 @@ function registerHermesTools(server) {
13469
13531
  );
13470
13532
  server.tool(
13471
13533
  "record_observation",
13472
- 'Record an agent observation ("Hermes noticed X this week"). Surfaces in UnifiedAnalytics > Playbook tab. Use for weekly reflection digests and mid-campaign pulse summaries.',
13534
+ 'Record an agent observation (e.g. "topic X engagement up 23% this week"). Surfaces in the analytics Playbook. Use for weekly reflection digests and mid-campaign pulse summaries.',
13473
13535
  {
13474
- summary: z34.string().min(1).max(2e3).describe("One-paragraph summary, founder-facing."),
13536
+ summary: z34.string().min(1).max(2e3).describe("One-paragraph summary, shown to the account owner."),
13475
13537
  deltas: z34.record(z34.string(), z34.union([z34.number(), z34.string(), z34.boolean()])).optional().describe("Optional structured key/value payload (e.g. {topic_x_er_pct: 23})."),
13476
- run_id: z34.string().optional().describe("Hermes cron run id for traceability."),
13538
+ run_id: z34.string().optional().describe("Agent run id for traceability."),
13477
13539
  response_format: z34.enum(["text", "json"]).optional()
13478
13540
  },
13479
13541
  async ({ summary, deltas, run_id, response_format }) => {
@@ -13543,7 +13605,7 @@ function registerHermesTools(server) {
13543
13605
  );
13544
13606
  server.tool(
13545
13607
  "record_campaign_spend",
13546
- "Log a campaign cost line. Use when Hermes incurs spend that should be attributed to a campaign \u2014 drafts, renders, paid amp. Categories: hermes_drafts / carousel_renders / analytics_pulls / paid_amplification / other. Read aggregate via get_active_campaigns.",
13608
+ "Log a campaign cost line. Use when an autonomous agent incurs spend that should be attributed to a campaign \u2014 drafts, renders, paid amp. Read aggregate via get_active_campaigns.",
13547
13609
  {
13548
13610
  campaign_id: z34.string().min(1).max(200).describe("Campaign identifier (slug)."),
13549
13611
  category: z34.enum([
@@ -13587,7 +13649,7 @@ function registerHermesTools(server) {
13587
13649
  );
13588
13650
  server.tool(
13589
13651
  "get_active_campaigns",
13590
- "List currently-running campaigns with thesis, budget, hero format, and current spend. Used by Hermes pitch skill to bias drafts toward active campaign themes.",
13652
+ "List currently-running campaigns with thesis, budget, hero format, and current spend. Use to bias drafts toward active campaign themes.",
13591
13653
  {
13592
13654
  response_format: z34.enum(["text", "json"]).optional()
13593
13655
  },
@@ -13730,7 +13792,7 @@ ${blocks}` }]
13730
13792
  );
13731
13793
  server.tool(
13732
13794
  "run_skill",
13733
- "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.",
13795
+ "Run a Social Neuron workflow skill end-to-end (brand-locked content production). Returns a structured run preview with the exact step plan, credit cost, and a deep-link to launch the run in the SN dashboard. A future release executes in-process so you can stream step-by-step progress back to the user. Call list_skills first to discover available skill ids.",
13734
13796
  {
13735
13797
  skill_id: z35.string().describe(
13736
13798
  'The id of the skill to run (e.g. "skill-brand-locked-viral-hook-reel"). Use list_skills to discover available ids.'
@@ -13775,7 +13837,7 @@ ${blocks}` }]
13775
13837
  inputs,
13776
13838
  runUrl,
13777
13839
  previewedAt,
13778
- note: "PR #4.4 v1 returns a preview. PR #4.5 will execute in-process via the run-skill EF."
13840
+ note: "This release returns a preview. A future release will execute in-process."
13779
13841
  }),
13780
13842
  null,
13781
13843
  2
@@ -13804,7 +13866,7 @@ ${blocks}` }]
13804
13866
  "",
13805
13867
  `Launch in dashboard: ${runUrl}`,
13806
13868
  "",
13807
- "\u26A0\uFE0F PR #4.4 v1: preview only. End-to-end MCP execution arrives in the next release."
13869
+ "\u26A0\uFE0F Preview only. End-to-end MCP execution arrives in a future release."
13808
13870
  ];
13809
13871
  return {
13810
13872
  content: [{ type: "text", text: lines.join("\n") }]
@@ -13824,7 +13886,7 @@ function asEnvelope27(data) {
13824
13886
  function registerLoopPulseTools(server) {
13825
13887
  server.tool(
13826
13888
  "get_loop_pulse",
13827
- '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.',
13889
+ 'Read the dynamic loop-health KPIs for the Social Neuron growth loop over the last 7 days. Returns reflection coverage, decision coverage, visual gate pass rate, learning-update application rate, per-platform learning uptake, autopilot lag, and pattern aggregation counts \u2014 each with a status ("ok" / "warn" / "bad") and a why-line explaining what the metric measures. Use this to decide whether the loop is closing or where it is stuck before recommending next moves.',
13828
13890
  {
13829
13891
  response_format: z36.enum(["text", "json"]).optional().describe("Output format. Defaults to text.")
13830
13892
  },
@@ -13880,7 +13942,7 @@ function asEnvelope28(data) {
13880
13942
  function registerBanditStateTools(server) {
13881
13943
  server.tool(
13882
13944
  "get_bandit_state",
13883
- "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.",
13945
+ "Read the current content learning state for a project. Returns top-K arms per (arm_type, platform) with expected performance and uncertainty. Use this to reason about which hook family / format / timing slot currently performs best on each platform before recommending next moves. Arm types: hook_family, length_bucket (xs/s/m/l/xl by platform), posting_time_bucket (morning/midday/evening/late), content_format (video/carousel/image/caption/text/avatar/storyboard).",
13884
13946
  {
13885
13947
  project_id: z37.string().uuid().optional().describe("Project UUID. Defaults to the authenticated user default project."),
13886
13948
  platform: z37.enum([
@@ -13931,9 +13993,7 @@ function registerBanditStateTools(server) {
13931
13993
  });
13932
13994
  if (error || !data) {
13933
13995
  return {
13934
- content: [
13935
- { type: "text", text: `Failed to read bandit state: ${error || "no data"}` }
13936
- ],
13996
+ content: [{ type: "text", text: `Failed to read bandit state: ${error || "no data"}` }],
13937
13997
  isError: true
13938
13998
  };
13939
13999
  }
@@ -15280,7 +15340,7 @@ async function computeDiscoveryCatalog() {
15280
15340
  err?.message
15281
15341
  );
15282
15342
  }
15283
- return TOOL_CATALOG.filter((t) => !t.localOnly).map((t) => ({
15343
+ return TOOL_CATALOG.filter((t) => !t.localOnly && !t.internal).map((t) => ({
15284
15344
  name: t.name,
15285
15345
  description: t.description,
15286
15346
  inputSchema: schemaByName.get(t.name) ?? { type: "object", properties: {} }
@@ -15633,8 +15693,8 @@ app.get("/.well-known/mcp/server-card.json", (_req, res) => {
15633
15693
  required: true,
15634
15694
  schemes: ["oauth2"]
15635
15695
  },
15636
- toolCount: TOOL_CATALOG.filter((t) => !t.localOnly).length,
15637
- tools: TOOL_CATALOG.filter((t) => !t.localOnly).map((t) => ({
15696
+ toolCount: TOOL_CATALOG.filter((t) => !t.localOnly && !t.internal).length,
15697
+ tools: TOOL_CATALOG.filter((t) => !t.localOnly && !t.internal).map((t) => ({
15638
15698
  name: t.name,
15639
15699
  description: t.description,
15640
15700
  module: t.module,
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ var MCP_VERSION;
19
19
  var init_version = __esm({
20
20
  "src/lib/version.ts"() {
21
21
  "use strict";
22
- MCP_VERSION = "1.7.14";
22
+ MCP_VERSION = "1.7.15";
23
23
  }
24
24
  });
25
25
 
@@ -1235,7 +1235,7 @@ var init_tool_catalog = __esm({
1235
1235
  // niche research
1236
1236
  {
1237
1237
  name: "find_winning_content",
1238
- description: "Find QA-gated high-performing short-form videos in the project's niche. Returns extracted hook patterns, content structures, and pre-compiled replication prompts (backed by niche_winners view, qa_score >= 0.5).",
1238
+ description: "Find QA-gated high-performing short-form videos in the project's niche. Returns extracted hook patterns, content structures, and pre-compiled replication prompts.",
1239
1239
  module: "research",
1240
1240
  scope: "mcp:read"
1241
1241
  },
@@ -1386,59 +1386,68 @@ var init_tool_catalog = __esm({
1386
1386
  // agentic-harness — learning loop write-back
1387
1387
  {
1388
1388
  name: "write_agent_reflection",
1389
- description: "Persist a verbal reflection for an agent loop. Provenance keys are restricted (Anti-Goodhart safety): only content_history_id, outcome_event_id, prm_score_ids, and handoff_ids are accepted.",
1389
+ description: "Persist a verbal reflection for an agent loop. Provenance keys are restricted to an allowlist: only content_history_id, outcome_event_id, prm_score_ids, and handoff_ids are accepted.",
1390
1390
  module: "harness",
1391
- scope: "mcp:write"
1391
+ scope: "mcp:write",
1392
+ internal: true
1392
1393
  },
1393
1394
  {
1394
1395
  name: "record_outcome",
1395
- description: "Record an outcome for a published decision event. Idempotent on (decision_event_id, horizon). Only horizon=24h triggers a content_bandits posterior update.",
1396
+ description: "Record an outcome for a published decision event. Idempotent on (decision_event_id, horizon). Only horizon=24h triggers a learning-loop update.",
1396
1397
  module: "harness",
1397
- scope: "mcp:write"
1398
+ scope: "mcp:write",
1399
+ internal: true
1398
1400
  },
1399
1401
  // agentic-harness — learning loop read-back
1400
1402
  {
1401
1403
  name: "read_agent_reflection",
1402
1404
  description: "Read past agent reflections for a brand. Ordered by created_at DESC, id ASC (deterministic tiebreak). Only active reflections returned (superseded_by IS NULL). Optional generated_by_agent filter.",
1403
1405
  module: "harness",
1404
- scope: "mcp:read"
1406
+ scope: "mcp:read",
1407
+ internal: true
1405
1408
  },
1406
1409
  // hermes — autonomous agent integration (closed-loop content)
1407
1410
  {
1408
1411
  name: "save_draft_to_library",
1409
- description: "Save a draft post to the SN content library with origin=hermes. Used by Hermes to persist drafts before the founder approves them.",
1412
+ description: "Save a draft post to the SN content library for review before publishing. Drafts land in the content library pending approval.",
1410
1413
  module: "hermes",
1411
- scope: "mcp:write"
1414
+ scope: "mcp:write",
1415
+ internal: true
1412
1416
  },
1413
1417
  {
1414
1418
  name: "record_voice_lesson",
1415
- description: "Persist a learned voice lesson to brand_profiles.brand_context.voiceProfile.voice_lessons via atomic RPC. Used by Hermes reflection cron.",
1419
+ description: "Persist a learned voice lesson to the brand voice profile.",
1416
1420
  module: "hermes",
1417
- scope: "mcp:write"
1421
+ scope: "mcp:write",
1422
+ internal: true
1418
1423
  },
1419
1424
  {
1420
1425
  name: "record_observation",
1421
- description: 'Record an agent observation (e.g. "topic X engagement up 23%") for the UnifiedAnalytics > Playbook surface.',
1426
+ description: 'Record an agent observation (e.g. "topic X engagement up 23%") for the analytics playbook.',
1422
1427
  module: "hermes",
1423
- scope: "mcp:write"
1428
+ scope: "mcp:write",
1429
+ internal: true
1424
1430
  },
1425
1431
  {
1426
1432
  name: "record_intel_signal",
1427
- description: "Record a research/trend signal from Hermes watchers (news, HN, competitor, etc.) for Niche Intelligence. Dedupes by URL.",
1433
+ description: "Record a research/trend signal (news, competitor, community sources) for niche intelligence. Dedupes by URL.",
1428
1434
  module: "hermes",
1429
- scope: "mcp:write"
1435
+ scope: "mcp:write",
1436
+ internal: true
1430
1437
  },
1431
1438
  {
1432
1439
  name: "record_campaign_spend",
1433
- description: "Log a campaign cost line (hermes_drafts, carousel_renders, analytics_pulls, paid_amplification, other). Ownership-checked.",
1440
+ description: "Log a campaign cost line item. Ownership-checked.",
1434
1441
  module: "hermes",
1435
- scope: "mcp:write"
1442
+ scope: "mcp:write",
1443
+ internal: true
1436
1444
  },
1437
1445
  {
1438
1446
  name: "get_active_campaigns",
1439
- description: "List currently-running campaigns with thesis, budget, hero format, and current spend. Used by Hermes pitch skill to bias drafts.",
1447
+ description: "List currently-running campaigns with thesis, budget, hero format, and current spend.",
1440
1448
  module: "hermes",
1441
- scope: "mcp:read"
1449
+ scope: "mcp:read",
1450
+ internal: true
1442
1451
  },
1443
1452
  // skills (workflow skills — multi-step brand-locked content pipelines)
1444
1453
  {
@@ -1449,20 +1458,20 @@ var init_tool_catalog = __esm({
1449
1458
  },
1450
1459
  {
1451
1460
  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.",
1461
+ description: "Run a Social Neuron workflow skill end-to-end (brand-locked content production). Returns a structured run preview with the step plan, credit cost, and a deep-link to launch in the SN dashboard.",
1453
1462
  module: "skills",
1454
1463
  scope: "mcp:write"
1455
1464
  },
1456
- // loop observability (growth-loop KPIs + Thompson Sampling bandit posteriors)
1465
+ // loop observability (growth-loop KPIs + content learning state)
1457
1466
  {
1458
1467
  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.",
1468
+ description: "Read dynamic loop-health KPIs for the growth loop over the last 7 days (reflection/decision coverage, visual gate pass rate, learning-update application rate, per-platform uptake, autopilot lag) \u2014 each with an ok/warn/bad status. Use to decide whether the loop is closing or where it is stuck.",
1460
1469
  module: "loop",
1461
1470
  scope: "mcp:read"
1462
1471
  },
1463
1472
  {
1464
1473
  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.",
1474
+ description: "Read the current content learning state for a project \u2014 top-K arms per (arm_type, platform) with expected performance and uncertainty. Use to reason about which hook family / format / timing slot currently performs best per platform.",
1466
1475
  module: "loop",
1467
1476
  scope: "mcp:read"
1468
1477
  }
@@ -2637,6 +2646,7 @@ async function handleTools(args, asJson) {
2637
2646
  if (typeof module === "string") {
2638
2647
  tools = getToolsByModule(module);
2639
2648
  }
2649
+ tools = tools.filter((t) => !t.internal);
2640
2650
  if (asJson) {
2641
2651
  emitSnResult({ ok: true, command: "tools", toolCount: tools.length, tools }, true);
2642
2652
  return;
@@ -2667,7 +2677,7 @@ Module: ${moduleName} (${moduleTools.length} tools)`);
2667
2677
  async function handleInfo(args, asJson) {
2668
2678
  const info = {
2669
2679
  version: MCP_VERSION,
2670
- toolCount: TOOL_CATALOG.length,
2680
+ toolCount: TOOL_CATALOG.filter((t) => !t.internal).length,
2671
2681
  modules: getModules()
2672
2682
  };
2673
2683
  try {
@@ -4270,10 +4280,10 @@ var TOOL_SCOPES = {
4270
4280
  record_campaign_spend: "mcp:write",
4271
4281
  // mcp:read
4272
4282
  get_active_campaigns: "mcp:read",
4273
- // mcp:read / mcp:write (Skills — PR #4.4)
4283
+ // mcp:read / mcp:write (Skills)
4274
4284
  list_skills: "mcp:read",
4275
4285
  run_skill: "mcp:write",
4276
- // mcp:read (Loop observability — growth-loop KPIs + bandit posteriors)
4286
+ // mcp:read (Loop observability — growth-loop KPIs + content learning state)
4277
4287
  get_loop_pulse: "mcp:read",
4278
4288
  get_bandit_state: "mcp:read"
4279
4289
  };
@@ -12615,7 +12625,10 @@ function toolKnowledgeDocument(tool) {
12615
12625
  };
12616
12626
  }
12617
12627
  function getKnowledgeDocuments() {
12618
- return [...STATIC_KNOWLEDGE_DOCUMENTS, ...TOOL_CATALOG.map(toolKnowledgeDocument)];
12628
+ return [
12629
+ ...STATIC_KNOWLEDGE_DOCUMENTS,
12630
+ ...TOOL_CATALOG.filter((t) => !t.internal).map(toolKnowledgeDocument)
12631
+ ];
12619
12632
  }
12620
12633
  function tokenize(input) {
12621
12634
  return input.toLowerCase().split(/[^a-z0-9:_-]+/).map((token) => token.trim()).filter(Boolean);
@@ -12731,6 +12744,7 @@ function registerDiscoveryTools(server2) {
12731
12744
  if (query) {
12732
12745
  results = searchTools(query);
12733
12746
  }
12747
+ results = results.filter((t) => !t.internal);
12734
12748
  if (module) {
12735
12749
  const moduleTools = getToolsByModule(module);
12736
12750
  results = results.filter((t) => moduleTools.some((mt) => mt.name === t.name));
@@ -12810,6 +12824,7 @@ var PLATFORM_ENUM2 = z24.enum([
12810
12824
  ]);
12811
12825
  var BASE_PLAN_CREDITS = 15;
12812
12826
  var SOURCE_EXTRACTION_CREDITS = 5;
12827
+ var SCHEDULE_POST_CREDITS = 1;
12813
12828
  function registerPipelineTools(server2) {
12814
12829
  server2.tool(
12815
12830
  "check_pipeline_readiness",
@@ -12926,7 +12941,7 @@ function registerPipelineTools(server2) {
12926
12941
  );
12927
12942
  server2.tool(
12928
12943
  "run_content_pipeline",
12929
- "Run the full content pipeline: research trends \u2192 generate plan \u2192 quality check \u2192 auto-approve \u2192 schedule posts. Chains all stages in one call for maximum efficiency. Set dry_run=true to preview the plan without publishing. Check check_pipeline_readiness first to verify credits, OAuth, and brand profile are ready.",
12944
+ "Run the full content pipeline: research trends \u2192 generate plan \u2192 quality check \u2192 auto-approve \u2192 schedule posts. Chains all stages in one call for maximum efficiency. Set dry_run=true to preview the plan without publishing. To schedule posts, set schedule_confirmed=true after the user explicitly approves publishing. Check check_pipeline_readiness first to verify credits, OAuth, and brand profile are ready.",
12930
12945
  {
12931
12946
  project_id: z24.string().uuid().optional().describe("Project ID (auto-detected if omitted)"),
12932
12947
  topic: z24.string().optional().describe("Content topic (required if no source_url)"),
@@ -12942,6 +12957,9 @@ function registerPipelineTools(server2) {
12942
12957
  ),
12943
12958
  max_credits: z24.number().optional().describe("Credit budget cap"),
12944
12959
  dry_run: z24.boolean().default(false).describe("If true, skip scheduling and return plan only"),
12960
+ schedule_confirmed: z24.boolean().default(false).describe(
12961
+ "Required to schedule posts. Set true only after explicit user confirmation to publish/schedule."
12962
+ ),
12945
12963
  skip_stages: z24.array(z24.enum(["research", "quality", "schedule"])).optional().describe("Stages to skip"),
12946
12964
  response_format: z24.enum(["text", "json"]).default("json")
12947
12965
  },
@@ -12956,6 +12974,7 @@ function registerPipelineTools(server2) {
12956
12974
  auto_approve_threshold,
12957
12975
  max_credits,
12958
12976
  dry_run,
12977
+ schedule_confirmed,
12959
12978
  skip_stages,
12960
12979
  response_format
12961
12980
  }) => {
@@ -12971,6 +12990,29 @@ function registerPipelineTools(server2) {
12971
12990
  };
12972
12991
  }
12973
12992
  const skipSet = new Set(skip_stages ?? []);
12993
+ const schedulingRequested = !dry_run && !skipSet.has("schedule");
12994
+ if (schedulingRequested && !schedule_confirmed) {
12995
+ return {
12996
+ content: [
12997
+ {
12998
+ type: "text",
12999
+ text: 'Scheduling requires explicit confirmation. Re-run with schedule_confirmed=true after the user approves publishing, or set dry_run=true / skip_stages=["schedule"].'
13000
+ }
13001
+ ],
13002
+ isError: true
13003
+ };
13004
+ }
13005
+ if (schedulingRequested && skipSet.has("quality")) {
13006
+ return {
13007
+ content: [
13008
+ {
13009
+ type: "text",
13010
+ text: "Scheduling cannot run when the quality stage is skipped."
13011
+ }
13012
+ ],
13013
+ isError: true
13014
+ };
13015
+ }
12974
13016
  try {
12975
13017
  const resolvedProjectId = project_id ?? await getDefaultProjectId() ?? void 0;
12976
13018
  const estimatedCost = BASE_PLAN_CREDITS + (source_url ? SOURCE_EXTRACTION_CREDITS : 0);
@@ -13013,6 +13055,7 @@ function registerPipelineTools(server2) {
13013
13055
  approval_mode,
13014
13056
  auto_approve_threshold,
13015
13057
  dry_run,
13058
+ schedule_confirmed,
13016
13059
  skip_stages: skip_stages ?? []
13017
13060
  },
13018
13061
  current_stage: "planning",
@@ -13078,7 +13121,9 @@ function registerPipelineTools(server2) {
13078
13121
  stagesCompleted.push("planning");
13079
13122
  const rawText = String(planData.text ?? planData.content ?? "");
13080
13123
  const postsArray = extractJsonArray(rawText);
13081
- const posts = (postsArray ?? []).map((p) => ({
13124
+ const requestedPlatformSet = new Set(platforms);
13125
+ const maxPosts = platforms.length * days * posts_per_day;
13126
+ const parsedPosts = (postsArray ?? []).map((p) => ({
13082
13127
  id: String(p.id ?? randomUUID3().slice(0, 8)),
13083
13128
  day: Number(p.day ?? 1),
13084
13129
  date: String(p.date ?? ""),
@@ -13092,6 +13137,23 @@ function registerPipelineTools(server2) {
13092
13137
  visual_direction: p.visual_direction ? String(p.visual_direction) : void 0,
13093
13138
  media_type: p.media_type ? String(p.media_type) : void 0
13094
13139
  }));
13140
+ const platformFilteredPosts = parsedPosts.filter(
13141
+ (post) => requestedPlatformSet.has(post.platform)
13142
+ );
13143
+ const posts = platformFilteredPosts.slice(0, maxPosts);
13144
+ if (parsedPosts.length > maxPosts) {
13145
+ errors.push({
13146
+ stage: "planning",
13147
+ message: `AI returned ${parsedPosts.length} posts; truncated to ${maxPosts}.`
13148
+ });
13149
+ }
13150
+ const invalidPlatformCount = parsedPosts.length - platformFilteredPosts.length;
13151
+ if (invalidPlatformCount > 0) {
13152
+ errors.push({
13153
+ stage: "planning",
13154
+ message: `Dropped ${invalidPlatformCount} post(s) with unrequested or invalid platform.`
13155
+ });
13156
+ }
13095
13157
  let postsApproved = 0;
13096
13158
  let postsFlagged = 0;
13097
13159
  if (!skipSet.has("quality")) {
@@ -13244,6 +13306,7 @@ function registerPipelineTools(server2) {
13244
13306
  });
13245
13307
  } else {
13246
13308
  postsScheduled++;
13309
+ creditsUsed += SCHEDULE_POST_CREDITS;
13247
13310
  }
13248
13311
  } catch (schedErr) {
13249
13312
  errors.push({
@@ -15313,13 +15376,13 @@ function asEnvelope24(data) {
15313
15376
  function registerNicheResearchTools(server2) {
15314
15377
  server2.tool(
15315
15378
  "find_winning_content",
15316
- "Find QA-gated high-performing short-form videos in the project's niche. Returns extracted hook patterns, content structures, and pre-compiled replication prompts you can use to generate new content on a different topic. Backed by niche_winners view (qa_score >= 0.5 + replication_prompt populated).",
15379
+ "Find QA-gated high-performing short-form videos in the project's niche. Returns extracted hook patterns, content structures, and pre-compiled replication prompts you can use to generate new content on a different topic. Only QA-gated winners with a populated replication prompt are returned.",
15317
15380
  {
15318
15381
  project_id: z29.string().uuid().optional().describe("Project ID (auto-detected if omitted)"),
15319
15382
  platform: z29.enum(["tiktok", "instagram", "youtube", "reddit", "twitter"]).optional().describe("Filter to one platform. Omit for all platforms."),
15320
15383
  days: z29.number().int().min(1).max(365).default(30).describe("Window: only return winners scanned within this many days."),
15321
15384
  limit: z29.number().int().min(1).max(50).default(10).describe("Number of winners to return (1-50)."),
15322
- min_qa_score: z29.number().min(0).max(1).default(0.5).describe("Minimum QA score (0..1). Default 0.5 matches the niche_winners view floor."),
15385
+ min_qa_score: z29.number().min(0).max(1).default(0.5).describe("Minimum QA score (0..1). Default 0.5."),
15323
15386
  response_format: z29.enum(["text", "json"]).optional()
15324
15387
  },
15325
15388
  async ({ project_id, platform: platform3, days, limit, min_qa_score, response_format }) => {
@@ -15981,7 +16044,7 @@ var writeReflectionSchema = {
15981
16044
  prm_score_ids: z33.array(z33.string()).optional().describe("PRM score IDs that informed this reflection."),
15982
16045
  handoff_ids: z33.array(z33.string()).optional().describe("Handoff event IDs that triggered this reflection.")
15983
16046
  }).strict().describe(
15984
- "Source evidence for this reflection. Only these four keys are accepted (Anti-Goodhart guard \u2014 unknown keys are rejected to prevent spurious provenance claims)."
16047
+ "Source evidence for this reflection. Only these four keys are accepted \u2014 unknown keys are rejected to prevent spurious provenance claims."
15985
16048
  ),
15986
16049
  brand_id: z33.string().describe("Brand profile UUID this reflection belongs to."),
15987
16050
  pipeline_id: z33.string().optional().describe("Optional: pipeline run UUID."),
@@ -15999,7 +16062,7 @@ var readReflectionSchema = {
15999
16062
  var recordOutcomeSchema = {
16000
16063
  decision_event_id: z33.string().describe("UUID of the decision_events row to record an outcome for."),
16001
16064
  horizon: z33.enum(["1h", "6h", "24h"]).describe(
16002
- "Observation horizon. Only horizon=24h with a non-null reward triggers a content_bandits posterior update; 1h/6h are stored but inert for learning."
16065
+ "Observation horizon. Only horizon=24h with a non-null reward triggers a learning-loop update; 1h/6h are stored but inert for learning."
16003
16066
  ),
16004
16067
  reward: z33.number().min(0).max(1).describe("Normalised reward signal in [0, 1]. Higher = better outcome."),
16005
16068
  outcome_metrics: z33.record(z33.string(), z33.number()).optional().describe(
@@ -16009,7 +16072,7 @@ var recordOutcomeSchema = {
16009
16072
  function registerHarnessTools(server2, _ctx) {
16010
16073
  server2.tool(
16011
16074
  "write_agent_reflection",
16012
- "Persist a verbal reflection for an agent loop. Provenance keys are restricted (Anti-Goodhart safety): only content_history_id, outcome_event_id, prm_score_ids, and handoff_ids are accepted \u2014 unknown keys are rejected at the input layer. Requires mcp:write scope. Returns the created reflection UUID on success.",
16075
+ "Persist a verbal reflection for an agent loop. Provenance keys are restricted to an allowlist: only content_history_id, outcome_event_id, prm_score_ids, and handoff_ids are accepted \u2014 unknown keys are rejected at the input layer. Requires mcp:write scope. Returns the created reflection UUID on success.",
16013
16076
  writeReflectionSchema,
16014
16077
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
16015
16078
  async (args) => {
@@ -16037,7 +16100,7 @@ function registerHarnessTools(server2, _ctx) {
16037
16100
  );
16038
16101
  server2.tool(
16039
16102
  "record_outcome",
16040
- "Record an outcome for a published decision event. Idempotent on (decision_event_id, horizon) \u2014 safe to call multiple times. Returns idempotent:true when the row already existed (UPDATE), idempotent:false on fresh INSERT. Note: only horizon=24h with reward != null triggers a content_bandits posterior update; 1h/6h outcomes are stored but are inert for the learning loop. Requires mcp:write scope.",
16103
+ "Record an outcome for a published decision event. Idempotent on (decision_event_id, horizon) \u2014 safe to call multiple times. Returns idempotent:true when the row already existed (UPDATE), idempotent:false on fresh INSERT. Note: only horizon=24h with reward != null triggers a learning-loop update; 1h/6h outcomes are stored but are inert for the learning loop. Requires mcp:write scope.",
16041
16104
  recordOutcomeSchema,
16042
16105
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
16043
16106
  async (args) => {
@@ -16123,13 +16186,13 @@ var PLATFORM = z34.enum([
16123
16186
  function registerHermesTools(server2) {
16124
16187
  server2.tool(
16125
16188
  "save_draft_to_library",
16126
- "Save a draft post to the SN content library. Use when an autonomous agent (Hermes) wants to persist a draft before the founder approves it. Lands in ContentLibrary with status='draft', origin='hermes'. The draft can then be approved/edited in the SN UI.",
16189
+ "Save a draft post to the SN content library. Use when an autonomous agent wants to persist a draft for review before publishing. Lands in the content library with status='draft'. The draft can then be approved/edited in the SN UI.",
16127
16190
  {
16128
16191
  platform: PLATFORM.describe("Target platform for the draft."),
16129
16192
  copy: z34.string().min(1).max(8e3).describe("The draft post body."),
16130
16193
  project_id: z34.string().optional().describe("SN project UUID. Optional but recommended."),
16131
16194
  media_url: z34.string().url().optional().describe("Optional cover/media URL (R2 signed URL)."),
16132
- hermes_run_id: z34.string().optional().describe("Hermes cron run id, for traceability."),
16195
+ hermes_run_id: z34.string().optional().describe("Agent run id, for traceability."),
16133
16196
  source_intel_ids: z34.array(z34.string()).optional().describe("Optional list of intel_signals.id rows that informed this draft."),
16134
16197
  response_format: z34.enum(["text", "json"]).optional()
16135
16198
  },
@@ -16211,11 +16274,11 @@ function registerHermesTools(server2) {
16211
16274
  );
16212
16275
  server2.tool(
16213
16276
  "record_observation",
16214
- 'Record an agent observation ("Hermes noticed X this week"). Surfaces in UnifiedAnalytics > Playbook tab. Use for weekly reflection digests and mid-campaign pulse summaries.',
16277
+ 'Record an agent observation (e.g. "topic X engagement up 23% this week"). Surfaces in the analytics Playbook. Use for weekly reflection digests and mid-campaign pulse summaries.',
16215
16278
  {
16216
- summary: z34.string().min(1).max(2e3).describe("One-paragraph summary, founder-facing."),
16279
+ summary: z34.string().min(1).max(2e3).describe("One-paragraph summary, shown to the account owner."),
16217
16280
  deltas: z34.record(z34.string(), z34.union([z34.number(), z34.string(), z34.boolean()])).optional().describe("Optional structured key/value payload (e.g. {topic_x_er_pct: 23})."),
16218
- run_id: z34.string().optional().describe("Hermes cron run id for traceability."),
16281
+ run_id: z34.string().optional().describe("Agent run id for traceability."),
16219
16282
  response_format: z34.enum(["text", "json"]).optional()
16220
16283
  },
16221
16284
  async ({ summary, deltas, run_id, response_format }) => {
@@ -16285,7 +16348,7 @@ function registerHermesTools(server2) {
16285
16348
  );
16286
16349
  server2.tool(
16287
16350
  "record_campaign_spend",
16288
- "Log a campaign cost line. Use when Hermes incurs spend that should be attributed to a campaign \u2014 drafts, renders, paid amp. Categories: hermes_drafts / carousel_renders / analytics_pulls / paid_amplification / other. Read aggregate via get_active_campaigns.",
16351
+ "Log a campaign cost line. Use when an autonomous agent incurs spend that should be attributed to a campaign \u2014 drafts, renders, paid amp. Read aggregate via get_active_campaigns.",
16289
16352
  {
16290
16353
  campaign_id: z34.string().min(1).max(200).describe("Campaign identifier (slug)."),
16291
16354
  category: z34.enum([
@@ -16329,7 +16392,7 @@ function registerHermesTools(server2) {
16329
16392
  );
16330
16393
  server2.tool(
16331
16394
  "get_active_campaigns",
16332
- "List currently-running campaigns with thesis, budget, hero format, and current spend. Used by Hermes pitch skill to bias drafts toward active campaign themes.",
16395
+ "List currently-running campaigns with thesis, budget, hero format, and current spend. Use to bias drafts toward active campaign themes.",
16333
16396
  {
16334
16397
  response_format: z34.enum(["text", "json"]).optional()
16335
16398
  },
@@ -16473,7 +16536,7 @@ ${blocks}` }]
16473
16536
  );
16474
16537
  server2.tool(
16475
16538
  "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.",
16539
+ "Run a Social Neuron workflow skill end-to-end (brand-locked content production). Returns a structured run preview with the exact step plan, credit cost, and a deep-link to launch the run in the SN dashboard. A future release executes in-process so you can stream step-by-step progress back to the user. Call list_skills first to discover available skill ids.",
16477
16540
  {
16478
16541
  skill_id: z35.string().describe(
16479
16542
  'The id of the skill to run (e.g. "skill-brand-locked-viral-hook-reel"). Use list_skills to discover available ids.'
@@ -16518,7 +16581,7 @@ ${blocks}` }]
16518
16581
  inputs,
16519
16582
  runUrl,
16520
16583
  previewedAt,
16521
- note: "PR #4.4 v1 returns a preview. PR #4.5 will execute in-process via the run-skill EF."
16584
+ note: "This release returns a preview. A future release will execute in-process."
16522
16585
  }),
16523
16586
  null,
16524
16587
  2
@@ -16547,7 +16610,7 @@ ${blocks}` }]
16547
16610
  "",
16548
16611
  `Launch in dashboard: ${runUrl}`,
16549
16612
  "",
16550
- "\u26A0\uFE0F PR #4.4 v1: preview only. End-to-end MCP execution arrives in the next release."
16613
+ "\u26A0\uFE0F Preview only. End-to-end MCP execution arrives in a future release."
16551
16614
  ];
16552
16615
  return {
16553
16616
  content: [{ type: "text", text: lines.join("\n") }]
@@ -16569,7 +16632,7 @@ function asEnvelope27(data) {
16569
16632
  function registerLoopPulseTools(server2) {
16570
16633
  server2.tool(
16571
16634
  "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.',
16635
+ 'Read the dynamic loop-health KPIs for the Social Neuron growth loop over the last 7 days. Returns reflection coverage, decision coverage, visual gate pass rate, learning-update application rate, per-platform learning uptake, autopilot lag, and pattern aggregation counts \u2014 each with a status ("ok" / "warn" / "bad") and a why-line explaining what the metric measures. Use this to decide whether the loop is closing or where it is stuck before recommending next moves.',
16573
16636
  {
16574
16637
  response_format: z36.enum(["text", "json"]).optional().describe("Output format. Defaults to text.")
16575
16638
  },
@@ -16627,7 +16690,7 @@ function asEnvelope28(data) {
16627
16690
  function registerBanditStateTools(server2) {
16628
16691
  server2.tool(
16629
16692
  "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.",
16693
+ "Read the current content learning state for a project. Returns top-K arms per (arm_type, platform) with expected performance and uncertainty. Use this to reason about which hook family / format / timing slot currently performs best on each platform before recommending next moves. Arm types: hook_family, length_bucket (xs/s/m/l/xl by platform), posting_time_bucket (morning/midday/evening/late), content_format (video/carousel/image/caption/text/avatar/storyboard).",
16631
16694
  {
16632
16695
  project_id: z37.string().uuid().optional().describe("Project UUID. Defaults to the authenticated user default project."),
16633
16696
  platform: z37.enum([
@@ -16678,9 +16741,7 @@ function registerBanditStateTools(server2) {
16678
16741
  });
16679
16742
  if (error || !data) {
16680
16743
  return {
16681
- content: [
16682
- { type: "text", text: `Failed to read bandit state: ${error || "no data"}` }
16683
- ],
16744
+ content: [{ type: "text", text: `Failed to read bandit state: ${error || "no data"}` }],
16684
16745
  isError: true
16685
16746
  };
16686
16747
  }
package/dist/sn.js CHANGED
@@ -19,7 +19,7 @@ var MCP_VERSION;
19
19
  var init_version = __esm({
20
20
  "src/lib/version.ts"() {
21
21
  "use strict";
22
- MCP_VERSION = "1.7.14";
22
+ MCP_VERSION = "1.7.15";
23
23
  }
24
24
  });
25
25
 
@@ -2377,7 +2377,7 @@ var init_tool_catalog = __esm({
2377
2377
  // niche research
2378
2378
  {
2379
2379
  name: "find_winning_content",
2380
- description: "Find QA-gated high-performing short-form videos in the project's niche. Returns extracted hook patterns, content structures, and pre-compiled replication prompts (backed by niche_winners view, qa_score >= 0.5).",
2380
+ description: "Find QA-gated high-performing short-form videos in the project's niche. Returns extracted hook patterns, content structures, and pre-compiled replication prompts.",
2381
2381
  module: "research",
2382
2382
  scope: "mcp:read"
2383
2383
  },
@@ -2528,59 +2528,68 @@ var init_tool_catalog = __esm({
2528
2528
  // agentic-harness — learning loop write-back
2529
2529
  {
2530
2530
  name: "write_agent_reflection",
2531
- description: "Persist a verbal reflection for an agent loop. Provenance keys are restricted (Anti-Goodhart safety): only content_history_id, outcome_event_id, prm_score_ids, and handoff_ids are accepted.",
2531
+ description: "Persist a verbal reflection for an agent loop. Provenance keys are restricted to an allowlist: only content_history_id, outcome_event_id, prm_score_ids, and handoff_ids are accepted.",
2532
2532
  module: "harness",
2533
- scope: "mcp:write"
2533
+ scope: "mcp:write",
2534
+ internal: true
2534
2535
  },
2535
2536
  {
2536
2537
  name: "record_outcome",
2537
- description: "Record an outcome for a published decision event. Idempotent on (decision_event_id, horizon). Only horizon=24h triggers a content_bandits posterior update.",
2538
+ description: "Record an outcome for a published decision event. Idempotent on (decision_event_id, horizon). Only horizon=24h triggers a learning-loop update.",
2538
2539
  module: "harness",
2539
- scope: "mcp:write"
2540
+ scope: "mcp:write",
2541
+ internal: true
2540
2542
  },
2541
2543
  // agentic-harness — learning loop read-back
2542
2544
  {
2543
2545
  name: "read_agent_reflection",
2544
2546
  description: "Read past agent reflections for a brand. Ordered by created_at DESC, id ASC (deterministic tiebreak). Only active reflections returned (superseded_by IS NULL). Optional generated_by_agent filter.",
2545
2547
  module: "harness",
2546
- scope: "mcp:read"
2548
+ scope: "mcp:read",
2549
+ internal: true
2547
2550
  },
2548
2551
  // hermes — autonomous agent integration (closed-loop content)
2549
2552
  {
2550
2553
  name: "save_draft_to_library",
2551
- description: "Save a draft post to the SN content library with origin=hermes. Used by Hermes to persist drafts before the founder approves them.",
2554
+ description: "Save a draft post to the SN content library for review before publishing. Drafts land in the content library pending approval.",
2552
2555
  module: "hermes",
2553
- scope: "mcp:write"
2556
+ scope: "mcp:write",
2557
+ internal: true
2554
2558
  },
2555
2559
  {
2556
2560
  name: "record_voice_lesson",
2557
- description: "Persist a learned voice lesson to brand_profiles.brand_context.voiceProfile.voice_lessons via atomic RPC. Used by Hermes reflection cron.",
2561
+ description: "Persist a learned voice lesson to the brand voice profile.",
2558
2562
  module: "hermes",
2559
- scope: "mcp:write"
2563
+ scope: "mcp:write",
2564
+ internal: true
2560
2565
  },
2561
2566
  {
2562
2567
  name: "record_observation",
2563
- description: 'Record an agent observation (e.g. "topic X engagement up 23%") for the UnifiedAnalytics > Playbook surface.',
2568
+ description: 'Record an agent observation (e.g. "topic X engagement up 23%") for the analytics playbook.',
2564
2569
  module: "hermes",
2565
- scope: "mcp:write"
2570
+ scope: "mcp:write",
2571
+ internal: true
2566
2572
  },
2567
2573
  {
2568
2574
  name: "record_intel_signal",
2569
- description: "Record a research/trend signal from Hermes watchers (news, HN, competitor, etc.) for Niche Intelligence. Dedupes by URL.",
2575
+ description: "Record a research/trend signal (news, competitor, community sources) for niche intelligence. Dedupes by URL.",
2570
2576
  module: "hermes",
2571
- scope: "mcp:write"
2577
+ scope: "mcp:write",
2578
+ internal: true
2572
2579
  },
2573
2580
  {
2574
2581
  name: "record_campaign_spend",
2575
- description: "Log a campaign cost line (hermes_drafts, carousel_renders, analytics_pulls, paid_amplification, other). Ownership-checked.",
2582
+ description: "Log a campaign cost line item. Ownership-checked.",
2576
2583
  module: "hermes",
2577
- scope: "mcp:write"
2584
+ scope: "mcp:write",
2585
+ internal: true
2578
2586
  },
2579
2587
  {
2580
2588
  name: "get_active_campaigns",
2581
- description: "List currently-running campaigns with thesis, budget, hero format, and current spend. Used by Hermes pitch skill to bias drafts.",
2589
+ description: "List currently-running campaigns with thesis, budget, hero format, and current spend.",
2582
2590
  module: "hermes",
2583
- scope: "mcp:read"
2591
+ scope: "mcp:read",
2592
+ internal: true
2584
2593
  },
2585
2594
  // skills (workflow skills — multi-step brand-locked content pipelines)
2586
2595
  {
@@ -2591,20 +2600,20 @@ var init_tool_catalog = __esm({
2591
2600
  },
2592
2601
  {
2593
2602
  name: "run_skill",
2594
- 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.",
2603
+ description: "Run a Social Neuron workflow skill end-to-end (brand-locked content production). Returns a structured run preview with the step plan, credit cost, and a deep-link to launch in the SN dashboard.",
2595
2604
  module: "skills",
2596
2605
  scope: "mcp:write"
2597
2606
  },
2598
- // loop observability (growth-loop KPIs + Thompson Sampling bandit posteriors)
2607
+ // loop observability (growth-loop KPIs + content learning state)
2599
2608
  {
2600
2609
  name: "get_loop_pulse",
2601
- 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.",
2610
+ description: "Read dynamic loop-health KPIs for the growth loop over the last 7 days (reflection/decision coverage, visual gate pass rate, learning-update application rate, per-platform uptake, autopilot lag) \u2014 each with an ok/warn/bad status. Use to decide whether the loop is closing or where it is stuck.",
2602
2611
  module: "loop",
2603
2612
  scope: "mcp:read"
2604
2613
  },
2605
2614
  {
2606
2615
  name: "get_bandit_state",
2607
- 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.",
2616
+ description: "Read the current content learning state for a project \u2014 top-K arms per (arm_type, platform) with expected performance and uncertainty. Use to reason about which hook family / format / timing slot currently performs best per platform.",
2608
2617
  module: "loop",
2609
2618
  scope: "mcp:read"
2610
2619
  }
@@ -2628,6 +2637,7 @@ async function handleTools(args, asJson) {
2628
2637
  if (typeof module === "string") {
2629
2638
  tools = getToolsByModule(module);
2630
2639
  }
2640
+ tools = tools.filter((t) => !t.internal);
2631
2641
  if (asJson) {
2632
2642
  emitSnResult({ ok: true, command: "tools", toolCount: tools.length, tools }, true);
2633
2643
  return;
@@ -2658,7 +2668,7 @@ Module: ${moduleName} (${moduleTools.length} tools)`);
2658
2668
  async function handleInfo(args, asJson) {
2659
2669
  const info = {
2660
2670
  version: MCP_VERSION,
2661
- toolCount: TOOL_CATALOG.length,
2671
+ toolCount: TOOL_CATALOG.filter((t) => !t.internal).length,
2662
2672
  modules: getModules()
2663
2673
  };
2664
2674
  try {
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@socialneuron/mcp-server",
3
- "version": "1.7.14",
3
+ "mcpName": "com.socialneuron/mcp-server",
4
+ "version": "1.7.15",
4
5
  "description": "Official MCP server, REST API & CLI for Social Neuron \u2014 create, schedule, and optimize social content with 35+ AI models across YouTube, TikTok, and more.",
5
6
  "type": "module",
6
7
  "main": "dist/index.js",
@@ -78,7 +79,8 @@
78
79
  "test:watch": "vitest",
79
80
  "clean": "rm -rf dist/",
80
81
  "prepublishOnly": "npm run clean && npm run build:all && npm test",
81
- "build:docs": "node scripts/build-tools-reference.mjs"
82
+ "build:docs": "node scripts/build-tools-reference.mjs",
83
+ "verify:metadata": "node scripts/verify-metadata.mjs"
82
84
  },
83
85
  "dependencies": {
84
86
  "@modelcontextprotocol/ext-apps": "1.7.4",
package/tools.lock.json CHANGED
@@ -43,16 +43,16 @@
43
43
  "fetch_trends": "9efd2a24c9f9d9a3a6fa707f2355c8ef0d12dbc25f416e9237c0b9b73d15007b",
44
44
  "fetch_youtube_analytics": "c1d6cf67a5d0ad1696b4320ee91de7ddf58af6cf4a3be537636be746a4822af7",
45
45
  "find_next_slots": "fccf3bc7a8ac54b7ee243c59272252dbfeb307ca8d2987b1527b3b904408c909",
46
- "find_winning_content": "d5112618e5c386ff7cde1a18ec694284365888794b8b0d44154b4e2e527f4ff3",
46
+ "find_winning_content": "f124ae937ea5c3bda8ff9eae82c0ad5b6b095c9a8b1b1812931e10a5b6b35766",
47
47
  "generate_carousel": "e6420e3ccc766f5f0fa2e783ecada527dc4ca23b2bb34954963c912eacba84a9",
48
48
  "generate_content": "1425c9bc1a8ce800448344a5dea61dd1359eb30872a563b7014d461a696fc6bb",
49
49
  "generate_image": "0aa3f14e2b4475ec5909f3b5cc141556b3315989af886a817d9fa7efa1be40e0",
50
50
  "generate_performance_digest": "8bbb53edc46698927a10372444239ba9973c5c84e2d0a8160f74192495dae74b",
51
51
  "generate_video": "371eda383af033b4f99a1f4447e8a555d16d10688995f6fd55b9c6eb0a619313",
52
52
  "generate_voiceover": "ced5ecaf3d936bb7ba0f17d2efe3c15594a061226fbcec1fb572ec87555eff20",
53
- "get_active_campaigns": "3a28a6eb17131d48df8ec6938fba7af807a9c4148cb570dc4a82ae891730c631",
53
+ "get_active_campaigns": "2e593f72a0a4e1a7744049556444c64eda760a40f5c08d603c32b3b34b1d760a",
54
54
  "get_autopilot_status": "c6e689867a32a491a7afbbae46326d223bba412f0c66113ddf4be831c73fd769",
55
- "get_bandit_state": "bcb21aa0c2ca055fd7093d05c1d78a23691627920de9dc1dd7087472c57b8a34",
55
+ "get_bandit_state": "1e58ef7836e24d6c9d474bea52916c15397611200e779ded692cc34fe882352f",
56
56
  "get_best_posting_times": "78932988a8a2238d81c7b2c065e95349fda3e226702998fb08262b74baff6a53",
57
57
  "get_brand_profile": "a0ebd63ebd7032de497751d2b35c0ec78c847869d64e54f7019baa5e532fed0e",
58
58
  "get_brand_runtime": "5ac3149d38eac100aca5d769ddb0f5d900832fb29eef78c18dc97678902e3a41",
@@ -60,7 +60,7 @@
60
60
  "get_content_plan": "24a3845da81533d8c22affd76ce28dc179d79f2556c50317eafafbff6b1c520a",
61
61
  "get_credit_balance": "e5a086ab75278640ec589893a7575aa561fe0cbc30bbbd67a328fb4032d3164d",
62
62
  "get_ideation_context": "fa07379c69b5045de910d8f70445b4bab0bded9f04a1cb41b157cab5a5385f9d",
63
- "get_loop_pulse": "58fdca648d6da7b92f36ae08f94791697d5aebdd8e8084788833b04b7307143c",
63
+ "get_loop_pulse": "cf3c386feaaf6c07d0d1c831451da8f7043ab43ef1e685ae7f90c824f58dd4cd",
64
64
  "get_loop_summary": "e37bbc18048c039876dd4f4ba640dbb5e1efaf968fb887cb5479683e2840c1cc",
65
65
  "get_mcp_usage": "3e75743113a8e232c831f915edec38143528e92c504219ac3921d524f738df10",
66
66
  "get_media_url": "2132e420dc3df1a3c6b8d421b3530be23a7282b8f03af53d26707b337de73370",
@@ -84,22 +84,22 @@
84
84
  "quality_check": "a627658f804100276c6335cda8c2359edea163c155fa48aba6adb8749a80416a",
85
85
  "quality_check_plan": "9ce43929418427b4b256afc621283c7e887b97e208da849f4ae02418c2103954",
86
86
  "read_agent_reflection": "bdf834b25577eb7d8b992b7e7b2462949b2ffd6da664a38eba8b53d1ec8fa5a6",
87
- "record_campaign_spend": "7cade3b29ccfb9bdc1634ec8199eea14abd79594da1bb1ad59f6f481db7ca2e6",
88
- "record_intel_signal": "9f3db98ec4343e53b488367273383e15f1d89ef27a7e96b3b2db1aa3d6175a91",
89
- "record_observation": "64f26ca189c6b4ebf6c02436fc3392ac12fdc86361ba5a50feb7d8c9dab1a849",
90
- "record_outcome": "8d5be6f47e9d925bb8f13d6a94041e72c097337f34deecb246e576c7d651e122",
91
- "record_voice_lesson": "47707d359ccdae2953e63aa8443ea981784c4e3f0a0e986ecc84ea6b564814db",
87
+ "record_campaign_spend": "a7d94f00b4f8875b21f851d5f608a590c958e0acf75cfc45f16c1688f8cd2d2e",
88
+ "record_intel_signal": "35e0841dd77b8d388692fdc15de13f745b8629a57444ca41fc431a4bb2bff3fd",
89
+ "record_observation": "6b560716f8e594016e7a00cca06c1c7988cf72e9890132c680510713e03f1e70",
90
+ "record_outcome": "4b89861c57ea54cbefe48e372c4f0e7a1dbedd1aa3588729c359e5dcf8029f6a",
91
+ "record_voice_lesson": "c296119b1985179f9196e5c220847f0e696dc6b1eaa0017f21f817948bb15914",
92
92
  "refresh_platform_analytics": "2cb4e3c060aa26c6616c09fd5c8831f324fb232a2cdb17c69413db26ee732503",
93
93
  "render_demo_video": "cd7c3e1c0aef1f4c785f3dda46134cf37717911cfccb8c0513cd1768213005f2",
94
94
  "render_hyperframes": "517ccab72014c626074c6da94867b0e81cda51b20b6609287dec9b8dd22f8386",
95
95
  "render_template_video": "e696bc84a44e797807f247d5ceb85c66e65657edb82d8ed3e23ef82760fadace",
96
96
  "reply_to_comment": "25756f7c2a8c2cd133d7b9623b395c76a452be11f1e9e795a7b03111fd2c1858",
97
97
  "respond_plan_approval": "60ef3827a8e199b140a1008746947d6753e766505b2dc69291dccddb276216e2",
98
- "run_content_pipeline": "bea1d7d9fab5248af8cc5668527606b8299fa8f884cea0e8f226f6d32d91f98e",
99
- "run_skill": "b7e6fd660ba7763be53536ac72af56bbf601ba23c2d2a8a55dcc7b678ba4190e",
98
+ "run_content_pipeline": "f34d8e01125aa430bde4b3ac06d8d2bdcee64d45e7227ba8e08100e300ad2a69",
99
+ "run_skill": "c9180db96375a64952e292d06f4e39e33241906ba855e77443a528d20cbf474d",
100
100
  "save_brand_profile": "70c40bbd3f80b0fb7251050cbd75e5dab79e03b7b6cacc4fa702301e6c7cda67",
101
101
  "save_content_plan": "e876db2b255acd39bdced9d5ff9ea0f1b6472343cb6833f9b9d402c7edb27e67",
102
- "save_draft_to_library": "87ed0dcd83f92c76ebd2f080119c92415b7bdffefd404caa32f91be09d3bd9e5",
102
+ "save_draft_to_library": "cb7c13b408e959ce7b78ed4f9c87872558366a9b9bd7cec8a32f0d9c59541f75",
103
103
  "schedule_content_plan": "c61002f034b198e8f405cf9a3a05032d9639ddee75ea6649f08a432fca46542d",
104
104
  "schedule_post": "4bd2efdcf9584c579e8adacdfca8d75bdb2c4783013432b047bf3cc521bdd5c1",
105
105
  "search": "a985b8fb8cdfc6260bab53bd7aa0c2f02b1f0107c2d3cc4e6c845f7b5f5b0018",
@@ -114,6 +114,6 @@
114
114
  "visual_gate_constraints": "77d8c1eeba11d9aa4d3af08609c4c1f1ee365c2d14410efd16a2b9911dd76a8e",
115
115
  "visual_quality_check": "63a52d8f38864162b901bdb38cc69032e9f530a8a53f11db617f87b62d70f36c",
116
116
  "wait_for_connection": "3f3a587e9a6d3a32e6dd23600665eee1a428721ea352daf6eefafd746bfff8e7",
117
- "write_agent_reflection": "04384e55b02a2fbefa6d1d6777dbb161cac91f7d6f080a09094ff2d12f9c2538"
117
+ "write_agent_reflection": "eb8faeb1dc7e1e6484a475a82b801108e9c7bbcca20918f73a07f7f4eae724b0"
118
118
  }
119
119
  }