@finchagentic/mcp 4.0.0 โ 4.1.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 +1 -1
- package/README.md +96 -249
- package/dist/agent-loop.js +73 -6
- package/dist/cli.js +78 -21
- package/dist/config.js +16 -16
- package/dist/llm.js +12 -48
- package/dist/local-memory-file.js +134 -0
- package/dist/local-memory.js +29 -9
- package/dist/resources.js +2 -2
- package/dist/tools/agents.js +4 -4
- package/dist/tools/base-mcp.js +4 -4
- package/dist/tools/chronicle.js +4 -4
- package/dist/tools/deep-research.js +10 -5
- package/dist/tools/defi.js +26 -5
- package/dist/tools/insight.js +23 -27
- package/dist/tools/memory.js +33 -29
- package/dist/tools/monitor.js +1 -1
- package/dist/tools/os.js +10 -5
- package/dist/tools/packets.js +3 -3
- package/dist/tools/research.js +2 -2
- package/dist/tools/rh-mcp.js +12 -0
- package/dist/tools/vault.js +23 -21
- package/dist/wallet.js +93 -16
- package/package.json +8 -20
package/dist/tools/base-mcp.js
CHANGED
|
@@ -331,7 +331,7 @@ async function handleBaseMcpTool(name, args) {
|
|
|
331
331
|
case "base_mcp_status": {
|
|
332
332
|
// Combine chain stats + wallet address into one status snapshot
|
|
333
333
|
const [chainRes, walletRes] = await Promise.all([
|
|
334
|
-
(0, base_js_1.handleBaseTool)("
|
|
334
|
+
(0, base_js_1.handleBaseTool)("base_mcp_network", {}),
|
|
335
335
|
(0, wallet_js_1.handleWalletTool)("get_wallet_address", {}).catch(() => null),
|
|
336
336
|
]);
|
|
337
337
|
const chainText = chainRes?.content?.[0]?.text ?? "Chain stats unavailable";
|
|
@@ -415,9 +415,9 @@ async function handleBaseMcpTool(name, args) {
|
|
|
415
415
|
// Two parallel lookups - Morpho vaults + Moonwell markets - unless venue specified
|
|
416
416
|
const promises = [];
|
|
417
417
|
if (venue !== "moonwell")
|
|
418
|
-
promises.push((0, base_js_1.handleBaseTool)("
|
|
418
|
+
promises.push((0, base_js_1.handleBaseTool)("base_mcp_yield_vaults", { asset }));
|
|
419
419
|
if (venue !== "morpho")
|
|
420
|
-
promises.push((0, base_js_1.handleBaseTool)("
|
|
420
|
+
promises.push((0, base_js_1.handleBaseTool)("base_mcp_lending_rates", { asset }));
|
|
421
421
|
const results = await Promise.all(promises);
|
|
422
422
|
const blocks = results
|
|
423
423
|
.filter((r) => !!r && Array.isArray(r.content))
|
|
@@ -430,7 +430,7 @@ async function handleBaseMcpTool(name, args) {
|
|
|
430
430
|
];
|
|
431
431
|
if (a.amount) {
|
|
432
432
|
// If amount provided, also prepare a deposit tx for the top option
|
|
433
|
-
const prep = await (0, base_js_1.handleBaseTool)("
|
|
433
|
+
const prep = await (0, base_js_1.handleBaseTool)("base_mcp_deposit_guide", { asset, amount: a.amount });
|
|
434
434
|
if (prep?.content?.[0]?.text) {
|
|
435
435
|
lines.push("", "### ๐ Prepared deposit", prep.content[0].text);
|
|
436
436
|
}
|
package/dist/tools/chronicle.js
CHANGED
|
@@ -10,7 +10,7 @@ const CHRONICLE_TYPES = ["vault", "memory", "agent", "tool", "automation", "moni
|
|
|
10
10
|
exports.CHRONICLE_TOOLS = [
|
|
11
11
|
{
|
|
12
12
|
name: "chronicle_add",
|
|
13
|
-
description: "Log an event to
|
|
13
|
+
description: "Log an event to Finch Chronicle - the system-wide audit log for your AI runtime. " +
|
|
14
14
|
"Records anything meaningful: vault saves, agent updates, automation triggers, " +
|
|
15
15
|
"custom milestones, research completions. Chronicle is your permanent timeline of what happened. " +
|
|
16
16
|
"Types: vault | memory | agent | tool | automation | monitor | system | custom.",
|
|
@@ -40,7 +40,7 @@ exports.CHRONICLE_TOOLS = [
|
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
42
|
name: "chronicle_list",
|
|
43
|
-
description: "Read the
|
|
43
|
+
description: "Read the Finch Chronicle event log - your AI runtime timeline. Returns recent events in reverse chronological order. " +
|
|
44
44
|
"Filter by type to see only vault saves, agent activity, automations, etc.",
|
|
45
45
|
inputSchema: {
|
|
46
46
|
type: "object",
|
|
@@ -59,7 +59,7 @@ exports.CHRONICLE_TOOLS = [
|
|
|
59
59
|
},
|
|
60
60
|
{
|
|
61
61
|
name: "chronicle_search",
|
|
62
|
-
description: "Search the
|
|
62
|
+
description: "Search the Finch Chronicle by keyword. Matches against event titles and details. " +
|
|
63
63
|
"Useful for finding when something specific happened: 'when did I last research ETH?' or 'find all vault saves for Base'.",
|
|
64
64
|
inputSchema: {
|
|
65
65
|
type: "object",
|
|
@@ -187,7 +187,7 @@ async function handleChronicle(name, args) {
|
|
|
187
187
|
};
|
|
188
188
|
}
|
|
189
189
|
const lines = [
|
|
190
|
-
`## ๐
|
|
190
|
+
`## ๐ Finch Chronicle${type ? ` ยท ${type}` : ""}`,
|
|
191
191
|
`*${entries.length} event${entries.length !== 1 ? "s" : ""}*`,
|
|
192
192
|
"",
|
|
193
193
|
];
|
|
@@ -267,7 +267,7 @@ async function fcSearch(query, limit) {
|
|
|
267
267
|
}
|
|
268
268
|
// Backend-proxy path - session-authed; Finch covers Firecrawl cost.
|
|
269
269
|
try {
|
|
270
|
-
const data = await (0, convex_js_1.callConvex)("/research/firecrawl-search", "POST", { query, limit }, "
|
|
270
|
+
const data = await (0, convex_js_1.callConvex)("/research/firecrawl-search", "POST", { query, limit }, "web_search", 20000);
|
|
271
271
|
return normalizeSearchHits(data?.results);
|
|
272
272
|
}
|
|
273
273
|
catch {
|
|
@@ -300,7 +300,7 @@ async function fcScrape(url) {
|
|
|
300
300
|
// which means continueFrom can't auto-date proxied scrapes. Acceptable
|
|
301
301
|
// tradeoff for now; markdown is the primary signal.
|
|
302
302
|
try {
|
|
303
|
-
const data = await (0, convex_js_1.callConvex)("/research/firecrawl-scrape", "POST", { url }, "
|
|
303
|
+
const data = await (0, convex_js_1.callConvex)("/research/firecrawl-scrape", "POST", { url }, "web_scrape", 25000);
|
|
304
304
|
if (data?.markdown)
|
|
305
305
|
return { markdown: data.markdown };
|
|
306
306
|
}
|
|
@@ -568,7 +568,7 @@ async function synthesize(query, sources, isFinal, liveSearch, priorContext, fre
|
|
|
568
568
|
let memoryContext = "";
|
|
569
569
|
if (isFinal) {
|
|
570
570
|
const [profileData, memHits] = await Promise.allSettled([
|
|
571
|
-
(0, convex_js_1.callConvex)("/vault/profile-context?maxChars=1200", "GET", undefined, "
|
|
571
|
+
(0, convex_js_1.callConvex)("/vault/profile-context?maxChars=1200", "GET", undefined, "vault_read"),
|
|
572
572
|
(0, memory_js_1.searchSupermemory)(query, 4),
|
|
573
573
|
]);
|
|
574
574
|
if (profileData.status === "fulfilled") {
|
|
@@ -744,7 +744,12 @@ Write the ${isFinal ? "final" : "draft"} report now. Markdown only - no preamble
|
|
|
744
744
|
// pick for reasoning, JSON, code). Override via env or pass the same
|
|
745
745
|
// model string to FINCH_MODEL to bypass.
|
|
746
746
|
const researchModel = process.env.FINCH_RESEARCH_MODEL ?? "grok-4.3";
|
|
747
|
-
|
|
747
|
+
// Deep mode (critic notes present, or the 5-angle planner ran) produces a
|
|
748
|
+
// longer report than the standard 6-section template - a fixed 4000-token
|
|
749
|
+
// budget was cutting deep reports off mid-sentence around risk #6-7 of 13+.
|
|
750
|
+
const isDeepMode = !!criticNotes || (angles?.length ?? 0) >= 5;
|
|
751
|
+
const finalTokens = isDeepMode ? 7000 : 4000;
|
|
752
|
+
const raw = await (0, llm_js_1.callLLM)(sys, user, isFinal ? finalTokens : 2000, [], 90000, { liveSearch, model: researchModel });
|
|
748
753
|
const { content: report, liveCitations } = extractLiveCitations(raw);
|
|
749
754
|
// Citation density check - only for final reports. If the report has many
|
|
750
755
|
// numerical claims but very few [N] citations, retry once with a stricter
|
|
@@ -757,7 +762,7 @@ Write the ${isFinal ? "final" : "draft"} report now. Markdown only - no preamble
|
|
|
757
762
|
|
|
758
763
|
โ ๏ธ Your previous draft had ${density.numericalClaims} numerical claims but only ${density.citations} [N] citations. That ratio is too low. Rewrite with stricter citation density: every percentage, dollar amount, count, date, and named entity must carry [N]. Use the At a Glance table to anchor the key metrics.`;
|
|
759
764
|
try {
|
|
760
|
-
const rawRetry = await (0, llm_js_1.callLLM)(sys, retryUser,
|
|
765
|
+
const rawRetry = await (0, llm_js_1.callLLM)(sys, retryUser, finalTokens, [], 90000, { liveSearch, model: researchModel });
|
|
761
766
|
const { content: retryReport, liveCitations: retryCitations } = extractLiveCitations(rawRetry);
|
|
762
767
|
return { report: retryReport, liveCitations: retryCitations.length > 0 ? retryCitations : liveCitations };
|
|
763
768
|
}
|
package/dist/tools/defi.js
CHANGED
|
@@ -103,7 +103,7 @@ async function handleDefiTool(name, args) {
|
|
|
103
103
|
// Pass the MCP local wallet as the swap taker so 0x routes output back
|
|
104
104
|
// to the wallet that's signing - not the backend's custodial wallet.
|
|
105
105
|
const localWallet = await (0, wallet_js_1.getOrCreateWallet)();
|
|
106
|
-
const result = await (0, convex_js_1.callConvex)("/mcp/defi/swap", "POST", { fromToken, toToken, amount, taker: localWallet.address, slippagePercentage: slippageLimit / 100 }, "
|
|
106
|
+
const result = await (0, convex_js_1.callConvex)("/mcp/defi/swap", "POST", { fromToken, toToken, amount, taker: localWallet.address, slippagePercentage: slippageLimit / 100 }, "swap_tokens");
|
|
107
107
|
if (!result.success)
|
|
108
108
|
return { content: [{ type: "text", text: `Estimate failed: ${result.error}` }], isError: true };
|
|
109
109
|
const q = result.quote;
|
|
@@ -163,17 +163,25 @@ async function handleDefiTool(name, args) {
|
|
|
163
163
|
}
|
|
164
164
|
const txHash = await (0, wallet_js_1.signAndBroadcast)(wallet, q);
|
|
165
165
|
const buyAmountHuman = formatTokenAmount(q.buyAmount, q.buyToken ?? toToken);
|
|
166
|
+
const receipt = await (0, wallet_js_1.waitForReceipt)(txHash);
|
|
167
|
+
const header = !receipt.mined
|
|
168
|
+
? `โณ Broadcast, not yet confirmed within the wait window.`
|
|
169
|
+
: receipt.ok
|
|
170
|
+
? `โ
Swap confirmed on-chain (block ${receipt.blockNumber}).`
|
|
171
|
+
: `โ Transaction REVERTED - no swap occurred (gas was still spent).`;
|
|
166
172
|
return {
|
|
167
173
|
content: [{
|
|
168
174
|
type: "text",
|
|
169
175
|
text: [
|
|
170
|
-
|
|
171
|
-
`${amount} ${fromToken.toUpperCase()} โ ${buyAmountHuman} ${q.buyToken}`,
|
|
176
|
+
header,
|
|
177
|
+
`${amount} ${fromToken.toUpperCase()} โ ${receipt.mined && receipt.ok ? buyAmountHuman : "(quoted, not received)"} ${q.buyToken}`,
|
|
172
178
|
`Slippage cap: ${slippageLimit}% ยท Price impact: ${impactPct.toFixed(3)}%`,
|
|
173
179
|
`Tx Hash: \`${txHash}\``,
|
|
174
180
|
`https://basescan.org/tx/${txHash}`,
|
|
175
|
-
|
|
181
|
+
!receipt.mined ? `Check the link above before telling the user this succeeded or failed.` : "",
|
|
182
|
+
].filter(Boolean).join("\n"),
|
|
176
183
|
}],
|
|
184
|
+
isError: receipt.mined && !receipt.ok,
|
|
177
185
|
};
|
|
178
186
|
}
|
|
179
187
|
case "send_token": {
|
|
@@ -186,11 +194,24 @@ async function handleDefiTool(name, args) {
|
|
|
186
194
|
if (!result.success)
|
|
187
195
|
return { content: [{ type: "text", text: `Send failed: ${result.error}` }], isError: true };
|
|
188
196
|
const txHash = await (0, wallet_js_1.signAndBroadcast)(wallet, result.txData);
|
|
197
|
+
const receipt = await (0, wallet_js_1.waitForReceipt)(txHash);
|
|
198
|
+
const header = !receipt.mined
|
|
199
|
+
? `โณ Broadcast, not yet confirmed within the wait window.`
|
|
200
|
+
: receipt.ok
|
|
201
|
+
? `โ
Sent - confirmed on-chain (block ${receipt.blockNumber}).`
|
|
202
|
+
: `โ Transaction REVERTED - funds were NOT sent (gas was still spent).`;
|
|
189
203
|
return {
|
|
190
204
|
content: [{
|
|
191
205
|
type: "text",
|
|
192
|
-
text: [
|
|
206
|
+
text: [
|
|
207
|
+
header,
|
|
208
|
+
`${amount} ${token.toUpperCase()} โ \`${toAddress}\``,
|
|
209
|
+
`Tx Hash: \`${txHash}\``,
|
|
210
|
+
`https://basescan.org/tx/${txHash}`,
|
|
211
|
+
!receipt.mined ? `Check the link above before telling the user this succeeded or failed.` : "",
|
|
212
|
+
].filter(Boolean).join("\n"),
|
|
193
213
|
}],
|
|
214
|
+
isError: receipt.mined && !receipt.ok,
|
|
194
215
|
};
|
|
195
216
|
}
|
|
196
217
|
case "analyze_wallet": {
|
package/dist/tools/insight.js
CHANGED
|
@@ -11,11 +11,11 @@ const signal_gate_js_1 = require("../signal-gate.js");
|
|
|
11
11
|
exports.INSIGHT_TOOLS = [
|
|
12
12
|
{
|
|
13
13
|
name: "ask_finch",
|
|
14
|
-
description: "Ask
|
|
14
|
+
description: "Ask Finch anything - analysis, opinions, explanations, strategy, or ideas. Finch loads your saved memory to personalize every answer. Use for: research questions, content ideas, code explanations, decision-making, DeFi analysis, trade ideas, or just thinking out loud. Pass previous messages to continue a conversation across tool calls. If YOU are already a reasoning model (Claude, GPT, etc. calling this via MCP) and other tools already gave you the data you need, just answer directly instead of calling this - it runs a separate LLM call and won't tell you anything you can't already work out yourself from that data.",
|
|
15
15
|
inputSchema: {
|
|
16
16
|
type: "object",
|
|
17
17
|
properties: {
|
|
18
|
-
question: { type: "string", description: "Your question or request for
|
|
18
|
+
question: { type: "string", description: "Your question or request for Finch" },
|
|
19
19
|
messages: {
|
|
20
20
|
type: "array",
|
|
21
21
|
description: "Previous conversation messages for context (optional)",
|
|
@@ -63,7 +63,7 @@ exports.INSIGHT_TOOLS = [
|
|
|
63
63
|
},
|
|
64
64
|
},
|
|
65
65
|
];
|
|
66
|
-
const
|
|
66
|
+
const AskFinchSchema = zod_1.z.object({
|
|
67
67
|
question: zod_1.z.string().min(1),
|
|
68
68
|
messages: zod_1.z.array(zod_1.z.object({ role: zod_1.z.enum(["user", "assistant"]), content: zod_1.z.string() })).optional(),
|
|
69
69
|
});
|
|
@@ -78,12 +78,12 @@ const TradePlanSchema = zod_1.z.object({
|
|
|
78
78
|
riskTolerance: zod_1.z.enum(["conservative", "moderate", "aggressive"]).optional(),
|
|
79
79
|
timeframe: zod_1.z.string().optional(),
|
|
80
80
|
});
|
|
81
|
-
const
|
|
81
|
+
const FINCH_BASE_PROMPT = `You are Finch, the core intelligence of the Finch runtime - the persistent state layer for AI assistants. You are direct, sharp, and thorough. You have access to memory that accumulates, vaults that version knowledge, agents that keep running between sessions, workflows that execute on schedule, plus execution domains: web research, market intelligence, code, and DeFi on Base. When asked anything, give your honest read backed by real reasoning. No filler, no disclaimers.
|
|
82
82
|
|
|
83
83
|
When the user's vault or memory contains relevant prior research, build on it explicitly rather than starting from scratch. Reference vault entries by title when you cite them.`;
|
|
84
84
|
async function searchVault(question, limit = 3) {
|
|
85
85
|
try {
|
|
86
|
-
const data = await (0, convex_js_1.callConvex)("/vault/search", "POST", { query: question, limit }, "
|
|
86
|
+
const data = await (0, convex_js_1.callConvex)("/vault/search", "POST", { query: question, limit }, "vault_search");
|
|
87
87
|
const entries = data?.entries ?? data?.results ?? [];
|
|
88
88
|
return Array.isArray(entries) ? entries.slice(0, limit) : [];
|
|
89
89
|
}
|
|
@@ -93,7 +93,7 @@ async function searchVault(question, limit = 3) {
|
|
|
93
93
|
}
|
|
94
94
|
async function fetchProfileContext() {
|
|
95
95
|
try {
|
|
96
|
-
const data = await (0, convex_js_1.callConvex)("/vault/profile-context?maxChars=3000", "GET", undefined, "
|
|
96
|
+
const data = await (0, convex_js_1.callConvex)("/vault/profile-context?maxChars=3000", "GET", undefined, "vault_read");
|
|
97
97
|
return (data?.context ?? "").trim();
|
|
98
98
|
}
|
|
99
99
|
catch {
|
|
@@ -138,8 +138,8 @@ async function buildSystemPrompt(question) {
|
|
|
138
138
|
blocks.push(`<user_vault>\nThe user already has these prior artifacts on closely related topics - build on them, don't repeat them:\n${vaultBlock}\n</user_vault>`);
|
|
139
139
|
}
|
|
140
140
|
const prompt = blocks.length === 0
|
|
141
|
-
?
|
|
142
|
-
: `${
|
|
141
|
+
? FINCH_BASE_PROMPT
|
|
142
|
+
: `${FINCH_BASE_PROMPT}\n\n${blocks.join("\n\n")}`;
|
|
143
143
|
return { prompt, meta };
|
|
144
144
|
}
|
|
145
145
|
function formatContextHeader(meta) {
|
|
@@ -391,7 +391,7 @@ function spreadNote(priceData) {
|
|
|
391
391
|
}
|
|
392
392
|
async function handleInsightTool(name, args) {
|
|
393
393
|
if (name === "ask_finch") {
|
|
394
|
-
const parsed =
|
|
394
|
+
const parsed = AskFinchSchema.safeParse(args);
|
|
395
395
|
if (!parsed.success)
|
|
396
396
|
return { content: [{ type: "text", text: `Invalid input: question ${parsed.error.issues[0].message}` }], isError: true };
|
|
397
397
|
const { question, messages = [] } = parsed.data;
|
|
@@ -419,25 +419,21 @@ async function handleInsightTool(name, args) {
|
|
|
419
419
|
systemPrompt += `\n\nCRITICAL: User is asking about a current price but no live data was retrieved. Reply honestly: "I don't have a live price quote right now - check CoinGecko or DexScreener directly." DO NOT state any specific price number. DO NOT recall a price from training data.`;
|
|
420
420
|
}
|
|
421
421
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
question,
|
|
435
|
-
agentId: "noel-default",
|
|
436
|
-
messages,
|
|
437
|
-
systemPrompt,
|
|
438
|
-
}, "ask_finch");
|
|
439
|
-
answer = data.answer ?? JSON.stringify(data);
|
|
422
|
+
if (!(0, llm_js_1.hasDirectLLMKey)()) {
|
|
423
|
+
return {
|
|
424
|
+
content: [{
|
|
425
|
+
type: "text",
|
|
426
|
+
text: "ask_finch needs its own LLM key to reason server-side - set one of " +
|
|
427
|
+
"BANKR_API_KEY, ANTHROPIC_API_KEY, OPENAI_API_KEY, or GROK_API_KEY as an " +
|
|
428
|
+
"environment variable, then retry. If YOU (the calling model) already have " +
|
|
429
|
+
"the context needed to answer this, just answer directly instead - that's " +
|
|
430
|
+
"usually faster and doesn't need a key at all.",
|
|
431
|
+
}],
|
|
432
|
+
isError: true,
|
|
433
|
+
};
|
|
440
434
|
}
|
|
435
|
+
const history = messages.map(m => ({ role: m.role, content: m.content }));
|
|
436
|
+
const answer = await (0, llm_js_1.callLLM)(systemPrompt, question, 1024, history);
|
|
441
437
|
// Signal gate - if the model returned thin/meta output, flag it so the
|
|
442
438
|
// user knows to retry with more specific framing or use deep_research.
|
|
443
439
|
const signal = (0, signal_gate_js_1.checkSignal)(answer);
|
package/dist/tools/memory.js
CHANGED
|
@@ -101,7 +101,7 @@ async function findDuplicateMemory(hash) {
|
|
|
101
101
|
const local = (0, local_memory_js_1.getLocalMemoryConfig)();
|
|
102
102
|
const results = local
|
|
103
103
|
? await (0, local_memory_js_1.localMemoryList)(local, 50)
|
|
104
|
-
: ((await (0, convex_js_1.callConvex)("/memory/list", "POST", { n: 50 }))?.results ?? []);
|
|
104
|
+
: ((await (0, convex_js_1.callConvex)("/memory/list", "POST", { n: 50 }, "memory_list"))?.results ?? []);
|
|
105
105
|
const match = results.find((r) => r.metadata?.contentHash === hash);
|
|
106
106
|
if (!match)
|
|
107
107
|
return null;
|
|
@@ -154,7 +154,7 @@ async function syncToSupermemory(content, metadata, sourceUrl) {
|
|
|
154
154
|
};
|
|
155
155
|
for (let attempt = 0; attempt <= SYNC_RETRY_DELAYS_MS.length; attempt++) {
|
|
156
156
|
try {
|
|
157
|
-
await (0, convex_js_1.callConvex)("/memory/add", "POST", payload);
|
|
157
|
+
await (0, convex_js_1.callConvex)("/memory/add", "POST", payload, "memory_add");
|
|
158
158
|
return;
|
|
159
159
|
}
|
|
160
160
|
catch (err) {
|
|
@@ -183,7 +183,7 @@ async function searchSupermemory(query, limit = 10) {
|
|
|
183
183
|
const local = (0, local_memory_js_1.getLocalMemoryConfig)();
|
|
184
184
|
if (local)
|
|
185
185
|
return await (0, local_memory_js_1.localMemorySearch)(local, query, limit);
|
|
186
|
-
const data = await (0, convex_js_1.callConvex)("/memory/search", "POST", { q: query, n: limit });
|
|
186
|
+
const data = await (0, convex_js_1.callConvex)("/memory/search", "POST", { q: query, n: limit }, "memory_search");
|
|
187
187
|
return data?.results ?? [];
|
|
188
188
|
}
|
|
189
189
|
catch {
|
|
@@ -195,7 +195,7 @@ async function searchSupermemory(query, limit = 10) {
|
|
|
195
195
|
// directly without re-normalizing raw scores.
|
|
196
196
|
async function lexicalSearch(query, limit = 30) {
|
|
197
197
|
try {
|
|
198
|
-
const data = await (0, convex_js_1.callConvex)("/memory/lexical", "POST", { q: query, n: limit });
|
|
198
|
+
const data = await (0, convex_js_1.callConvex)("/memory/lexical", "POST", { q: query, n: limit }, "memory_search");
|
|
199
199
|
const rows = (data?.results ?? []);
|
|
200
200
|
return rows.map((r, idx) => ({
|
|
201
201
|
id: r.id,
|
|
@@ -284,12 +284,13 @@ async function hybridMemorySearch(query, limit = 30) {
|
|
|
284
284
|
exports.MEMORY_TOOLS = [
|
|
285
285
|
{
|
|
286
286
|
name: "memory_add",
|
|
287
|
-
description: "Add content to your Finch
|
|
287
|
+
description: "Add content to your Finch memory - no setup needed, no extra API keys. " +
|
|
288
288
|
"Unlike vault_save, memory_add is instant: no versioning, no type required. " +
|
|
289
|
-
"Use for notes, decisions, preferences, or anything you want to find later
|
|
289
|
+
"Use for notes, decisions, preferences, or anything you want to find later. " +
|
|
290
290
|
"Pass sourceUrl to fetch and index any web page, GitHub repo, or Notion page automatically - " +
|
|
291
|
-
"searchable in ~30s.
|
|
292
|
-
"
|
|
291
|
+
"searchable in ~30s. Retrieval is full-text (keyword) search, not embeddings - " +
|
|
292
|
+
"'what did I say about ETH yield?' finds notes containing those words or close variants, " +
|
|
293
|
+
"not unrelated phrasing with the same meaning. " +
|
|
293
294
|
"Auto-deduplicates: identical content in your recent 50 memories is skipped (override with force:true).",
|
|
294
295
|
inputSchema: {
|
|
295
296
|
type: "object",
|
|
@@ -305,10 +306,11 @@ exports.MEMORY_TOOLS = [
|
|
|
305
306
|
},
|
|
306
307
|
{
|
|
307
308
|
name: "memory_search",
|
|
308
|
-
description: "
|
|
309
|
-
"
|
|
310
|
-
"
|
|
311
|
-
"
|
|
309
|
+
description: "Full-text (keyword) search over your stored memories, with 90-day time-decay weighting so " +
|
|
310
|
+
"recent notes outrank stale ones with similar wording. Good for exact-token lookups (env var " +
|
|
311
|
+
"names, contract addresses, IDs, specific phrases) - it does not understand meaning, so " +
|
|
312
|
+
"'low risk crypto yield' will not match a note phrased as 'conservative DeFi strategies' " +
|
|
313
|
+
"unless the words themselves overlap.",
|
|
312
314
|
inputSchema: {
|
|
313
315
|
type: "object",
|
|
314
316
|
properties: {
|
|
@@ -320,9 +322,10 @@ exports.MEMORY_TOOLS = [
|
|
|
320
322
|
},
|
|
321
323
|
{
|
|
322
324
|
name: "memory_context",
|
|
323
|
-
description: "Retrieve the most
|
|
325
|
+
description: "Retrieve the most relevant memories for a topic, formatted as AI-ready context. " +
|
|
324
326
|
"Use at the start of research tasks to prime with everything stored about a topic. " +
|
|
325
|
-
"Uses
|
|
327
|
+
"Uses full-text (keyword) search, not embeddings - phrase your topic with the words " +
|
|
328
|
+
"you expect were actually used when the memory was saved.",
|
|
326
329
|
inputSchema: {
|
|
327
330
|
type: "object",
|
|
328
331
|
properties: {
|
|
@@ -334,7 +337,7 @@ exports.MEMORY_TOOLS = [
|
|
|
334
337
|
},
|
|
335
338
|
{
|
|
336
339
|
name: "memory_profile",
|
|
337
|
-
description: "Show your
|
|
340
|
+
description: "Show your memory stats - total memories stored, your memory space, and connected sources. " +
|
|
338
341
|
"Useful for auditing what Finch knows about you.",
|
|
339
342
|
inputSchema: {
|
|
340
343
|
type: "object",
|
|
@@ -373,7 +376,7 @@ exports.MEMORY_TOOLS = [
|
|
|
373
376
|
},
|
|
374
377
|
{
|
|
375
378
|
name: "memory_insight",
|
|
376
|
-
description: "Get a full intelligence report on any topic - combines
|
|
379
|
+
description: "Get a full intelligence report on any topic - combines memory AND vault entries, " +
|
|
377
380
|
"then identifies knowledge gaps and suggests next actions. " +
|
|
378
381
|
"Use this before starting any research or trade decision to see everything Finch already knows. " +
|
|
379
382
|
"Returns: confidence level, what you know, coverage timeline, gaps, and recommended next steps.",
|
|
@@ -388,7 +391,7 @@ exports.MEMORY_TOOLS = [
|
|
|
388
391
|
},
|
|
389
392
|
{
|
|
390
393
|
name: "memory_extract",
|
|
391
|
-
description: "Save discrete facts, preferences and decisions to
|
|
394
|
+
description: "Save discrete facts, preferences and decisions to memory as individually searchable atoms " +
|
|
392
395
|
"instead of one wall of text. Two-pass, no API key needed. " +
|
|
393
396
|
"PASS 1 โ call with `text`: returns the text with the extraction rubric. " +
|
|
394
397
|
"PASS 2 โ call with `facts: [...]`: stores each fact separately, deduped. " +
|
|
@@ -560,7 +563,7 @@ async function handleMemoryTool(name, args) {
|
|
|
560
563
|
const localAdd = (0, local_memory_js_1.getLocalMemoryConfig)();
|
|
561
564
|
const data = localAdd
|
|
562
565
|
? await (0, local_memory_js_1.localMemoryAdd)(localAdd, content, addMetadata, sourceUrl).catch((err) => ({ error: err.message }))
|
|
563
|
-
: await (0, convex_js_1.callConvex)("/memory/add", "POST", { content, metadata: addMetadata, ...(sourceUrl ? { sourceUrl } : {}) }).catch((err) => ({ error: err.message }));
|
|
566
|
+
: await (0, convex_js_1.callConvex)("/memory/add", "POST", { content, metadata: addMetadata, ...(sourceUrl ? { sourceUrl } : {}) }, "memory_add").catch((err) => ({ error: err.message }));
|
|
564
567
|
if (data?.error)
|
|
565
568
|
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
566
569
|
// Cache the hash so an immediate second call with identical content
|
|
@@ -588,11 +591,12 @@ async function handleMemoryTool(name, args) {
|
|
|
588
591
|
const { query, limit = 10 } = parsed.data;
|
|
589
592
|
// Over-fetch so post-decay ranking still has enough material.
|
|
590
593
|
const overfetch = Math.min(50, Math.max(limit * 2, 20));
|
|
591
|
-
// โโโ
|
|
592
|
-
// Fan out
|
|
593
|
-
//
|
|
594
|
-
//
|
|
595
|
-
//
|
|
594
|
+
// โโโ Retrieval โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
595
|
+
// Fan out two full-text queries (searchSupermemory and lexicalSearch -
|
|
596
|
+
// both call Convex full-text search under different names, there is no
|
|
597
|
+
// embedding step despite the "semantic"/"Supermemory" naming) in
|
|
598
|
+
// parallel, fuse via Reciprocal Rank Fusion so results present in both
|
|
599
|
+
// rank highest.
|
|
596
600
|
const fused = await hybridMemorySearch(query, overfetch);
|
|
597
601
|
const raw = fused;
|
|
598
602
|
if (!raw.length)
|
|
@@ -697,7 +701,7 @@ async function handleMemoryTool(name, args) {
|
|
|
697
701
|
const localProfileCfg = (0, local_memory_js_1.getLocalMemoryConfig)();
|
|
698
702
|
const data = localProfileCfg
|
|
699
703
|
? await (0, local_memory_js_1.localMemoryProfile)(localProfileCfg).catch(() => null)
|
|
700
|
-
: await (0, convex_js_1.callConvex)("/memory/profile", "GET").catch(() => null);
|
|
704
|
+
: await (0, convex_js_1.callConvex)("/memory/profile", "GET", undefined, "memory_profile").catch(() => null);
|
|
701
705
|
const total = data?.total ?? 0;
|
|
702
706
|
const status = data?.status ?? "unknown";
|
|
703
707
|
const space = data?.space ?? "-";
|
|
@@ -738,7 +742,7 @@ async function handleMemoryTool(name, args) {
|
|
|
738
742
|
}
|
|
739
743
|
}
|
|
740
744
|
else {
|
|
741
|
-
const data = await (0, convex_js_1.callConvex)("/memory/list", "POST", { n: limit, tag }).catch((err) => ({ error: err.message }));
|
|
745
|
+
const data = await (0, convex_js_1.callConvex)("/memory/list", "POST", { n: limit, tag }, "memory_list").catch((err) => ({ error: err.message }));
|
|
742
746
|
if (data?.error)
|
|
743
747
|
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
744
748
|
results = data?.results ?? [];
|
|
@@ -777,7 +781,7 @@ async function handleMemoryTool(name, args) {
|
|
|
777
781
|
}
|
|
778
782
|
}
|
|
779
783
|
else {
|
|
780
|
-
const data = await (0, convex_js_1.callConvex)("/memory/delete", "POST", { id: parsed.data.id }).catch((err) => ({ error: err.message }));
|
|
784
|
+
const data = await (0, convex_js_1.callConvex)("/memory/delete", "POST", { id: parsed.data.id }, "memory_delete").catch((err) => ({ error: err.message }));
|
|
781
785
|
if (data?.error)
|
|
782
786
|
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
783
787
|
}
|
|
@@ -794,7 +798,7 @@ async function handleMemoryTool(name, args) {
|
|
|
794
798
|
// matches. Vault side runs in parallel as before.
|
|
795
799
|
const [memResults, vaultData] = await Promise.all([
|
|
796
800
|
hybridMemorySearch(topic, memLimit),
|
|
797
|
-
(0, convex_js_1.callConvex)(`/vault/search?q=${encodeURIComponent(topic)}&limit=6`, "GET", undefined, "
|
|
801
|
+
(0, convex_js_1.callConvex)(`/vault/search?q=${encodeURIComponent(topic)}&limit=6`, "GET", undefined, "vault_search").catch(() => ({ results: [] })),
|
|
798
802
|
]);
|
|
799
803
|
const vaultResults = vaultData.results ?? [];
|
|
800
804
|
const total = memResults.length + vaultResults.length;
|
|
@@ -905,7 +909,7 @@ async function handleMemoryTool(name, args) {
|
|
|
905
909
|
const metaFor = () => ({ source, addedAt: Date.now() });
|
|
906
910
|
const results = await Promise.allSettled(facts.map((fact) => localExtractCfg
|
|
907
911
|
? (0, local_memory_js_1.localMemoryAdd)(localExtractCfg, fact, metaFor())
|
|
908
|
-
: (0, convex_js_1.callConvex)("/memory/add", "POST", { content: fact, metadata: metaFor() })));
|
|
912
|
+
: (0, convex_js_1.callConvex)("/memory/add", "POST", { content: fact, metadata: metaFor() }, "memory_add")));
|
|
909
913
|
const saved = results.filter((r) => r.status === "fulfilled").length;
|
|
910
914
|
const failed = results.length - saved;
|
|
911
915
|
return {
|
|
@@ -934,7 +938,7 @@ async function handleMemoryTool(name, args) {
|
|
|
934
938
|
const meta = { title: `Consolidated: ${topic}`, source: "memory_consolidate", addedAt: Date.now() };
|
|
935
939
|
const saved = localConsolidateCfg
|
|
936
940
|
? await (0, local_memory_js_1.localMemoryAdd)(localConsolidateCfg, summary, meta).catch((err) => ({ error: err.message }))
|
|
937
|
-
: await (0, convex_js_1.callConvex)("/memory/add", "POST", { content: summary, metadata: meta }).catch((err) => ({ error: err.message }));
|
|
941
|
+
: await (0, convex_js_1.callConvex)("/memory/add", "POST", { content: summary, metadata: meta }, "memory_add").catch((err) => ({ error: err.message }));
|
|
938
942
|
if (saved?.error) {
|
|
939
943
|
return { content: [{ type: "text", text: `Error saving consolidated memory: ${saved.error}` }], isError: true };
|
|
940
944
|
}
|
package/dist/tools/monitor.js
CHANGED
|
@@ -117,7 +117,7 @@ async function handleMonitorTool(name, args) {
|
|
|
117
117
|
// created 4ร by an over-eager agent loop, which is what produced the
|
|
118
118
|
// duplicate Telegram briefings users complained about.
|
|
119
119
|
try {
|
|
120
|
-
const existingMonitors = await (0, convex_js_1.callConvex)("/vault/list", "POST", { type: "workflow", tags: ["monitor-config"] }, "
|
|
120
|
+
const existingMonitors = await (0, convex_js_1.callConvex)("/vault/list", "POST", { type: "workflow", tags: ["monitor-config"] }, "vault_list");
|
|
121
121
|
const duplicates = (existingMonitors?.entries ?? []).filter((e) => {
|
|
122
122
|
try {
|
|
123
123
|
const cfg = JSON.parse(e.content ?? "{}");
|
package/dist/tools/os.js
CHANGED
|
@@ -45,14 +45,14 @@ async function handleOsTool(name, args) {
|
|
|
45
45
|
const localStatusCfg = (0, local_memory_js_1.getLocalMemoryConfig)();
|
|
46
46
|
const memoryProfileCall = localStatusCfg
|
|
47
47
|
? (0, local_memory_js_1.localMemoryProfile)(localStatusCfg)
|
|
48
|
-
: (0, convex_js_1.callConvex)("/memory/profile", "GET");
|
|
48
|
+
: (0, convex_js_1.callConvex)("/memory/profile", "GET", undefined, "memory_profile");
|
|
49
49
|
const [tierResult, walletResult, memRes, autoRes, vaultRes, agentsRes] = await Promise.allSettled([
|
|
50
50
|
(0, token_gate_js_1.getTier)(),
|
|
51
51
|
(0, wallet_js_1.getOrCreateWallet)(),
|
|
52
52
|
memoryProfileCall,
|
|
53
53
|
(0, convex_js_1.callConvex)("/automations/list", "GET", undefined, "list_automations"),
|
|
54
|
-
(0, convex_js_1.callConvex)("/vault/list?type=research&limit=5", "GET", undefined, "
|
|
55
|
-
(0, convex_js_1.callConvex)("/vault/list?type=memory&limit=20", "GET", undefined, "
|
|
54
|
+
(0, convex_js_1.callConvex)("/vault/list?type=research&limit=5", "GET", undefined, "vault_list"),
|
|
55
|
+
(0, convex_js_1.callConvex)("/vault/list?type=memory&limit=20", "GET", undefined, "vault_list"),
|
|
56
56
|
]);
|
|
57
57
|
const tier = tierResult.status === "fulfilled" ? tierResult.value : "basic";
|
|
58
58
|
const wallet = walletResult.status === "fulfilled" ? walletResult.value : null;
|
|
@@ -171,8 +171,13 @@ async function handleOsTool(name, args) {
|
|
|
171
171
|
hints.push(`โ No FIRECRAWL_API_KEY โ deep_research falls back to Finch proxy (requires session token).`);
|
|
172
172
|
if (!localMemCfg)
|
|
173
173
|
hints.push(`โ Memory tools use the Finch-hosted proxy. Run \`finch setup\` for free, self-hosted local memory.`);
|
|
174
|
-
else if (localSmStatus !== "ok")
|
|
174
|
+
else if (localMemCfg.kind === "supermemory" && localSmStatus !== "ok")
|
|
175
175
|
hints.push(`โ Local memory configured but ${localMemCfg.url} isn't reachable โ memory tools will fail. Run \`npx -y supermemory local\`.`);
|
|
176
|
+
const localMemLabel = !localMemCfg
|
|
177
|
+
? "not configured โ run `finch setup`"
|
|
178
|
+
: localMemCfg.kind === "file"
|
|
179
|
+
? "file-based at ~/.finch/memory"
|
|
180
|
+
: localSmStatus === "ok" ? `reachable at ${localMemCfg.url}` : `configured but unreachable at ${localMemCfg.url}`;
|
|
176
181
|
const lines = [
|
|
177
182
|
`## ๐ฉบ Finch Diagnostics`,
|
|
178
183
|
``,
|
|
@@ -180,7 +185,7 @@ async function handleOsTool(name, args) {
|
|
|
180
185
|
` ${statusIcon(convexStatus)} Convex backend ${convexStatus === "ok" ? "reachable" : "unreachable โ check FINCH_CONVEX_URL"}`,
|
|
181
186
|
` ${statusIcon(fcStatus)} Firecrawl ${fcStatus === "ok" ? "reachable" : fcStatus === "unconfigured" ? "no FIRECRAWL_API_KEY โ deep_research will use proxy" : "unreachable"}`,
|
|
182
187
|
` ${statusIcon(smStatus)} Supermemory (cloud) ${localMemCfg ? "not used โ local memory active" : smStatus === "ok" ? "reachable" : "unreachable โ memory tools may fail"}`,
|
|
183
|
-
` ${statusIcon(localSmStatus)}
|
|
188
|
+
` ${statusIcon(localSmStatus)} Local memory ${localMemLabel}`,
|
|
184
189
|
``,
|
|
185
190
|
`**API Keys configured:** _(none of the LLM keys are required โ see below)_`,
|
|
186
191
|
...Object.entries(envKeys).map(([k, v]) => ` ${v ? "โ
" : "โช"} ${k}`),
|
package/dist/tools/packets.js
CHANGED
|
@@ -134,7 +134,7 @@ async function handlePacket(toolName, args) {
|
|
|
134
134
|
content,
|
|
135
135
|
contentType: "json",
|
|
136
136
|
tags: ["packet", ...(tags ?? [])],
|
|
137
|
-
}, "
|
|
137
|
+
}, "vault_save");
|
|
138
138
|
return {
|
|
139
139
|
content: [{
|
|
140
140
|
type: "text",
|
|
@@ -153,7 +153,7 @@ async function handlePacket(toolName, args) {
|
|
|
153
153
|
}
|
|
154
154
|
if (toolName === "packet_run") {
|
|
155
155
|
const { name } = args;
|
|
156
|
-
const data = await (0, convex_js_1.callConvex)(`/vault/entry?key=${encodeURIComponent(packetKey(name))}`, "GET", undefined, "
|
|
156
|
+
const data = await (0, convex_js_1.callConvex)(`/vault/entry?key=${encodeURIComponent(packetKey(name))}`, "GET", undefined, "vault_read");
|
|
157
157
|
if (!data?.content || data?.error) {
|
|
158
158
|
return {
|
|
159
159
|
content: [{ type: "text", text: `Packet \`${name}\` not found. Use \`packet_list\` to see available packets.` }],
|
|
@@ -271,7 +271,7 @@ async function handlePacket(toolName, args) {
|
|
|
271
271
|
const data = await (0, convex_js_1.callConvex)("/vault/publish", "POST", {
|
|
272
272
|
key: packetKey(name),
|
|
273
273
|
authorName: authorName ?? "anonymous",
|
|
274
|
-
}, "
|
|
274
|
+
}, "vault_publish");
|
|
275
275
|
if (data.error)
|
|
276
276
|
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
277
277
|
return {
|
package/dist/tools/research.js
CHANGED
|
@@ -68,7 +68,7 @@ async function firecrawlScrape(url) {
|
|
|
68
68
|
// FIRECRAWL_API_KEY set either, in which case web_scrape silently falls
|
|
69
69
|
// through to basicFetch below.
|
|
70
70
|
try {
|
|
71
|
-
const data = await (0, convex_js_1.callConvex)("/research/firecrawl-scrape", "POST", { url }, "
|
|
71
|
+
const data = await (0, convex_js_1.callConvex)("/research/firecrawl-scrape", "POST", { url }, "web_scrape", 25000);
|
|
72
72
|
if (data?.markdown)
|
|
73
73
|
return data.markdown;
|
|
74
74
|
}
|
|
@@ -149,7 +149,7 @@ async function handleResearchTool(name, args) {
|
|
|
149
149
|
}
|
|
150
150
|
if (!results) {
|
|
151
151
|
try {
|
|
152
|
-
const data = await (0, convex_js_1.callConvex)("/research/firecrawl-search", "POST", { query, limit }, "
|
|
152
|
+
const data = await (0, convex_js_1.callConvex)("/research/firecrawl-search", "POST", { query, limit }, "web_search", 30000);
|
|
153
153
|
results = data?.results ?? [];
|
|
154
154
|
}
|
|
155
155
|
catch (err) {
|
package/dist/tools/rh-mcp.js
CHANGED
|
@@ -33,6 +33,15 @@ exports.RH_EXPLORER = "https://robinhoodchain.blockscout.com";
|
|
|
33
33
|
const NATIVE_ETH = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
|
|
34
34
|
const RH_PERMIT2 = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
35
35
|
const RH_UNIVERSAL_ROUTER = "0x8876789976decbfcbbbe364623c63652db8c0904";
|
|
36
|
+
/**
|
|
37
|
+
* Canonical Global Dollar (USDG) โ RH chain's settlement stablecoin (same
|
|
38
|
+
* address as app/convex/_settlement.ts). Hardcoded rather than resolved via
|
|
39
|
+
* DexScreener ticker search: "USDG" is the exact symbol an imposter contract
|
|
40
|
+
* would spoof, and dexSearchByTicker ranks by pool liquidity, not authenticity
|
|
41
|
+
* โ a fake pool with inflated liquidity could otherwise outrank the real token.
|
|
42
|
+
*/
|
|
43
|
+
const USDG_ADDRESS = "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168";
|
|
44
|
+
const USDG_DECIMALS = 6;
|
|
36
45
|
/** ClawHood / Finch catalog (22 tokenized stocks โ official "Robinhood Token" contracts). */
|
|
37
46
|
exports.RH_STOCKS = [
|
|
38
47
|
{ address: "0xaf3d76f1834a1d425780943c99ea8a608f8a93f9", symbol: "AAPL", name: "Apple" },
|
|
@@ -336,6 +345,9 @@ async function resolveTokenSmart(input) {
|
|
|
336
345
|
if (isEth(input))
|
|
337
346
|
return { kind: "eth", address: NATIVE_ETH, symbol: "ETH", decimals: 18 };
|
|
338
347
|
const t = input.trim();
|
|
348
|
+
if (t.toUpperCase() === "USDG") {
|
|
349
|
+
return { kind: "token", address: USDG_ADDRESS, symbol: "USDG", name: "Global Dollar", decimals: USDG_DECIMALS };
|
|
350
|
+
}
|
|
339
351
|
// 1) catalog symbol fast-path (22 tokenized stocks, all 18-decimals)
|
|
340
352
|
const cat = exports.RH_STOCKS.find((s) => s.symbol === t.toUpperCase());
|
|
341
353
|
if (cat)
|