@finchagentic/mcp 4.1.0 → 4.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/README.md +14 -14
- package/dist/agent-loop.js +75 -72
- package/dist/annotations.js +14 -13
- package/dist/convex.js +15 -18
- package/dist/index.js +4 -5
- package/dist/llm.js +12 -1
- package/dist/local-memory-file.js +14 -1
- package/dist/local-memory.js +13 -0
- package/dist/output-schemas.js +71 -17
- package/dist/resources.js +6 -11
- package/dist/server.js +27 -13
- package/dist/token-gate.js +2 -2
- package/dist/tool-filter.js +13 -4
- package/dist/tools/agents.js +62 -390
- package/dist/tools/base-mcp.js +3 -15
- package/dist/tools/base.js +34 -20
- package/dist/tools/defi.js +10 -30
- package/dist/tools/equity.js +9 -1
- package/dist/tools/insight.js +2 -2
- package/dist/tools/memory.js +44 -80
- package/dist/tools/monitor.js +6 -6
- package/dist/tools/os.js +0 -1
- package/dist/tools/packets.js +2 -2
- package/dist/tools/rh-mcp.js +13 -2
- package/dist/tools/rh-orders.js +201 -123
- package/dist/tools/scanner.js +30 -0
- package/dist/tools/stake.js +329 -0
- package/dist/tools/vault.js +99 -21
- package/dist/wallet.js +130 -19
- package/package.json +2 -2
- package/dist/tools/framework.js +0 -150
package/dist/tools/base.js
CHANGED
|
@@ -4,10 +4,35 @@ exports.BASE_TOOLS = void 0;
|
|
|
4
4
|
exports.handleBaseTool = handleBaseTool;
|
|
5
5
|
const MORPHO_API = "https://blue-api.morpho.org/graphql";
|
|
6
6
|
const MOONWELL_API = "https://api.moonwell.fi/v1/markets";
|
|
7
|
+
/** Morpho's public API occasionally 403s transiently (rate-limit/WAF blip,
|
|
8
|
+
* confirmed by hand: identical requests succeed moments later) - one retry
|
|
9
|
+
* after a short delay clears most of these without adding real latency to
|
|
10
|
+
* the common case. */
|
|
11
|
+
async function fetchMorphoGql(query) {
|
|
12
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
13
|
+
if (attempt > 0)
|
|
14
|
+
await new Promise((r) => setTimeout(r, 800));
|
|
15
|
+
const res = await fetch(MORPHO_API, {
|
|
16
|
+
method: "POST",
|
|
17
|
+
headers: { "Content-Type": "application/json" },
|
|
18
|
+
body: JSON.stringify({ query }),
|
|
19
|
+
signal: AbortSignal.timeout(15000),
|
|
20
|
+
});
|
|
21
|
+
if (res.ok)
|
|
22
|
+
return res.json();
|
|
23
|
+
if (attempt === 1)
|
|
24
|
+
throw new Error(`Morpho API error: ${res.status} (after retry)`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
7
27
|
exports.BASE_TOOLS = [
|
|
8
28
|
{
|
|
9
29
|
name: "base_mcp_yield_vaults",
|
|
10
|
-
description: "Find the best yield/earning opportunities on Base chain using Morpho vaults
|
|
30
|
+
description: "Find the best yield/earning opportunities on Base chain using Morpho vaults - queries Morpho's own " +
|
|
31
|
+
"API directly, so numbers are fresher than get_defi_yields' DeFiLlama-aggregated Morpho figures, " +
|
|
32
|
+
"but this tool only covers Morpho (not Aerodrome/Uniswap/other protocols - use get_defi_yields for " +
|
|
33
|
+
"cross-protocol comparison). Returns all vaults ranked by APY — use this when the user asks about " +
|
|
34
|
+
"yield farming, APY, best rates to earn, where to put USDC, or passive income on Base. For yield on " +
|
|
35
|
+
"a specific token, also see base_mcp_lend.",
|
|
11
36
|
inputSchema: {
|
|
12
37
|
type: "object",
|
|
13
38
|
properties: {
|
|
@@ -25,7 +50,12 @@ exports.BASE_TOOLS = [
|
|
|
25
50
|
},
|
|
26
51
|
{
|
|
27
52
|
name: "base_mcp_lending_rates",
|
|
28
|
-
description: "Get lending and borrowing rates across all Moonwell markets on Base
|
|
53
|
+
description: "Get lending and borrowing rates across all Moonwell markets on Base - queries Moonwell's own API " +
|
|
54
|
+
"directly, so numbers are fresher than get_defi_yields' DeFiLlama-aggregated Moonwell figures, but " +
|
|
55
|
+
"this tool only covers Moonwell (use get_defi_yields for cross-protocol comparison, or " +
|
|
56
|
+
"base_mcp_yield_vaults for Morpho specifically). Returns supply APY, borrow APY, liquidity, and " +
|
|
57
|
+
"utilization per asset. Use when the user asks about borrow rates, lending rates, interest rates, " +
|
|
58
|
+
"or supply APY on Base.",
|
|
29
59
|
inputSchema: {
|
|
30
60
|
type: "object",
|
|
31
61
|
properties: {
|
|
@@ -99,15 +129,7 @@ async function fetchMorphoVaults(asset, limit = 10) {
|
|
|
99
129
|
}
|
|
100
130
|
}
|
|
101
131
|
}`;
|
|
102
|
-
const
|
|
103
|
-
method: "POST",
|
|
104
|
-
headers: { "Content-Type": "application/json" },
|
|
105
|
-
body: JSON.stringify({ query: gql }),
|
|
106
|
-
signal: AbortSignal.timeout(15000),
|
|
107
|
-
});
|
|
108
|
-
if (!res.ok)
|
|
109
|
-
throw new Error(`Morpho API error: ${res.status}`);
|
|
110
|
-
const data = await res.json();
|
|
132
|
+
const data = await fetchMorphoGql(gql);
|
|
111
133
|
let vaults = data?.data?.vaults?.items ?? [];
|
|
112
134
|
// Filter out test/spam vaults: min $10k TVL, max 500% APY
|
|
113
135
|
vaults = vaults.filter((v) => {
|
|
@@ -199,15 +221,7 @@ async function prepareDeposit(vaultName, asset, amount) {
|
|
|
199
221
|
}
|
|
200
222
|
}
|
|
201
223
|
}`;
|
|
202
|
-
const
|
|
203
|
-
method: "POST",
|
|
204
|
-
headers: { "Content-Type": "application/json" },
|
|
205
|
-
body: JSON.stringify({ query: gql }),
|
|
206
|
-
signal: AbortSignal.timeout(15000),
|
|
207
|
-
});
|
|
208
|
-
if (!res.ok)
|
|
209
|
-
throw new Error(`Morpho API error: ${res.status}`);
|
|
210
|
-
const data = await res.json();
|
|
224
|
+
const data = await fetchMorphoGql(gql);
|
|
211
225
|
let vaults = data?.data?.vaults?.items ?? [];
|
|
212
226
|
// Filter by asset first
|
|
213
227
|
vaults = vaults.filter((v) => v.asset?.symbol?.toLowerCase() === asset.toLowerCase());
|
package/dist/tools/defi.js
CHANGED
|
@@ -11,7 +11,11 @@ exports.DEFI_TOOLS = [
|
|
|
11
11
|
{
|
|
12
12
|
name: "get_defi_yields",
|
|
13
13
|
description: "Fetch top DeFi yield opportunities on Base - Morpho, Moonwell, Aerodrome, Uniswap, and more. " +
|
|
14
|
-
"Returns
|
|
14
|
+
"Returns APY, TVL, and pool info aggregated from DeFiLlama (no API key required) - broadest " +
|
|
15
|
+
"protocol coverage, but DeFiLlama's numbers can lag the protocol's own API by hours. Good first " +
|
|
16
|
+
"stop for comparing across protocols. For the freshest Morpho-vault-specific numbers use " +
|
|
17
|
+
"base_mcp_yield_vaults instead; for the freshest Moonwell supply/borrow rates use " +
|
|
18
|
+
"base_mcp_lending_rates instead - both query the protocol directly. " +
|
|
15
19
|
"Filter by token or minimum APY. Use before depositing to find the best rates.",
|
|
16
20
|
inputSchema: {
|
|
17
21
|
type: "object",
|
|
@@ -34,7 +38,6 @@ const SwapSchema = zod_1.z.object({
|
|
|
34
38
|
const DEFAULT_MAX_SLIPPAGE_PCT = 1.0;
|
|
35
39
|
const DEFAULT_MAX_PRICE_IMPACT_PCT = 3.0;
|
|
36
40
|
const SendSchema = zod_1.z.object({ token: zod_1.z.string().min(1), toAddress: zod_1.z.string().regex(/^0x[0-9a-fA-F]{40}$/, "must be a valid 0x address"), amount: zod_1.z.string().min(1) });
|
|
37
|
-
const AnalyzeWalletSchema = zod_1.z.object({ address: zod_1.z.string().regex(/^0x[0-9a-fA-F]{40}$/, "must be a valid 0x address"), label: zod_1.z.string().optional() });
|
|
38
41
|
const DefiYieldsSchema = zod_1.z.object({
|
|
39
42
|
token: zod_1.z.string().optional(),
|
|
40
43
|
minApy: zod_1.z.number().optional(),
|
|
@@ -214,34 +217,11 @@ async function handleDefiTool(name, args) {
|
|
|
214
217
|
isError: receipt.mined && !receipt.ok,
|
|
215
218
|
};
|
|
216
219
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const data = await (0, convex_js_1.callConvex)("/wallet/analyze", "POST", { address, label }, "analyze_wallet");
|
|
223
|
-
if (data.error)
|
|
224
|
-
return { content: [{ type: "text", text: `Wallet analysis failed: ${data.error}` }], isError: true };
|
|
225
|
-
const total = (data.totalUsd ?? 0).toFixed(2);
|
|
226
|
-
const walletLabel = label ? ` - ${label}` : "";
|
|
227
|
-
const topHoldings = (data.holdings ?? [])
|
|
228
|
-
.slice(0, 8)
|
|
229
|
-
.map(h => `• **${h.token}**: $${(h.valueUsd ?? 0).toFixed(2)}${h.pct != null ? ` (${h.pct}%)` : ""}`)
|
|
230
|
-
.join("\n");
|
|
231
|
-
const profileLine = data.profile ? `**Profile:** ${data.profile}\n` : "";
|
|
232
|
-
const header = [
|
|
233
|
-
`**Wallet Analysis**${walletLabel}`,
|
|
234
|
-
`\`${address}\``,
|
|
235
|
-
`**Portfolio value:** $${total}`,
|
|
236
|
-
``,
|
|
237
|
-
profileLine,
|
|
238
|
-
`**Holdings:**`,
|
|
239
|
-
topHoldings || "No token holdings found.",
|
|
240
|
-
``,
|
|
241
|
-
].join("\n");
|
|
242
|
-
const body = data.analysis ?? (data.analysisError ? `*AI analysis unavailable: ${data.analysisError}*` : "*AI analysis not available*");
|
|
243
|
-
return { content: [{ type: "text", text: header + body }] };
|
|
244
|
-
}
|
|
220
|
+
// analyze_wallet was removed - it called POST /wallet/analyze, which was
|
|
221
|
+
// never registered in app/convex/http.ts (only a dangling section-header
|
|
222
|
+
// comment exists there). The tool was never in the exported DEFI_TOOLS
|
|
223
|
+
// list either, so this case was already unreachable dead code - no Tool
|
|
224
|
+
// registers the name "analyze_wallet" for the MCP dispatcher to route to.
|
|
245
225
|
case "get_defi_yields": {
|
|
246
226
|
const parsed = DefiYieldsSchema.safeParse(args ?? {});
|
|
247
227
|
if (!parsed.success)
|
package/dist/tools/equity.js
CHANGED
|
@@ -11,8 +11,13 @@
|
|
|
11
11
|
// and is a stronger analyst than anything this module could embed.
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
13
|
exports.EQUITY_TOOLS = void 0;
|
|
14
|
+
exports.buildStockFundamentals = buildStockFundamentals;
|
|
14
15
|
exports.handleEquityTool = handleEquityTool;
|
|
15
16
|
const zod_1 = require("zod");
|
|
17
|
+
// ── Structured output builder (schema in output-schemas.ts) ─────────────────
|
|
18
|
+
function buildStockFundamentals(ticker, companyName, cik, quote, quarterly, annual) {
|
|
19
|
+
return { ticker, companyName, cik, quote, quarterly, annual };
|
|
20
|
+
}
|
|
16
21
|
const SEC_UA = "Finch MCP research (contact: support@finchagentic.com)";
|
|
17
22
|
const SEC_TICKERS = "https://www.sec.gov/files/company_tickers.json";
|
|
18
23
|
const SEC_CONCEPT = (cik, tag) => `https://data.sec.gov/api/xbrl/companyconcept/CIK${cik}/us-gaap/${tag}.json`;
|
|
@@ -360,5 +365,8 @@ async function handleEquityTool(name, args) {
|
|
|
360
365
|
// Issuers tag the same concept differently, so naming the tag behind each
|
|
361
366
|
// series makes the figures checkable against EDGAR rather than trusted.
|
|
362
367
|
`_XBRL concepts used: ${usedTags.join(" · ")}_`, ``, `_Figures are as filed with the SEC. Not investment advice._`);
|
|
363
|
-
return {
|
|
368
|
+
return {
|
|
369
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
370
|
+
structuredContent: buildStockFundamentals(ticker, company.name ?? null, company.cik ?? null, quote, quarterly, annual),
|
|
371
|
+
};
|
|
364
372
|
}
|
package/dist/tools/insight.js
CHANGED
|
@@ -481,7 +481,7 @@ async function handleInsightTool(name, args) {
|
|
|
481
481
|
// thesis is model work — and the caller is a model that can also weigh the
|
|
482
482
|
// user's stated context. Hand over grounded data plus the structure.
|
|
483
483
|
const suggest = process.env.TRIGGER_SECRET_KEY
|
|
484
|
-
? `\n\n---\n💡 Use \`
|
|
484
|
+
? `\n\n---\n💡 Use \`schedule_research\` for scheduled briefings on ${token.toUpperCase()}.`
|
|
485
485
|
: "";
|
|
486
486
|
return {
|
|
487
487
|
content: [{
|
|
@@ -575,7 +575,7 @@ async function handleInsightTool(name, args) {
|
|
|
575
575
|
]
|
|
576
576
|
: [`**Max position (${riskTolerance}):** ${band.label} — pass \`portfolioSize\` for USD figures.`];
|
|
577
577
|
const suggest = process.env.TRIGGER_SECRET_KEY
|
|
578
|
-
? `\n\n---\n💡 Use \`
|
|
578
|
+
? `\n\n---\n💡 Use \`schedule_research\` for scheduled briefings on ${token.toUpperCase()}.`
|
|
579
579
|
: "";
|
|
580
580
|
return {
|
|
581
581
|
content: [{
|
package/dist/tools/memory.js
CHANGED
|
@@ -47,6 +47,7 @@ const zod_1 = require("zod");
|
|
|
47
47
|
const crypto = __importStar(require("crypto"));
|
|
48
48
|
const convex_js_1 = require("../convex.js");
|
|
49
49
|
const public_url_js_1 = require("../public-url.js");
|
|
50
|
+
const local_vault_js_1 = require("../local-vault.js");
|
|
50
51
|
const local_memory_js_1 = require("../local-memory.js");
|
|
51
52
|
// memory_extract and memory_consolidate used to run their own LLM calls here
|
|
52
53
|
// (and a matching pair of Convex routes did the same server-side). Both are now
|
|
@@ -89,6 +90,18 @@ function lookupRecentHash(hash) {
|
|
|
89
90
|
}
|
|
90
91
|
return hit;
|
|
91
92
|
}
|
|
93
|
+
// memory_delete must purge this too - otherwise a deleted memory's hash stays
|
|
94
|
+
// cached (up to an hour) and re-adding the exact same content within that
|
|
95
|
+
// window gets silently skipped as a "duplicate" of an id that no longer
|
|
96
|
+
// exists, when there is no longer any real duplicate to skip.
|
|
97
|
+
function forgetRecentHashById(id) {
|
|
98
|
+
for (const [hash, entry] of recentHashCache) {
|
|
99
|
+
if (entry.id === id) {
|
|
100
|
+
recentHashCache.delete(hash);
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
92
105
|
// Two-tier dedup lookup: in-process cache first (catches same-session
|
|
93
106
|
// dupes during eventual-consistency window), then a list call (catches
|
|
94
107
|
// cross-session dupes once they've been indexed). Returns null on any
|
|
@@ -410,26 +423,6 @@ exports.MEMORY_TOOLS = [
|
|
|
410
423
|
required: [],
|
|
411
424
|
},
|
|
412
425
|
},
|
|
413
|
-
{
|
|
414
|
-
name: "memory_publish",
|
|
415
|
-
description: "IRREVERSIBLE, PUBLIC. Publish a memory snippet to the Memory Marketplace — visible to " +
|
|
416
|
-
"ALL Finch users at /memory-marketplace. Reversible with vault_unpublish, but only for " +
|
|
417
|
-
"future discovery — anyone who already read it keeps what they saw. " +
|
|
418
|
-
"Requires confirm: true. Never call this on the user's behalf without them explicitly asking " +
|
|
419
|
-
"to publish; re-read the content for anything private (keys, addresses, personal details) first. " +
|
|
420
|
-
"Saved as a public vault entry (type=memory).",
|
|
421
|
-
inputSchema: {
|
|
422
|
-
type: "object",
|
|
423
|
-
properties: {
|
|
424
|
-
title: { type: "string", description: "Short title for the memory (shown publicly in the marketplace)" },
|
|
425
|
-
content: { type: "string", description: "The memory content to share — this becomes PUBLIC" },
|
|
426
|
-
tags: { type: "array", items: { type: "string" }, description: "Optional tags (e.g. ['DeFi', 'Base', 'research'])" },
|
|
427
|
-
authorName: { type: "string", description: "Public display name. Defaults to \"Anonymous\" — do NOT pass a wallet address unless the user asks to be identified." },
|
|
428
|
-
confirm: { type: "boolean", description: "Must be true to publish. Guards against accidental public disclosure." },
|
|
429
|
-
},
|
|
430
|
-
required: ["title", "content", "confirm"],
|
|
431
|
-
},
|
|
432
|
-
},
|
|
433
426
|
{
|
|
434
427
|
name: "memory_consolidate",
|
|
435
428
|
description: "Clean up fragmented knowledge after heavy research sessions. Two-pass, no API key needed. " +
|
|
@@ -709,7 +702,7 @@ async function handleMemoryTool(name, args) {
|
|
|
709
702
|
content: [{
|
|
710
703
|
type: "text",
|
|
711
704
|
text: [
|
|
712
|
-
`🧠 **Finch
|
|
705
|
+
`🧠 **Finch Memory**`,
|
|
713
706
|
``,
|
|
714
707
|
`Space: \`${space}\``,
|
|
715
708
|
`Total memories: **${total}**`,
|
|
@@ -720,7 +713,7 @@ async function handleMemoryTool(name, args) {
|
|
|
720
713
|
`• memory_add (URL indexing) - ✅`,
|
|
721
714
|
`• Google Drive / Gmail / Notion - connect at finchagentic.com`,
|
|
722
715
|
``,
|
|
723
|
-
`**Capabilities:**
|
|
716
|
+
`**Capabilities:** Full-text (keyword) search with 90-day time-decay ranking - not embeddings, no vector search.`,
|
|
724
717
|
].join("\n"),
|
|
725
718
|
}],
|
|
726
719
|
structuredContent: buildMemoryProfile(data),
|
|
@@ -785,6 +778,7 @@ async function handleMemoryTool(name, args) {
|
|
|
785
778
|
if (data?.error)
|
|
786
779
|
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
787
780
|
}
|
|
781
|
+
forgetRecentHashById(parsed.data.id);
|
|
788
782
|
return { content: [{ type: "text", text: `🗑️ Memory deleted: \`${parsed.data.id}\`` }] };
|
|
789
783
|
}
|
|
790
784
|
case "memory_insight": {
|
|
@@ -795,10 +789,15 @@ async function handleMemoryTool(name, args) {
|
|
|
795
789
|
const memLimit = depth === "deep" ? 15 : depth === "quick" ? 5 : 8;
|
|
796
790
|
// v3.25.1: hybrid retrieval for memory side (was semantic-only) so the
|
|
797
791
|
// intelligence report surfaces exact-token matches alongside meaning
|
|
798
|
-
// matches. Vault side runs in parallel as before
|
|
792
|
+
// matches. Vault side runs in parallel as before - but only against
|
|
793
|
+
// Convex when the vault isn't local; a local vault's topic string must
|
|
794
|
+
// never leave the machine, and this never checked that before.
|
|
795
|
+
const localVault = (0, local_vault_js_1.getLocalVaultConfig)();
|
|
799
796
|
const [memResults, vaultData] = await Promise.all([
|
|
800
797
|
hybridMemorySearch(topic, memLimit),
|
|
801
|
-
|
|
798
|
+
localVault
|
|
799
|
+
? Promise.resolve((0, local_vault_js_1.localVaultSearch)(localVault, topic, { limit: 6 }))
|
|
800
|
+
: (0, convex_js_1.callConvex)(`/vault/search?q=${encodeURIComponent(topic)}&limit=6`, "GET", undefined, "vault_search").catch(() => ({ results: [] })),
|
|
802
801
|
]);
|
|
803
802
|
const vaultResults = vaultData.results ?? [];
|
|
804
803
|
const total = memResults.length + vaultResults.length;
|
|
@@ -955,7 +954,11 @@ async function handleMemoryTool(name, args) {
|
|
|
955
954
|
};
|
|
956
955
|
}
|
|
957
956
|
// ── PASS 1: fetch every memory on the topic, numbered ───────────────
|
|
958
|
-
|
|
957
|
+
// Uses the same RRF-fused semantic+lexical retrieval as memory_search/
|
|
958
|
+
// memory_context - a plain single-source searchSupermemory() call here
|
|
959
|
+
// used to mean consolidation could miss memories only the lexical/BM25
|
|
960
|
+
// side would surface, while still claiming an exhaustive "N memories".
|
|
961
|
+
const rows = await hybridMemorySearch(topic, limit);
|
|
959
962
|
if (rows.length === 0) {
|
|
960
963
|
return { content: [{ type: "text", text: `No memories found for "${topic}" to consolidate.` }], isError: true };
|
|
961
964
|
}
|
|
@@ -989,61 +992,22 @@ async function handleMemoryTool(name, args) {
|
|
|
989
992
|
}],
|
|
990
993
|
};
|
|
991
994
|
}
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
return {
|
|
1009
|
-
content: [{
|
|
1010
|
-
type: "text",
|
|
1011
|
-
text: "Refusing to publish with a wallet address as the author name — that permanently " +
|
|
1012
|
-
"links your on-chain identity to this public entry. Use a handle, or omit authorName " +
|
|
1013
|
-
"to publish as \"Anonymous\".",
|
|
1014
|
-
}],
|
|
1015
|
-
isError: true,
|
|
1016
|
-
};
|
|
1017
|
-
}
|
|
1018
|
-
const data = await (0, convex_js_1.callConvex)("/vault/save", "POST", {
|
|
1019
|
-
type: "memory",
|
|
1020
|
-
title,
|
|
1021
|
-
content,
|
|
1022
|
-
tags: tags ?? [],
|
|
1023
|
-
isPublic: true,
|
|
1024
|
-
authorName: authorName ?? "Anonymous",
|
|
1025
|
-
commitMsg: "published to marketplace",
|
|
1026
|
-
}, "vault_save");
|
|
1027
|
-
if (data.error)
|
|
1028
|
-
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
1029
|
-
return {
|
|
1030
|
-
content: [{
|
|
1031
|
-
type: "text",
|
|
1032
|
-
text: [
|
|
1033
|
-
`🧠 **Memory Published**`,
|
|
1034
|
-
``,
|
|
1035
|
-
`**Title:** ${title}`,
|
|
1036
|
-
`**Key:** \`${data.key}\``,
|
|
1037
|
-
`**Version:** ${data.version ?? 1}`,
|
|
1038
|
-
``,
|
|
1039
|
-
`Now visible at the Memory Marketplace in the Finch app.`,
|
|
1040
|
-
``,
|
|
1041
|
-
`Make it private again: \`vault_unpublish key: "${data.key}"\``,
|
|
1042
|
-
`That stops future discovery. Anyone who already read it keeps what they saw.`,
|
|
1043
|
-
].join("\n"),
|
|
1044
|
-
}],
|
|
1045
|
-
};
|
|
1046
|
-
}
|
|
995
|
+
// memory_publish was removed - it was broken two levels deep. The tool
|
|
996
|
+
// called POST /vault/save with isPublic/authorName fields that the
|
|
997
|
+
// handler silently dropped (only type/title/content/key/contentType/
|
|
998
|
+
// agentId/tags/commitMsg/metadata are forwarded to vault.saveEntry - see
|
|
999
|
+
// app/convex/http.ts), so `published` was never actually set true; the
|
|
1000
|
+
// real publishEntry mutation (POST /vault/publish) existed but this tool
|
|
1001
|
+
// never called it. And even a correctly-wired publish would have done
|
|
1002
|
+
// nothing observable: nothing anywhere reads the `published` field for
|
|
1003
|
+
// cross-user browsing - no /vault/community route (dangling comment
|
|
1004
|
+
// only, same pattern as the other removed routes), no backend query for
|
|
1005
|
+
// it, no "Memory Marketplace" page in app/src. The tool asked users to
|
|
1006
|
+
// accept an "IRREVERSIBLE, PUBLIC" risk for a marketplace that doesn't
|
|
1007
|
+
// exist at any layer. Re-add only alongside building the actual
|
|
1008
|
+
// discovery path: a community-browse query + route + UI that reads
|
|
1009
|
+
// `published: true` entries. vault_unpublish is left in place - it's a
|
|
1010
|
+
// correct, harmless no-op until then.
|
|
1047
1011
|
default:
|
|
1048
1012
|
return null;
|
|
1049
1013
|
}
|
package/dist/tools/monitor.js
CHANGED
|
@@ -22,8 +22,8 @@ const MONITOR_INPUT_SCHEMA = {
|
|
|
22
22
|
exports.MONITOR_TOOLS = [
|
|
23
23
|
{
|
|
24
24
|
name: "schedule_research",
|
|
25
|
-
description: "Schedule recurring autonomous research on any topic - runs on a cron schedule, saves findings to vault
|
|
26
|
-
"
|
|
25
|
+
description: "Schedule recurring autonomous research on any topic - runs on a cron schedule, saves findings to vault. " +
|
|
26
|
+
"The agent runs completely on its own with no prompting needed. " +
|
|
27
27
|
"Requires TRIGGER_SECRET_KEY env var (trigger.dev). " +
|
|
28
28
|
"Examples: daily morning briefing, weekly competitor analysis, hourly price alerts, monthly industry report.",
|
|
29
29
|
inputSchema: MONITOR_INPUT_SCHEMA,
|
|
@@ -103,7 +103,7 @@ function buildMonitorList(schedules, configs = {}) {
|
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
105
|
async function handleMonitorTool(name, args) {
|
|
106
|
-
if (name === "schedule_research"
|
|
106
|
+
if (name === "schedule_research") {
|
|
107
107
|
const parsed = CreateSchema.safeParse(args);
|
|
108
108
|
if (!parsed.success)
|
|
109
109
|
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
@@ -192,7 +192,7 @@ async function handleMonitorTool(name, args) {
|
|
|
192
192
|
key: `monitor-config/${externalId}`,
|
|
193
193
|
agentId: "os",
|
|
194
194
|
tags: ["monitor-config"],
|
|
195
|
-
commitMsg: "
|
|
195
|
+
commitMsg: "schedule_research config",
|
|
196
196
|
}, "vault_save");
|
|
197
197
|
}
|
|
198
198
|
catch {
|
|
@@ -210,7 +210,7 @@ async function handleMonitorTool(name, args) {
|
|
|
210
210
|
data.nextRun ? `⏭️ Next run: ${new Date(data.nextRun).toUTCString()}` : "",
|
|
211
211
|
``,
|
|
212
212
|
configSaved
|
|
213
|
-
? `The agent will research "${topic}" on schedule
|
|
213
|
+
? `The agent will research "${topic}" on schedule and save findings to vault.`
|
|
214
214
|
: `⚠️ Monitor schedule created but config save failed - the agent may use a default topic on first run. Try \`cancel_monitor\` and recreate.`,
|
|
215
215
|
`Use \`list_monitors\` to see all active monitors.`,
|
|
216
216
|
].filter(Boolean).join("\n"),
|
|
@@ -240,7 +240,7 @@ async function handleMonitorTool(name, args) {
|
|
|
240
240
|
return {
|
|
241
241
|
content: [{
|
|
242
242
|
type: "text",
|
|
243
|
-
text: `No active monitors.\n\nUse \`
|
|
243
|
+
text: `No active monitors.\n\nUse \`schedule_research\` to set up an autonomous agent that runs on a schedule.`,
|
|
244
244
|
}],
|
|
245
245
|
structuredContent: buildMonitorList([]),
|
|
246
246
|
};
|
package/dist/tools/os.js
CHANGED
|
@@ -138,7 +138,6 @@ async function handleOsTool(name, args) {
|
|
|
138
138
|
"FIRECRAWL_API_KEY": !!process.env.FIRECRAWL_API_KEY,
|
|
139
139
|
"FINCH_SESSION_TOKEN": !!process.env.FINCH_SESSION_TOKEN,
|
|
140
140
|
"FINCH_API_KEY": !!process.env.FINCH_API_KEY,
|
|
141
|
-
"TELEGRAM_BOT_TOKEN": !!process.env.TELEGRAM_BOT_TOKEN,
|
|
142
141
|
};
|
|
143
142
|
const localMemCfg = (0, local_memory_js_1.getLocalMemoryConfig)();
|
|
144
143
|
const pingLocalMemory = async () => {
|
package/dist/tools/packets.js
CHANGED
|
@@ -245,8 +245,8 @@ async function handlePacket(toolName, args) {
|
|
|
245
245
|
}
|
|
246
246
|
if (toolName === "packet_share") {
|
|
247
247
|
const { name, authorName, confirm } = args;
|
|
248
|
-
// Same bar as
|
|
249
|
-
//
|
|
248
|
+
// Same bar as vault_delete: an action the user cannot fully take back
|
|
249
|
+
// needs an explicit second step, not a single call.
|
|
250
250
|
if (confirm !== true) {
|
|
251
251
|
return {
|
|
252
252
|
content: [{
|
package/dist/tools/rh-mcp.js
CHANGED
|
@@ -14,6 +14,7 @@ exports.dexTokenBest = dexTokenBest;
|
|
|
14
14
|
exports.resolveTokenSmart = resolveTokenSmart;
|
|
15
15
|
exports.buildRhAnalysis = buildRhAnalysis;
|
|
16
16
|
exports.buildRhStocksList = buildRhStocksList;
|
|
17
|
+
exports.rhProviderAsync = rhProviderAsync;
|
|
17
18
|
exports.rhErc20Balance = rhErc20Balance;
|
|
18
19
|
exports.rhPriceUsd = rhPriceUsd;
|
|
19
20
|
exports.buildRhSafetyStructured = buildRhSafetyStructured;
|
|
@@ -990,7 +991,16 @@ async function quoteRh(args) {
|
|
|
990
991
|
throw new Error("RH swaps route ETH ↔ token. Buy a token with ETH, or sell a token for ETH.");
|
|
991
992
|
}
|
|
992
993
|
const sellAmount = parseHumanToWei(args.amount, from.decimals);
|
|
993
|
-
const
|
|
994
|
+
const slippagePct = args.maxSlippagePct ?? 2.0;
|
|
995
|
+
// Same bound defi.ts's SwapSchema already enforces for Base swaps
|
|
996
|
+
// (.positive().max(50)) - this file had no equivalent check anywhere, so a
|
|
997
|
+
// negative or absurd value flowed straight through to the backend as
|
|
998
|
+
// slippageBps, both for direct rh_mcp_swap calls and for every unattended
|
|
999
|
+
// rh_orders_tick execution of a DCA/bracket order created with a bad value.
|
|
1000
|
+
if (!(slippagePct > 0) || slippagePct > 50) {
|
|
1001
|
+
throw new Error(`maxSlippagePct must be greater than 0 and at most 50 (got ${args.maxSlippagePct}).`);
|
|
1002
|
+
}
|
|
1003
|
+
const slippageBps = Math.round(slippagePct * 100);
|
|
994
1004
|
const result = await (0, convex_js_1.callConvex)("/mcp/rh/quote", "POST", {
|
|
995
1005
|
sellToken: from.address,
|
|
996
1006
|
buyToken: to.address,
|
|
@@ -999,7 +1009,7 @@ async function quoteRh(args) {
|
|
|
999
1009
|
slippageBps,
|
|
1000
1010
|
fromSymbol: from.symbol,
|
|
1001
1011
|
toSymbol: to.symbol,
|
|
1002
|
-
}, "rh_mcp_estimate");
|
|
1012
|
+
}, args.toolName ?? "rh_mcp_estimate");
|
|
1003
1013
|
if (result.error)
|
|
1004
1014
|
throw new Error(result.error);
|
|
1005
1015
|
return { ...result, from, to, sellAmount, slippageBps };
|
|
@@ -1269,6 +1279,7 @@ async function handleRhMcpTool(name, args) {
|
|
|
1269
1279
|
amount: String(a.amount),
|
|
1270
1280
|
maxSlippagePct: a.maxSlippagePct,
|
|
1271
1281
|
taker: wallet.address,
|
|
1282
|
+
toolName: "rh_mcp_swap",
|
|
1272
1283
|
});
|
|
1273
1284
|
const quote = q.quote ?? q;
|
|
1274
1285
|
const tx = quote.transaction ?? {
|