@noelclaw/mcp 2.3.1 → 2.4.0

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.
@@ -5,6 +5,7 @@ exports.handleSwarmTool = handleSwarmTool;
5
5
  const zod_1 = require("zod");
6
6
  const convex_js_1 = require("../convex.js");
7
7
  const market_js_1 = require("./market.js");
8
+ const memory_js_1 = require("./memory.js");
8
9
  function formatDate(ts) {
9
10
  return new Date(ts).toUTCString();
10
11
  }
@@ -82,21 +83,22 @@ exports.SWARM_TOOLS = [
82
83
  {
83
84
  name: "trigger_agent",
84
85
  description: "Run a single swarm agent immediately — automatically starts the swarm if needed. " +
85
- "Costs 100 credits per call. " +
86
- "market-monitor fetches live price data, sentiment-tracker analyzes sentiment, " +
87
- "memory-manager compresses ephemeral memory, risk-verifier evaluates an action for risk. " +
86
+ "Agents: market-monitor (live prices), sentiment-tracker (social/sentiment), " +
87
+ "memory-manager (compress memory), risk-verifier (evaluate risk), " +
88
+ "onchain-analyst (on-chain data: wallets, flows, TVL), " +
89
+ "news-aggregator (latest news + narrative tracking). " +
88
90
  "Results are saved to vault automatically.",
89
91
  inputSchema: {
90
92
  type: "object",
91
93
  properties: {
92
94
  agentId: {
93
95
  type: "string",
94
- enum: ["market-monitor", "sentiment-tracker", "memory-manager", "risk-verifier", "workflow-executor"],
96
+ enum: ["market-monitor", "sentiment-tracker", "memory-manager", "risk-verifier", "workflow-executor", "onchain-analyst", "news-aggregator"],
95
97
  description: "Which agent to run",
96
98
  },
97
99
  params: {
98
100
  type: "object",
99
- description: "Agent-specific params. market-monitor: { token: 'BTC' }. sentiment-tracker: { token: 'ETH' } or { topic: 'Layer 2s' }. risk-verifier: { type: 'swap', params: { ... } }.",
101
+ description: "Agent-specific params. market-monitor: { token: 'BTC' }. sentiment-tracker: { token: 'ETH' } or { topic: 'Layer 2s' }. onchain-analyst: { address: '0x...' } or { protocol: 'Morpho' }. news-aggregator: { topic: 'Base ecosystem' }.",
100
102
  },
101
103
  },
102
104
  required: ["agentId"],
@@ -115,6 +117,71 @@ exports.SWARM_TOOLS = [
115
117
  required: [],
116
118
  },
117
119
  },
120
+ {
121
+ name: "swarm_broadcast",
122
+ description: "Broadcast a message or signal to all active swarm agents simultaneously. " +
123
+ "All agents will receive and act on the message in their next cycle. " +
124
+ "Use to coordinate the swarm: change focus, alert about market conditions, or inject a directive.",
125
+ inputSchema: {
126
+ type: "object",
127
+ properties: {
128
+ message: { type: "string", description: "Message to broadcast to all agents (max 500 chars)" },
129
+ priority: { type: "string", enum: ["low", "normal", "high", "urgent"], description: "Message priority (default: normal)" },
130
+ targetAgents: { type: "array", items: { type: "string" }, description: "Optional: target specific agent IDs. Omit to broadcast to all." },
131
+ },
132
+ required: ["message"],
133
+ },
134
+ },
135
+ {
136
+ name: "swarm_pulse",
137
+ description: "Get an instant snapshot from all swarm agents — market prices, sentiment, on-chain activity, and agent health. " +
138
+ "Unlike get_swarm_status, swarm_pulse triggers all agents to report their latest readings right now. " +
139
+ "Best for a quick market briefing or sanity check before making decisions.",
140
+ inputSchema: {
141
+ type: "object",
142
+ properties: {
143
+ token: { type: "string", description: "Optional: focus the pulse on a specific token (e.g. 'BTC', 'ETH')" },
144
+ },
145
+ required: [],
146
+ },
147
+ },
148
+ {
149
+ name: "swarm_reflect",
150
+ description: "Consolidate everything the swarm has learned into a single coherent intelligence summary. " +
151
+ "Reads all recent research vault entries from swarm agents, groups by agent, extracts key signals, " +
152
+ "and saves a unified reflection to vault. " +
153
+ "Run this after swarm_research or swarm_pulse to crystallize findings into long-term memory. " +
154
+ "Best used once per research session or daily for ongoing monitoring.",
155
+ inputSchema: {
156
+ type: "object",
157
+ properties: {
158
+ hoursBack: { type: "number", description: "How many hours of swarm activity to include (default: 24)" },
159
+ focus: { type: "string", description: "Optional: focus reflection on a specific topic or token" },
160
+ },
161
+ required: [],
162
+ },
163
+ },
164
+ {
165
+ name: "swarm_watch",
166
+ description: "Register a topic or token for continuous swarm monitoring. " +
167
+ "Swarm agents will prioritize this topic in every cycle — price changes, sentiment shifts, news, on-chain flows. " +
168
+ "Findings are saved to vault and semantic memory automatically. " +
169
+ "Use memory_insight or swarm_brief to check accumulated findings. " +
170
+ "Alert conditions: price_spike | sentiment_shift | news | whale_move | all.",
171
+ inputSchema: {
172
+ type: "object",
173
+ properties: {
174
+ topic: { type: "string", description: "What to watch — token symbol, protocol name, or any topic (e.g. 'ETH', 'Lido', 'Base ecosystem')" },
175
+ priority: { type: "string", enum: ["low", "normal", "high"], description: "Monitoring priority (default: normal)" },
176
+ alertOn: {
177
+ type: "array",
178
+ items: { type: "string", enum: ["price_spike", "sentiment_shift", "news", "whale_move", "all"] },
179
+ description: "Which signals to watch for (default: price_spike, sentiment_shift, news)",
180
+ },
181
+ },
182
+ required: ["topic"],
183
+ },
184
+ },
118
185
  ];
119
186
  const StartSwarmSchema = zod_1.z.object({
120
187
  config: zod_1.z.object({
@@ -127,9 +194,21 @@ const GetMemorySchema = zod_1.z.object({ key: zod_1.z.string().min(1) });
127
194
  const ResearchSchema = zod_1.z.object({ topic: zod_1.z.string().min(1), depth: zod_1.z.enum(["quick", "standard", "deep"]).optional() });
128
195
  const BriefSchema = zod_1.z.object({ limit: zod_1.z.number().optional() });
129
196
  const TriggerAgentSchema = zod_1.z.object({
130
- agentId: zod_1.z.enum(["market-monitor", "sentiment-tracker", "memory-manager", "risk-verifier", "workflow-executor"]),
197
+ agentId: zod_1.z.enum(["market-monitor", "sentiment-tracker", "memory-manager", "risk-verifier", "workflow-executor", "onchain-analyst", "news-aggregator"]),
131
198
  params: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).optional(),
132
199
  });
200
+ const BroadcastSchema = zod_1.z.object({
201
+ message: zod_1.z.string().min(1).max(500),
202
+ priority: zod_1.z.enum(["low", "normal", "high", "urgent"]).optional(),
203
+ targetAgents: zod_1.z.array(zod_1.z.string()).optional(),
204
+ });
205
+ const PulseSchema = zod_1.z.object({ token: zod_1.z.string().optional() });
206
+ const ReflectSchema = zod_1.z.object({ hoursBack: zod_1.z.number().positive().optional(), focus: zod_1.z.string().optional() });
207
+ const WatchSchema = zod_1.z.object({
208
+ topic: zod_1.z.string().min(1),
209
+ priority: zod_1.z.enum(["low", "normal", "high"]).optional(),
210
+ alertOn: zod_1.z.array(zod_1.z.enum(["price_spike", "sentiment_shift", "news", "whale_move", "all"])).optional(),
211
+ });
133
212
  const NO_KEY_MSG = `🔑 Swarm tools require a NoelClaw API key.\n\n` +
134
213
  `→ Get yours free at: https://noelclaw.com\n\n` +
135
214
  `Then add it to your MCP config:\n` +
@@ -272,6 +351,9 @@ async function handleSwarmTool(name, args) {
272
351
  if (!parsed.success)
273
352
  return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
274
353
  const { topic, depth = "standard" } = parsed.data;
354
+ // Check what's already known before launching — show value immediately
355
+ const [priorMem] = await Promise.allSettled([(0, memory_js_1.searchSupermemory)(topic, 4)]);
356
+ const priorResults = priorMem.status === "fulfilled" ? priorMem.value : [];
275
357
  await (0, convex_js_1.callConvex)("/swarm/start", "POST", {}, "start_swarm").catch(() => { });
276
358
  let data;
277
359
  try {
@@ -282,17 +364,26 @@ async function handleSwarmTool(name, args) {
282
364
  }
283
365
  if (data.error)
284
366
  return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
367
+ const priorSection = priorResults.length > 0
368
+ ? [
369
+ ``,
370
+ `**Prior knowledge loaded (${priorResults.length} memories):**`,
371
+ ...priorResults.map(r => `• ${r.metadata?.title ?? r.content.slice(0, 80).replace(/\n/g, " ")}`),
372
+ `Agents will build on this — no re-discovering what you already know.`,
373
+ ]
374
+ : [``, `No prior knowledge found — agents starting fresh.`];
285
375
  return {
286
376
  content: [{
287
377
  type: "text",
288
378
  text: [
289
379
  `🔬 **Swarm Research Started**`,
290
- `Topic: ${topic}`,
291
- `Depth: ${depth}`,
380
+ `Topic: ${topic} · Depth: ${depth}`,
381
+ ...priorSection,
292
382
  ``,
293
- data.message ?? "Research triggered. Findings will appear in vault.",
383
+ data.message ?? "Research triggered. Findings will appear in vault automatically.",
294
384
  ``,
295
- `Use \`vault_context topic: "${topic}"\` to retrieve results once complete.`,
385
+ `**Next:** \`swarm_reflect focus: "${topic}"\` to consolidate findings`,
386
+ `Or: \`memory_insight topic: "${topic}"\` to see full intelligence report`,
296
387
  ].join("\n"),
297
388
  }],
298
389
  };
@@ -303,9 +394,16 @@ async function handleSwarmTool(name, args) {
303
394
  return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
304
395
  const { agentId, params = {} } = parsed.data;
305
396
  await (0, convex_js_1.callConvex)("/swarm/start", "POST", {}, "start_swarm").catch(() => { });
397
+ // Auto-load prior context so agent doesn't re-discover what's already known
398
+ const contextQuery = [agentId, params?.token, params?.topic, params?.protocol].filter(Boolean).join(" ");
399
+ const priorContext = await (0, memory_js_1.searchSupermemory)(contextQuery, 3);
400
+ const priorSummary = priorContext.length > 0
401
+ ? priorContext.map(r => r.content.slice(0, 150)).join(" | ")
402
+ : null;
403
+ const enrichedParams = priorSummary ? { ...params, priorContext: priorSummary } : params;
306
404
  let data;
307
405
  try {
308
- data = await (0, convex_js_1.callConvex)("/swarm/trigger", "POST", { agentId, params }, "trigger_agent");
406
+ data = await (0, convex_js_1.callConvex)("/swarm/trigger", "POST", { agentId, params: enrichedParams }, "trigger_agent");
309
407
  }
310
408
  catch (err) {
311
409
  return swarmAuthError(err);
@@ -313,15 +411,17 @@ async function handleSwarmTool(name, args) {
313
411
  if (data.error)
314
412
  return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
315
413
  const resultText = data.result ? `\n\`\`\`json\n${JSON.stringify(data.result, null, 2).slice(0, 800)}\n\`\`\`` : "";
414
+ const contextNote = priorContext.length > 0 ? `🧠 ${priorContext.length} prior memory entries injected into agent context.` : "";
316
415
  return {
317
416
  content: [{
318
417
  type: "text",
319
418
  text: [
320
419
  `⚡ **${agentId} triggered**`,
321
- `100 credits charged.`,
420
+ contextNote,
322
421
  resultText,
323
422
  ``,
324
- `Use \`vault_context topic: "${agentId.replace("-", " ")}"\` to retrieve findings.`,
423
+ `Findings saved to vault automatically.`,
424
+ `Use \`swarm_reflect\` to consolidate or \`memory_insight topic: "${contextQuery.split(" ")[1] ?? agentId}"\` for full report.`,
325
425
  ].filter(Boolean).join("\n"),
326
426
  }],
327
427
  };
@@ -350,9 +450,222 @@ async function handleSwarmTool(name, args) {
350
450
  lines.push(`**[${e.agentId}]** ${e.title}`);
351
451
  lines.push(` _${e.key}_ · v${e.version} · ${formatDate(e.updatedAt)}`);
352
452
  }
353
- lines.push(`\nUse \`vault_context topic: "<topic>"\` to load full content for any research area.`);
453
+ lines.push(`\nUse \`memory_context topic: "<topic>"\` to load full content for any research area.`);
454
+ return { content: [{ type: "text", text: lines.join("\n") }] };
455
+ }
456
+ case "swarm_broadcast": {
457
+ const parsed = BroadcastSchema.safeParse(args);
458
+ if (!parsed.success)
459
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
460
+ const { message, priority = "normal", targetAgents } = parsed.data;
461
+ let data;
462
+ try {
463
+ data = await (0, convex_js_1.callConvex)("/swarm/broadcast", "POST", { message, priority, targetAgents }, "swarm_broadcast");
464
+ }
465
+ catch (err) {
466
+ return swarmAuthError(err);
467
+ }
468
+ if (data.error)
469
+ return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
470
+ const targets = targetAgents?.join(", ") ?? "all agents";
471
+ return {
472
+ content: [{
473
+ type: "text",
474
+ text: [
475
+ `📡 **Broadcast sent** [${priority}]`,
476
+ `To: ${targets}`,
477
+ `Message: "${message}"`,
478
+ data.deliveredTo ? `Delivered to ${data.deliveredTo} agent(s)` : "",
479
+ ].filter(Boolean).join("\n"),
480
+ }],
481
+ };
482
+ }
483
+ case "swarm_pulse": {
484
+ const parsed = PulseSchema.safeParse(args ?? {});
485
+ if (!parsed.success)
486
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
487
+ const { token } = parsed.data;
488
+ await (0, convex_js_1.callConvex)("/swarm/start", "POST", {}, "start_swarm").catch(() => { });
489
+ let data;
490
+ try {
491
+ data = await (0, convex_js_1.callConvex)("/swarm/pulse", "POST", { token }, "swarm_pulse");
492
+ }
493
+ catch (err) {
494
+ return swarmAuthError(err);
495
+ }
496
+ if (data.error)
497
+ return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
498
+ const readings = data.readings ?? [];
499
+ const lines = [
500
+ `💓 **Swarm Pulse**${token ? ` — ${token}` : ""}`,
501
+ `Agents reporting: ${readings.length}`,
502
+ ``,
503
+ ];
504
+ for (const r of readings) {
505
+ lines.push(`**[${r.agentId}]** ${r.summary ?? "No data"}`);
506
+ if (r.data) {
507
+ const preview = JSON.stringify(r.data).slice(0, 120);
508
+ lines.push(` ${preview}${preview.length === 120 ? "…" : ""}`);
509
+ }
510
+ lines.push("");
511
+ }
512
+ if (!readings.length)
513
+ lines.push("No agents responding. Use `start_swarm` first.");
354
514
  return { content: [{ type: "text", text: lines.join("\n") }] };
355
515
  }
516
+ case "swarm_reflect": {
517
+ const parsed = ReflectSchema.safeParse(args ?? {});
518
+ if (!parsed.success)
519
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
520
+ const { hoursBack = 24, focus } = parsed.data;
521
+ const params = new URLSearchParams({ type: "research", limit: "30" });
522
+ let data;
523
+ try {
524
+ data = await (0, convex_js_1.callConvex)(`/vault/list?${params}`, "GET", undefined, "swarm_reflect");
525
+ }
526
+ catch (err) {
527
+ return swarmAuthError(err);
528
+ }
529
+ if (data.error)
530
+ return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
531
+ const SWARM_AGENTS = new Set(["market-monitor", "sentiment-tracker", "onchain-analyst", "news-aggregator", "risk-verifier", "memory-manager"]);
532
+ const cutoff = Date.now() - hoursBack * 3600000;
533
+ const entries = (data.entries ?? []).filter((e) => {
534
+ const isSwarm = SWARM_AGENTS.has(e.agentId);
535
+ const isRecent = e.updatedAt >= cutoff;
536
+ const matchesFocus = !focus || e.title.toLowerCase().includes(focus.toLowerCase());
537
+ return isSwarm && isRecent && matchesFocus;
538
+ });
539
+ if (!entries.length) {
540
+ return {
541
+ content: [{
542
+ type: "text",
543
+ text: [
544
+ `📋 **Swarm Reflection** — Nothing to consolidate`,
545
+ ``,
546
+ `No swarm research found in the last ${hoursBack}h${focus ? ` about "${focus}"` : ""}.`,
547
+ ``,
548
+ `Start one: \`swarm_research topic: "${focus ?? "market overview"}"\``,
549
+ ].join("\n"),
550
+ }],
551
+ };
552
+ }
553
+ // Group by agent
554
+ const byAgent = {};
555
+ for (const e of entries) {
556
+ const a = e.agentId ?? "unknown";
557
+ (byAgent[a] ?? (byAgent[a] = [])).push(e);
558
+ }
559
+ const agentSummaries = Object.entries(byAgent).map(([agent, es]) => {
560
+ const items = es.map((e) => ` • ${e.title}`).join("\n");
561
+ return `**[${agent}]** — ${es.length} finding(s)\n${items}`;
562
+ });
563
+ const signals = entries
564
+ .sort((a, b) => b.updatedAt - a.updatedAt)
565
+ .slice(0, 6)
566
+ .map((e) => `• [${e.agentId}] ${e.title}`);
567
+ // Write synthesis to vault for long-term memory
568
+ const now = new Date();
569
+ const reflectionKey = `swarm/reflection-${now.toISOString().slice(0, 10)}-${now.getHours()}h`;
570
+ const reflectionContent = [
571
+ `# Swarm Reflection — ${now.toUTCString()}`,
572
+ `Period: last ${hoursBack}h${focus ? ` · Focus: ${focus}` : ""} · ${entries.length} entries from ${Object.keys(byAgent).length} agent(s)`,
573
+ ``,
574
+ `## Agent Findings`,
575
+ agentSummaries.join("\n\n"),
576
+ ``,
577
+ `## Key Signals`,
578
+ signals.join("\n"),
579
+ ].join("\n");
580
+ const saved = await (0, convex_js_1.callConvex)("/vault/save", "POST", {
581
+ type: "research",
582
+ title: `Swarm Reflection — ${now.toLocaleDateString("en-US")}${focus ? ` — ${focus}` : ""}`,
583
+ content: reflectionContent,
584
+ key: reflectionKey,
585
+ agentId: "swarm-coordinator",
586
+ tags: ["reflection", "swarm", ...(focus ? [focus] : [])],
587
+ commitMsg: "swarm_reflect auto-consolidation",
588
+ }, "swarm_reflect").catch(() => null);
589
+ // Also sync to semantic memory so it's retrievable by topic
590
+ if (saved) {
591
+ (0, memory_js_1.syncToSupermemory)(reflectionContent, {
592
+ vaultKey: reflectionKey, title: `Swarm Reflection ${now.toLocaleDateString("en-US")}`,
593
+ type: "research", tags: ["reflection"], source: "swarm_reflect",
594
+ });
595
+ }
596
+ return {
597
+ content: [{
598
+ type: "text",
599
+ text: [
600
+ `📋 **Swarm Reflection** — ${entries.length} findings from ${Object.keys(byAgent).length} agent(s)`,
601
+ `Period: last ${hoursBack}h${focus ? ` · Focus: ${focus}` : ""}`,
602
+ ``,
603
+ agentSummaries.join("\n\n"),
604
+ ``,
605
+ `**Key signals:**`,
606
+ ...signals,
607
+ ``,
608
+ saved ? `✅ Synthesis saved to vault: \`${reflectionKey}\`` : "⚠️ Could not save synthesis",
609
+ ``,
610
+ `Use \`memory_insight topic: "${focus ?? "swarm research"}"\` for the full intelligence picture.`,
611
+ ].filter(Boolean).join("\n"),
612
+ }],
613
+ };
614
+ }
615
+ case "swarm_watch": {
616
+ const parsed = WatchSchema.safeParse(args);
617
+ if (!parsed.success)
618
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
619
+ const { topic, priority = "normal", alertOn = ["price_spike", "sentiment_shift", "news"] } = parsed.data;
620
+ const watchKey = `watch:${topic.toLowerCase().replace(/\s+/g, "-")}`;
621
+ const watchConfig = JSON.stringify({
622
+ topic, priority,
623
+ alertOn: alertOn.includes("all") ? ["price_spike", "sentiment_shift", "news", "whale_move"] : alertOn,
624
+ registeredAt: Date.now(),
625
+ });
626
+ // Write to swarm shared memory (picked up by agents in next cycle)
627
+ await (0, convex_js_1.callConvex)("/swarm/memory/write", "POST", {
628
+ agentId: "swarm-coordinator",
629
+ key: watchKey,
630
+ value: watchConfig,
631
+ }, "swarm_watch").catch(() => { });
632
+ // Persist to vault so it survives swarm restarts
633
+ await (0, convex_js_1.callConvex)("/vault/save", "POST", {
634
+ type: "memory",
635
+ title: `Watch: ${topic}`,
636
+ content: watchConfig,
637
+ key: `watch/${topic.toLowerCase().replace(/\s+/g, "-")}`,
638
+ agentId: "swarm-coordinator",
639
+ tags: ["watch", "monitor", topic.toLowerCase()],
640
+ commitMsg: "swarm_watch registered",
641
+ }, "swarm_watch").catch(() => { });
642
+ const alertList = (alertOn.includes("all")
643
+ ? ["price_spike", "sentiment_shift", "news", "whale_move"]
644
+ : alertOn).join(", ");
645
+ return {
646
+ content: [{
647
+ type: "text",
648
+ text: [
649
+ `👁️ **Watch Registered: ${topic}**`,
650
+ `Priority: ${priority} · Alerts: ${alertList}`,
651
+ ``,
652
+ `Swarm agents will now prioritize "${topic}" in every monitoring cycle.`,
653
+ `Watch key: \`${watchKey}\``,
654
+ ``,
655
+ `**What happens next:**`,
656
+ `• market-monitor will track price & volume anomalies`,
657
+ `• sentiment-tracker will watch social signal shifts`,
658
+ `• news-aggregator will flag relevant news & narratives`,
659
+ alertOn.includes("whale_move") || alertOn.includes("all") ? `• onchain-analyst will detect large wallet movements` : "",
660
+ ``,
661
+ `**Check findings:**`,
662
+ `• \`swarm_brief\` — latest research entries`,
663
+ `• \`memory_insight topic: "${topic}"\` — full intelligence report`,
664
+ `• \`swarm_reflect focus: "${topic}"\` — consolidated summary`,
665
+ ].filter(Boolean).join("\n"),
666
+ }],
667
+ };
668
+ }
356
669
  default:
357
670
  return null;
358
671
  }