@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 +156 -146
- package/dist/index.js +3 -2
- package/dist/server.js +7 -4
- package/dist/tools/memory.js +339 -0
- package/dist/tools/swarm.js +54 -9
- package/dist/tools/vault.js +136 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,257 +2,267 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@noelclaw/mcp)
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
15
|
+
## Install
|
|
16
16
|
|
|
17
|
-
### Claude Code
|
|
17
|
+
### Claude Code (CLI)
|
|
18
18
|
```bash
|
|
19
|
-
claude mcp add noelclaw
|
|
19
|
+
claude mcp add noelclaw -- npx -y @noelclaw/mcp
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
### Claude Desktop
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
54
|
+
## What It Does
|
|
68
55
|
|
|
69
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
85
|
+
### Base Chain (4 tools)
|
|
83
86
|
|
|
84
87
|
| Tool | Description |
|
|
85
88
|
|------|-------------|
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
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
|
-
###
|
|
94
|
+
### Market & AI (5 tools)
|
|
90
95
|
|
|
91
96
|
| Tool | Description |
|
|
92
97
|
|------|-------------|
|
|
93
|
-
| `
|
|
94
|
-
| `
|
|
95
|
-
| `ask_noel` |
|
|
96
|
-
| `
|
|
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
|
-
###
|
|
104
|
+
### DeFi Execution (5 tools)
|
|
99
105
|
|
|
100
|
-
>
|
|
106
|
+
> Transactions are signed client-side — no private key ever leaves your machine.
|
|
101
107
|
|
|
102
108
|
| Tool | Description |
|
|
103
109
|
|------|-------------|
|
|
104
|
-
| `
|
|
105
|
-
| `
|
|
106
|
-
| `
|
|
107
|
-
| `
|
|
108
|
-
| `
|
|
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
|
-
###
|
|
116
|
+
### Vault — Persistent Memory (13 tools)
|
|
113
117
|
|
|
114
|
-
>
|
|
118
|
+
> Save research, notes, and data across sessions. Every save auto-versions.
|
|
115
119
|
|
|
116
120
|
| Tool | Description |
|
|
117
121
|
|------|-------------|
|
|
118
|
-
| `
|
|
119
|
-
| `
|
|
120
|
-
| `
|
|
121
|
-
| `
|
|
122
|
-
| `
|
|
123
|
-
| `
|
|
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
|
-
|
|
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
|
-
|
|
151
|
+
### Automation (5 tools)
|
|
128
152
|
|
|
129
153
|
| Tool | Description |
|
|
130
154
|
|------|-------------|
|
|
131
|
-
| `
|
|
132
|
-
| `
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
###
|
|
161
|
+
### Framework & Sentinel (6 tools)
|
|
137
162
|
|
|
138
|
-
>
|
|
163
|
+
> Sentinel-gated agent execution — every action checked against rules before it runs.
|
|
139
164
|
|
|
140
165
|
| Tool | Description |
|
|
141
166
|
|------|-------------|
|
|
142
|
-
| `
|
|
143
|
-
| `
|
|
144
|
-
| `
|
|
145
|
-
| `
|
|
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
|
-
###
|
|
174
|
+
### Scanner (3 tools)
|
|
148
175
|
|
|
149
176
|
| Tool | Description |
|
|
150
177
|
|------|-------------|
|
|
151
|
-
| `
|
|
152
|
-
| `
|
|
153
|
-
| `
|
|
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
|
-
###
|
|
182
|
+
### MiroShark Simulation (3 tools)
|
|
157
183
|
|
|
158
|
-
>
|
|
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
|
-
| `
|
|
163
|
-
| `
|
|
164
|
-
| `
|
|
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
|
-
###
|
|
192
|
+
### Coder (6 tools)
|
|
170
193
|
|
|
171
194
|
| Tool | Description |
|
|
172
195
|
|------|-------------|
|
|
173
|
-
| `
|
|
174
|
-
| `
|
|
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
|
-
|
|
205
|
+
| Tool | Description |
|
|
206
|
+
|------|-------------|
|
|
207
|
+
| `list_agents` | Browse available AI agents |
|
|
208
|
+
| `hire_agent` | Hire an agent for a specific task |
|
|
179
209
|
|
|
180
|
-
###
|
|
210
|
+
### Utilities (4 tools)
|
|
181
211
|
|
|
182
|
-
|
|
|
183
|
-
|
|
184
|
-
| `
|
|
212
|
+
| Tool | Description |
|
|
213
|
+
|------|-------------|
|
|
214
|
+
| `humanize_text` | Remove AI writing patterns — fixes 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
|
-
|
|
219
|
+
---
|
|
187
220
|
|
|
188
|
-
|
|
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
|
-
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
|
245
|
-
|
|
255
|
+
| Problem | Fix |
|
|
256
|
+
|---------|-----|
|
|
246
257
|
| Tools not appearing | Restart your MCP client after adding the config |
|
|
247
|
-
|
|
|
248
|
-
|
|
|
249
|
-
|
|
|
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:
|
|
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.
|
|
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, //
|
|
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
|
-
//
|
|
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.
|
|
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
|
+
}
|
package/dist/tools/swarm.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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");
|
package/dist/tools/vault.js
CHANGED
|
@@ -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: "
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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": {
|