@noelclaw/mcp 3.2.0 → 3.3.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.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@noelclaw/mcp.svg)](https://www.npmjs.com/package/@noelclaw/mcp)
4
4
 
5
- 74 MCP tools for your AI — persistent memory, DeFi execution on Base, multi-agent research, automations, and on-chain actions.
5
+ 81 MCP tools for your AI — persistent memory, autonomous research monitors, live web search, DeFi execution on Base, multi-agent swarms, and on-chain actions.
6
6
 
7
7
  No API key required to start. Ask your AI in plain English.
8
8
 
@@ -21,37 +21,7 @@ npx -y @noelclaw/mcp
21
21
 
22
22
  ### Claude Code
23
23
  ```bash
24
- claude mcp add noelclaw -- npx -y @noelclaw/mcp
25
- ```
26
-
27
- ### Aeon
28
-
29
- Open **Settings → MCP Servers** and add:
30
-
31
- ```json
32
- {
33
- "mcpServers": {
34
- "noelclaw": {
35
- "command": "npx",
36
- "args": ["-y", "@noelclaw/mcp"]
37
- }
38
- }
39
- }
40
- ```
41
-
42
- ### Hermes
43
-
44
- Open **Settings → MCP Servers** and add:
45
-
46
- ```json
47
- {
48
- "mcpServers": {
49
- "noelclaw": {
50
- "command": "npx",
51
- "args": ["-y", "@noelclaw/mcp"]
52
- }
53
- }
54
- }
24
+ claude mcp add noelclaw -s user -- npx -y @noelclaw/mcp
55
25
  ```
56
26
 
57
27
  ### Claude Desktop
@@ -69,7 +39,7 @@ Add to `%APPDATA%\Claude\claude_desktop_config.json` (Windows) or `~/Library/App
69
39
  }
70
40
  ```
71
41
 
72
- ### Any Other MCP Client
42
+ ### Aeon / Hermes / Cursor / Windsurf
73
43
 
74
44
  ```json
75
45
  {
@@ -91,27 +61,24 @@ Restart your client. Tools load automatically.
91
61
  Ask your AI anything — it routes to the right tool automatically.
92
62
 
93
63
  ```
94
- what's the ETH gas price and latest block on Base?
64
+ search for AI agent news today
65
+ → Returns top results from across the web, summarized
66
+
67
+ set up a daily monitor for AI agents news
68
+ → ✓ monitor created · runs daily at 8am · findings saved to vault + Telegram
69
+
70
+ what's the ETH gas price on Base right now?
95
71
  → Gas: 0.006 gwei · Block: 46,773,463 · ETH: $1,991
96
72
 
97
73
  show me the best yield vaults on Base right now
98
74
  → Clearstar USDC Reactor: 7.57% APY ($2.6M TVL)
99
75
  → Moonwell Flagship USDC: 4.63% APY ($9.1M TVL)
100
76
 
101
- remember my Aerodrome thesis for next time
102
- saved to Vault · auto-loaded every session
103
-
104
- set up weekly $100 ETH DCA every Monday
105
- → ✓ automation created · runs weekly 09:00 UTC
77
+ give me a bull vs bear thesis on ETH
78
+ Full analysis · auto-saved to vault
106
79
 
107
80
  swap 0.5 ETH to USDC on Base
108
81
  → ✓ swapped → 1,842 USDC · tx confirmed in 2s
109
-
110
- swarm research topic: "ETH vs SOL which wins 2025"
111
- → Triggers multi-agent research, saves findings to your vault
112
-
113
- generate a Solidity ERC-20 with burn and pause features
114
- → Returns production-ready OpenZeppelin contract
115
82
  ```
116
83
 
117
84
  ---
@@ -126,7 +93,7 @@ Three pillars: **Remember** · **Act** · **Know**
126
93
 
127
94
  Your AI loads your context before you type a single word.
128
95
 
129
- #### Vault — Structured Notes (15 tools)
96
+ #### Vault — Structured Notes (12 tools)
130
97
 
131
98
  > Save research, decisions, and notes. Every entry is versioned and auto-tagged.
132
99
 
@@ -141,14 +108,11 @@ Your AI loads your context before you type a single word.
141
108
  | **Vault Export** `vault_export` | Export vault as JSON or markdown |
142
109
  | **Store Credential** `vault_store_credential` | Securely store an API key or secret |
143
110
  | **Get Credential** `vault_get_credential` | Retrieve a stored credential |
144
- | **Vault Publish** `vault_publish` | Publish an entry as a public note |
145
- | **Vault Explore** `vault_explore` | Browse by tag or category |
146
111
  | **Vault Pin** `vault_pin` | Pin an important entry to the top |
147
112
  | **Vault Delete** `vault_delete` | Delete an entry |
148
- | **Vault Link** `vault_link` | Link two entries together |
149
113
  | **Vault Tag** `vault_tag` | Add or update tags on an entry |
150
114
 
151
- #### Memory — Semantic Search (7 tools)
115
+ #### Memory — Semantic Search (9 tools)
152
116
 
153
117
  > Find anything by meaning, not keywords. "What did I say about ETH yield?" just works.
154
118
 
@@ -161,6 +125,8 @@ Your AI loads your context before you type a single word.
161
125
  | **Memory List** `memory_list` | List recent memory entries |
162
126
  | **Memory Delete** `memory_delete` | Remove a memory entry |
163
127
  | **Memory Insight** `memory_insight` | AI-generated insights from your memory patterns |
128
+ | **Memory Extract** `memory_extract` | Auto-extract discrete facts from any text and save each individually |
129
+ | **Memory Consolidate** `memory_consolidate` | Merge overlapping memories on a topic into one clean summary |
164
130
 
165
131
  #### OS — Session Lifecycle (3 tools)
166
132
 
@@ -176,6 +142,16 @@ Your AI loads your context before you type a single word.
176
142
 
177
143
  Tell it what to do. It runs — on schedule, on-chain, or right now.
178
144
 
145
+ #### Autonomous Monitor (3 tools)
146
+
147
+ > Runs research on a schedule with no prompting needed.
148
+
149
+ | Name | Description |
150
+ |------|-------------|
151
+ | **Create Monitor** `create_monitor` | Schedule a recurring research agent for any topic |
152
+ | **List Monitors** `list_monitors` | View all active monitors with schedule and next run |
153
+ | **Cancel Monitor** `cancel_monitor` | Stop a monitor by ID |
154
+
179
155
  #### Automations (6 tools)
180
156
 
181
157
  | Name | Description |
@@ -187,7 +163,7 @@ Tell it what to do. It runs — on schedule, on-chain, or right now.
187
163
  | **Automation Runs** `get_automation_runs` | Execution history |
188
164
  | **Run Now** `run_automation` | Trigger an automation manually |
189
165
 
190
- #### DeFi Execution (7 tools)
166
+ #### DeFi Execution (6 tools)
191
167
 
192
168
  > Transactions are signed client-side — your private key never leaves your machine.
193
169
 
@@ -197,7 +173,6 @@ Tell it what to do. It runs — on schedule, on-chain, or right now.
197
173
  | **Estimate Swap** `estimate_swap` | Get a swap quote via 0x before executing |
198
174
  | **Swap Tokens** `swap_tokens` | Execute a token swap on Base |
199
175
  | **Send Token** `send_token` | Send ETH or ERC-20 to any address |
200
- | **Scan Wallet** `scan_wallet` | Analyze a wallet — holdings, activity, risk signals |
201
176
  | **Analyze Wallet** `analyze_wallet` | Deep wallet analysis with on-chain patterns |
202
177
  | **DeFi Yields** `get_defi_yields` | Find the best yield opportunities on Base |
203
178
 
@@ -215,20 +190,17 @@ Tell it what to do. It runs — on schedule, on-chain, or right now.
215
190
  | Name | Description |
216
191
  |------|-------------|
217
192
  | **Wallet Address** `get_wallet_address` | Get or generate your MCP wallet address |
218
- | **Set Telegram** `set_telegram` | Connect Telegram for automation notifications |
193
+ | **Set Telegram** `set_telegram` | Connect Telegram for monitor and automation notifications |
219
194
 
220
- #### Playbooks (6 tools)
195
+ #### Playbooks (3 tools)
221
196
 
222
197
  > Reusable sequences with safety rules applied before any action runs.
223
198
 
224
199
  | Name | Description |
225
200
  |------|-------------|
226
- | **Create Task** `create_task_packet` | Convert intent into a structured task with constraints |
227
- | **List Tasks** `list_task_packets` | List all task packets |
228
201
  | **List Playbooks** `list_playbooks` | Browse available playbooks |
229
202
  | **Run Playbook** `run_playbook` | Execute a playbook by ID |
230
- | **Noel Ledger** `get_noel_ledger` | Credits and full audit trail |
231
- | **Sentinel Rules** `get_sentinel_rules` | View safety rules applied before actions |
203
+ | **Noel Ledger** `get_noel_ledger` | Audit trail of all agent actions |
232
204
 
233
205
  ---
234
206
 
@@ -236,6 +208,13 @@ Tell it what to do. It runs — on schedule, on-chain, or right now.
236
208
 
237
209
  Always informed before you act.
238
210
 
211
+ #### Web Research (2 tools)
212
+
213
+ | Name | Description |
214
+ |------|-------------|
215
+ | **Web Search** `web_search` | Search the web in real time for any topic |
216
+ | **Web Scrape** `web_scrape` | Read the full content of any URL |
217
+
239
218
  #### Market & Prices (5 tools)
240
219
 
241
220
  | Name | Description |
@@ -260,10 +239,10 @@ Always informed before you act.
260
239
  | Name | Description |
261
240
  |------|-------------|
262
241
  | **Ask Noel** `ask_noel` | AI crypto analyst — opinions, trade ideas, market outlook |
263
- | **Market Thesis** `market_thesis` | Investment thesis for any token or sector |
264
- | **Trade Plan** `trade_plan` | Structured trade plan with entry, exit, and risk levels |
242
+ | **Market Thesis** `market_thesis` | Investment thesis for any token or sector — auto-saved to vault |
243
+ | **Trade Plan** `trade_plan` | Structured trade plan with entry, exit, and risk levels — auto-saved to vault |
265
244
 
266
- #### Agent Network (15 tools)
245
+ #### Agent Network (8 tools)
267
246
 
268
247
  > Spin up multiple AI agents that research and monitor in parallel.
269
248
 
@@ -272,16 +251,9 @@ Always informed before you act.
272
251
  | **Start Swarm** `start_swarm` | Start the agent network |
273
252
  | **Stop Swarm** `stop_swarm` | Stop the active swarm |
274
253
  | **Swarm Status** `get_swarm_status` | Status and shared memory snapshot |
275
- | **Trigger Agent** `trigger_agent` | Run a specific agent now |
276
- | **Write Memory** `write_swarm_memory` | Write to shared agent memory |
277
- | **Read Memory** `get_swarm_memory` | Read from shared agent memory |
278
- | **Execution Scores** `get_execution_scores` | Performance scores across all agents |
279
254
  | **Swarm Research** `swarm_research` | Multi-agent research on any topic — saves to vault |
255
+ | **Trigger Agent** `trigger_agent` | Run a specific agent now |
280
256
  | **Swarm Brief** `swarm_brief` | Summary of everything the swarm has found |
281
- | **Broadcast** `swarm_broadcast` | Send a message to all active agents |
282
- | **Swarm Pulse** `swarm_pulse` | Heartbeat — active agents and last activity |
283
- | **Swarm Reflect** `swarm_reflect` | Agents self-evaluate what went well |
284
- | **Swarm Watch** `swarm_watch` | Watch a token or topic for changes |
285
257
  | **List Agents** `list_agents` | Browse available specialist agents |
286
258
  | **Hire Agent** `hire_agent` | Hire an agent for a specific task |
287
259
 
@@ -289,17 +261,15 @@ Always informed before you act.
289
261
 
290
262
  ### 🛠 BUILD — Developer & Content Tools
291
263
 
292
- #### Coder (7 tools)
264
+ #### Coder (5 tools)
293
265
 
294
266
  | Name | Description |
295
267
  |------|-------------|
296
- | **Scaffold Project** `scaffold_project` | Scaffold a DeFi or Web3 project |
297
- | **Generate Component** `generate_component` | React component with wagmi/viem |
298
268
  | **Generate Contract** `generate_contract` | Solidity smart contract |
299
269
  | **Audit Contract** `audit_contract` | Contract vulnerability audit |
300
270
  | **Explain Code** `explain_code` | Explain any code in plain English |
301
- | **Generate MCP Skill** `generate_mcp_skill` | Generate a new MCP tool from plain English |
302
271
  | **Review Code** `review_code` | Code review with actionable feedback |
272
+ | **Generate MCP Skill** `generate_mcp_skill` | Generate a new MCP tool from plain English |
303
273
 
304
274
  #### Content & Humanizer (3 tools)
305
275
 
@@ -321,9 +291,7 @@ Always informed before you act.
321
291
 
322
292
  ## Configuration
323
293
 
324
- No config required to start — all tools work out of the box via the Noelclaw backend.
325
-
326
- For additional capabilities, add to the `env` block in your MCP config:
294
+ All tools work out of the box. Add optional keys for extra features:
327
295
 
328
296
  ```json
329
297
  {
@@ -332,8 +300,8 @@ For additional capabilities, add to the `env` block in your MCP config:
332
300
  "command": "npx",
333
301
  "args": ["-y", "@noelclaw/mcp"],
334
302
  "env": {
335
- "NOELCLAW_API_KEY": "noel_sk_...",
336
- "ANTHROPIC_API_KEY": "sk-ant-..."
303
+ "FIRECRAWL_API_KEY": "fc-...",
304
+ "TRIGGER_SECRET_KEY": "tr_prod_..."
337
305
  }
338
306
  }
339
307
  }
@@ -342,10 +310,14 @@ For additional capabilities, add to the `env` block in your MCP config:
342
310
 
343
311
  | Variable | Purpose |
344
312
  |----------|---------|
345
- | `NOELCLAW_API_KEY` | Unlocks swarm, automation, framework, and agent tools. Get one at [app.noelclaw.com](https://app.noelclaw.com) |
346
- | `ANTHROPIC_API_KEY` | AI tools use your Claude quota — you pay, not the server |
347
- | `NOELCLAW_SESSION_TOKEN` | Session token from app.noelclaw.com (alternative to API key) |
348
- | `NOELCLAW_CONVEX_URL` | Override the backend URL (for self-hosted deployments) |
313
+ | `NOELCLAW_SESSION_TOKEN` | Session token from noelclaw.com recommended for full access |
314
+ | `ANTHROPIC_API_KEY` | Use your own Claude quota |
315
+ | `BANKR_API_KEY` | Use Bankr/Grok instead of Anthropic |
316
+ | `FIRECRAWL_API_KEY` | Required for `web_search` and `web_scrape` |
317
+ | `TRIGGER_SECRET_KEY` | Required for `create_monitor` |
318
+ | `TELEGRAM_BOT_TOKEN` | Your Telegram bot token — for monitor notifications |
319
+ | `TELEGRAM_CHAT_ID` | Your Telegram chat ID |
320
+ | `ALCHEMY_API_KEY` | Faster swap quotes and Base balance lookups |
349
321
 
350
322
  ---
351
323
 
@@ -354,9 +326,12 @@ For additional capabilities, add to the `env` block in your MCP config:
354
326
  | Problem | Fix |
355
327
  |---------|-----|
356
328
  | Tools not appearing | Restart your MCP client after adding the config |
357
- | Swarm returns auth error | Add `NOELCLAW_API_KEY` get one at app.noelclaw.com |
358
- | Server starts but no output | Expected server waits for MCP stdin, not HTTP |
359
- | Old version loading | Run `npx clear-npx-cache` then restart your client |
329
+ | Old version loading | Run `npx clear-npx-cache` then restart |
330
+ | `web_search` fails | Add `FIRECRAWL_API_KEY` to env |
331
+ | `create_monitor` fails | Add `TRIGGER_SECRET_KEY` to env |
332
+ | No Telegram notifications | Add `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` to env |
333
+ | Swap fails | Check balance with `get_portfolio`, confirm Base mainnet connectivity |
334
+ | Rate limit (429) | Auto-retries up to 3× with backoff — no action needed |
360
335
 
361
336
  ---
362
337
 
@@ -365,4 +340,4 @@ For additional capabilities, add to the `env` block in your MCP config:
365
340
  - npm: [npmjs.com/package/@noelclaw/mcp](https://www.npmjs.com/package/@noelclaw/mcp)
366
341
  - GitHub: [github.com/noelclaw/mcp](https://github.com/noelclaw/mcp)
367
342
  - App: [app.noelclaw.com](https://app.noelclaw.com)
368
- - Docs: [docs.noelclaw.fun](https://docs.noelclaw.fun)
343
+ - Docs: [docs.noelclaw.com](https://docs.noelclaw.com)
@@ -3,7 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.runAgent = runAgent;
4
4
  const server_js_1 = require("./server.js");
5
5
  const llm_js_1 = require("./llm.js");
6
- const SYSTEM_PROMPT = "You are Noelclaw, a persistent AI with 74 tools covering memory, automations, DeFi execution, research, and code. " +
6
+ const convex_js_1 = require("./convex.js");
7
+ const SYSTEM_PROMPT = "You are Noelclaw, a persistent AI with 76 tools covering memory, automations, DeFi execution, research, and code. " +
7
8
  "Be concise and direct. Use tools when needed. Summarize tool results in plain English.";
8
9
  async function runAgent(userMessage, history, onToolCall) {
9
10
  const anthropicKey = process.env.ANTHROPIC_API_KEY;
@@ -12,9 +13,16 @@ async function runAgent(userMessage, history, onToolCall) {
12
13
  return runAnthropicLoop(anthropicKey, userMessage, history, onToolCall);
13
14
  if (bankrKey)
14
15
  return runBankrLoop(bankrKey, userMessage, history, onToolCall);
15
- // Fallbackno direct tool execution, just LLM chat via Convex
16
- const text = await (0, llm_js_1.callLLM)(SYSTEM_PROMPT, userMessage, 1024, history);
17
- return { text, toolCalls: [] };
16
+ // No direct key proxy through Noelclaw backend. Wallet auto-creates at ~/.noelclaw/wallet.json
17
+ // on first use and signs requests transparently. No account or config needed.
18
+ try {
19
+ return await runConvexProxiedLoop(userMessage, history, onToolCall);
20
+ }
21
+ catch {
22
+ // Network down or backend unavailable — plain chat fallback
23
+ const text = await (0, llm_js_1.callLLM)(SYSTEM_PROMPT, userMessage, 1024, history);
24
+ return { text, toolCalls: [] };
25
+ }
18
26
  }
19
27
  // ── Anthropic agent loop ─────────────────────────────────────────────────────
20
28
  function toAnthropicTool(tool) {
@@ -80,6 +88,55 @@ async function runAnthropicLoop(apiKey, userMessage, history, onToolCall) {
80
88
  }
81
89
  return { text: "Reached max tool iterations.", toolCalls };
82
90
  }
91
+ // ── Convex-proxied Anthropic loop (session token only — platform covers LLM) ──
92
+ async function runConvexProxiedLoop(userMessage, history, onToolCall) {
93
+ const model = process.env.ANTHROPIC_MODEL ?? "claude-haiku-4-5-20251001";
94
+ const tools = server_js_1.ALL_TOOLS.map(toAnthropicTool);
95
+ const toolCalls = [];
96
+ const messages = [
97
+ ...history.map(h => ({ role: h.role, content: h.content })),
98
+ { role: "user", content: userMessage },
99
+ ];
100
+ for (let turn = 0; turn < 10; turn++) {
101
+ // callConvex handles wallet/session auth automatically; 90s timeout matches the proxy endpoint
102
+ const data = await (0, convex_js_1.callConvex)("/llm/complete", "POST", {
103
+ model,
104
+ max_tokens: 2048,
105
+ system: SYSTEM_PROMPT,
106
+ tools,
107
+ messages,
108
+ }, "llm_complete", 90000);
109
+ messages.push({ role: "assistant", content: data.content });
110
+ if (data.stop_reason !== "tool_use") {
111
+ const text = data.content
112
+ .filter(b => b.type === "text")
113
+ .map(b => b.text)
114
+ .join("");
115
+ return { text, toolCalls };
116
+ }
117
+ const toolResults = [];
118
+ for (const block of data.content) {
119
+ if (block.type !== "tool_use")
120
+ continue;
121
+ onToolCall(block.name);
122
+ toolCalls.push({ name: block.name });
123
+ let resultText;
124
+ try {
125
+ const handler = server_js_1.HANDLER_MAP.get(block.name);
126
+ if (!handler)
127
+ throw new Error(`Unknown tool: ${block.name}`);
128
+ const result = await handler(block.name, block.input ?? {});
129
+ resultText = result?.content?.[0]?.text ?? "Done.";
130
+ }
131
+ catch (err) {
132
+ resultText = `Error: ${err.message}`;
133
+ }
134
+ toolResults.push({ type: "tool_result", tool_use_id: block.id, content: resultText });
135
+ }
136
+ messages.push({ role: "user", content: toolResults });
137
+ }
138
+ return { text: "Reached max tool iterations.", toolCalls };
139
+ }
83
140
  // ── Bankr (OpenAI-compatible) agent loop ─────────────────────────────────────
84
141
  function toBankrTool(tool) {
85
142
  return {
package/dist/cli.js CHANGED
@@ -90,7 +90,7 @@ async function main() {
90
90
  ? `${C.green}Anthropic${C.reset} ${C.dim}(full tool use)${C.reset}`
91
91
  : process.env.BANKR_API_KEY
92
92
  ? `${C.green}Bankr${C.reset} ${C.dim}(full tool use)${C.reset}`
93
- : `${C.yellow}Convex chat${C.reset} ${C.dim}(no tool execution set ANTHROPIC_API_KEY for full mode)${C.reset}`;
93
+ : `${C.green}Noelclaw${C.reset} ${C.dim}(full tool use · auto-wallet)${C.reset}`;
94
94
  console.log(` ${C.dim}Mode:${C.reset} ${mode}\n`);
95
95
  const history = [];
96
96
  const rl = readline.createInterface({
package/dist/convex.js CHANGED
@@ -19,15 +19,15 @@ exports.PaymentRequiredError = PaymentRequiredError;
19
19
  function buildPaymentHeader(txHash, requestId) {
20
20
  return Buffer.from(`${txHash}:${requestId}`).toString("base64");
21
21
  }
22
- async function attemptConvex(url, method, headers, body) {
22
+ async function attemptConvex(url, method, headers, body, timeoutMs = 30000) {
23
23
  return fetch(url, {
24
24
  method,
25
25
  headers,
26
26
  body: body ? JSON.stringify(body) : undefined,
27
- signal: AbortSignal.timeout(30000),
27
+ signal: AbortSignal.timeout(timeoutMs),
28
28
  });
29
29
  }
30
- async function callConvex(path, method, body, toolName = "unknown") {
30
+ async function callConvex(path, method, body, toolName = "unknown", timeoutMs = 30000) {
31
31
  const url = `${CONVEX_SITE}${path}`;
32
32
  const headers = { "Content-Type": "application/json" };
33
33
  const apiKey = process.env.NOELCLAW_API_KEY;
@@ -72,7 +72,7 @@ async function callConvex(path, method, body, toolName = "unknown") {
72
72
  }
73
73
  let res;
74
74
  try {
75
- res = await attemptConvex(url, method, headers, body);
75
+ res = await attemptConvex(url, method, headers, body, timeoutMs);
76
76
  }
77
77
  catch (err) {
78
78
  lastError = err;
@@ -85,8 +85,8 @@ async function callConvex(path, method, body, toolName = "unknown") {
85
85
  if (res.status === 401) {
86
86
  const b = await res.json().catch(() => ({}));
87
87
  throw new Error(`🔑 ${b.message || "Authentication required"}\n\n` +
88
- `→ Get your API key: ${b.url || "https://noelclaw.com"}\n\n` +
89
- `Hint: ${b.hint || "Set NOELCLAW_API_KEY=noel_sk_xxx in your MCP config"}\n\n` +
88
+ `→ Sign in at: ${b.url || "https://noelclaw.com"}\n\n` +
89
+ `Hint: ${b.hint || 'Add NOELCLAW_SESSION_TOKEN=noel_... to the env block in your MCP config'}\n\n` +
90
90
  `${b.alternative ? `Alternative: ${b.alternative}` : ""}`);
91
91
  }
92
92
  if (RETRY_STATUSES.has(res.status) && attempt < RETRY_DELAYS.length) {
package/dist/index.js CHANGED
@@ -35,26 +35,33 @@ async function main() {
35
35
  const categories = [
36
36
  { label: "Market", count: 5, tools: "get_market_data · get_token_data · compare_tokens · market_overview · token_history" },
37
37
  { label: "Insight", count: 3, tools: "ask_noel · market_thesis · trade_plan" },
38
- { label: "DeFi", count: 6, tools: "portfolio · swap · send · scan_wallet · estimate · get_defi_yields" },
39
- { label: "Automation", count: 5, tools: "create · list · pause · delete · runs" },
38
+ { label: "DeFi", count: 6, tools: "get_portfolio · estimate_swap · swap_tokens · send_token · analyze_wallet · get_defi_yields" },
39
+ { label: "Automation", count: 6, tools: "create · list · pause · delete · runs · run_now" },
40
+ { label: "Swarm", count: 6, tools: "start_swarm · stop_swarm · get_swarm_status · swarm_research · trigger_agent · swarm_brief" },
41
+ { label: "Framework", count: 3, tools: "list_playbooks · run_playbook · get_noel_ledger" },
42
+ { label: "Vault", count: 12, tools: "save · read · list · search · history · diff · export · credential · pin · delete · link · tag" },
43
+ { label: "Wallet", count: 2, tools: "get_wallet_address · set_telegram" },
44
+ { label: "MiroShark", count: 3, tools: "simulate · status · stop" },
40
45
  { label: "Scanner", count: 4, tools: "scan_dips · scan_momentum · score_token · check_token" },
41
46
  { label: "Agents", count: 2, tools: "list_agents · hire_agent" },
42
- { label: "Swarm", count: 11, tools: "start · stop · status · memory · scores · research · trigger · brief · broadcast · pulse" },
43
- { label: "Framework", count: 6, tools: "create_task · list_tasks · list_playbooks · run_playbook · ledger · sentinel" },
44
- { label: "Vault", count: 18, tools: "save · read · list · search · history · diff · export · remember · context · credential · publish · explore · connect · pin · delete · link · tag" },
45
- { label: "Memory", count: 8, tools: "add · search · context · profile · connect · list · delete · update" },
46
- { label: "MiroShark", count: 3, tools: "simulate · status · stop" },
47
- { label: "Wallet", count: 2, tools: "get_wallet_address · set_telegram" },
48
47
  { label: "Social", count: 3, tools: "humanize_text · write_thread · write_post" },
49
- { label: "Coder", count: 7, tools: "scaffold_project · generate_component · generate_contract · audit_contract · explain_code · review_code · generate_mcp_skill" },
48
+ { label: "Coder", count: 5, tools: "generate_contract · audit_contract · explain_code · review_code · generate_mcp_skill" },
50
49
  { label: "Base", count: 4, tools: "query_vaults · list_markets · prepare_deposit · chain_stats" },
50
+ { label: "Memory", count: 9, tools: "add · search · context · profile · list · delete · insight · extract · consolidate" },
51
+ { label: "OS", count: 3, tools: "noel_boot · noel_status · noel_shutdown" },
52
+ { label: "Research", count: 2, tools: "web_scrape · web_search" },
53
+ { label: "Monitor", count: 3, tools: "create_monitor · list_monitors · cancel_monitor" },
51
54
  ];
52
55
  const total = server_js_1.ALL_TOOLS.length;
53
56
  divider();
54
57
  process.stderr.write(`\n`);
55
- line("version", `v2.4.0 ${C.dim}MCP protocol 2.1.0${C.reset}`);
56
- line("network", `Base mainnet ${C.dim}via 0x Protocol · ethers v6${C.reset}`);
57
- line("ai", `Bankr LLM ${C.dim}grok-3 · llm.bankr.bot${C.reset}`);
58
+ const aiMode = process.env.ANTHROPIC_API_KEY
59
+ ? `Anthropic ${C.dim}claude-haiku-4.5${C.reset}`
60
+ : process.env.BANKR_API_KEY
61
+ ? `Bankr ${C.dim}claude-haiku-4.5${C.reset}`
62
+ : `Noelclaw ${C.dim}proxy · auto-auth${C.reset}`;
63
+ line("version", `v3.3.0 ${C.dim}MCP protocol 2.1.0${C.reset}`);
64
+ line("ai", aiMode);
58
65
  line("tools", `${C.white}${C.bold}${total} tools loaded${C.reset} ${C.dim}across ${categories.length} categories${C.reset}`);
59
66
  process.stderr.write(`\n`);
60
67
  divider();
package/dist/llm.js CHANGED
@@ -75,7 +75,7 @@ async function callAnthropic(apiKey, systemPrompt, userPrompt, maxTokens, histor
75
75
  return data.content?.find(b => b.type === "text")?.text ?? "";
76
76
  }
77
77
  async function callBankr(apiKey, systemPrompt, userPrompt, maxTokens, history, timeoutMs) {
78
- const model = process.env.BANKR_MODEL ?? "grok-3";
78
+ const model = process.env.BANKR_MODEL ?? "claude-haiku-4.5";
79
79
  const res = await fetch(BANKR_URL, {
80
80
  method: "POST",
81
81
  headers: { "Content-Type": "application/json", "X-API-Key": apiKey },
package/dist/server.js CHANGED
@@ -22,6 +22,8 @@ const coder_js_1 = require("./tools/coder.js");
22
22
  const base_js_1 = require("./tools/base.js");
23
23
  const memory_js_1 = require("./tools/memory.js");
24
24
  const os_js_1 = require("./tools/os.js");
25
+ const research_js_1 = require("./tools/research.js");
26
+ const monitor_js_1 = require("./tools/monitor.js");
25
27
  const PRIVATE_KEY_RESPONSE = {
26
28
  content: [{
27
29
  type: "text",
@@ -52,7 +54,9 @@ exports.ALL_TOOLS = [
52
54
  ...base_js_1.BASE_TOOLS, // 4 — query_vaults, list_markets, prepare_deposit, chain_stats
53
55
  ...memory_js_1.MEMORY_TOOLS, // 9 — memory_add, memory_search, memory_context, memory_profile, memory_list, memory_delete, memory_insight, memory_extract, memory_consolidate
54
56
  ...os_js_1.OS_TOOLS, // 3 — noel_status, noel_boot, noel_shutdown
55
- // total: 76
57
+ ...research_js_1.RESEARCH_TOOLS, // 2 — web_scrape, web_search
58
+ ...monitor_js_1.MONITOR_TOOLS, // 3 — create_monitor, list_monitors, cancel_monitor
59
+ // total: 81
56
60
  ];
57
61
  exports.HANDLER_MAP = new Map([
58
62
  ...market_js_1.MARKET_TOOLS.map(t => [t.name, market_js_1.handleMarketTool]),
@@ -71,8 +75,10 @@ exports.HANDLER_MAP = new Map([
71
75
  ...base_js_1.BASE_TOOLS.map(t => [t.name, base_js_1.handleBaseTool]),
72
76
  ...memory_js_1.MEMORY_TOOLS.map(t => [t.name, memory_js_1.handleMemoryTool]),
73
77
  ...os_js_1.OS_TOOLS.map(t => [t.name, os_js_1.handleOsTool]),
78
+ ...research_js_1.RESEARCH_TOOLS.map(t => [t.name, research_js_1.handleResearchTool]),
79
+ ...monitor_js_1.MONITOR_TOOLS.map(t => [t.name, monitor_js_1.handleMonitorTool]),
74
80
  ]);
75
- exports.server = new index_js_1.Server({ name: "noelclaw", version: "3.2.0" }, { capabilities: { tools: {} } });
81
+ exports.server = new index_js_1.Server({ name: "noelclaw", version: "3.3.0" }, { capabilities: { tools: {} } });
76
82
  exports.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({ tools: exports.ALL_TOOLS }));
77
83
  exports.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
78
84
  const { name, arguments: args } = request.params;
@@ -168,12 +168,6 @@ async function handleDefiTool(name, args) {
168
168
  if (!result.success)
169
169
  return { content: [{ type: "text", text: `Send failed: ${result.error}` }], isError: true };
170
170
  const txHash = await (0, wallet_js_1.signAndBroadcast)(wallet, result.txData);
171
- if (result.feeTxData) {
172
- try {
173
- await (0, wallet_js_1.signAndBroadcast)(wallet, result.feeTxData);
174
- }
175
- catch { /* non-fatal */ }
176
- }
177
171
  return {
178
172
  content: [{
179
173
  type: "text",
@@ -167,7 +167,20 @@ async function handleInsightTool(name, args) {
167
167
  try {
168
168
  const systemPrompt = await buildSystemPrompt(`${token} ${context ?? ""}`);
169
169
  const answer = await (0, llm_js_1.callLLM)(systemPrompt, prompt, 1200);
170
- return { content: [{ type: "text", text: answer }] };
170
+ const date = new Date().toISOString().slice(0, 10);
171
+ (0, convex_js_1.callConvex)("/vault/save", "POST", {
172
+ type: "research",
173
+ title: `${token.toUpperCase()} Thesis — ${date}`,
174
+ content: answer,
175
+ key: `thesis/${token.toLowerCase()}-${date}`,
176
+ agentId: "noel",
177
+ tags: ["thesis", token.toLowerCase()],
178
+ commitMsg: "market_thesis auto-save",
179
+ }, "vault_save").catch(() => { });
180
+ const suggest = process.env.TRIGGER_SECRET_KEY
181
+ ? `\n\n---\n💡 Want to stay on top of this? Use \`create_monitor\` to get automatic research briefings on ${token.toUpperCase()} delivered on a schedule — no prompting needed.`
182
+ : "";
183
+ return { content: [{ type: "text", text: answer + suggest }] };
171
184
  }
172
185
  catch (err) {
173
186
  return { content: [{ type: "text", text: `market_thesis error: ${err.message}` }], isError: true };
@@ -228,7 +241,20 @@ async function handleInsightTool(name, args) {
228
241
  try {
229
242
  const systemPrompt = await buildSystemPrompt(`${token} trade ${riskTolerance} ${timeframe ?? ""}`);
230
243
  const answer = await (0, llm_js_1.callLLM)(systemPrompt, prompt, 1200);
231
- return { content: [{ type: "text", text: answer }] };
244
+ const date = new Date().toISOString().slice(0, 10);
245
+ (0, convex_js_1.callConvex)("/vault/save", "POST", {
246
+ type: "execution",
247
+ title: `Trade Plan: ${token.toUpperCase()} ${side.toUpperCase()} — ${date}`,
248
+ content: answer,
249
+ key: `trade-plan/${token.toLowerCase()}-${side}-${date}`,
250
+ agentId: "noel",
251
+ tags: ["trade-plan", token.toLowerCase(), side],
252
+ commitMsg: "trade_plan auto-save",
253
+ }, "vault_save").catch(() => { });
254
+ const suggest = process.env.TRIGGER_SECRET_KEY
255
+ ? `\n\n---\n💡 Want to stay on top of this? Use \`create_monitor\` to get automatic research briefings on ${token.toUpperCase()} delivered on a schedule — no prompting needed.`
256
+ : "";
257
+ return { content: [{ type: "text", text: answer + suggest }] };
232
258
  }
233
259
  catch (err) {
234
260
  return { content: [{ type: "text", text: `trade_plan error: ${err.message}` }], isError: true };
@@ -0,0 +1,226 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MONITOR_TOOLS = void 0;
4
+ exports.handleMonitorTool = handleMonitorTool;
5
+ const zod_1 = require("zod");
6
+ const convex_js_1 = require("../convex.js");
7
+ const TRIGGER_BASE = "https://api.trigger.dev/api/v1";
8
+ const MONITOR_TASK_ID = "noelclaw-monitor";
9
+ exports.MONITOR_TOOLS = [
10
+ {
11
+ name: "create_monitor",
12
+ description: "Set up a recurring autonomous monitor — runs on a schedule, researches a topic, saves findings to vault, " +
13
+ "and sends a Telegram notification. No chat needed: the agent runs completely on its own. " +
14
+ "Requires TRIGGER_SECRET_KEY env var (trigger.dev). " +
15
+ "Examples: monitor news on a topic daily, track competitors weekly, get morning briefings.",
16
+ inputSchema: {
17
+ type: "object",
18
+ properties: {
19
+ topic: { type: "string", description: "What to monitor — topic, keyword, or research question" },
20
+ schedule: {
21
+ type: "string",
22
+ description: "Cron expression or preset. Presets: 'daily-8am', 'daily-6pm', 'weekly-monday', 'hourly'. Or raw cron: '0 8 * * *'",
23
+ },
24
+ label: { type: "string", description: "Optional: short label to identify this monitor (e.g. 'morning brief', 'competitor watch')" },
25
+ },
26
+ required: ["topic", "schedule"],
27
+ },
28
+ },
29
+ {
30
+ name: "list_monitors",
31
+ description: "List all active autonomous monitors — shows topic, schedule, next run, and monitor ID.",
32
+ inputSchema: { type: "object", properties: {}, required: [] },
33
+ },
34
+ {
35
+ name: "cancel_monitor",
36
+ description: "Cancel and delete an autonomous monitor by its ID. Use list_monitors to get the ID.",
37
+ inputSchema: {
38
+ type: "object",
39
+ properties: {
40
+ id: { type: "string", description: "Monitor ID (from list_monitors)" },
41
+ },
42
+ required: ["id"],
43
+ },
44
+ },
45
+ ];
46
+ const CRON_PRESETS = {
47
+ "daily-8am": "0 8 * * *",
48
+ "daily-6pm": "0 18 * * *",
49
+ "weekly-monday": "0 8 * * 1",
50
+ "hourly": "0 * * * *",
51
+ };
52
+ const CreateSchema = zod_1.z.object({
53
+ topic: zod_1.z.string().min(1).max(200),
54
+ schedule: zod_1.z.string().min(1),
55
+ label: zod_1.z.string().max(80).optional(),
56
+ });
57
+ const CancelSchema = zod_1.z.object({ id: zod_1.z.string().min(1) });
58
+ function getKey() {
59
+ return process.env.TRIGGER_SECRET_KEY ?? null;
60
+ }
61
+ function noKeyMsg() {
62
+ return {
63
+ content: [{
64
+ type: "text",
65
+ text: [
66
+ `⚠️ **TRIGGER_SECRET_KEY not set.**`,
67
+ ``,
68
+ `To enable autonomous monitors:`,
69
+ `1. Sign up at trigger.dev (free tier available)`,
70
+ `2. Create a project, go to API Keys → copy your Secret Key`,
71
+ `3. Add to your MCP config env block: \`"TRIGGER_SECRET_KEY": "tr_prod_..."\``,
72
+ `4. In the noelclaw worker directory, run: \`npx trigger.dev@latest deploy\``,
73
+ ``,
74
+ `Then monitors will run autonomously — no chat needed.`,
75
+ ].join("\n"),
76
+ }],
77
+ isError: true,
78
+ };
79
+ }
80
+ function resolveCron(input) {
81
+ return CRON_PRESETS[input] ?? input;
82
+ }
83
+ async function handleMonitorTool(name, args) {
84
+ if (name === "create_monitor") {
85
+ const parsed = CreateSchema.safeParse(args);
86
+ if (!parsed.success)
87
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
88
+ const key = getKey();
89
+ if (!key)
90
+ return noKeyMsg();
91
+ const { topic, schedule, label } = parsed.data;
92
+ const cron = resolveCron(schedule);
93
+ // Unique stable ID — worker reads config from vault using this as key
94
+ const externalId = `monitor-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`;
95
+ try {
96
+ const res = await fetch(`${TRIGGER_BASE}/schedules`, {
97
+ method: "POST",
98
+ headers: { "Content-Type": "application/json", "Authorization": `Bearer ${key}` },
99
+ body: JSON.stringify({ task: MONITOR_TASK_ID, cron, externalId, deduplicationKey: externalId }),
100
+ signal: AbortSignal.timeout(10000),
101
+ });
102
+ if (!res.ok) {
103
+ const err = await res.text();
104
+ return { content: [{ type: "text", text: `Failed to create monitor: ${err}` }], isError: true };
105
+ }
106
+ const data = await res.json();
107
+ const scheduleId = data.id ?? externalId;
108
+ // Save config to vault so the worker can retrieve topic + metadata
109
+ let configSaved = true;
110
+ try {
111
+ await (0, convex_js_1.callConvex)("/vault/save", "POST", {
112
+ type: "workflow",
113
+ title: `Monitor: ${label ?? topic}`,
114
+ content: JSON.stringify({ topic, label: label ?? topic, cron, scheduleId, externalId }),
115
+ key: `monitor-config/${externalId}`,
116
+ agentId: "os",
117
+ tags: ["monitor-config"],
118
+ commitMsg: "create_monitor config",
119
+ }, "vault_save");
120
+ }
121
+ catch {
122
+ configSaved = false;
123
+ }
124
+ return {
125
+ content: [{
126
+ type: "text",
127
+ text: [
128
+ `✅ **Monitor created**`,
129
+ ``,
130
+ `📌 Topic: ${topic}`,
131
+ `🕐 Schedule: ${cron}${CRON_PRESETS[schedule] ? ` (${schedule})` : ""}`,
132
+ `🆔 ID: ${scheduleId}`,
133
+ data.nextRun ? `⏭️ Next run: ${new Date(data.nextRun).toUTCString()}` : "",
134
+ ``,
135
+ configSaved
136
+ ? `The agent will research "${topic}" on schedule, save findings to vault, and send a Telegram notification if configured.`
137
+ : `⚠️ Monitor schedule created but config save failed — the agent may use a default topic on first run. Try \`cancel_monitor\` and recreate.`,
138
+ `Use \`list_monitors\` to see all active monitors.`,
139
+ ].filter(Boolean).join("\n"),
140
+ }],
141
+ };
142
+ }
143
+ catch (err) {
144
+ return { content: [{ type: "text", text: `Monitor error: ${err.message}` }], isError: true };
145
+ }
146
+ }
147
+ if (name === "list_monitors") {
148
+ const key = getKey();
149
+ if (!key)
150
+ return noKeyMsg();
151
+ try {
152
+ const res = await fetch(`${TRIGGER_BASE}/schedules`, {
153
+ headers: { "Authorization": `Bearer ${key}` },
154
+ signal: AbortSignal.timeout(10000),
155
+ });
156
+ if (!res.ok) {
157
+ const err = await res.text();
158
+ return { content: [{ type: "text", text: `Failed to list monitors: ${err}` }], isError: true };
159
+ }
160
+ const data = await res.json();
161
+ const schedules = (data.data ?? []).filter((s) => (s.task ?? s.taskIdentifier) === MONITOR_TASK_ID);
162
+ if (!schedules.length) {
163
+ return {
164
+ content: [{
165
+ type: "text",
166
+ text: `No active monitors.\n\nUse \`create_monitor\` to set up an autonomous agent that runs on a schedule.`,
167
+ }],
168
+ };
169
+ }
170
+ // Load topic labels from vault for each schedule
171
+ const configMap = new Map();
172
+ await Promise.allSettled(schedules
173
+ .filter(s => s.externalId)
174
+ .map(async (s) => {
175
+ try {
176
+ const cfg = await (0, convex_js_1.callConvex)(`/vault/read?key=monitor-config/${s.externalId}`, "GET", undefined, "vault_read");
177
+ if (cfg?.content) {
178
+ const parsed = JSON.parse(cfg.content);
179
+ configMap.set(s.externalId, { topic: parsed.topic, label: parsed.label ?? parsed.topic });
180
+ }
181
+ }
182
+ catch { /* skip — show raw externalId */ }
183
+ }));
184
+ const lines = [`📋 **Active Monitors** — ${schedules.length} running\n`];
185
+ for (const s of schedules) {
186
+ const cfg = s.externalId ? configMap.get(s.externalId) : undefined;
187
+ const label = cfg?.label ?? s.externalId ?? s.id;
188
+ const topic = cfg?.topic ? ` — ${cfg.topic}` : "";
189
+ const next = s.nextRun ? new Date(s.nextRun).toUTCString() : "unknown";
190
+ lines.push(`**${label}**${topic}`);
191
+ lines.push(` ID: \`${s.id}\` · Cron: \`${s.cron}\` · Next: ${next}`);
192
+ lines.push("");
193
+ }
194
+ lines.push(`Use \`cancel_monitor id: "<id>"\` to stop a monitor.`);
195
+ return { content: [{ type: "text", text: lines.join("\n") }] };
196
+ }
197
+ catch (err) {
198
+ return { content: [{ type: "text", text: `List error: ${err.message}` }], isError: true };
199
+ }
200
+ }
201
+ if (name === "cancel_monitor") {
202
+ const parsed = CancelSchema.safeParse(args);
203
+ if (!parsed.success)
204
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
205
+ const key = getKey();
206
+ if (!key)
207
+ return noKeyMsg();
208
+ const { id } = parsed.data;
209
+ try {
210
+ const res = await fetch(`${TRIGGER_BASE}/schedules/${id}`, {
211
+ method: "DELETE",
212
+ headers: { "Authorization": `Bearer ${key}` },
213
+ signal: AbortSignal.timeout(10000),
214
+ });
215
+ if (!res.ok) {
216
+ const err = await res.text();
217
+ return { content: [{ type: "text", text: `Failed to cancel monitor: ${err}` }], isError: true };
218
+ }
219
+ return { content: [{ type: "text", text: `✅ Monitor \`${id}\` cancelled.` }] };
220
+ }
221
+ catch (err) {
222
+ return { content: [{ type: "text", text: `Cancel error: ${err.message}` }], isError: true };
223
+ }
224
+ }
225
+ return null;
226
+ }
package/dist/tools/os.js CHANGED
@@ -99,7 +99,7 @@ async function handleOsTool(name, args) {
99
99
  lines.push("");
100
100
  }
101
101
  lines.push(swarmActive
102
- ? `💡 Run \`swarm_pulse\` for a live snapshot from all agents.`
102
+ ? `💡 Run \`get_swarm_status\` for a live snapshot from all agents.`
103
103
  : `💡 Run \`noel_boot\` to wake up the full system.`);
104
104
  return { content: [{ type: "text", text: lines.join("\n") }] };
105
105
  }
@@ -169,7 +169,7 @@ async function handleOsTool(name, args) {
169
169
  lines.push("");
170
170
  }
171
171
  lines.push(`**Quick actions:**`);
172
- lines.push(` • \`swarm_pulse\` — live readings from all agents`);
172
+ lines.push(` • \`get_swarm_status\` — live readings from all agents`);
173
173
  if (focus) {
174
174
  lines.push(` • \`swarm_research topic: "${focus}"\` — deep research session`);
175
175
  lines.push(` • \`memory_insight topic: "${focus}"\` — full intelligence report`);
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RESEARCH_TOOLS = void 0;
4
+ exports.handleResearchTool = handleResearchTool;
5
+ const zod_1 = require("zod");
6
+ const FC_BASE = "https://api.firecrawl.dev/v1";
7
+ exports.RESEARCH_TOOLS = [
8
+ {
9
+ name: "web_scrape",
10
+ description: "Fetch and extract clean readable content from any URL — returns markdown. " +
11
+ "Use when an agent needs to read an article, docs page, GitHub repo, or any web page. " +
12
+ "Set FIRECRAWL_API_KEY for best quality (firecrawl.dev). Falls back to basic fetch if not set.",
13
+ inputSchema: {
14
+ type: "object",
15
+ properties: {
16
+ url: { type: "string", description: "URL to fetch" },
17
+ focus: { type: "string", description: "Optional: specific topic or section to extract from the page" },
18
+ },
19
+ required: ["url"],
20
+ },
21
+ },
22
+ {
23
+ name: "web_search",
24
+ description: "Search the web and get clean results: titles, URLs, and content snippets. " +
25
+ "Use for research on any topic, finding recent news, or gathering live information. " +
26
+ "Requires FIRECRAWL_API_KEY (firecrawl.dev).",
27
+ inputSchema: {
28
+ type: "object",
29
+ properties: {
30
+ query: { type: "string", description: "Search query" },
31
+ limit: { type: "number", description: "Max results (default 5, max 10)" },
32
+ },
33
+ required: ["query"],
34
+ },
35
+ },
36
+ ];
37
+ const ScrapeSchema = zod_1.z.object({
38
+ url: zod_1.z.string().url(),
39
+ focus: zod_1.z.string().optional(),
40
+ });
41
+ const SearchSchema = zod_1.z.object({
42
+ query: zod_1.z.string().min(1),
43
+ limit: zod_1.z.number().int().min(1).max(10).optional(),
44
+ });
45
+ async function firecrawlScrape(url) {
46
+ const key = process.env.FIRECRAWL_API_KEY;
47
+ if (!key)
48
+ return null;
49
+ try {
50
+ const res = await fetch(`${FC_BASE}/scrape`, {
51
+ method: "POST",
52
+ headers: { "Content-Type": "application/json", "Authorization": `Bearer ${key}` },
53
+ body: JSON.stringify({ url, formats: ["markdown"], onlyMainContent: true }),
54
+ signal: AbortSignal.timeout(20000),
55
+ });
56
+ if (!res.ok)
57
+ return null;
58
+ const data = await res.json();
59
+ return data.data?.markdown ?? null;
60
+ }
61
+ catch {
62
+ return null;
63
+ }
64
+ }
65
+ async function basicFetch(url) {
66
+ try {
67
+ const res = await fetch(url, {
68
+ headers: { "User-Agent": "Mozilla/5.0 (compatible; NoelclawBot/1.0)" },
69
+ signal: AbortSignal.timeout(10000),
70
+ });
71
+ if (!res.ok)
72
+ return null;
73
+ const html = await res.text();
74
+ return html
75
+ .replace(/<script[\s\S]*?<\/script>/gi, "")
76
+ .replace(/<style[\s\S]*?<\/style>/gi, "")
77
+ .replace(/<[^>]+>/g, " ")
78
+ .replace(/\s+/g, " ")
79
+ .trim()
80
+ .slice(0, 4000);
81
+ }
82
+ catch {
83
+ return null;
84
+ }
85
+ }
86
+ async function handleResearchTool(name, args) {
87
+ if (name === "web_scrape") {
88
+ const parsed = ScrapeSchema.safeParse(args);
89
+ if (!parsed.success)
90
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
91
+ const { url, focus } = parsed.data;
92
+ let content = await firecrawlScrape(url);
93
+ const source = content ? "Firecrawl" : "basic fetch";
94
+ if (!content)
95
+ content = await basicFetch(url);
96
+ if (!content) {
97
+ return { content: [{ type: "text", text: `Could not fetch ${url} — page may require JavaScript or block crawlers.` }], isError: true };
98
+ }
99
+ const focusNote = focus ? `\n\n_Focus: ${focus}_\n\n` : "\n\n";
100
+ const body = content.length > 6000 ? content.slice(0, 6000) + "\n\n…(truncated)" : content;
101
+ return { content: [{ type: "text", text: `**${url}**${focusNote}${body}\n\n_Source: ${source}_` }] };
102
+ }
103
+ if (name === "web_search") {
104
+ const parsed = SearchSchema.safeParse(args);
105
+ if (!parsed.success)
106
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
107
+ const { query, limit = 5 } = parsed.data;
108
+ const key = process.env.FIRECRAWL_API_KEY;
109
+ if (!key) {
110
+ return {
111
+ content: [{
112
+ type: "text",
113
+ text: [
114
+ `web_search requires FIRECRAWL_API_KEY.`,
115
+ ``,
116
+ `Get a free key at firecrawl.dev, then add to your MCP config:`,
117
+ `\`"env": { "FIRECRAWL_API_KEY": "fc-..." }\``,
118
+ ].join("\n"),
119
+ }],
120
+ isError: true,
121
+ };
122
+ }
123
+ try {
124
+ const res = await fetch(`${FC_BASE}/search`, {
125
+ method: "POST",
126
+ headers: { "Content-Type": "application/json", "Authorization": `Bearer ${key}` },
127
+ body: JSON.stringify({ query, limit }),
128
+ signal: AbortSignal.timeout(15000),
129
+ });
130
+ if (!res.ok) {
131
+ const err = await res.text();
132
+ return { content: [{ type: "text", text: `Search failed: ${err}` }], isError: true };
133
+ }
134
+ const data = await res.json();
135
+ const results = data.data ?? [];
136
+ if (!results.length) {
137
+ return { content: [{ type: "text", text: `No results found for: "${query}"` }] };
138
+ }
139
+ const lines = [`🔍 **Web Search: "${query}"** — ${results.length} results\n`];
140
+ for (const r of results) {
141
+ lines.push(`**${r.title ?? r.url}**`);
142
+ lines.push(r.url);
143
+ if (r.description)
144
+ lines.push(r.description.slice(0, 200));
145
+ lines.push("");
146
+ }
147
+ return { content: [{ type: "text", text: lines.join("\n") }] };
148
+ }
149
+ catch (err) {
150
+ return { content: [{ type: "text", text: `Search error: ${err.message}` }], isError: true };
151
+ }
152
+ }
153
+ return null;
154
+ }
@@ -102,10 +102,9 @@ const TriggerAgentSchema = zod_1.z.object({
102
102
  agentId: zod_1.z.enum(["market-monitor", "sentiment-tracker", "memory-manager", "risk-verifier", "workflow-executor", "onchain-analyst", "news-aggregator"]),
103
103
  params: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).optional(),
104
104
  });
105
- const NO_KEY_MSG = `🔑 Swarm tools require a NoelClaw API key.\n\n` +
106
- `→ Get yours free at: https://noelclaw.com\n\n` +
107
- `Then add it to your MCP config:\n` +
108
- ` "env": { "NOELCLAW_API_KEY": "noel_sk_..." }`;
105
+ const NO_KEY_MSG = `⚠️ Swarm auth failed.\n\n` +
106
+ `Your local wallet auto-generates on first use at ~/.noelclaw/wallet.json — no API key needed.\n\n` +
107
+ `Try restarting your MCP client. If the error persists, run \`get_wallet_address\` to confirm the wallet is initialised.`;
109
108
  function swarmAuthError(err) {
110
109
  const msg = err instanceof Error ? err.message : String(err);
111
110
  const isAuth = msg.includes("🔑") || msg.includes("Authentication") || msg.includes("401");
@@ -240,7 +239,7 @@ async function handleSwarmTool(name, args) {
240
239
  ``,
241
240
  data.message ?? "Research triggered. Findings will appear in vault automatically.",
242
241
  ``,
243
- `**Next:** \`swarm_reflect focus: "${topic}"\` to consolidate findings`,
242
+ `**Next:** \`memory_consolidate topic: "${topic}"\` to merge findings into one summary`,
244
243
  `Or: \`memory_insight topic: "${topic}"\` to see full intelligence report`,
245
244
  ].join("\n"),
246
245
  }],
@@ -279,7 +278,7 @@ async function handleSwarmTool(name, args) {
279
278
  resultText,
280
279
  ``,
281
280
  `Findings saved to vault automatically.`,
282
- `Use \`swarm_reflect\` to consolidate or \`memory_insight topic: "${contextQuery.split(" ")[1] ?? agentId}"\` for full report.`,
281
+ `Use \`memory_consolidate topic: "${contextQuery.split(" ")[1] ?? agentId}"\` to merge findings, or \`memory_insight\` for full report.`,
283
282
  ].filter(Boolean).join("\n"),
284
283
  }],
285
284
  };
@@ -299,7 +298,8 @@ async function handleSwarmTool(name, args) {
299
298
  }
300
299
  if (data.error)
301
300
  return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
302
- const entries = (data.entries ?? []).filter((e) => e.agentId === "market-monitor" || e.agentId === "sentiment-tracker");
301
+ const SWARM_AGENTS = new Set(["market-monitor", "sentiment-tracker", "onchain-analyst", "news-aggregator", "risk-verifier", "memory-manager", "workflow-executor"]);
302
+ const entries = (data.entries ?? []).filter((e) => e.agentId && SWARM_AGENTS.has(e.agentId));
303
303
  if (!entries.length) {
304
304
  return { content: [{ type: "text", text: `No swarm research in vault yet.\nStart the swarm with \`start_swarm\` or run \`swarm_research topic: "BTC"\` to build your knowledge base.` }] };
305
305
  }
@@ -18,7 +18,7 @@ exports.VAULT_TOOLS = [
18
18
  type: "object",
19
19
  properties: {
20
20
  type: { type: "string", enum: [...VAULT_TYPES], description: "Entry type" },
21
- title: { type: "string", description: "Human-readable title" },
21
+ title: { type: "string", description: "Human-readable title (auto-generated from content if omitted)" },
22
22
  content: { type: "string", description: "Main content — markdown, JSON, code, or plain text" },
23
23
  key: { type: "string", description: "Optional slug key e.g. 'research/btc-dominance-analysis'. Auto-generated if omitted." },
24
24
  contentType: { type: "string", enum: ["markdown", "json", "text", "code"], description: "Content format hint" },
@@ -27,7 +27,7 @@ exports.VAULT_TOOLS = [
27
27
  commitMsg: { type: "string", description: "Commit message for this version, e.g. 'initial research', 'refined with on-chain data'" },
28
28
  metadata: { type: "string", description: "Optional JSON string for extra structured fields" },
29
29
  },
30
- required: ["type", "title", "content"],
30
+ required: ["type", "content"],
31
31
  },
32
32
  },
33
33
  {
@@ -179,7 +179,7 @@ exports.VAULT_TOOLS = [
179
179
  // ─── Zod schemas ─────────────────────────────────────────────────────────────
180
180
  const SaveSchema = zod_1.z.object({
181
181
  type: zod_1.z.enum(VAULT_TYPES),
182
- title: zod_1.z.string().min(1),
182
+ title: zod_1.z.string().optional(),
183
183
  content: zod_1.z.string().min(1),
184
184
  key: zod_1.z.string().optional(),
185
185
  contentType: zod_1.z.enum(["markdown", "json", "text", "code"]).optional(),
@@ -228,15 +228,19 @@ async function handleVaultTool(name, args) {
228
228
  const parsed = SaveSchema.safeParse(args);
229
229
  if (!parsed.success)
230
230
  return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
231
- const data = await (0, convex_js_1.callConvex)("/vault/save", "POST", parsed.data, "vault_save");
231
+ // Auto-generate title from content if not provided
232
+ const firstLine = parsed.data.content.split("\n")[0].replace(/^#+\s*/, "").slice(0, 80);
233
+ const autoTitle = parsed.data.title ?? (firstLine || `${parsed.data.type} — ${new Date().toISOString().slice(0, 10)}`);
234
+ const savePayload = { ...parsed.data, title: autoTitle };
235
+ const data = await (0, convex_js_1.callConvex)("/vault/save", "POST", savePayload, "vault_save");
232
236
  if (data.error)
233
237
  return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
234
238
  const { key, version, changed } = data;
235
239
  // Mirror to semantic memory (fire-and-forget)
236
- if (parsed.data.type !== "credential") {
237
- (0, memory_js_1.syncToSupermemory)(parsed.data.content, {
238
- vaultKey: key, title: parsed.data.title, type: parsed.data.type,
239
- tags: parsed.data.tags, version, source: "vault_save",
240
+ if (savePayload.type !== "credential") {
241
+ (0, memory_js_1.syncToSupermemory)(savePayload.content, {
242
+ vaultKey: key, title: autoTitle, type: savePayload.type,
243
+ tags: savePayload.tags, version, source: "vault_save",
240
244
  });
241
245
  }
242
246
  const lines = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noelclaw/mcp",
3
- "version": "3.2.0",
3
+ "version": "3.3.0",
4
4
  "description": "Noelclaw AI Operating System — persistent memory, multi-agent swarm, DeFi execution, market intelligence, and Sentinel-gated playbooks.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {