@noelclaw/mcp 2.2.4 → 2.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,257 +2,267 @@
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
- Noelclaw as an MCP skill persistent memory, multi-agent coordination, scenario simulation, DeFi execution, and Sentinel-gated playbooks. Works with Claude, Cursor, Hermes, Windsurf, and any MCP-compatible client.
5
+ 62 MCP tools for crypto, DeFi, and AI runs as a skill in Claude, Cursor, Windsurf, or any MCP-compatible client.
6
6
 
7
- **37 tools** across market data, research, vault, swarm, MiroShark simulation, DeFi, automation, and framework.
7
+ No API key required to start. Ask your AI in plain English.
8
8
 
9
9
  ```bash
10
- npx @noelclaw/mcp
10
+ npx -y @noelclaw/mcp
11
11
  ```
12
12
 
13
13
  ---
14
14
 
15
- ## Quick Install
15
+ ## Install
16
16
 
17
- ### Claude Code
17
+ ### Claude Code (CLI)
18
18
  ```bash
19
- claude mcp add noelclaw -e NOELCLAW_API_KEY=noel_... -- npx @noelclaw/mcp
19
+ claude mcp add noelclaw -- npx -y @noelclaw/mcp
20
20
  ```
21
21
 
22
22
  ### Claude Desktop
23
- Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (Mac) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
23
+
24
+ Add to `%APPDATA%\Claude\claude_desktop_config.json` (Windows) or `~/Library/Application Support/Claude/claude_desktop_config.json` (Mac):
24
25
 
25
26
  ```json
26
27
  {
27
28
  "mcpServers": {
28
29
  "noelclaw": {
29
30
  "command": "npx",
30
- "args": ["@noelclaw/mcp"],
31
- "env": {
32
- "NOELCLAW_API_KEY": "noel_..."
33
- }
31
+ "args": ["-y", "@noelclaw/mcp"]
34
32
  }
35
33
  }
36
34
  }
37
35
  ```
38
36
 
39
- ### Cursor / Windsurf
37
+ ### Cursor / Windsurf / Any MCP Client
38
+
40
39
  ```json
41
40
  {
42
41
  "mcpServers": {
43
42
  "noelclaw": {
44
43
  "command": "npx",
45
- "args": ["@noelclaw/mcp"],
46
- "env": {
47
- "NOELCLAW_API_KEY": "noel_..."
48
- }
44
+ "args": ["-y", "@noelclaw/mcp"]
49
45
  }
50
46
  }
51
47
  }
52
48
  ```
53
49
 
54
- ### Hermes
55
- ```yaml
56
- mcp_servers:
57
- noelclaw:
58
- command: npx
59
- args:
60
- - "@noelclaw/mcp"
61
- env:
62
- NOELCLAW_API_KEY: "noel_..."
63
- ```
50
+ Restart your client. Tools load automatically.
64
51
 
65
52
  ---
66
53
 
67
- ## Authentication
54
+ ## What It Does
68
55
 
69
- Get a key instantlyno signup:
56
+ Ask your AI anything about Base, DeFi, and crypto it routes to the right tool automatically.
70
57
 
71
- ```bash
72
- curl -X POST https://api.noelclaw.com/auth/key
73
- # → { "apiKey": "noel_..." }
74
58
  ```
59
+ what's the ETH gas price and latest block on Base?
60
+ → Gas: 0.006 gwei · Block: 46,773,463 · ETH: $1,991
61
+
62
+ show me the best yield vaults on Base right now
63
+ → Clearstar USDC Reactor: 7.57% APY ($2.6M TVL)
64
+ → Moonwell Flagship USDC: 4.63% APY ($9.1M TVL)
65
+ → ...10 vaults ranked by APY
66
+
67
+ what are the lending rates on Moonwell?
68
+ → EURC: 16.57% supply / 20.46% borrow (91.5% util)
69
+ → USDC: 5.43% supply / 6.73% borrow
75
70
 
76
- Set `NOELCLAW_API_KEY` in your MCP config. That's it.
71
+ swarm research topic: "ETH vs SOL which wins 2025"
72
+ → Triggers multi-agent research, saves findings to your vault
73
+
74
+ humanize this text: "Leveraging cutting-edge blockchain..."
75
+ → Returns clean, natural prose — no AI tells
76
+
77
+ generate a Solidity ERC-20 with burn and pause features
78
+ → Returns production-ready OpenZeppelin contract
79
+ ```
77
80
 
78
81
  ---
79
82
 
80
83
  ## Tools
81
84
 
82
- ### Market Data
85
+ ### Base Chain (4 tools)
83
86
 
84
87
  | Tool | Description |
85
88
  |------|-------------|
86
- | `get_market_data` | Live prices for BTC, ETH, SOL sourced from CoinGecko via swarm. Pass a token symbol to focus |
87
- | `get_token_data` | Price, 24h change, market cap, and volume for any specific token |
89
+ | `base_chain_stats` | Live ETH price, gas price, latest block on Base mainnet |
90
+ | `base_query_vaults` | Top Morpho yield vaults ranked by APY with TVL filter |
91
+ | `base_list_markets` | Moonwell lending and borrowing rates with utilization |
92
+ | `base_prepare_deposit` | Prepare a deposit transaction into a Morpho vault |
88
93
 
89
- ### Research & AI
94
+ ### Market & AI (5 tools)
90
95
 
91
96
  | Tool | Description |
92
97
  |------|-------------|
93
- | `research` | Deep research via Bankr (real-time). Returns overview, key findings, market impact, sentiment |
94
- | `get_insight` | Noel's daily crypto + macro insight — price action, narratives, on-chain signals, one takeaway |
95
- | `ask_noel` | Ask Noel AI for DeFi analysis, trade ideas, and market research with live context |
96
- | `humanize_text` | Remove AI tells from text fixes 29 AI patterns, makes output sound natural. Powered by MiniMax-M2.7. Requires `MINIMAX_API_KEY` |
98
+ | `get_market_data` | Live prices for BTC, ETH, SOL, and other tokens |
99
+ | `get_token_data` | Token info — price, volume, market cap |
100
+ | `ask_noel` | Crypto AI analyst opinions, trade ideas, market outlook |
101
+ | `get_insight` | On-chain signal data and market insights |
102
+ | `swarm_research` | Multi-agent research on any topic — auto-saves to vault |
97
103
 
98
- ### Noel-Vault
104
+ ### DeFi Execution (5 tools)
99
105
 
100
- > Persistent memory across sessions. Save findings, recall by key, search full-text. Every save auto-versions.
106
+ > Transactions are signed client-side no private key ever leaves your machine.
101
107
 
102
108
  | Tool | Description |
103
109
  |------|-------------|
104
- | `vault_save` | Save any content research, execution logs, workflows, prompts, files, memory |
105
- | `vault_read` | Read an entry by key |
106
- | `vault_list` | List all entries with type, title, version, last updated |
107
- | `vault_search` | Full-text search across all content |
108
- | `vault_history` | Version history with commit messages |
109
- | `vault_diff` | Line-by-line diff between two versions |
110
- | `vault_export` | Export full vault or filter by type |
110
+ | `get_portfolio` | View wallet holdings and token balances |
111
+ | `estimate_swap` | Get a swap quote via 0x before executing |
112
+ | `swap_tokens` | Execute a token swap on Base via 0x |
113
+ | `send_token` | Send ETH or ERC-20 tokens to any address |
114
+ | `scan_wallet` | Analyze a wallet holdings, activity, risk signals |
111
115
 
112
- ### Noel-Swarm
116
+ ### Vault — Persistent Memory (13 tools)
113
117
 
114
- > Shared memory bus for multi-agent coordination. All agents read/write the same store with freshness tracking and execution scoring.
118
+ > Save research, notes, and data across sessions. Every save auto-versions.
115
119
 
116
120
  | Tool | Description |
117
121
  |------|-------------|
118
- | `start_swarm` | Start a swarm session |
119
- | `stop_swarm` | Stop the active session |
120
- | `get_swarm_status` | Session state, memory snapshot, execution scores |
121
- | `write_swarm_memory` | Write a key-value entry with optional TTL. Use `market/*` / `dex/*` keys for live CoinGecko / DexScreener data |
122
- | `get_swarm_memory` | Read by key returns value + freshness metadata |
123
- | `get_execution_scores` | Per-agent, per-skill performance scores |
122
+ | `vault_save` | Save a key-value entry to your personal vault |
123
+ | `vault_read` | Read a vault entry by key |
124
+ | `vault_list` | List recent vault entries |
125
+ | `vault_search` | Full-text search across your vault |
126
+ | `vault_context` | Load all vault entries relevant to a topic |
127
+ | `vault_remember` | Natural-language save "remember that ETH support is at $1800" |
128
+ | `vault_history` | Version history for a vault entry |
129
+ | `vault_diff` | Diff two versions of a vault entry |
130
+ | `vault_export` | Export vault entries as JSON or markdown |
131
+ | `vault_publish` | Publish a vault entry as a public note |
132
+ | `vault_explore` | Browse vault by tag or category |
133
+ | `store_credential` | Securely store an API key or secret |
134
+ | `get_credential` | Retrieve a stored credential |
135
+
136
+ ### Swarm — Multi-Agent System (8 tools)
137
+
138
+ > Multi-agent coordination with shared memory. Agents research, monitor, and analyze in parallel.
124
139
 
125
- ### MiroShark
140
+ | Tool | Description |
141
+ |------|-------------|
142
+ | `start_swarm` | Start the multi-agent swarm for autonomous monitoring |
143
+ | `stop_swarm` | Stop the active swarm session |
144
+ | `get_swarm_status` | Status, shared memory snapshot, and execution scores |
145
+ | `trigger_agent` | Run one agent now (market-monitor, sentiment-tracker, risk-verifier, etc.) |
146
+ | `write_swarm_memory` | Write to the swarm's shared memory |
147
+ | `get_swarm_memory` | Read from swarm shared memory |
148
+ | `get_execution_scores` | Self-improvement scores across all skills |
149
+ | `get_swarm_brief` | Summary of everything the swarm has researched |
126
150
 
127
- > Multi-agent scenario simulation — describe any scenario in plain English, get back a full simulation with AI agents acting as market participants, analysts, and social actors.
151
+ ### Automation (5 tools)
128
152
 
129
153
  | Tool | Description |
130
154
  |------|-------------|
131
- | `miroshark_simulate` | Run a simulation from a plain-English scenario. Handles full setup automatically (knowledge graph, 42+ agents). Returns a simulation ID |
132
- | `miroshark_status` | Poll progress surfaces agent actions, round count, consensus when complete |
133
-
134
- No extra env vars needed. MiroShark is hosted on Noelclaw's infrastructure.
155
+ | `create_automation` | Create a trigger-based automation price alert, DCA, scheduled task |
156
+ | `list_automations` | View all active automations |
157
+ | `pause_automation` | Pause or resume an automation |
158
+ | `delete_automation` | Delete an automation |
159
+ | `get_automation_runs` | Execution history for automations |
135
160
 
136
- ### Wallet & DeFi `beta`
161
+ ### Framework & Sentinel (6 tools)
137
162
 
138
- > On-chain operations on Base mainnet. Transactions are signed client-side no private key ever leaves your machine.
163
+ > Sentinel-gated agent execution every action checked against rules before it runs.
139
164
 
140
165
  | Tool | Description |
141
166
  |------|-------------|
142
- | `get_wallet_address` | Show your local MCP wallet address |
143
- | `swap_tokens` | Swap tokens on Base mainnet via 0x Permit2 (ETH, USDC, USDT, DAI, WETH) |
144
- | `send_token` | Send ETH or any ERC-20 to any address |
145
- | `claim_fees` | Claim accumulated ETH from Flaunch token swap fees |
167
+ | `create_task_packet` | Convert intent into a structured task with permissions and constraints |
168
+ | `list_task_packets` | List all task packets |
169
+ | `list_playbooks` | List available execution playbooks |
170
+ | `get_framework_runs` | Execution history for framework runs |
171
+ | `get_noel_ledger` | Credits ledger and full audit trail |
172
+ | `run_sentinel` | Run a Sentinel gate check before an action |
146
173
 
147
- ### Automations `beta`
174
+ ### Scanner (3 tools)
148
175
 
149
176
  | Tool | Description |
150
177
  |------|-------------|
151
- | `create_automation` | Create an automation in plain English DCA, price alerts, conditional buys/sells |
152
- | `list_automations` | List all automations with status and next scheduled run |
153
- | `pause_automation` | Pause or resume an automation |
154
- | `delete_automation` | Permanently delete an automation |
178
+ | `score_token` | Risk and quality score for any token |
179
+ | `check_token` | Contract audit flags and honeypot detection |
180
+ | `scan_dips` | Find tokens currently dipping with recovery signals |
155
181
 
156
- ### Noel Framework `beta`
182
+ ### MiroShark Simulation (3 tools)
157
183
 
158
- > Sentinel-gated agent execution. Every action checked against 5 mechanical rules before it runs.
184
+ > Multi-agent scenario simulation describe any scenario, get back AI agents acting as market participants, traders, journalists, and social actors.
159
185
 
160
186
  | Tool | Description |
161
187
  |------|-------------|
162
- | `create_task_packet` | Convert plain-English intent into a structured task scope with permissions and constraints |
163
- | `list_task_packets` | List all task packets |
164
- | `list_playbooks` | List available playbooks |
165
- | `run_playbook` | Execute a Sentinel-gated playbook — halts if any step is blocked |
166
- | `get_noel_ledger` | Full audit trail of every Sentinel decision |
167
- | `get_sentinel_rules` | Exact rules per agent role |
188
+ | `miroshark_simulate` | Simulate a scenario from plain English. Returns a simulation ID |
189
+ | `miroshark_status` | Poll progress agent activity feed, round count, AI brief on completion |
190
+ | `miroshark_stop` | Stop a running simulation |
168
191
 
169
- ### Social
192
+ ### Coder (6 tools)
170
193
 
171
194
  | Tool | Description |
172
195
  |------|-------------|
173
- | `post_tweet` | Post a tweet. Requires `TWITTER_BEARER_TOKEN` or delegated via Noelclaw |
174
- | `set_telegram` | Connect Telegram for push notifications from market and swarm events |
196
+ | `scaffold_project` | Scaffold a DeFi or Web3 project |
197
+ | `generate_component` | Generate a React component with wagmi/viem |
198
+ | `generate_contract` | Generate a Solidity smart contract |
199
+ | `audit_contract` | Audit a Solidity contract for vulnerabilities |
200
+ | `explain_code` | Explain any code in plain English |
201
+ | `review_code` | Code review with actionable feedback |
175
202
 
176
- ---
203
+ ### Agents Marketplace (2 tools)
177
204
 
178
- ## Environment Variables
205
+ | Tool | Description |
206
+ |------|-------------|
207
+ | `list_agents` | Browse available AI agents |
208
+ | `hire_agent` | Hire an agent for a specific task |
179
209
 
180
- ### Required
210
+ ### Utilities (4 tools)
181
211
 
182
- | Var | Description |
183
- |-----|-------------|
184
- | `NOELCLAW_API_KEY` | Your API key (`noel_...`)get one at `POST https://api.noelclaw.com/auth/key` |
212
+ | Tool | Description |
213
+ |------|-------------|
214
+ | `humanize_text` | Remove AI writing patternsfixes 29 common AI tells |
215
+ | `get_wallet_address` | Get or generate your MCP wallet address |
216
+ | `set_telegram` | Connect Telegram for notifications |
217
+ | `post_tweet` | Post a tweet via connected Twitter account |
185
218
 
186
- ### Optional
219
+ ---
187
220
 
188
- | Var | Used for |
189
- |-----|---------|
190
- | `MINIMAX_API_KEY` | `humanize_text` tool — powered by MiniMax-M2.7 |
191
- | `BANKR_API_KEY` | `research` tool — Bankr Agent deep research |
192
- | `TELEGRAM_BOT_TOKEN` | Your own Telegram bot for notifications |
193
- | `TELEGRAM_CHAT_ID` | Your Telegram chat ID |
194
- | `ALCHEMY_API_KEY` | Faster Base RPC for swaps |
195
- | `GROK_API_KEY` | Grok integration via BYOK |
221
+ ## Configuration
196
222
 
197
- ---
223
+ No config required to start — all tools work out of the box via the Noelclaw backend.
198
224
 
199
- ## Usage Examples
225
+ For additional capabilities, add to the `env` block in your MCP config:
200
226
 
227
+ ```json
228
+ {
229
+ "mcpServers": {
230
+ "noelclaw": {
231
+ "command": "npx",
232
+ "args": ["-y", "@noelclaw/mcp"],
233
+ "env": {
234
+ "NOELCLAW_API_KEY": "noel_sk_...",
235
+ "ANTHROPIC_API_KEY": "sk-ant-...",
236
+ "BANKR_API_KEY": "bk_usr_..."
237
+ }
238
+ }
239
+ }
240
+ }
201
241
  ```
202
- # Live market data
203
- get_market_data() # BTC, ETH, SOL prices
204
- get_token_data(question: "What is the price of HYPE?")
205
-
206
- # Research
207
- research(query: "What is happening with the Base ecosystem this week?")
208
- get_insight()
209
- ask_noel(question: "What are the risks of holding ETH through a Fed meeting?")
210
-
211
- # Humanize AI-generated text
212
- humanize_text(text: "In today's rapidly evolving landscape, it is worth noting...")
213
-
214
- # Save findings to vault
215
- vault_save(type: "research", key: "research/base-may-2026", title: "Base Ecosystem", content: "...")
216
- vault_search(query: "Base ecosystem")
217
- vault_history(key: "research/base-may-2026")
218
-
219
- # Coordinate agents via swarm
220
- start_swarm()
221
- write_swarm_memory(agentId: "analyst", key: "research/btc", value: "bullish", ttlSeconds: 3600)
222
- get_swarm_memory(key: "market/BTC") # real-time CoinGecko price
223
- get_swarm_memory(key: "dex/PEPE") # real-time DexScreener data
224
-
225
- # Run a MiroShark simulation
226
- miroshark_simulate(scenario: "What happens if the US passes a Bitcoin strategic reserve bill?")
227
- miroshark_status(simulation_id: "sim_abc123")
228
-
229
- # DeFi (beta)
230
- swap_tokens(fromToken: "ETH", toToken: "USDC", amount: "0.01")
231
- send_token(token: "USDC", toAddress: "0x...", amount: "10")
232
- claim_fees()
233
-
234
- # Sentinel-gated execution (beta)
235
- create_task_packet(task: "Monitor portfolio, max $0 spend, read only")
236
- run_playbook(playbook_name: "Daily Market Scan")
237
- get_noel_ledger()
238
- ```
242
+
243
+ | Variable | Purpose |
244
+ |----------|---------|
245
+ | `NOELCLAW_API_KEY` | Unlocks swarm, automation, framework, and agent tools. Get one at [noelclaw.com](https://noelclaw.com) |
246
+ | `ANTHROPIC_API_KEY` | AI tools use your Claude quota — you pay, not the server |
247
+ | `BANKR_API_KEY` | Alternative LLM provider |
248
+ | `NOELCLAW_SESSION_TOKEN` | Session token from noelclaw.com (alternative to API key) |
249
+ | `NOELCLAW_CONVEX_URL` | Override the backend URL (for self-hosted deployments) |
239
250
 
240
251
  ---
241
252
 
242
253
  ## Troubleshooting
243
254
 
244
- | Error | Fix |
245
- |-------|-----|
255
+ | Problem | Fix |
256
+ |---------|-----|
246
257
  | Tools not appearing | Restart your MCP client after adding the config |
247
- | `401 Unauthorized` | Check `NOELCLAW_API_KEY` is set — get one at `POST https://api.noelclaw.com/auth/key` |
248
- | `humanize_text` fails | Set `MINIMAX_API_KEY` in your MCP env config |
249
- | `research` fails | Set `BANKR_API_KEY` Bankr access required for deep research |
250
- | Server starts but no response | Expected — server waits for MCP stdin, not HTTP |
258
+ | Swarm returns auth error | Add `NOELCLAW_API_KEY` — get one at noelclaw.com |
259
+ | Server starts but no output | Expected server waits for MCP stdin, not HTTP |
260
+ | Old version loading | Run `npx clear-npx-cache` then restart your client |
251
261
 
252
262
  ---
253
263
 
254
264
  ## Links
255
265
 
256
- - npm: [npmjs.com/package/@noelclaw/mcp](https://npmjs.com/package/@noelclaw/mcp)
266
+ - npm: [npmjs.com/package/@noelclaw/mcp](https://www.npmjs.com/package/@noelclaw/mcp)
257
267
  - GitHub: [github.com/noelclaw/mcp](https://github.com/noelclaw/mcp)
258
268
  - Platform: [noelclaw.com](https://noelclaw.com)
package/dist/index.js CHANGED
@@ -40,7 +40,8 @@ async function main() {
40
40
  { label: "Agents", count: 2, tools: "list_agents · hire_agent" },
41
41
  { label: "Swarm", count: 6, tools: "start · stop · status · read_memory · write_memory · scores" },
42
42
  { label: "Framework", count: 6, tools: "create_task · list_tasks · list_playbooks · run_playbook · ledger · sentinel" },
43
- { label: "Vault", count: 7, tools: "save · read · list · search · history · diff · export" },
43
+ { label: "Vault", count: 8, tools: "save · read · list · search · history · diff · export · connect" },
44
+ { label: "Memory", count: 5, tools: "add · search · context · profile · connect" },
44
45
  { label: "MiroShark", count: 3, tools: "simulate · status · stop" },
45
46
  { label: "Wallet", count: 2, tools: "get_wallet_address · set_telegram" },
46
47
  { label: "Social", count: 1, tools: "humanize_text" },
@@ -50,7 +51,7 @@ async function main() {
50
51
  const total = server_js_1.ALL_TOOLS.length;
51
52
  divider();
52
53
  process.stderr.write(`\n`);
53
- line("version", `v2.0.0 ${C.dim}MCP protocol 2.1.0${C.reset}`);
54
+ line("version", `v2.3.0 ${C.dim}MCP protocol 2.1.0${C.reset}`);
54
55
  line("network", `Base mainnet ${C.dim}via 0x Protocol · ethers v6${C.reset}`);
55
56
  line("ai", `Bankr LLM ${C.dim}grok-3 · llm.bankr.bot${C.reset}`);
56
57
  line("tools", `${C.white}${C.bold}${total} tools loaded${C.reset} ${C.dim}across ${categories.length} categories${C.reset}`);
package/dist/server.js CHANGED
@@ -20,6 +20,7 @@ const agents_js_1 = require("./tools/agents.js");
20
20
  const scanner_js_1 = require("./tools/scanner.js");
21
21
  const coder_js_1 = require("./tools/coder.js");
22
22
  const base_js_1 = require("./tools/base.js");
23
+ const memory_js_1 = require("./tools/memory.js");
23
24
  const PRIVATE_KEY_RESPONSE = {
24
25
  content: [{
25
26
  type: "text",
@@ -40,7 +41,7 @@ exports.ALL_TOOLS = [
40
41
  ...automation_js_1.AUTOMATION_TOOLS, // 5 — create, list, pause, delete, get_runs
41
42
  ...swarm_js_1.SWARM_TOOLS, // 9 — start, stop, status, read/write memory, scores, research, brief, trigger_agent
42
43
  ...framework_js_1.FRAMEWORK_TOOLS, // 6 — task packets, playbooks, sentinel, ledger
43
- ...vault_js_1.VAULT_TOOLS, // 13 — save, read, list, search, history, diff, export, remember, context, store_credential, get_credential, publish, explore
44
+ ...vault_js_1.VAULT_TOOLS, // 14 — save, read, list, search, history, diff, export, remember, context, store_credential, get_credential, publish, explore, connect
44
45
  ...wallet_js_1.WALLET_TOOLS, // 2 — get_wallet_address, set_telegram
45
46
  ...miroshark_js_1.MIROSHARK_TOOLS, // 3 — simulate, status, stop
46
47
  ...humanizer_js_1.HUMANIZER_TOOLS, // 1 — humanize_text
@@ -48,9 +49,10 @@ exports.ALL_TOOLS = [
48
49
  ...scanner_js_1.SCANNER_TOOLS, // 3 — score_token, check_token, scan_dips
49
50
  ...coder_js_1.CODER_TOOLS, // 6 — scaffold_project, generate_component, generate_contract, audit_contract, explain_code, review_code
50
51
  ...base_js_1.BASE_TOOLS, // 4 — query_vaults, list_markets, prepare_deposit, chain_stats
51
- // total: 62
52
+ ...memory_js_1.MEMORY_TOOLS, // 5 — memory_add, memory_search, memory_context, memory_profile, memory_connect
53
+ // total: 67
52
54
  ];
53
- exports.server = new index_js_1.Server({ name: "noelclaw", version: "2.2.0" }, { capabilities: { tools: {} } });
55
+ exports.server = new index_js_1.Server({ name: "noelclaw", version: "2.3.0" }, { capabilities: { tools: {} } });
54
56
  exports.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({ tools: exports.ALL_TOOLS }));
55
57
  exports.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
56
58
  const { name, arguments: args } = request.params;
@@ -70,7 +72,8 @@ exports.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (reques
70
72
  await (0, agents_js_1.handleAgentTool)(name, args) ??
71
73
  await (0, scanner_js_1.handleScannerTool)(name, args) ??
72
74
  await (0, coder_js_1.handleCoderTool)(name, args) ??
73
- await (0, base_js_1.handleBaseTool)(name, args);
75
+ await (0, base_js_1.handleBaseTool)(name, args) ??
76
+ await (0, memory_js_1.handleMemoryTool)(name, args);
74
77
  if (result)
75
78
  return result;
76
79
  return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
@@ -0,0 +1,339 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MEMORY_TOOLS = exports.SM_SPACE = void 0;
4
+ exports.smFetch = smFetch;
5
+ exports.syncToSupermemory = syncToSupermemory;
6
+ exports.searchSupermemory = searchSupermemory;
7
+ exports.handleMemoryTool = handleMemoryTool;
8
+ const zod_1 = require("zod");
9
+ const SM_BASE = "https://api.supermemory.ai/v3";
10
+ exports.SM_SPACE = "noelclaw-vault";
11
+ // ─── Supermemory HTTP helpers ────────────────────────────────────────────────
12
+ async function smFetch(path, method, body) {
13
+ const apiKey = process.env.SUPERMEMORY_API_KEY;
14
+ if (!apiKey)
15
+ return { ok: false, data: null, error: "SUPERMEMORY_API_KEY not set" };
16
+ try {
17
+ const res = await fetch(`${SM_BASE}${path}`, {
18
+ method,
19
+ headers: {
20
+ Authorization: `Bearer ${apiKey}`,
21
+ "Content-Type": "application/json",
22
+ },
23
+ body: body ? JSON.stringify(body) : undefined,
24
+ signal: AbortSignal.timeout(15000),
25
+ });
26
+ if (!res.ok) {
27
+ const err = await res.text().catch(() => "");
28
+ return { ok: false, data: null, error: `${res.status}: ${err.slice(0, 120)}` };
29
+ }
30
+ return { ok: true, data: await res.json() };
31
+ }
32
+ catch (err) {
33
+ return { ok: false, data: null, error: err.message };
34
+ }
35
+ }
36
+ async function syncToSupermemory(content, metadata, sourceUrl) {
37
+ await smFetch("/memories", "POST", {
38
+ content,
39
+ metadata,
40
+ spaces: [exports.SM_SPACE],
41
+ ...(sourceUrl ? { sourceUrl } : {}),
42
+ });
43
+ }
44
+ async function searchSupermemory(query, limit = 10) {
45
+ const { ok, data } = await smFetch("/search", "POST", {
46
+ q: query,
47
+ n: limit,
48
+ spaces: [exports.SM_SPACE],
49
+ returnContext: true,
50
+ });
51
+ if (!ok || !data?.results)
52
+ return [];
53
+ return data.results;
54
+ }
55
+ // ─── Tool definitions ────────────────────────────────────────────────────────
56
+ exports.MEMORY_TOOLS = [
57
+ {
58
+ name: "memory_add",
59
+ description: "Add content to Noelclaw's semantic memory layer (powered by Supermemory). " +
60
+ "Unlike vault_save, memory_add is quick — no versioning, no type required. " +
61
+ "Use this for notes, decisions, preferences, URLs, or anything you want to " +
62
+ "find later with natural language search. Memory is indexed semantically — " +
63
+ "'what did I say about ETH yield?' will find it even without exact keywords.",
64
+ inputSchema: {
65
+ type: "object",
66
+ properties: {
67
+ content: { type: "string", description: "Content to remember — text, markdown, or a note" },
68
+ title: { type: "string", description: "Optional title for this memory" },
69
+ tags: { type: "array", items: { type: "string" }, description: "Tags for grouping" },
70
+ sourceUrl: { type: "string", description: "Optional URL — if provided, Supermemory also fetches and indexes the page content" },
71
+ },
72
+ required: ["content"],
73
+ },
74
+ },
75
+ {
76
+ name: "memory_search",
77
+ description: "Semantic search across all Noelclaw memories. Unlike keyword search, this " +
78
+ "understands meaning — 'low risk crypto yield' will match 'conservative DeFi strategies'. " +
79
+ "Also searches memories synced from vault_save and vault_remember automatically.",
80
+ inputSchema: {
81
+ type: "object",
82
+ properties: {
83
+ query: { type: "string", description: "Natural language query" },
84
+ limit: { type: "number", description: "Max results (default 10)" },
85
+ },
86
+ required: ["query"],
87
+ },
88
+ },
89
+ {
90
+ name: "memory_context",
91
+ description: "Retrieve the most semantically relevant memories for a topic and format them " +
92
+ "as ready-to-inject context. Use at the start of research tasks to prime with " +
93
+ "everything stored about a topic. Smarter than vault_context — uses vector search, " +
94
+ "not just keyword matching.",
95
+ inputSchema: {
96
+ type: "object",
97
+ properties: {
98
+ topic: { type: "string", description: "Topic to load context for, e.g. 'ETH liquid staking' or 'user DeFi preferences'" },
99
+ limit: { type: "number", description: "Max entries to include (default 8)" },
100
+ },
101
+ required: ["topic"],
102
+ },
103
+ },
104
+ {
105
+ name: "memory_profile",
106
+ description: "Show your semantic memory stats — how many memories, recent additions, and " +
107
+ "connected data sources. Useful for auditing what Noelclaw knows about you.",
108
+ inputSchema: {
109
+ type: "object",
110
+ properties: {},
111
+ required: [],
112
+ },
113
+ },
114
+ {
115
+ name: "memory_connect",
116
+ description: "Connect an external data source to Noelclaw's semantic memory, or add content " +
117
+ "from a specific URL. Supported sources:\n" +
118
+ "- url: fetch and index any web page or GitHub file\n" +
119
+ "- github: index a GitHub repository README/docs\n" +
120
+ "- notion: add content from a public Notion page URL\n" +
121
+ "- manual: paste content directly (same as memory_add with sourceUrl)\n" +
122
+ "For Google Drive, Gmail, and full Notion sync, connect via your Noelclaw dashboard.",
123
+ inputSchema: {
124
+ type: "object",
125
+ properties: {
126
+ source: {
127
+ type: "string",
128
+ enum: ["url", "github", "notion", "manual"],
129
+ description: "Source type",
130
+ },
131
+ url: {
132
+ type: "string",
133
+ description: "URL to fetch and index (required for url/github/notion sources)",
134
+ },
135
+ content: {
136
+ type: "string",
137
+ description: "Direct content (for source: manual)",
138
+ },
139
+ title: { type: "string", description: "Title for this memory" },
140
+ },
141
+ required: ["source"],
142
+ },
143
+ },
144
+ ];
145
+ // ─── Zod schemas ─────────────────────────────────────────────────────────────
146
+ const AddSchema = zod_1.z.object({
147
+ content: zod_1.z.string().min(1),
148
+ title: zod_1.z.string().optional(),
149
+ tags: zod_1.z.array(zod_1.z.string()).optional(),
150
+ sourceUrl: zod_1.z.string().url().optional(),
151
+ });
152
+ const SearchSchema = zod_1.z.object({
153
+ query: zod_1.z.string().min(1),
154
+ limit: zod_1.z.number().optional(),
155
+ });
156
+ const ContextSchema = zod_1.z.object({
157
+ topic: zod_1.z.string().min(1),
158
+ limit: zod_1.z.number().optional(),
159
+ });
160
+ const ConnectSchema = zod_1.z.object({
161
+ source: zod_1.z.enum(["url", "github", "notion", "manual"]),
162
+ url: zod_1.z.string().url().optional(),
163
+ content: zod_1.z.string().optional(),
164
+ title: zod_1.z.string().optional(),
165
+ });
166
+ // ─── Handler ─────────────────────────────────────────────────────────────────
167
+ async function handleMemoryTool(name, args) {
168
+ const apiKey = process.env.SUPERMEMORY_API_KEY;
169
+ switch (name) {
170
+ case "memory_add": {
171
+ const parsed = AddSchema.safeParse(args);
172
+ if (!parsed.success)
173
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
174
+ if (!apiKey)
175
+ return { content: [{ type: "text", text: "⚠️ SUPERMEMORY_API_KEY not configured.\nSet it in your MCP env to enable semantic memory.\nGet a free key at supermemory.ai" }], isError: true };
176
+ const { content, title, tags, sourceUrl } = parsed.data;
177
+ const { ok, data, error } = await smFetch("/memories", "POST", {
178
+ content,
179
+ metadata: { title, tags, source: "noelclaw-mcp", addedAt: Date.now() },
180
+ spaces: [exports.SM_SPACE],
181
+ ...(sourceUrl ? { sourceUrl } : {}),
182
+ });
183
+ if (!ok)
184
+ return { content: [{ type: "text", text: `Error adding to memory: ${error}` }], isError: true };
185
+ const memId = data?.id ?? data?.memoryId ?? "saved";
186
+ return {
187
+ content: [{
188
+ type: "text",
189
+ text: [
190
+ `🧠 **Memory added** — ID: \`${memId}\``,
191
+ title ? `Title: ${title}` : "",
192
+ sourceUrl ? `Source: ${sourceUrl}` : "",
193
+ tags?.length ? `Tags: ${tags.join(", ")}` : "",
194
+ ``,
195
+ `Find it later with: \`memory_search query: "${(title ?? content).slice(0, 40)}"\``,
196
+ ].filter(Boolean).join("\n"),
197
+ }],
198
+ };
199
+ }
200
+ case "memory_search": {
201
+ const parsed = SearchSchema.safeParse(args);
202
+ if (!parsed.success)
203
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
204
+ if (!apiKey)
205
+ return { content: [{ type: "text", text: "⚠️ SUPERMEMORY_API_KEY not configured. Set it in your MCP env to enable semantic memory." }], isError: true };
206
+ const { query, limit = 10 } = parsed.data;
207
+ const results = await searchSupermemory(query, limit);
208
+ if (!results.length)
209
+ return { content: [{ type: "text", text: `No memories found for: "${query}"\nTry adding content with \`memory_add\` or \`vault_save\`.` }] };
210
+ const header = `🔍 **Semantic Memory Search**: "${query}" — ${results.length} result(s)`;
211
+ const rows = results.map((r, i) => {
212
+ const score = r.score != null ? ` [${(r.score * 100).toFixed(0)}%]` : "";
213
+ const title = r.metadata?.title ?? "";
214
+ const preview = r.content.slice(0, 200).replace(/\n/g, " ");
215
+ return [
216
+ `${i + 1}.${score}${title ? ` **${title}**` : ""} \`${r.id}\``,
217
+ ` ${preview}${r.content.length > 200 ? "…" : ""}`,
218
+ ].join("\n");
219
+ });
220
+ return { content: [{ type: "text", text: [header, "", ...rows].join("\n") }] };
221
+ }
222
+ case "memory_context": {
223
+ const parsed = ContextSchema.safeParse(args);
224
+ if (!parsed.success)
225
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
226
+ if (!apiKey)
227
+ return { content: [{ type: "text", text: "⚠️ SUPERMEMORY_API_KEY not configured. Set it in your MCP env to enable semantic memory." }], isError: true };
228
+ const { topic, limit = 8 } = parsed.data;
229
+ const results = await searchSupermemory(topic, limit);
230
+ if (!results.length)
231
+ return { content: [{ type: "text", text: `No semantic context found for: "${topic}"\nBuild your memory base with vault_save, vault_remember, or memory_add.` }] };
232
+ const contextParts = results.map((r, i) => {
233
+ const title = r.metadata?.title ? `### ${r.metadata.title}` : `### Memory ${i + 1}`;
234
+ return `${title}\n${r.content}`;
235
+ });
236
+ const summary = results.map(r => r.metadata?.title ?? r.content.slice(0, 50)).join(", ");
237
+ return {
238
+ content: [{
239
+ type: "text",
240
+ text: [
241
+ `🧠 **Semantic Context** for: "${topic}"`,
242
+ `Loaded ${results.length} relevant memories: ${summary}`,
243
+ ``,
244
+ `---`,
245
+ ``,
246
+ contextParts.join("\n\n---\n\n"),
247
+ ].join("\n"),
248
+ }],
249
+ };
250
+ }
251
+ case "memory_profile": {
252
+ if (!apiKey)
253
+ return { content: [{ type: "text", text: "⚠️ SUPERMEMORY_API_KEY not configured.\n\nSet `SUPERMEMORY_API_KEY` in your MCP env vars.\nGet a free key at supermemory.ai" }], isError: true };
254
+ const { ok, data, error } = await smFetch(`/memories?spaces=${exports.SM_SPACE}&limit=1`, "GET");
255
+ if (!ok)
256
+ return { content: [{ type: "text", text: `Error fetching profile: ${error}` }], isError: true };
257
+ const count = data?.total ?? data?.count ?? "unknown";
258
+ return {
259
+ content: [{
260
+ type: "text",
261
+ text: [
262
+ `🧠 **Noelclaw Semantic Memory Profile**`,
263
+ ``,
264
+ `Space: \`${exports.SM_SPACE}\``,
265
+ `Total memories: **${count}**`,
266
+ `Status: ✅ Connected (Supermemory)`,
267
+ ``,
268
+ `**Data sources:**`,
269
+ `• vault_save / vault_remember — auto-synced ✅`,
270
+ `• memory_add — direct input ✅`,
271
+ `• memory_connect — URL ingestion ✅`,
272
+ `• Google Drive / Gmail / Notion — connect via noelclaw.com dashboard`,
273
+ ``,
274
+ `**Capabilities:**`,
275
+ `• Semantic search (vector similarity)`,
276
+ `• 81.6% LongMemEval accuracy`,
277
+ `• Context injection for AI tasks`,
278
+ ].join("\n"),
279
+ }],
280
+ };
281
+ }
282
+ case "memory_connect": {
283
+ const parsed = ConnectSchema.safeParse(args);
284
+ if (!parsed.success)
285
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
286
+ if (!apiKey)
287
+ return { content: [{ type: "text", text: "⚠️ SUPERMEMORY_API_KEY not configured. Set it in your MCP env to enable semantic memory." }], isError: true };
288
+ const { source, url, content, title } = parsed.data;
289
+ if (source === "manual") {
290
+ if (!content)
291
+ return { content: [{ type: "text", text: "content is required for source: manual" }], isError: true };
292
+ const { ok, data, error } = await smFetch("/memories", "POST", {
293
+ content,
294
+ metadata: { title, source: "manual", addedAt: Date.now() },
295
+ spaces: [exports.SM_SPACE],
296
+ });
297
+ if (!ok)
298
+ return { content: [{ type: "text", text: `Error: ${error}` }], isError: true };
299
+ return { content: [{ type: "text", text: `✅ Memory connected — ID: \`${data?.id ?? "saved"}\`` }] };
300
+ }
301
+ if (!url)
302
+ return { content: [{ type: "text", text: `url is required for source: ${source}` }], isError: true };
303
+ const { ok, data, error } = await smFetch("/memories", "POST", {
304
+ content: title ?? "",
305
+ sourceUrl: url,
306
+ metadata: { title, source, connectedAt: Date.now() },
307
+ spaces: [exports.SM_SPACE],
308
+ });
309
+ if (!ok)
310
+ return { content: [{ type: "text", text: `Error connecting source: ${error}` }], isError: true };
311
+ const sourceLabels = {
312
+ url: "Web page",
313
+ github: "GitHub repo",
314
+ notion: "Notion page",
315
+ };
316
+ return {
317
+ content: [{
318
+ type: "text",
319
+ text: [
320
+ `✅ **${sourceLabels[source] ?? source} connected**`,
321
+ `URL: ${url}`,
322
+ `Memory ID: \`${data?.id ?? "queued"}\``,
323
+ ``,
324
+ `Supermemory is fetching and indexing the content in the background.`,
325
+ `Search it in ~30s with: \`memory_search query: "${title ?? url.split("/").pop()}"\``,
326
+ ``,
327
+ source === "github"
328
+ ? `💡 Tip: For full repo sync (all files), connect via the Noelclaw dashboard at noelclaw.com`
329
+ : source === "notion"
330
+ ? `💡 Tip: For full Notion workspace sync, connect via the Noelclaw dashboard at noelclaw.com`
331
+ : "",
332
+ ].filter(Boolean).join("\n"),
333
+ }],
334
+ };
335
+ }
336
+ default:
337
+ return null;
338
+ }
339
+ }
@@ -130,13 +130,31 @@ const TriggerAgentSchema = zod_1.z.object({
130
130
  agentId: zod_1.z.enum(["market-monitor", "sentiment-tracker", "memory-manager", "risk-verifier", "workflow-executor"]),
131
131
  params: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).optional(),
132
132
  });
133
+ const NO_KEY_MSG = `🔑 Swarm tools require a NoelClaw API key.\n\n` +
134
+ `→ Get yours free at: https://noelclaw.com\n\n` +
135
+ `Then add it to your MCP config:\n` +
136
+ ` "env": { "NOELCLAW_API_KEY": "noel_sk_..." }`;
137
+ function swarmAuthError(err) {
138
+ const msg = err instanceof Error ? err.message : String(err);
139
+ const isAuth = msg.includes("🔑") || msg.includes("Authentication") || msg.includes("401");
140
+ return {
141
+ content: [{ type: "text", text: isAuth ? NO_KEY_MSG : `Swarm error: ${msg}` }],
142
+ isError: true,
143
+ };
144
+ }
133
145
  async function handleSwarmTool(name, args) {
134
146
  switch (name) {
135
147
  case "start_swarm": {
136
148
  const parsed = StartSwarmSchema.safeParse(args ?? {});
137
149
  if (!parsed.success)
138
150
  return { content: [{ type: "text", text: `Invalid input: config ${parsed.error.issues[0].message}` }], isError: true };
139
- const data = await (0, convex_js_1.callConvex)("/swarm/start", "POST", { config: parsed.data.config }, "start_swarm");
151
+ let data;
152
+ try {
153
+ data = await (0, convex_js_1.callConvex)("/swarm/start", "POST", { config: parsed.data.config }, "start_swarm");
154
+ }
155
+ catch (err) {
156
+ return swarmAuthError(err);
157
+ }
140
158
  if (!data.success)
141
159
  return { content: [{ type: "text", text: `Failed: ${data.error}` }], isError: true };
142
160
  const snapshot = await (0, market_js_1.fetchMarketSnapshot)();
@@ -169,13 +187,25 @@ async function handleSwarmTool(name, args) {
169
187
  };
170
188
  }
171
189
  case "stop_swarm": {
172
- const data = await (0, convex_js_1.callConvex)("/swarm/stop", "POST", {}, "stop_swarm");
190
+ let data;
191
+ try {
192
+ data = await (0, convex_js_1.callConvex)("/swarm/stop", "POST", {}, "stop_swarm");
193
+ }
194
+ catch (err) {
195
+ return swarmAuthError(err);
196
+ }
173
197
  if (!data.success)
174
198
  return { content: [{ type: "text", text: `Failed: ${data.error}` }], isError: true };
175
199
  return { content: [{ type: "text", text: `⏹️ Swarm stopped.` }] };
176
200
  }
177
201
  case "get_swarm_status": {
178
- const data = await (0, convex_js_1.callConvex)("/swarm/status", "GET", undefined, "get_swarm_status");
202
+ let data;
203
+ try {
204
+ data = await (0, convex_js_1.callConvex)("/swarm/status", "GET", undefined, "get_swarm_status");
205
+ }
206
+ catch (err) {
207
+ return swarmAuthError(err);
208
+ }
179
209
  if (data.error)
180
210
  return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
181
211
  const session = data.session;
@@ -242,9 +272,14 @@ async function handleSwarmTool(name, args) {
242
272
  if (!parsed.success)
243
273
  return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
244
274
  const { topic, depth = "standard" } = parsed.data;
245
- // Auto-start swarm if not already running
246
275
  await (0, convex_js_1.callConvex)("/swarm/start", "POST", {}, "start_swarm").catch(() => { });
247
- const data = await (0, convex_js_1.callConvex)("/swarm/research", "POST", { topic, depth }, "swarm_research");
276
+ let data;
277
+ try {
278
+ data = await (0, convex_js_1.callConvex)("/swarm/research", "POST", { topic, depth }, "swarm_research");
279
+ }
280
+ catch (err) {
281
+ return swarmAuthError(err);
282
+ }
248
283
  if (data.error)
249
284
  return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
250
285
  return {
@@ -267,9 +302,14 @@ async function handleSwarmTool(name, args) {
267
302
  if (!parsed.success)
268
303
  return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
269
304
  const { agentId, params = {} } = parsed.data;
270
- // Auto-start swarm if not already running
271
305
  await (0, convex_js_1.callConvex)("/swarm/start", "POST", {}, "start_swarm").catch(() => { });
272
- const data = await (0, convex_js_1.callConvex)("/swarm/trigger", "POST", { agentId, params }, "trigger_agent");
306
+ let data;
307
+ try {
308
+ data = await (0, convex_js_1.callConvex)("/swarm/trigger", "POST", { agentId, params }, "trigger_agent");
309
+ }
310
+ catch (err) {
311
+ return swarmAuthError(err);
312
+ }
273
313
  if (data.error)
274
314
  return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
275
315
  const resultText = data.result ? `\n\`\`\`json\n${JSON.stringify(data.result, null, 2).slice(0, 800)}\n\`\`\`` : "";
@@ -291,9 +331,14 @@ async function handleSwarmTool(name, args) {
291
331
  if (!parsed.success)
292
332
  return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
293
333
  const limit = parsed.data.limit ?? 10;
294
- // Pull recent research entries written by swarm agents from vault
295
334
  const params = new URLSearchParams({ type: "research", limit: String(limit) });
296
- const data = await (0, convex_js_1.callConvex)(`/vault/list?${params}`, "GET", undefined, "swarm_brief");
335
+ let data;
336
+ try {
337
+ data = await (0, convex_js_1.callConvex)(`/vault/list?${params}`, "GET", undefined, "swarm_brief");
338
+ }
339
+ catch (err) {
340
+ return swarmAuthError(err);
341
+ }
297
342
  if (data.error)
298
343
  return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
299
344
  const entries = (data.entries ?? []).filter((e) => e.agentId === "market-monitor" || e.agentId === "sentiment-tracker");
@@ -4,6 +4,7 @@ exports.VAULT_TOOLS = void 0;
4
4
  exports.handleVaultTool = handleVaultTool;
5
5
  const zod_1 = require("zod");
6
6
  const convex_js_1 = require("../convex.js");
7
+ const memory_js_1 = require("./memory.js");
7
8
  const VAULT_TYPES = ["research", "execution", "workflow", "prompt", "file", "memory", "credential"];
8
9
  const LINK_RELATIONS = ["references", "derived_from", "supersedes", "related", "continues"];
9
10
  exports.VAULT_TOOLS = [
@@ -56,12 +57,14 @@ exports.VAULT_TOOLS = [
56
57
  },
57
58
  {
58
59
  name: "vault_search",
59
- description: "Full-text search across Noel-Vault. Searches content, titles, and tags. " +
60
+ description: "Search Noel-Vault using semantic AI search (powered by Supermemory) when available, " +
61
+ "with automatic fallback to full-text search. Semantic search understands meaning — " +
62
+ "'low risk DeFi yield' matches 'conservative staking strategies' without exact keywords. " +
60
63
  "Optionally filter by type. Returns ranked results with previews.",
61
64
  inputSchema: {
62
65
  type: "object",
63
66
  properties: {
64
- query: { type: "string", description: "Search query" },
67
+ query: { type: "string", description: "Search query — natural language works best with semantic mode" },
65
68
  type: { type: "string", enum: [...VAULT_TYPES], description: "Narrow search to a specific type" },
66
69
  limit: { type: "number", description: "Max results (default 20)" },
67
70
  },
@@ -124,9 +127,11 @@ exports.VAULT_TOOLS = [
124
127
  },
125
128
  {
126
129
  name: "vault_context",
127
- description: "Load relevant vault entries for a topic and return them as formatted context ready to inject into a prompt. " +
130
+ description: "Load semantically relevant vault entries for a topic formatted context ready to inject into a prompt. " +
131
+ "Uses Supermemory vector search when available (understands meaning, not just keywords), " +
132
+ "with automatic fallback to full-text search. " +
128
133
  "Call this at the start of any research or analysis task to prime your AI with all past knowledge on the topic. " +
129
- "Returns full content of matching entries, filtered by relevance. Credentials are never included.",
134
+ "Returns full content of matching entries. Credentials are never included.",
130
135
  inputSchema: {
131
136
  type: "object",
132
137
  properties: {
@@ -193,6 +198,23 @@ exports.VAULT_TOOLS = [
193
198
  required: [],
194
199
  },
195
200
  },
201
+ {
202
+ name: "vault_connect",
203
+ description: "Connect an external URL or data source to your vault — fetches, indexes, and stores the content " +
204
+ "as a vault entry AND mirrors it to semantic memory for future retrieval. " +
205
+ "Perfect for indexing GitHub repos, research papers, Notion pages, or any web page. " +
206
+ "For full Google Drive / Gmail / Notion workspace sync, connect via the Noelclaw dashboard.",
207
+ inputSchema: {
208
+ type: "object",
209
+ properties: {
210
+ url: { type: "string", description: "URL to fetch and index (GitHub, Notion, web page, etc.)" },
211
+ title: { type: "string", description: "Title for this vault entry" },
212
+ type: { type: "string", enum: ["research", "file", "memory", "workflow"], description: "Vault entry type (default: research)" },
213
+ tags: { type: "array", items: { type: "string" }, description: "Tags for filtering" },
214
+ },
215
+ required: ["url"],
216
+ },
217
+ },
196
218
  ];
197
219
  // ─── Zod schemas ─────────────────────────────────────────────────────────────
198
220
  const SaveSchema = zod_1.z.object({
@@ -227,6 +249,7 @@ const StoreCredentialSchema = zod_1.z.object({ name: zod_1.z.string().min(1), va
227
249
  const GetCredentialSchema = zod_1.z.object({ name: zod_1.z.string().min(1) });
228
250
  const PublishSchema = zod_1.z.object({ key: zod_1.z.string().min(1), isPublic: zod_1.z.boolean().optional(), authorName: zod_1.z.string().optional() });
229
251
  const ExploreSchema = zod_1.z.object({ type: zod_1.z.enum(["research", "execution", "workflow", "prompt", "file", "memory"]).optional(), search: zod_1.z.string().optional(), limit: zod_1.z.number().optional() });
252
+ const VaultConnectSchema = zod_1.z.object({ url: zod_1.z.string().url(), title: zod_1.z.string().optional(), type: zod_1.z.enum(["research", "file", "memory", "workflow"]).optional(), tags: zod_1.z.array(zod_1.z.string()).optional() });
230
253
  // ─── Helpers ─────────────────────────────────────────────────────────────────
231
254
  function formatBytes(n) {
232
255
  if (!n)
@@ -251,11 +274,19 @@ async function handleVaultTool(name, args) {
251
274
  if (data.error)
252
275
  return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
253
276
  const { key, version, changed } = data;
277
+ // Mirror to semantic memory (fire-and-forget, don't fail the save if SM is unavailable)
278
+ if (parsed.data.type !== "credential") {
279
+ (0, memory_js_1.syncToSupermemory)(parsed.data.content, {
280
+ vaultKey: key, title: parsed.data.title, type: parsed.data.type,
281
+ tags: parsed.data.tags, version, source: "vault_save",
282
+ }).catch(() => { });
283
+ }
254
284
  const lines = [
255
285
  `📦 **Vault ${changed ? (version === 1 ? "Created" : "Updated") : "Unchanged"}**`,
256
286
  `Key: \`${key}\``,
257
287
  `Version: v${version}`,
258
288
  changed && version > 1 ? `Previous version auto-snapshotted.` : "",
289
+ process.env.SUPERMEMORY_API_KEY ? `🧠 Synced to semantic memory` : "",
259
290
  ``,
260
291
  `Use \`vault_read\` to retrieve, \`vault_history\` to see all versions.`,
261
292
  ].filter(Boolean);
@@ -310,6 +341,32 @@ async function handleVaultTool(name, args) {
310
341
  const parsed = SearchSchema.safeParse(args);
311
342
  if (!parsed.success)
312
343
  return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
344
+ // Try Supermemory semantic search first
345
+ if (process.env.SUPERMEMORY_API_KEY) {
346
+ const smResults = await (0, memory_js_1.searchSupermemory)(parsed.data.query, parsed.data.limit ?? 20);
347
+ if (smResults.length > 0) {
348
+ // Filter by type if requested
349
+ const filtered = parsed.data.type
350
+ ? smResults.filter(r => r.metadata?.type === parsed.data.type)
351
+ : smResults;
352
+ if (filtered.length > 0) {
353
+ const header = `🔍 **Vault Search** [Semantic]: "${parsed.data.query}" — ${filtered.length} result(s)`;
354
+ const rows = filtered.map((r, i) => {
355
+ const score = r.score != null ? ` ${(r.score * 100).toFixed(0)}%` : "";
356
+ const title = r.metadata?.title ?? r.content.slice(0, 60);
357
+ const type = r.metadata?.type ?? "memory";
358
+ const key = r.metadata?.vaultKey ?? r.id;
359
+ const preview = r.content.slice(0, 150).replace(/\n/g, " ");
360
+ return [
361
+ `${i + 1}.${score} [\`${key}\`] **${title}** (${type})`,
362
+ ` ${preview}${r.content.length > 150 ? "…" : ""}`,
363
+ ].join("\n");
364
+ });
365
+ return { content: [{ type: "text", text: [header, "", ...rows].join("\n") }] };
366
+ }
367
+ }
368
+ }
369
+ // Fallback: Convex full-text search
313
370
  const params = new URLSearchParams({ q: parsed.data.query });
314
371
  if (parsed.data.type)
315
372
  params.set("type", parsed.data.type);
@@ -395,15 +452,46 @@ async function handleVaultTool(name, args) {
395
452
  }, "vault_remember");
396
453
  if (data.error)
397
454
  return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
398
- return { content: [{ type: "text", text: `✅ Remembered — key: \`${data.key}\` (v${data.version})\nRetrieve later with: \`vault_context topic: "${inferredTitle.slice(0, 40)}"\`` }] };
455
+ // Mirror to semantic memory
456
+ (0, memory_js_1.syncToSupermemory)(content, {
457
+ vaultKey: data.key, title: inferredTitle, type: "memory",
458
+ tags, version: data.version, source: "vault_remember",
459
+ }).catch(() => { });
460
+ const smNote = process.env.SUPERMEMORY_API_KEY ? " 🧠 Synced to semantic memory" : "";
461
+ return { content: [{ type: "text", text: `✅ Remembered — key: \`${data.key}\` (v${data.version})${smNote}\nRetrieve later with: \`vault_context topic: "${inferredTitle.slice(0, 40)}"\`` }] };
399
462
  }
400
463
  case "vault_context": {
401
464
  const parsed = ContextSchema.safeParse(args);
402
465
  if (!parsed.success)
403
466
  return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
467
+ const limit = parsed.data.limit ?? 8;
468
+ // Semantic context via Supermemory
469
+ if (process.env.SUPERMEMORY_API_KEY) {
470
+ const smResults = await (0, memory_js_1.searchSupermemory)(parsed.data.topic, limit);
471
+ if (smResults.length > 0) {
472
+ const contextParts = smResults.map((r, i) => {
473
+ const title = r.metadata?.title ?? `Entry ${i + 1}`;
474
+ return `### ${title}\n${r.content}`;
475
+ });
476
+ const summary = smResults.map(r => r.metadata?.title ?? r.content.slice(0, 50)).join(", ");
477
+ return {
478
+ content: [{
479
+ type: "text",
480
+ text: [
481
+ `📚 **Vault Context** [Semantic] for: "${parsed.data.topic}"`,
482
+ `Entries: ${summary}`,
483
+ ``,
484
+ `---`,
485
+ ``,
486
+ contextParts.join("\n\n---\n\n"),
487
+ ].join("\n"),
488
+ }],
489
+ };
490
+ }
491
+ }
492
+ // Fallback: Convex full-text context
404
493
  const params = new URLSearchParams({ q: parsed.data.topic });
405
- if (parsed.data.limit)
406
- params.set("limit", String(parsed.data.limit));
494
+ params.set("limit", String(limit));
407
495
  const data = await (0, convex_js_1.callConvex)(`/vault/context?${params}`, "GET", undefined, "vault_context");
408
496
  if (data.error)
409
497
  return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
@@ -484,6 +572,47 @@ async function handleVaultTool(name, args) {
484
572
  }],
485
573
  };
486
574
  }
575
+ case "vault_connect": {
576
+ const parsed = VaultConnectSchema.safeParse(args);
577
+ if (!parsed.success)
578
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
579
+ const { url, title, type = "research", tags } = parsed.data;
580
+ const inferredTitle = title ?? url.split("/").filter(Boolean).pop() ?? url;
581
+ // Save to Convex vault
582
+ const vaultData = await (0, convex_js_1.callConvex)("/vault/save", "POST", {
583
+ type,
584
+ title: inferredTitle,
585
+ content: `Connected from: ${url}\n\n(Content indexed into semantic memory via Supermemory)`,
586
+ key: `connected/${inferredTitle.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 50)}`,
587
+ tags: [...(tags ?? []), "connected", new URL(url).hostname],
588
+ commitMsg: "vault_connect",
589
+ metadata: JSON.stringify({ sourceUrl: url }),
590
+ }, "vault_connect");
591
+ // Mirror full content to Supermemory with source URL for ingestion
592
+ if (process.env.SUPERMEMORY_API_KEY) {
593
+ (0, memory_js_1.syncToSupermemory)("", {
594
+ vaultKey: vaultData.key, title: inferredTitle, type, tags, source: "vault_connect",
595
+ }, url).catch(() => { });
596
+ }
597
+ if (vaultData.error)
598
+ return { content: [{ type: "text", text: `Error: ${vaultData.error}` }], isError: true };
599
+ return {
600
+ content: [{
601
+ type: "text",
602
+ text: [
603
+ `🔗 **Vault Connected**`,
604
+ `URL: ${url}`,
605
+ `Vault key: \`${vaultData.key}\``,
606
+ process.env.SUPERMEMORY_API_KEY
607
+ ? `🧠 Indexing to semantic memory… searchable in ~30s`
608
+ : `⚠️ Set SUPERMEMORY_API_KEY for full semantic indexing`,
609
+ ``,
610
+ `Search it with: \`vault_search query: "${inferredTitle}"\``,
611
+ `Or load as context: \`vault_context topic: "${inferredTitle}"\``,
612
+ ].filter(Boolean).join("\n"),
613
+ }],
614
+ };
615
+ }
487
616
  default:
488
617
  return null;
489
618
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noelclaw/mcp",
3
- "version": "2.2.4",
3
+ "version": "2.3.0",
4
4
  "description": "Noelclaw as an MCP skill — persistent memory, multi-agent coordination, scenario simulation, DeFi execution, and Sentinel-gated playbooks.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {