@noelclaw/mcp 3.0.0 → 3.2.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/dist/agent-loop.js +141 -0
- package/dist/cli.js +170 -0
- package/dist/server.js +6 -6
- package/dist/tools/memory.js +83 -0
- package/dist/tools/os.js +15 -2
- package/package.json +3 -2
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runAgent = runAgent;
|
|
4
|
+
const server_js_1 = require("./server.js");
|
|
5
|
+
const llm_js_1 = require("./llm.js");
|
|
6
|
+
const SYSTEM_PROMPT = "You are Noelclaw, a persistent AI with 74 tools covering memory, automations, DeFi execution, research, and code. " +
|
|
7
|
+
"Be concise and direct. Use tools when needed. Summarize tool results in plain English.";
|
|
8
|
+
async function runAgent(userMessage, history, onToolCall) {
|
|
9
|
+
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
10
|
+
const bankrKey = process.env.BANKR_API_KEY;
|
|
11
|
+
if (anthropicKey)
|
|
12
|
+
return runAnthropicLoop(anthropicKey, userMessage, history, onToolCall);
|
|
13
|
+
if (bankrKey)
|
|
14
|
+
return runBankrLoop(bankrKey, userMessage, history, onToolCall);
|
|
15
|
+
// Fallback — no direct tool execution, just LLM chat via Convex
|
|
16
|
+
const text = await (0, llm_js_1.callLLM)(SYSTEM_PROMPT, userMessage, 1024, history);
|
|
17
|
+
return { text, toolCalls: [] };
|
|
18
|
+
}
|
|
19
|
+
// ── Anthropic agent loop ─────────────────────────────────────────────────────
|
|
20
|
+
function toAnthropicTool(tool) {
|
|
21
|
+
return {
|
|
22
|
+
name: tool.name,
|
|
23
|
+
description: tool.description ?? "",
|
|
24
|
+
input_schema: tool.inputSchema ?? { type: "object", properties: {} },
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
async function runAnthropicLoop(apiKey, userMessage, history, onToolCall) {
|
|
28
|
+
const model = process.env.ANTHROPIC_MODEL ?? "claude-haiku-4-5-20251001";
|
|
29
|
+
const tools = server_js_1.ALL_TOOLS.map(toAnthropicTool);
|
|
30
|
+
const toolCalls = [];
|
|
31
|
+
const messages = [
|
|
32
|
+
...history.map(h => ({ role: h.role, content: h.content })),
|
|
33
|
+
{ role: "user", content: userMessage },
|
|
34
|
+
];
|
|
35
|
+
for (let turn = 0; turn < 10; turn++) {
|
|
36
|
+
const res = await fetch("https://api.anthropic.com/v1/messages", {
|
|
37
|
+
method: "POST",
|
|
38
|
+
headers: {
|
|
39
|
+
"Content-Type": "application/json",
|
|
40
|
+
"x-api-key": apiKey,
|
|
41
|
+
"anthropic-version": "2023-06-01",
|
|
42
|
+
},
|
|
43
|
+
body: JSON.stringify({ model, max_tokens: 2048, system: SYSTEM_PROMPT, tools, messages }),
|
|
44
|
+
signal: AbortSignal.timeout(90000),
|
|
45
|
+
});
|
|
46
|
+
if (!res.ok) {
|
|
47
|
+
const body = await res.text().catch(() => "");
|
|
48
|
+
throw new Error(`Anthropic ${res.status}: ${body.slice(0, 300)}`);
|
|
49
|
+
}
|
|
50
|
+
const data = await res.json();
|
|
51
|
+
messages.push({ role: "assistant", content: data.content });
|
|
52
|
+
if (data.stop_reason !== "tool_use") {
|
|
53
|
+
const text = data.content
|
|
54
|
+
.filter(b => b.type === "text")
|
|
55
|
+
.map(b => b.text)
|
|
56
|
+
.join("");
|
|
57
|
+
return { text, toolCalls };
|
|
58
|
+
}
|
|
59
|
+
// Execute all tool_use blocks
|
|
60
|
+
const toolResults = [];
|
|
61
|
+
for (const block of data.content) {
|
|
62
|
+
if (block.type !== "tool_use")
|
|
63
|
+
continue;
|
|
64
|
+
onToolCall(block.name);
|
|
65
|
+
toolCalls.push({ name: block.name });
|
|
66
|
+
let resultText;
|
|
67
|
+
try {
|
|
68
|
+
const handler = server_js_1.HANDLER_MAP.get(block.name);
|
|
69
|
+
if (!handler)
|
|
70
|
+
throw new Error(`Unknown tool: ${block.name}`);
|
|
71
|
+
const result = await handler(block.name, block.input ?? {});
|
|
72
|
+
resultText = result?.content?.[0]?.text ?? "Done.";
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
resultText = `Error: ${err.message}`;
|
|
76
|
+
}
|
|
77
|
+
toolResults.push({ type: "tool_result", tool_use_id: block.id, content: resultText });
|
|
78
|
+
}
|
|
79
|
+
messages.push({ role: "user", content: toolResults });
|
|
80
|
+
}
|
|
81
|
+
return { text: "Reached max tool iterations.", toolCalls };
|
|
82
|
+
}
|
|
83
|
+
// ── Bankr (OpenAI-compatible) agent loop ─────────────────────────────────────
|
|
84
|
+
function toBankrTool(tool) {
|
|
85
|
+
return {
|
|
86
|
+
type: "function",
|
|
87
|
+
function: {
|
|
88
|
+
name: tool.name,
|
|
89
|
+
description: tool.description ?? "",
|
|
90
|
+
parameters: tool.inputSchema ?? { type: "object", properties: {} },
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
async function runBankrLoop(apiKey, userMessage, history, onToolCall) {
|
|
95
|
+
const model = process.env.BANKR_MODEL ?? "grok-3";
|
|
96
|
+
const tools = server_js_1.ALL_TOOLS.map(toBankrTool);
|
|
97
|
+
const toolCalls = [];
|
|
98
|
+
const messages = [
|
|
99
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
100
|
+
...history.map(h => ({ role: h.role, content: h.content })),
|
|
101
|
+
{ role: "user", content: userMessage },
|
|
102
|
+
];
|
|
103
|
+
for (let turn = 0; turn < 10; turn++) {
|
|
104
|
+
const res = await fetch("https://llm.bankr.bot/v1/chat/completions", {
|
|
105
|
+
method: "POST",
|
|
106
|
+
headers: { "Content-Type": "application/json", "X-API-Key": apiKey },
|
|
107
|
+
body: JSON.stringify({ model, messages, tools, max_tokens: 2048 }),
|
|
108
|
+
signal: AbortSignal.timeout(90000),
|
|
109
|
+
});
|
|
110
|
+
if (!res.ok) {
|
|
111
|
+
const body = await res.text().catch(() => "");
|
|
112
|
+
throw new Error(`Bankr ${res.status}: ${body.slice(0, 300)}`);
|
|
113
|
+
}
|
|
114
|
+
const data = await res.json();
|
|
115
|
+
const choice = data.choices?.[0]?.message;
|
|
116
|
+
if (!choice)
|
|
117
|
+
throw new Error("Empty response from Bankr");
|
|
118
|
+
messages.push(choice);
|
|
119
|
+
if (!choice.tool_calls?.length) {
|
|
120
|
+
return { text: choice.content ?? "", toolCalls };
|
|
121
|
+
}
|
|
122
|
+
for (const call of choice.tool_calls) {
|
|
123
|
+
onToolCall(call.function.name);
|
|
124
|
+
toolCalls.push({ name: call.function.name });
|
|
125
|
+
let resultText;
|
|
126
|
+
try {
|
|
127
|
+
const args = JSON.parse(call.function.arguments ?? "{}");
|
|
128
|
+
const handler = server_js_1.HANDLER_MAP.get(call.function.name);
|
|
129
|
+
if (!handler)
|
|
130
|
+
throw new Error(`Unknown tool: ${call.function.name}`);
|
|
131
|
+
const result = await handler(call.function.name, args);
|
|
132
|
+
resultText = result?.content?.[0]?.text ?? "Done.";
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
resultText = `Error: ${err.message}`;
|
|
136
|
+
}
|
|
137
|
+
messages.push({ role: "tool", tool_call_id: call.id, content: resultText });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return { text: "Reached max tool iterations.", toolCalls };
|
|
141
|
+
}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const readline = __importStar(require("readline"));
|
|
38
|
+
const agent_loop_js_1 = require("./agent-loop.js");
|
|
39
|
+
const server_js_1 = require("./server.js");
|
|
40
|
+
// ── ANSI ─────────────────────────────────────────────────────────────────────
|
|
41
|
+
const C = {
|
|
42
|
+
reset: "\x1b[0m",
|
|
43
|
+
bold: "\x1b[1m",
|
|
44
|
+
dim: "\x1b[2m",
|
|
45
|
+
green: "\x1b[32m",
|
|
46
|
+
cyan: "\x1b[36m",
|
|
47
|
+
violet: "\x1b[35m",
|
|
48
|
+
red: "\x1b[31m",
|
|
49
|
+
yellow: "\x1b[33m",
|
|
50
|
+
};
|
|
51
|
+
const BANNER = `
|
|
52
|
+
${C.cyan}${C.bold} NOELCLAW${C.reset} ${C.dim}v3.2.0 · 76 tools · persistent AI${C.reset}
|
|
53
|
+
${C.dim}─────────────────────────────────────────${C.reset}
|
|
54
|
+
${C.dim}Type anything. /help for commands. Ctrl+C to exit.${C.reset}
|
|
55
|
+
`;
|
|
56
|
+
// ── Spinner ───────────────────────────────────────────────────────────────────
|
|
57
|
+
function spinner(label) {
|
|
58
|
+
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
59
|
+
let i = 0;
|
|
60
|
+
const iv = setInterval(() => {
|
|
61
|
+
process.stdout.write(`\r ${C.dim}${frames[i % frames.length]} ${label}${C.reset} `);
|
|
62
|
+
i++;
|
|
63
|
+
}, 80);
|
|
64
|
+
return () => {
|
|
65
|
+
clearInterval(iv);
|
|
66
|
+
process.stdout.write("\r" + " ".repeat(label.length + 12) + "\r");
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// ── Help ─────────────────────────────────────────────────────────────────────
|
|
70
|
+
function printHelp() {
|
|
71
|
+
console.log(`
|
|
72
|
+
${C.cyan}Commands:${C.reset}
|
|
73
|
+
/clear Clear conversation history
|
|
74
|
+
/tools List all 74 available tools
|
|
75
|
+
/quit Exit
|
|
76
|
+
|
|
77
|
+
${C.dim}Examples:
|
|
78
|
+
remember my coding style for next time
|
|
79
|
+
what's ETH doing right now?
|
|
80
|
+
swap 0.5 ETH to USDC on Base
|
|
81
|
+
send me a weekly digest every Monday
|
|
82
|
+
research "best DeFi yields on Base"${C.reset}
|
|
83
|
+
`);
|
|
84
|
+
}
|
|
85
|
+
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
86
|
+
async function main() {
|
|
87
|
+
process.stdout.write(BANNER);
|
|
88
|
+
// Detect active LLM
|
|
89
|
+
const mode = process.env.ANTHROPIC_API_KEY
|
|
90
|
+
? `${C.green}Anthropic${C.reset} ${C.dim}(full tool use)${C.reset}`
|
|
91
|
+
: process.env.BANKR_API_KEY
|
|
92
|
+
? `${C.green}Bankr${C.reset} ${C.dim}(full tool use)${C.reset}`
|
|
93
|
+
: `${C.yellow}Convex chat${C.reset} ${C.dim}(no tool execution — set ANTHROPIC_API_KEY for full mode)${C.reset}`;
|
|
94
|
+
console.log(` ${C.dim}Mode:${C.reset} ${mode}\n`);
|
|
95
|
+
const history = [];
|
|
96
|
+
const rl = readline.createInterface({
|
|
97
|
+
input: process.stdin,
|
|
98
|
+
output: process.stdout,
|
|
99
|
+
prompt: `${C.green}>${C.reset} `,
|
|
100
|
+
});
|
|
101
|
+
rl.prompt();
|
|
102
|
+
rl.on("line", async (raw) => {
|
|
103
|
+
const line = raw.trim();
|
|
104
|
+
if (!line) {
|
|
105
|
+
rl.prompt();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Built-in commands
|
|
109
|
+
if (line === "/quit" || line === "/exit") {
|
|
110
|
+
console.log(`\n ${C.dim}Goodbye.${C.reset}\n`);
|
|
111
|
+
process.exit(0);
|
|
112
|
+
}
|
|
113
|
+
if (line === "/clear") {
|
|
114
|
+
history.length = 0;
|
|
115
|
+
console.log(` ${C.dim}History cleared.${C.reset}\n`);
|
|
116
|
+
rl.prompt();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (line === "/help") {
|
|
120
|
+
printHelp();
|
|
121
|
+
rl.prompt();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (line === "/tools") {
|
|
125
|
+
console.log(`\n ${C.cyan}${server_js_1.ALL_TOOLS.length} tools:${C.reset}`);
|
|
126
|
+
for (const t of server_js_1.ALL_TOOLS) {
|
|
127
|
+
console.log(` ${C.dim}·${C.reset} ${t.name} ${C.dim}${(t.description ?? "").slice(0, 60)}${C.reset}`);
|
|
128
|
+
}
|
|
129
|
+
console.log();
|
|
130
|
+
rl.prompt();
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
// Agent call
|
|
134
|
+
const stop = spinner("thinking");
|
|
135
|
+
let toolsUsed = 0;
|
|
136
|
+
try {
|
|
137
|
+
const result = await (0, agent_loop_js_1.runAgent)(line, history, (toolName) => {
|
|
138
|
+
stop();
|
|
139
|
+
toolsUsed++;
|
|
140
|
+
process.stdout.write(` ${C.dim}✦ ${toolName}${C.reset}\n`);
|
|
141
|
+
});
|
|
142
|
+
stop();
|
|
143
|
+
// Update conversation history (keep last 20 turns)
|
|
144
|
+
history.push({ role: "user", content: line });
|
|
145
|
+
history.push({ role: "assistant", content: result.text });
|
|
146
|
+
while (history.length > 20)
|
|
147
|
+
history.splice(0, 2);
|
|
148
|
+
// Output
|
|
149
|
+
console.log();
|
|
150
|
+
const lines = result.text.split("\n");
|
|
151
|
+
for (const l of lines) {
|
|
152
|
+
console.log(` ${l}`);
|
|
153
|
+
}
|
|
154
|
+
console.log();
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
stop();
|
|
158
|
+
console.log(`\n ${C.red}✗${C.reset} ${err.message}\n`);
|
|
159
|
+
}
|
|
160
|
+
rl.prompt();
|
|
161
|
+
});
|
|
162
|
+
rl.on("close", () => {
|
|
163
|
+
console.log(`\n ${C.dim}Goodbye.${C.reset}\n`);
|
|
164
|
+
process.exit(0);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
main().catch(err => {
|
|
168
|
+
console.error(`Fatal: ${err.message}`);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
});
|
package/dist/server.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.server = exports.ALL_TOOLS = void 0;
|
|
3
|
+
exports.server = exports.HANDLER_MAP = exports.ALL_TOOLS = void 0;
|
|
4
4
|
exports.startServer = startServer;
|
|
5
5
|
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
6
6
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
@@ -50,11 +50,11 @@ exports.ALL_TOOLS = [
|
|
|
50
50
|
...scanner_js_1.SCANNER_TOOLS, // 4 — score_token, check_token, scan_dips, scan_momentum
|
|
51
51
|
...coder_js_1.CODER_TOOLS, // 5 — generate_contract, audit_contract, explain_code, review_code, generate_mcp_skill
|
|
52
52
|
...base_js_1.BASE_TOOLS, // 4 — query_vaults, list_markets, prepare_deposit, chain_stats
|
|
53
|
-
...memory_js_1.MEMORY_TOOLS, //
|
|
53
|
+
...memory_js_1.MEMORY_TOOLS, // 9 — memory_add, memory_search, memory_context, memory_profile, memory_list, memory_delete, memory_insight, memory_extract, memory_consolidate
|
|
54
54
|
...os_js_1.OS_TOOLS, // 3 — noel_status, noel_boot, noel_shutdown
|
|
55
|
-
// total:
|
|
55
|
+
// total: 76
|
|
56
56
|
];
|
|
57
|
-
|
|
57
|
+
exports.HANDLER_MAP = new Map([
|
|
58
58
|
...market_js_1.MARKET_TOOLS.map(t => [t.name, market_js_1.handleMarketTool]),
|
|
59
59
|
...defi_js_1.DEFI_TOOLS.map(t => [t.name, defi_js_1.handleDefiTool]),
|
|
60
60
|
...automation_js_1.AUTOMATION_TOOLS.map(t => [t.name, automation_js_1.handleAutomationTool]),
|
|
@@ -72,13 +72,13 @@ const HANDLER_MAP = new Map([
|
|
|
72
72
|
...memory_js_1.MEMORY_TOOLS.map(t => [t.name, memory_js_1.handleMemoryTool]),
|
|
73
73
|
...os_js_1.OS_TOOLS.map(t => [t.name, os_js_1.handleOsTool]),
|
|
74
74
|
]);
|
|
75
|
-
exports.server = new index_js_1.Server({ name: "noelclaw", version: "2.
|
|
75
|
+
exports.server = new index_js_1.Server({ name: "noelclaw", version: "3.2.0" }, { capabilities: { tools: {} } });
|
|
76
76
|
exports.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({ tools: exports.ALL_TOOLS }));
|
|
77
77
|
exports.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
78
78
|
const { name, arguments: args } = request.params;
|
|
79
79
|
if (containsSensitiveRequest(args))
|
|
80
80
|
return PRIVATE_KEY_RESPONSE;
|
|
81
|
-
const handler = HANDLER_MAP.get(name);
|
|
81
|
+
const handler = exports.HANDLER_MAP.get(name);
|
|
82
82
|
if (!handler) {
|
|
83
83
|
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
84
84
|
}
|
package/dist/tools/memory.js
CHANGED
|
@@ -123,6 +123,36 @@ exports.MEMORY_TOOLS = [
|
|
|
123
123
|
required: ["topic"],
|
|
124
124
|
},
|
|
125
125
|
},
|
|
126
|
+
{
|
|
127
|
+
name: "memory_extract",
|
|
128
|
+
description: "Auto-extract discrete facts, preferences, and decisions from any text and save them individually to semantic memory. " +
|
|
129
|
+
"Instead of storing a wall of text, Noelclaw breaks it into 3-10 searchable atomic facts using AI. " +
|
|
130
|
+
"Best for processing chat logs, research notes, meeting summaries, or any unstructured content. " +
|
|
131
|
+
"Each extracted fact becomes independently searchable — 'what do I prefer about staking?' will find it.",
|
|
132
|
+
inputSchema: {
|
|
133
|
+
type: "object",
|
|
134
|
+
properties: {
|
|
135
|
+
text: { type: "string", description: "Text to extract facts from — notes, research, chat logs, any unstructured content" },
|
|
136
|
+
source: { type: "string", description: "Optional label for where this came from (e.g. 'telegram', 'research', 'meeting')" },
|
|
137
|
+
},
|
|
138
|
+
required: ["text"],
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: "memory_consolidate",
|
|
143
|
+
description: "Fetch all memories on a topic and consolidate them into a single comprehensive summary using AI. " +
|
|
144
|
+
"Removes redundancy, merges overlapping facts, and saves the result as a new 'consolidated' memory. " +
|
|
145
|
+
"Use this to clean up fragmented knowledge after heavy research sessions. " +
|
|
146
|
+
"Returns the summary and saves it automatically — the original memories remain intact.",
|
|
147
|
+
inputSchema: {
|
|
148
|
+
type: "object",
|
|
149
|
+
properties: {
|
|
150
|
+
topic: { type: "string", description: "Topic to consolidate memories for (e.g. 'ETH liquid staking', 'Base DeFi')" },
|
|
151
|
+
limit: { type: "number", description: "Max source memories to consolidate (default 12)" },
|
|
152
|
+
},
|
|
153
|
+
required: ["topic"],
|
|
154
|
+
},
|
|
155
|
+
},
|
|
126
156
|
];
|
|
127
157
|
// ─── Zod schemas ─────────────────────────────────────────────────────────────
|
|
128
158
|
const AddSchema = zod_1.z.object({
|
|
@@ -148,6 +178,14 @@ const InsightSchema = zod_1.z.object({
|
|
|
148
178
|
topic: zod_1.z.string().min(1),
|
|
149
179
|
depth: zod_1.z.enum(["quick", "standard", "deep"]).optional(),
|
|
150
180
|
});
|
|
181
|
+
const ExtractSchema = zod_1.z.object({
|
|
182
|
+
text: zod_1.z.string().min(1),
|
|
183
|
+
source: zod_1.z.string().optional(),
|
|
184
|
+
});
|
|
185
|
+
const ConsolidateSchema = zod_1.z.object({
|
|
186
|
+
topic: zod_1.z.string().min(1),
|
|
187
|
+
limit: zod_1.z.number().optional(),
|
|
188
|
+
});
|
|
151
189
|
// ─── Handler ─────────────────────────────────────────────────────────────────
|
|
152
190
|
async function handleMemoryTool(name, args) {
|
|
153
191
|
switch (name) {
|
|
@@ -361,6 +399,51 @@ async function handleMemoryTool(name, args) {
|
|
|
361
399
|
lines.push(`• \`swarm_watch topic: "${topic}"\` — monitor this topic continuously`);
|
|
362
400
|
return { content: [{ type: "text", text: lines.filter(l => l !== undefined).join("\n") }] };
|
|
363
401
|
}
|
|
402
|
+
case "memory_extract": {
|
|
403
|
+
const parsed = ExtractSchema.safeParse(args);
|
|
404
|
+
if (!parsed.success)
|
|
405
|
+
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
406
|
+
const { text, source = "extract" } = parsed.data;
|
|
407
|
+
const data = await (0, convex_js_1.callConvex)("/memory/extract", "POST", { text, source }).catch((err) => ({ error: err.message }));
|
|
408
|
+
if (data?.error)
|
|
409
|
+
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
410
|
+
const facts = data?.facts ?? [];
|
|
411
|
+
return {
|
|
412
|
+
content: [{
|
|
413
|
+
type: "text",
|
|
414
|
+
text: [
|
|
415
|
+
`🧠 **Auto-extracted ${data?.extracted ?? facts.length} facts** (${data?.saved ?? 0} saved)`,
|
|
416
|
+
``,
|
|
417
|
+
...facts.map((f, i) => `${i + 1}. ${f}`),
|
|
418
|
+
``,
|
|
419
|
+
`All facts are now searchable via \`memory_search\`.`,
|
|
420
|
+
].join("\n"),
|
|
421
|
+
}],
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
case "memory_consolidate": {
|
|
425
|
+
const parsed = ConsolidateSchema.safeParse(args);
|
|
426
|
+
if (!parsed.success)
|
|
427
|
+
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
428
|
+
const { topic, limit = 12 } = parsed.data;
|
|
429
|
+
const data = await (0, convex_js_1.callConvex)("/memory/consolidate", "POST", { topic, n: limit }).catch((err) => ({ error: err.message }));
|
|
430
|
+
if (data?.error)
|
|
431
|
+
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
432
|
+
return {
|
|
433
|
+
content: [{
|
|
434
|
+
type: "text",
|
|
435
|
+
text: [
|
|
436
|
+
`🧠 **Consolidated "${topic}"** — merged ${data?.consolidatedFrom ?? "?"} memories`,
|
|
437
|
+
`Saved as: \`${data?.id ?? "consolidated"}\``,
|
|
438
|
+
``,
|
|
439
|
+
`**Summary:**`,
|
|
440
|
+
data?.summary ?? "",
|
|
441
|
+
``,
|
|
442
|
+
`Use \`memory_search query: "${topic}"\` to find it.`,
|
|
443
|
+
].join("\n"),
|
|
444
|
+
}],
|
|
445
|
+
};
|
|
446
|
+
}
|
|
364
447
|
default:
|
|
365
448
|
return null;
|
|
366
449
|
}
|
package/dist/tools/os.js
CHANGED
|
@@ -108,18 +108,21 @@ async function handleOsTool(name, args) {
|
|
|
108
108
|
if (!parsed.success)
|
|
109
109
|
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
110
110
|
const { focus } = parsed.data;
|
|
111
|
-
const [swarmRes, marketRes, autoRes, memRes, focusRes] = await Promise.allSettled([
|
|
111
|
+
const [swarmRes, marketRes, autoRes, memRes, focusRes, prefRes] = await Promise.allSettled([
|
|
112
112
|
(0, convex_js_1.callConvex)("/swarm/start", "POST", {}, "start_swarm"),
|
|
113
113
|
(0, market_js_1.fetchMarketSnapshot)(),
|
|
114
114
|
(0, convex_js_1.callConvex)("/automations/list", "GET", undefined, "list_automations"),
|
|
115
115
|
(0, convex_js_1.callConvex)("/memory/profile", "GET"),
|
|
116
|
-
focus ? (0, memory_js_1.searchSupermemory)(focus,
|
|
116
|
+
focus ? (0, memory_js_1.searchSupermemory)(focus, 5) : Promise.resolve([]),
|
|
117
|
+
// Always load user preference/context memories for smart boot
|
|
118
|
+
(0, memory_js_1.searchSupermemory)("user preferences style goals priorities", 4),
|
|
117
119
|
]);
|
|
118
120
|
const swarm = swarmRes.status === "fulfilled" ? swarmRes.value : null;
|
|
119
121
|
const market = marketRes.status === "fulfilled" ? marketRes.value : null;
|
|
120
122
|
const autos = autoRes.status === "fulfilled" ? autoRes.value : null;
|
|
121
123
|
const mem = memRes.status === "fulfilled" ? memRes.value : null;
|
|
122
124
|
const focusMem = focusRes.status === "fulfilled" ? focusRes.value : [];
|
|
125
|
+
const prefMem = prefRes.status === "fulfilled" ? prefRes.value : [];
|
|
123
126
|
const automations = autos?.automations ?? [];
|
|
124
127
|
const activeAutos = automations.filter((a) => a.status === "active");
|
|
125
128
|
const memTotal = mem?.total ?? 0;
|
|
@@ -148,6 +151,15 @@ async function handleOsTool(name, args) {
|
|
|
148
151
|
}
|
|
149
152
|
lines.push("");
|
|
150
153
|
}
|
|
154
|
+
// Always show user context memories if available
|
|
155
|
+
if (prefMem.length > 0) {
|
|
156
|
+
lines.push(`**User context loaded (${prefMem.length} preferences):**`);
|
|
157
|
+
for (const r of prefMem) {
|
|
158
|
+
const title = r.metadata?.title ?? r.content.slice(0, 80).replace(/\n/g, " ");
|
|
159
|
+
lines.push(` • ${title}`);
|
|
160
|
+
}
|
|
161
|
+
lines.push("");
|
|
162
|
+
}
|
|
151
163
|
if (focus && focusMem.length > 0) {
|
|
152
164
|
lines.push(`**Memory context for "${focus}" (${focusMem.length} items):**`);
|
|
153
165
|
for (const r of focusMem) {
|
|
@@ -166,6 +178,7 @@ async function handleOsTool(name, args) {
|
|
|
166
178
|
lines.push(` • \`swarm_research topic: "BTC"\` — start morning research`);
|
|
167
179
|
lines.push(` • \`noel_status\` — full system dashboard`);
|
|
168
180
|
}
|
|
181
|
+
lines.push(` • \`memory_extract text: "...\"\` — auto-save facts from any note`);
|
|
169
182
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
170
183
|
}
|
|
171
184
|
case "noel_shutdown": {
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noelclaw/mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Noelclaw AI Operating System — persistent memory, multi-agent swarm, DeFi execution, market intelligence, and Sentinel-gated playbooks.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"noelclaw-mcp": "dist/index.js"
|
|
7
|
+
"noelclaw-mcp": "dist/index.js",
|
|
8
|
+
"noelclaw": "dist/cli.js"
|
|
8
9
|
},
|
|
9
10
|
"scripts": {
|
|
10
11
|
"build": "tsc",
|