@noelclaw/mcp 1.0.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/LICENSE +21 -0
- package/README.md +246 -0
- package/dist/convex.js +107 -0
- package/dist/index.js +31 -0
- package/dist/server.js +101 -0
- package/dist/tools/automation.js +116 -0
- package/dist/tools/defi.js +241 -0
- package/dist/tools/framework.js +259 -0
- package/dist/tools/humanizer.js +120 -0
- package/dist/tools/insight.js +59 -0
- package/dist/tools/market.js +229 -0
- package/dist/tools/miroshark.js +155 -0
- package/dist/tools/news.js +99 -0
- package/dist/tools/research.js +29 -0
- package/dist/tools/swarm.js +162 -0
- package/dist/tools/twitter.js +67 -0
- package/dist/tools/vault.js +293 -0
- package/dist/tools/wallet.js +99 -0
- package/dist/types.js +2 -0
- package/dist/wallet.js +137 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 noelclaw
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# @noelclaw/mcp
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@noelclaw/mcp)
|
|
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.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx @noelclaw/mcp@latest
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Quick Install
|
|
14
|
+
|
|
15
|
+
### Claude Code
|
|
16
|
+
```bash
|
|
17
|
+
claude mcp add noelclaw -- npx @noelclaw/mcp@latest
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Set your API key:
|
|
21
|
+
```bash
|
|
22
|
+
claude mcp add noelclaw -e NOELCLAW_API_KEY=noel_... -- npx @noelclaw/mcp@latest
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Claude Desktop
|
|
26
|
+
Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (Mac) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"mcpServers": {
|
|
31
|
+
"noelclaw": {
|
|
32
|
+
"command": "npx",
|
|
33
|
+
"args": ["@noelclaw/mcp@latest"],
|
|
34
|
+
"env": {
|
|
35
|
+
"NOELCLAW_API_KEY": "noel_..."
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Cursor / Windsurf
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"mcpServers": {
|
|
46
|
+
"noelclaw": {
|
|
47
|
+
"command": "npx",
|
|
48
|
+
"args": ["@noelclaw/mcp@latest"],
|
|
49
|
+
"env": {
|
|
50
|
+
"NOELCLAW_API_KEY": "noel_..."
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Hermes
|
|
58
|
+
```yaml
|
|
59
|
+
mcp_servers:
|
|
60
|
+
noelclaw:
|
|
61
|
+
command: npx
|
|
62
|
+
args:
|
|
63
|
+
- "@noelclaw/mcp@latest"
|
|
64
|
+
env:
|
|
65
|
+
NOELCLAW_API_KEY: "noel_..."
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Authentication
|
|
71
|
+
|
|
72
|
+
Get a key instantly — no signup:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
curl -X POST https://api.noelclaw.com/auth/key
|
|
76
|
+
# → { "apiKey": "noel_..." }
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Set `NOELCLAW_API_KEY` in your MCP config. That's it.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Tools
|
|
84
|
+
|
|
85
|
+
### Research & AI
|
|
86
|
+
|
|
87
|
+
| Tool | Description |
|
|
88
|
+
|------|-------------|
|
|
89
|
+
| `research` | Deep research via Bankr (real-time). Returns overview, key findings, market impact, sentiment |
|
|
90
|
+
| `ask_noel` | Ask Noel AI for analysis, trade ideas, and research |
|
|
91
|
+
| `humanize_text` | Remove AI tells from text — makes output sound natural and human-written |
|
|
92
|
+
|
|
93
|
+
### Noel-Vault
|
|
94
|
+
|
|
95
|
+
> Persistent memory across sessions. Save findings, recall by key, search full-text. Every save auto-versions.
|
|
96
|
+
|
|
97
|
+
| Tool | Description |
|
|
98
|
+
|------|-------------|
|
|
99
|
+
| `vault_save` | Save any content — research, execution logs, workflows, prompts, files |
|
|
100
|
+
| `vault_read` | Read an entry by key |
|
|
101
|
+
| `vault_list` | List all entries with type, title, version, last updated |
|
|
102
|
+
| `vault_search` | Full-text search across all content |
|
|
103
|
+
| `vault_history` | Version history with commit messages |
|
|
104
|
+
| `vault_diff` | Line-by-line diff between two versions |
|
|
105
|
+
| `vault_export` | Export as markdown or JSON |
|
|
106
|
+
|
|
107
|
+
### Noel-Swarm
|
|
108
|
+
|
|
109
|
+
> Shared memory bus for multi-agent coordination. All agents read/write the same store with freshness tracking.
|
|
110
|
+
|
|
111
|
+
| Tool | Description |
|
|
112
|
+
|------|-------------|
|
|
113
|
+
| `start_swarm` | Start a swarm session |
|
|
114
|
+
| `stop_swarm` | Stop the active session |
|
|
115
|
+
| `get_swarm_status` | Session state, memory snapshot, execution scores |
|
|
116
|
+
| `write_swarm_memory` | Write a key-value entry with optional TTL |
|
|
117
|
+
| `get_swarm_memory` | Read by key — returns value + freshness metadata |
|
|
118
|
+
| `get_execution_scores` | Per-agent, per-skill scores |
|
|
119
|
+
|
|
120
|
+
### MiroShark
|
|
121
|
+
|
|
122
|
+
> Scenario simulation engine — drop in any scenario and get back strategic insights from a network of AI agents reacting hour by hour. Requires `MIROSHARK_URL` + `MIROSHARK_ADMIN_TOKEN`.
|
|
123
|
+
|
|
124
|
+
| Tool | Description |
|
|
125
|
+
|------|-------------|
|
|
126
|
+
| `miroshark_simulate` | Run a multi-agent simulation from a plain-English scenario. Returns a simulation ID |
|
|
127
|
+
| `miroshark_status` | Poll simulation results by ID — surfaces insights and consensus when complete |
|
|
128
|
+
|
|
129
|
+
### Wallet & DeFi `beta`
|
|
130
|
+
|
|
131
|
+
> On-chain operations on Base mainnet. Transactions are built for client-side signing — no private key ever leaves your machine.
|
|
132
|
+
|
|
133
|
+
| Tool | Description |
|
|
134
|
+
|------|-------------|
|
|
135
|
+
| `get_wallet_address` | Show your local MCP wallet address |
|
|
136
|
+
| `get_portfolio` | Full token portfolio with ETH and ERC-20 balances and USD values |
|
|
137
|
+
| `swap_tokens` | Swap tokens on Base mainnet |
|
|
138
|
+
| `send_token` | Send ETH or any ERC-20 to any address |
|
|
139
|
+
|
|
140
|
+
### Automations `beta`
|
|
141
|
+
|
|
142
|
+
| Tool | Description |
|
|
143
|
+
|------|-------------|
|
|
144
|
+
| `create_automation` | Create an automation in plain English — DCA, price alerts, conditional buys/sells |
|
|
145
|
+
| `list_automations` | List all automations with status and next scheduled run |
|
|
146
|
+
| `pause_automation` | Pause or resume an automation |
|
|
147
|
+
| `delete_automation` | Permanently delete an automation |
|
|
148
|
+
|
|
149
|
+
### Noel Framework `beta`
|
|
150
|
+
|
|
151
|
+
> Sentinel-gated agent execution. Define what your AI can and can't do — before it runs. Every action checked against 5 mechanical rules before execution.
|
|
152
|
+
|
|
153
|
+
| Tool | Description |
|
|
154
|
+
|------|-------------|
|
|
155
|
+
| `create_task_packet` | Convert plain-English intent into a structured task scope with permissions and constraints |
|
|
156
|
+
| `list_task_packets` | List all task packets |
|
|
157
|
+
| `list_playbooks` | List available playbooks |
|
|
158
|
+
| `run_playbook` | Execute a Sentinel-gated playbook — halts if any step is blocked |
|
|
159
|
+
| `get_noel_ledger` | Full audit trail of every Sentinel decision |
|
|
160
|
+
| `get_sentinel_rules` | Exact rules per agent role |
|
|
161
|
+
|
|
162
|
+
### Notifications
|
|
163
|
+
|
|
164
|
+
| Tool | Description |
|
|
165
|
+
|------|-------------|
|
|
166
|
+
| `set_telegram` | Connect Telegram for push notifications |
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Environment Variables
|
|
171
|
+
|
|
172
|
+
### Required
|
|
173
|
+
|
|
174
|
+
| Var | Description |
|
|
175
|
+
|-----|-------------|
|
|
176
|
+
| `NOELCLAW_API_KEY` | Your API key (`noel_...`) — get one at `POST https://api.noelclaw.com/auth/key` |
|
|
177
|
+
|
|
178
|
+
### MiroShark (optional)
|
|
179
|
+
|
|
180
|
+
| Var | Description |
|
|
181
|
+
|-----|-------------|
|
|
182
|
+
| `MIROSHARK_URL` | URL of your deployed MiroShark instance |
|
|
183
|
+
| `MIROSHARK_ADMIN_TOKEN` | Admin token set on your MiroShark deployment |
|
|
184
|
+
|
|
185
|
+
### BYOK (optional)
|
|
186
|
+
|
|
187
|
+
| Var | Used for |
|
|
188
|
+
|-----|---------|
|
|
189
|
+
| `BANKR_API_KEY` | Bankr Agent — research, DeFi |
|
|
190
|
+
| `TELEGRAM_BOT_TOKEN` | Your own Telegram bot |
|
|
191
|
+
| `TELEGRAM_CHAT_ID` | Your Telegram chat ID |
|
|
192
|
+
| `ALCHEMY_API_KEY` | Faster Base RPC for swaps and portfolio |
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Usage Examples
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
# Research anything
|
|
200
|
+
research(query: "What is happening with the Base ecosystem this week?")
|
|
201
|
+
ask_noel(question: "What are the risks of holding ETH through a Fed meeting?")
|
|
202
|
+
|
|
203
|
+
# Save findings to vault
|
|
204
|
+
vault_save(type: "research", key: "research/base-may-2026", title: "Base Ecosystem", content: "...")
|
|
205
|
+
vault_search(query: "Base ecosystem")
|
|
206
|
+
|
|
207
|
+
# Coordinate agents via swarm
|
|
208
|
+
start_swarm
|
|
209
|
+
write_swarm_memory(agentId: "analyst", key: "research/btc", value: "bullish", ttlSeconds: 3600)
|
|
210
|
+
get_swarm_memory(key: "research/btc")
|
|
211
|
+
|
|
212
|
+
# Run a MiroShark simulation
|
|
213
|
+
miroshark_simulate(scenario: "What happens if a major L1 announces a 50% fee reduction?")
|
|
214
|
+
miroshark_status(simulation_id: "sim_abc123")
|
|
215
|
+
|
|
216
|
+
# Clean up AI-generated text
|
|
217
|
+
humanize_text(text: "Certainly! I'd be happy to assist you with...")
|
|
218
|
+
|
|
219
|
+
# DeFi (beta)
|
|
220
|
+
get_portfolio
|
|
221
|
+
swap_tokens(fromToken: "ETH", toToken: "USDC", amount: "0.01")
|
|
222
|
+
|
|
223
|
+
# Sentinel-gated execution (beta)
|
|
224
|
+
create_task_packet(task: "Monitor portfolio, max $0 spend, read only")
|
|
225
|
+
run_playbook(playbook_name: "Daily Market Scan")
|
|
226
|
+
get_noel_ledger
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Troubleshooting
|
|
232
|
+
|
|
233
|
+
| Error | Fix |
|
|
234
|
+
|-------|-----|
|
|
235
|
+
| Tools not appearing | Restart your MCP client after adding the config |
|
|
236
|
+
| `401 Unauthorized` | Check `NOELCLAW_API_KEY` is set — get one at `POST https://api.noelclaw.com/auth/key` |
|
|
237
|
+
| `miroshark_simulate` error | Set `MIROSHARK_URL` and `MIROSHARK_ADMIN_TOKEN` |
|
|
238
|
+
| Server starts but no response | Expected — server waits for MCP stdin, not HTTP |
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Links
|
|
243
|
+
|
|
244
|
+
- npm: [npmjs.com/package/@noelclaw/mcp](https://npmjs.com/package/@noelclaw/mcp)
|
|
245
|
+
- GitHub: [github.com/noelclaw/research](https://github.com/noelclaw/mcp)
|
|
246
|
+
- Platform: [noelclaw.com](https://noelclaw.com)
|
package/dist/convex.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PaymentRequiredError = void 0;
|
|
4
|
+
exports.buildPaymentHeader = buildPaymentHeader;
|
|
5
|
+
exports.callConvex = callConvex;
|
|
6
|
+
exports.notifyTelegram = notifyTelegram;
|
|
7
|
+
const wallet_js_1 = require("./wallet.js");
|
|
8
|
+
const CONVEX_SITE = process.env.NOELCLAW_CONVEX_URL ?? "https://api.noelclaw.com";
|
|
9
|
+
const RETRY_STATUSES = new Set([429, 500, 502, 503, 504]);
|
|
10
|
+
const RETRY_DELAYS = [500, 1000, 2000];
|
|
11
|
+
class PaymentRequiredError extends Error {
|
|
12
|
+
constructor(details) {
|
|
13
|
+
super("Payment required");
|
|
14
|
+
this.name = "PaymentRequiredError";
|
|
15
|
+
this.details = details;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.PaymentRequiredError = PaymentRequiredError;
|
|
19
|
+
function buildPaymentHeader(txHash, requestId) {
|
|
20
|
+
return Buffer.from(`${txHash}:${requestId}`).toString("base64");
|
|
21
|
+
}
|
|
22
|
+
async function attemptConvex(url, method, headers, body) {
|
|
23
|
+
return fetch(url, {
|
|
24
|
+
method,
|
|
25
|
+
headers,
|
|
26
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
27
|
+
signal: AbortSignal.timeout(30000),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
async function callConvex(path, method, body, toolName = "unknown") {
|
|
31
|
+
const url = `${CONVEX_SITE}${path}`;
|
|
32
|
+
const headers = { "Content-Type": "application/json" };
|
|
33
|
+
const apiKey = process.env.NOELCLAW_API_KEY;
|
|
34
|
+
const sessionToken = process.env.NOELCLAW_SESSION_TOKEN;
|
|
35
|
+
const authHeader = apiKey
|
|
36
|
+
? `Bearer ${apiKey}`
|
|
37
|
+
: sessionToken
|
|
38
|
+
? `Bearer ${sessionToken}`
|
|
39
|
+
: null;
|
|
40
|
+
if (authHeader) {
|
|
41
|
+
headers["Authorization"] = authHeader;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
try {
|
|
45
|
+
const { address, signature, timestamp } = await (0, wallet_js_1.signRequest)(toolName);
|
|
46
|
+
headers["X-Wallet-Address"] = address;
|
|
47
|
+
headers["X-Wallet-Signature"] = signature;
|
|
48
|
+
headers["X-Wallet-Timestamp"] = timestamp;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// continue without wallet headers — server will respond with 401/402
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const paymentHeader = process.env.NOELCLAW_PAYMENT_HEADER;
|
|
55
|
+
if (paymentHeader)
|
|
56
|
+
headers["X-Payment"] = paymentHeader;
|
|
57
|
+
// BYOK headers
|
|
58
|
+
if (process.env.GROK_API_KEY)
|
|
59
|
+
headers["X-User-Grok-Key"] = process.env.GROK_API_KEY;
|
|
60
|
+
if (process.env.BANKR_API_KEY)
|
|
61
|
+
headers["X-User-Bankr-Key"] = process.env.BANKR_API_KEY;
|
|
62
|
+
if (process.env.TELEGRAM_BOT_TOKEN)
|
|
63
|
+
headers["X-User-Telegram-Token"] = process.env.TELEGRAM_BOT_TOKEN;
|
|
64
|
+
if (process.env.TELEGRAM_CHAT_ID)
|
|
65
|
+
headers["X-User-Telegram-Chat"] = process.env.TELEGRAM_CHAT_ID;
|
|
66
|
+
let lastError = null;
|
|
67
|
+
for (let attempt = 0; attempt < RETRY_DELAYS.length; attempt++) {
|
|
68
|
+
if (attempt > 0) {
|
|
69
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAYS[attempt - 1]));
|
|
70
|
+
}
|
|
71
|
+
let res;
|
|
72
|
+
try {
|
|
73
|
+
res = await attemptConvex(url, method, headers, body);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
lastError = err;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (res.status === 402) {
|
|
80
|
+
const b = await res.json().catch(() => ({}));
|
|
81
|
+
throw new PaymentRequiredError(b);
|
|
82
|
+
}
|
|
83
|
+
if (res.status === 401) {
|
|
84
|
+
const b = await res.json().catch(() => ({}));
|
|
85
|
+
throw new Error(`🔑 ${b.message || "Authentication required"}\n\n` +
|
|
86
|
+
`→ Get your API key: ${b.url || "https://noelclaw.com"}\n\n` +
|
|
87
|
+
`Hint: ${b.hint || "Set NOELCLAW_API_KEY=noel_sk_xxx in your MCP config"}\n\n` +
|
|
88
|
+
`${b.alternative ? `Alternative: ${b.alternative}` : ""}`);
|
|
89
|
+
}
|
|
90
|
+
if (RETRY_STATUSES.has(res.status) && attempt < RETRY_DELAYS.length) {
|
|
91
|
+
lastError = new Error(`Noelclaw API error: ${res.status}`);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (!res.ok)
|
|
95
|
+
throw new Error(`Noelclaw API error: ${res.status} ${await res.text()}`);
|
|
96
|
+
return res.json();
|
|
97
|
+
}
|
|
98
|
+
throw lastError ?? new Error("Request failed after retries");
|
|
99
|
+
}
|
|
100
|
+
async function notifyTelegram(userId, message) {
|
|
101
|
+
try {
|
|
102
|
+
return await callConvex("/user/telegram/notify", "POST", { userId, message }, "set_telegram");
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
return { sent: false, reason: error.message ?? String(error) };
|
|
106
|
+
}
|
|
107
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const server_js_1 = require("./server.js");
|
|
5
|
+
const wallet_js_1 = require("./wallet.js");
|
|
6
|
+
const BANNER = `
|
|
7
|
+
\x1b[36m
|
|
8
|
+
███╗ ██╗ ██████╗ ███████╗██╗ ██████╗██╗ █████╗ ██╗ ██╗
|
|
9
|
+
████╗ ██║██╔═══██╗██╔════╝██║ ██╔════╝██║ ██╔══██╗██║ ██║
|
|
10
|
+
██╔██╗ ██║██║ ██║█████╗ ██║ ██║ ██║ ███████║██║ █╗ ██║
|
|
11
|
+
██║╚██╗██║██║ ██║██╔══╝ ██║ ██║ ██║ ██╔══██║██║███╗██║
|
|
12
|
+
██║ ╚████║╚██████╔╝███████╗███████╗╚██████╗███████╗██║ ██║╚███╔███╔╝
|
|
13
|
+
╚═╝ ╚═══╝ ╚═════╝ ╚══════╝╚══════╝ ╚═════╝╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝
|
|
14
|
+
\x1b[0m \x1b[90mcrypto AI · MCP · DeFi · research · swarm\x1b[0m
|
|
15
|
+
`;
|
|
16
|
+
async function main() {
|
|
17
|
+
console.error(BANNER);
|
|
18
|
+
await (0, server_js_1.startServer)();
|
|
19
|
+
try {
|
|
20
|
+
const wallet = await (0, wallet_js_1.getOrCreateWallet)();
|
|
21
|
+
console.error(`\x1b[36m ◆ wallet\x1b[0m ${wallet.address}`);
|
|
22
|
+
console.error(`\x1b[36m ◆ status\x1b[0m ready · 46 tools loaded\n`);
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
console.error(`[noelclaw] wallet init failed: ${err}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
main().catch((err) => {
|
|
29
|
+
console.error(err);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
});
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.server = exports.ALL_TOOLS = void 0;
|
|
4
|
+
exports.startServer = startServer;
|
|
5
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
6
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
7
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
8
|
+
const convex_js_1 = require("./convex.js");
|
|
9
|
+
const market_js_1 = require("./tools/market.js");
|
|
10
|
+
const research_js_1 = require("./tools/research.js");
|
|
11
|
+
const defi_js_1 = require("./tools/defi.js");
|
|
12
|
+
const automation_js_1 = require("./tools/automation.js");
|
|
13
|
+
const swarm_js_1 = require("./tools/swarm.js");
|
|
14
|
+
const insight_js_1 = require("./tools/insight.js");
|
|
15
|
+
const framework_js_1 = require("./tools/framework.js");
|
|
16
|
+
const wallet_js_1 = require("./tools/wallet.js");
|
|
17
|
+
const news_js_1 = require("./tools/news.js");
|
|
18
|
+
const vault_js_1 = require("./tools/vault.js");
|
|
19
|
+
const twitter_js_1 = require("./tools/twitter.js");
|
|
20
|
+
const miroshark_js_1 = require("./tools/miroshark.js");
|
|
21
|
+
const humanizer_js_1 = require("./tools/humanizer.js");
|
|
22
|
+
const PRIVATE_KEY_RESPONSE = {
|
|
23
|
+
content: [{
|
|
24
|
+
type: "text",
|
|
25
|
+
text: "I don't have access to your private key. Your wallet is secured by Noelclaw's encrypted vault. Only you can manage it at noelclaw.xyz",
|
|
26
|
+
}],
|
|
27
|
+
};
|
|
28
|
+
function containsSensitiveRequest(args) {
|
|
29
|
+
const text = JSON.stringify(args ?? "").toLowerCase();
|
|
30
|
+
return (text.includes("private key") ||
|
|
31
|
+
text.includes("seed phrase") ||
|
|
32
|
+
text.includes("mnemonic") ||
|
|
33
|
+
text.includes("privatekey"));
|
|
34
|
+
}
|
|
35
|
+
exports.ALL_TOOLS = [
|
|
36
|
+
...market_js_1.MARKET_TOOLS, // 6 — market data, signals, whale alerts, recap
|
|
37
|
+
...news_js_1.NEWS_TOOLS, // 2 — crypto news digest, manual signal gen
|
|
38
|
+
...research_js_1.RESEARCH_TOOLS, // 1 — deep research
|
|
39
|
+
...insight_js_1.INSIGHT_TOOLS, // 2 — get_insight, ask_noel
|
|
40
|
+
...defi_js_1.DEFI_TOOLS, // 6 — portfolio, swap, send, deploy, claim, mint
|
|
41
|
+
...automation_js_1.AUTOMATION_TOOLS, // 4 — create, list, pause, delete
|
|
42
|
+
...swarm_js_1.SWARM_TOOLS, // 6 — start, stop, status, memory, scores
|
|
43
|
+
...framework_js_1.FRAMEWORK_TOOLS, // 6 — task packets, playbooks, sentinel, ledger
|
|
44
|
+
...vault_js_1.VAULT_TOOLS, // 7 — save, read, list, search, history, diff, export
|
|
45
|
+
...wallet_js_1.WALLET_TOOLS, // 2 — wallet address, telegram connect
|
|
46
|
+
...twitter_js_1.TWITTER_TOOLS, // 1 — post tweet
|
|
47
|
+
...miroshark_js_1.MIROSHARK_TOOLS, // 2 — simulate, status
|
|
48
|
+
...humanizer_js_1.HUMANIZER_TOOLS, // 1 — humanize_text
|
|
49
|
+
// total: 46
|
|
50
|
+
];
|
|
51
|
+
exports.server = new index_js_1.Server({ name: "noelclaw", version: "2.1.0" }, { capabilities: { tools: {} } });
|
|
52
|
+
exports.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({ tools: exports.ALL_TOOLS }));
|
|
53
|
+
exports.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
54
|
+
const { name, arguments: args } = request.params;
|
|
55
|
+
if (containsSensitiveRequest(args))
|
|
56
|
+
return PRIVATE_KEY_RESPONSE;
|
|
57
|
+
try {
|
|
58
|
+
const result = await (0, market_js_1.handleMarketTool)(name, args) ??
|
|
59
|
+
await (0, news_js_1.handleNewsTool)(name, args) ??
|
|
60
|
+
await (0, research_js_1.handleResearchTool)(name, args) ??
|
|
61
|
+
await (0, defi_js_1.handleDefiTool)(name, args) ??
|
|
62
|
+
await (0, automation_js_1.handleAutomationTool)(name, args) ??
|
|
63
|
+
await (0, swarm_js_1.handleSwarmTool)(name, args) ??
|
|
64
|
+
await (0, framework_js_1.handleFrameworkTool)(name, args) ??
|
|
65
|
+
await (0, vault_js_1.handleVaultTool)(name, args) ??
|
|
66
|
+
await (0, wallet_js_1.handleWalletTool)(name, args) ??
|
|
67
|
+
await (0, insight_js_1.handleInsightTool)(name, args) ??
|
|
68
|
+
await (0, twitter_js_1.handleTwitterTool)(name, args) ??
|
|
69
|
+
await (0, miroshark_js_1.handleMirosharkTool)(name, args) ??
|
|
70
|
+
await (0, humanizer_js_1.handleHumanizerTool)(name, args);
|
|
71
|
+
if (result)
|
|
72
|
+
return result;
|
|
73
|
+
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
if (err instanceof convex_js_1.PaymentRequiredError) {
|
|
77
|
+
const d = err.details?.paymentDetails;
|
|
78
|
+
const lines = [
|
|
79
|
+
"⚠️ **Payment Required**", "",
|
|
80
|
+
"This tool requires a USDC micropayment on Base mainnet.",
|
|
81
|
+
...(d ? [
|
|
82
|
+
``, `Amount: **${d.amount} USDC**`, `To: \`${d.address}\``, `Request ID: \`${d.requestId}\``, ``,
|
|
83
|
+
"**To pay:**",
|
|
84
|
+
`1. Send ${d.amount} USDC to \`${d.address}\` on Base mainnet`,
|
|
85
|
+
`2. Copy the transaction hash`,
|
|
86
|
+
`3. Set env var: \`NOELCLAW_PAYMENT_HEADER=${(0, convex_js_1.buildPaymentHeader)("<txHash>", d.requestId)}\``,
|
|
87
|
+
` (replace \`<txHash>\` with the actual transaction hash)`,
|
|
88
|
+
`4. Retry the tool call`, ``,
|
|
89
|
+
"**Or bypass with a session token:**",
|
|
90
|
+
"Set `NOELCLAW_SESSION_TOKEN` with your Noelclaw session token from noelclaw.xyz",
|
|
91
|
+
] : []),
|
|
92
|
+
];
|
|
93
|
+
return { content: [{ type: "text", text: lines.join("\n") }], isError: true };
|
|
94
|
+
}
|
|
95
|
+
return { content: [{ type: "text", text: `Error: ${err.message}` }], isError: true };
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
async function startServer() {
|
|
99
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
100
|
+
await exports.server.connect(transport);
|
|
101
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AUTOMATION_TOOLS = void 0;
|
|
4
|
+
exports.handleAutomationTool = handleAutomationTool;
|
|
5
|
+
const zod_1 = require("zod");
|
|
6
|
+
const convex_js_1 = require("../convex.js");
|
|
7
|
+
exports.AUTOMATION_TOOLS = [
|
|
8
|
+
{
|
|
9
|
+
name: "create_automation",
|
|
10
|
+
description: "Create an automation in plain English. Supports DCA, price alerts, conditional buys/sells, and recurring market updates.",
|
|
11
|
+
inputSchema: {
|
|
12
|
+
type: "object",
|
|
13
|
+
properties: { rawInput: { type: "string", description: "Plain English description of the automation" } },
|
|
14
|
+
required: ["rawInput"],
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: "list_automations",
|
|
19
|
+
description: "List all your automations — active, paused, and completed — with status, run counts, and next scheduled run.",
|
|
20
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: "pause_automation",
|
|
24
|
+
description: "Pause or resume an automation by ID.",
|
|
25
|
+
inputSchema: {
|
|
26
|
+
type: "object",
|
|
27
|
+
properties: { automationId: { type: "string", description: "Automation ID (from list_automations)" } },
|
|
28
|
+
required: ["automationId"],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: "delete_automation",
|
|
33
|
+
description: "Permanently delete an automation. Cannot be undone.",
|
|
34
|
+
inputSchema: {
|
|
35
|
+
type: "object",
|
|
36
|
+
properties: { automationId: { type: "string", description: "Automation ID (from list_automations)" } },
|
|
37
|
+
required: ["automationId"],
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
const CreateAutomationSchema = zod_1.z.object({ rawInput: zod_1.z.string().min(1) });
|
|
42
|
+
const AutomationIdSchema = zod_1.z.object({ automationId: zod_1.z.string().min(1) });
|
|
43
|
+
async function handleAutomationTool(name, args) {
|
|
44
|
+
switch (name) {
|
|
45
|
+
case "create_automation": {
|
|
46
|
+
const parsed = CreateAutomationSchema.safeParse(args);
|
|
47
|
+
if (!parsed.success)
|
|
48
|
+
return { content: [{ type: "text", text: `Invalid input: rawInput ${parsed.error.issues[0].message}` }], isError: true };
|
|
49
|
+
const data = await (0, convex_js_1.callConvex)("/automations/create", "POST", { rawInput: parsed.data.rawInput }, "create_automation");
|
|
50
|
+
if (!data.success)
|
|
51
|
+
return { content: [{ type: "text", text: `Failed: ${data.error}` }], isError: true };
|
|
52
|
+
const triggerLabel = {
|
|
53
|
+
schedule: "⏰ Schedule", price_drop_pct: "📉 Price Drop %", price_rise_pct: "📈 Price Rise %",
|
|
54
|
+
price_below: "⬇️ Price Below", price_above: "⬆️ Price Above",
|
|
55
|
+
dominance_below: "📊 Dominance Below", dominance_above: "📊 Dominance Above",
|
|
56
|
+
};
|
|
57
|
+
const actionLabel = { swap: "💱 Swap", send: "📤 Send", alert: "🔔 Alert" };
|
|
58
|
+
return {
|
|
59
|
+
content: [{
|
|
60
|
+
type: "text",
|
|
61
|
+
text: [
|
|
62
|
+
`✅ **Automation Created**`, ``,
|
|
63
|
+
`**Name:** ${data.name}`, `**ID:** \`${data.automationId}\``,
|
|
64
|
+
`**Trigger:** ${triggerLabel[data.triggerType] ?? data.triggerType}`,
|
|
65
|
+
`**Action:** ${actionLabel[data.actionType] ?? data.actionType}`,
|
|
66
|
+
data.priceBaselineUsd ? `**Baseline price:** $${Number(data.priceBaselineUsd).toLocaleString()}` : ``,
|
|
67
|
+
``, `Use \`list_automations\` to see all your automations.`,
|
|
68
|
+
].filter(Boolean).join("\n"),
|
|
69
|
+
}],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
case "list_automations": {
|
|
73
|
+
const data = await (0, convex_js_1.callConvex)("/automations/list", "GET", undefined, "list_automations");
|
|
74
|
+
if (data.error)
|
|
75
|
+
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
76
|
+
const automations = data.automations ?? [];
|
|
77
|
+
if (!automations.length)
|
|
78
|
+
return { content: [{ type: "text", text: "No automations yet. Use `create_automation` to create one." }] };
|
|
79
|
+
const statusIcon = { active: "🟢", paused: "⏸️", completed: "✅", error: "❌" };
|
|
80
|
+
const lines = [`**Your Automations** (${automations.length})`, ""];
|
|
81
|
+
for (const auto of automations) {
|
|
82
|
+
lines.push(`${statusIcon[auto.status] ?? "•"} **${auto.name}** — \`${auto._id}\``);
|
|
83
|
+
lines.push(` Trigger: ${auto.triggerType} | Action: ${auto.actionType} | Runs: ${auto.totalRuns}`);
|
|
84
|
+
if (auto.totalSpentUsd > 0)
|
|
85
|
+
lines.push(` Total spent: $${Number(auto.totalSpentUsd).toFixed(2)}`);
|
|
86
|
+
if (auto.nextRunAt && auto.status === "active")
|
|
87
|
+
lines.push(` Next run: ${new Date(auto.nextRunAt).toUTCString()}`);
|
|
88
|
+
if (auto.lastError)
|
|
89
|
+
lines.push(` ⚠️ Last error: ${auto.lastError}`);
|
|
90
|
+
lines.push("");
|
|
91
|
+
}
|
|
92
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
93
|
+
}
|
|
94
|
+
case "pause_automation": {
|
|
95
|
+
const parsed = AutomationIdSchema.safeParse(args);
|
|
96
|
+
if (!parsed.success)
|
|
97
|
+
return { content: [{ type: "text", text: `Invalid input: automationId ${parsed.error.issues[0].message}` }], isError: true };
|
|
98
|
+
const data = await (0, convex_js_1.callConvex)("/automations/pause", "POST", { automationId: parsed.data.automationId }, "pause_automation");
|
|
99
|
+
if (data.error)
|
|
100
|
+
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
101
|
+
const icon = data.status === "active" ? "▶️ Resumed" : "⏸️ Paused";
|
|
102
|
+
return { content: [{ type: "text", text: `${icon} successfully.` }] };
|
|
103
|
+
}
|
|
104
|
+
case "delete_automation": {
|
|
105
|
+
const parsed = AutomationIdSchema.safeParse(args);
|
|
106
|
+
if (!parsed.success)
|
|
107
|
+
return { content: [{ type: "text", text: `Invalid input: automationId ${parsed.error.issues[0].message}` }], isError: true };
|
|
108
|
+
const data = await (0, convex_js_1.callConvex)("/automations/delete", "POST", { automationId: parsed.data.automationId }, "delete_automation");
|
|
109
|
+
if (data.error)
|
|
110
|
+
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
111
|
+
return { content: [{ type: "text", text: "🗑️ Automation deleted." }] };
|
|
112
|
+
}
|
|
113
|
+
default:
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|