@finchagentic/mcp 4.1.0 → 4.4.1
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 +19 -15
- package/dist/agent-loop.js +75 -72
- package/dist/annotations.js +23 -14
- package/dist/convex.js +15 -18
- package/dist/index.js +14 -12
- 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/project.js +36 -0
- package/dist/resources.js +6 -11
- package/dist/server.js +35 -13
- package/dist/token-gate.js +2 -2
- package/dist/tool-filter.js +14 -5
- package/dist/tools/agents.js +106 -394
- package/dist/tools/automation.js +42 -6
- package/dist/tools/base-mcp.js +3 -15
- package/dist/tools/base.js +34 -20
- package/dist/tools/coder.js +1 -1
- package/dist/tools/deep-research.js +1 -1
- package/dist/tools/defi.js +14 -34
- package/dist/tools/equity.js +10 -2
- package/dist/tools/events.js +1 -1
- package/dist/tools/github.js +51 -1
- package/dist/tools/insider.js +1 -1
- package/dist/tools/insight.js +4 -4
- package/dist/tools/market.js +5 -5
- package/dist/tools/memory.js +52 -88
- package/dist/tools/miroshark.js +8 -1
- package/dist/tools/monitor.js +8 -8
- package/dist/tools/os.js +9 -4
- package/dist/tools/packets.js +2 -2
- package/dist/tools/research-chain.js +1 -1
- package/dist/tools/research-compare.js +1 -1
- package/dist/tools/research.js +2 -2
- package/dist/tools/rh-bridge.js +1 -1
- package/dist/tools/rh-mcp.js +29 -4
- package/dist/tools/rh-orders.js +201 -123
- package/dist/tools/scanner.js +33 -3
- package/dist/tools/stake.js +369 -0
- package/dist/tools/vault.js +294 -40
- package/dist/wallet.js +130 -19
- package/package.json +4 -5
- package/dist/tools/framework.js +0 -150
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. " +
|
|
@@ -526,7 +519,7 @@ async function handleMemoryTool(name, args) {
|
|
|
526
519
|
case "memory_add": {
|
|
527
520
|
const parsed = AddSchema.safeParse(args);
|
|
528
521
|
if (!parsed.success)
|
|
529
|
-
return { content: [{ type: "text", text:
|
|
522
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
530
523
|
const { content, title, tags, sourceUrl, force } = parsed.data;
|
|
531
524
|
// `sourceUrl` is not stored, it is fetched and indexed — by the local
|
|
532
525
|
// memory server on the user's own machine when local mode is on. An
|
|
@@ -587,7 +580,7 @@ async function handleMemoryTool(name, args) {
|
|
|
587
580
|
case "memory_search": {
|
|
588
581
|
const parsed = SearchSchema.safeParse(args);
|
|
589
582
|
if (!parsed.success)
|
|
590
|
-
return { content: [{ type: "text", text:
|
|
583
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
591
584
|
const { query, limit = 10 } = parsed.data;
|
|
592
585
|
// Over-fetch so post-decay ranking still has enough material.
|
|
593
586
|
const overfetch = Math.min(50, Math.max(limit * 2, 20));
|
|
@@ -669,7 +662,7 @@ async function handleMemoryTool(name, args) {
|
|
|
669
662
|
case "memory_context": {
|
|
670
663
|
const parsed = ContextSchema.safeParse(args);
|
|
671
664
|
if (!parsed.success)
|
|
672
|
-
return { content: [{ type: "text", text:
|
|
665
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
673
666
|
const { topic, limit = 8 } = parsed.data;
|
|
674
667
|
// v3.25.1: use hybrid retrieval so context loading picks up exact-token
|
|
675
668
|
// matches (env var names, model IDs, contract addresses) that semantic-
|
|
@@ -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),
|
|
@@ -729,7 +722,7 @@ async function handleMemoryTool(name, args) {
|
|
|
729
722
|
case "memory_list": {
|
|
730
723
|
const parsed = ListSchema.safeParse(args ?? {});
|
|
731
724
|
if (!parsed.success)
|
|
732
|
-
return { content: [{ type: "text", text:
|
|
725
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
733
726
|
const { limit = 20, tag } = parsed.data;
|
|
734
727
|
const localList = (0, local_memory_js_1.getLocalMemoryConfig)();
|
|
735
728
|
let results;
|
|
@@ -760,7 +753,7 @@ async function handleMemoryTool(name, args) {
|
|
|
760
753
|
case "memory_delete": {
|
|
761
754
|
const parsed = DeleteMemSchema.safeParse(args);
|
|
762
755
|
if (!parsed.success)
|
|
763
|
-
return { content: [{ type: "text", text:
|
|
756
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
764
757
|
if (args?.confirm !== true) {
|
|
765
758
|
return {
|
|
766
759
|
content: [{
|
|
@@ -785,20 +778,26 @@ 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": {
|
|
791
785
|
const parsed = InsightSchema.safeParse(args);
|
|
792
786
|
if (!parsed.success)
|
|
793
|
-
return { content: [{ type: "text", text:
|
|
787
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
794
788
|
const { topic, depth = "standard" } = parsed.data;
|
|
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;
|
|
@@ -873,7 +872,7 @@ async function handleMemoryTool(name, args) {
|
|
|
873
872
|
case "memory_extract": {
|
|
874
873
|
const parsed = ExtractSchema.safeParse(args);
|
|
875
874
|
if (!parsed.success)
|
|
876
|
-
return { content: [{ type: "text", text:
|
|
875
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
877
876
|
const { text, facts, source = "extract" } = parsed.data;
|
|
878
877
|
// ── PASS 1: hand the text back with the extraction rubric ───────────
|
|
879
878
|
// Deciding what counts as a fact is judgement about *this user's*
|
|
@@ -930,7 +929,7 @@ async function handleMemoryTool(name, args) {
|
|
|
930
929
|
case "memory_consolidate": {
|
|
931
930
|
const parsed = ConsolidateSchema.safeParse(args);
|
|
932
931
|
if (!parsed.success)
|
|
933
|
-
return { content: [{ type: "text", text:
|
|
932
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
934
933
|
const { topic, limit = 12, summary } = parsed.data;
|
|
935
934
|
const localConsolidateCfg = (0, local_memory_js_1.getLocalMemoryConfig)();
|
|
936
935
|
// ── PASS 2: save the caller's merged version ────────────────────────
|
|
@@ -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/miroshark.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MIROSHARK_TOOLS = void 0;
|
|
4
4
|
exports.handleMirosharkTool = handleMirosharkTool;
|
|
5
|
+
const config_js_1 = require("../config.js");
|
|
5
6
|
const CONVEX_SITE = process.env.FINCH_CONVEX_URL ?? "https://befitting-porcupine-276.convex.site";
|
|
6
7
|
exports.MIROSHARK_TOOLS = [
|
|
7
8
|
{
|
|
@@ -50,7 +51,13 @@ exports.MIROSHARK_TOOLS = [
|
|
|
50
51
|
];
|
|
51
52
|
// ── HTTP helpers ──────────────────────────────────────────────────────────────
|
|
52
53
|
function authHeaders() {
|
|
53
|
-
|
|
54
|
+
// Bug fix: this used to read only process.env.FINCH_API_KEY/
|
|
55
|
+
// FINCH_SESSION_TOKEN directly, bypassing getSavedToken()'s env-var-then-
|
|
56
|
+
// saved-config fallback that every other tool goes through (via
|
|
57
|
+
// callConvex/callConvexRaw in convex.ts). A user authenticated via
|
|
58
|
+
// `finch login` (writes to ~/.finch/config.json, not an env var) got a
|
|
59
|
+
// silent, unauthenticated request here while every other tool worked.
|
|
60
|
+
const key = process.env.FINCH_API_KEY ?? (0, config_js_1.getSavedToken)();
|
|
54
61
|
return key ? { Authorization: `Bearer ${key}` } : {};
|
|
55
62
|
}
|
|
56
63
|
async function miroJson(path, method, body, timeoutMs = 90000) {
|
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,10 +103,10 @@ 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
|
-
return { content: [{ type: "text", text:
|
|
109
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
110
110
|
const key = getKey();
|
|
111
111
|
if (!key)
|
|
112
112
|
return noKeyMsg();
|
|
@@ -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
|
};
|
|
@@ -284,7 +284,7 @@ async function handleMonitorTool(name, args) {
|
|
|
284
284
|
if (name === "cancel_monitor") {
|
|
285
285
|
const parsed = CancelSchema.safeParse(args);
|
|
286
286
|
if (!parsed.success)
|
|
287
|
-
return { content: [{ type: "text", text:
|
|
287
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
288
288
|
if (args?.confirm !== true) {
|
|
289
289
|
return {
|
|
290
290
|
content: [{
|
package/dist/tools/os.js
CHANGED
|
@@ -6,6 +6,7 @@ const convex_js_1 = require("../convex.js");
|
|
|
6
6
|
const token_gate_js_1 = require("../token-gate.js");
|
|
7
7
|
const wallet_js_1 = require("../wallet.js");
|
|
8
8
|
const local_memory_js_1 = require("../local-memory.js");
|
|
9
|
+
const config_js_1 = require("../config.js");
|
|
9
10
|
const CONVEX_SITE = process.env.FINCH_CONVEX_URL ?? "https://befitting-porcupine-276.convex.site";
|
|
10
11
|
exports.OS_TOOLS = [
|
|
11
12
|
{
|
|
@@ -138,7 +139,6 @@ async function handleOsTool(name, args) {
|
|
|
138
139
|
"FIRECRAWL_API_KEY": !!process.env.FIRECRAWL_API_KEY,
|
|
139
140
|
"FINCH_SESSION_TOKEN": !!process.env.FINCH_SESSION_TOKEN,
|
|
140
141
|
"FINCH_API_KEY": !!process.env.FINCH_API_KEY,
|
|
141
|
-
"TELEGRAM_BOT_TOKEN": !!process.env.TELEGRAM_BOT_TOKEN,
|
|
142
142
|
};
|
|
143
143
|
const localMemCfg = (0, local_memory_js_1.getLocalMemoryConfig)();
|
|
144
144
|
const pingLocalMemory = async () => {
|
|
@@ -200,13 +200,18 @@ async function handleOsTool(name, args) {
|
|
|
200
200
|
if (!message)
|
|
201
201
|
return { content: [{ type: "text", text: "Error: message is required" }] };
|
|
202
202
|
try {
|
|
203
|
-
|
|
203
|
+
// Bug fix: this used to read only process.env.FINCH_SESSION_TOKEN/
|
|
204
|
+
// FINCH_API_KEY directly, bypassing getSavedToken()'s fallback to
|
|
205
|
+
// ~/.finch/config.json - a user authenticated via `finch login`
|
|
206
|
+
// (not an env var) silently sent an empty Authorization header here
|
|
207
|
+
// while every other tool (which goes through callConvex) worked.
|
|
208
|
+
const res = await fetch(`${CONVEX_SITE}/finch/shell/chat`, {
|
|
204
209
|
method: "POST",
|
|
205
210
|
headers: {
|
|
206
211
|
"Content-Type": "application/json",
|
|
207
|
-
"Authorization": `Bearer ${
|
|
212
|
+
"Authorization": `Bearer ${(0, config_js_1.getSavedToken)() ?? process.env.FINCH_API_KEY ?? ""}`,
|
|
208
213
|
},
|
|
209
|
-
body: JSON.stringify({ message, agentId: agent_id ?? "
|
|
214
|
+
body: JSON.stringify({ message, agentId: agent_id ?? "finch-default" }),
|
|
210
215
|
signal: AbortSignal.timeout(60000),
|
|
211
216
|
});
|
|
212
217
|
if (!res.ok) {
|
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: [{
|
|
@@ -147,7 +147,7 @@ async function handleResearchChain(name, args) {
|
|
|
147
147
|
return null;
|
|
148
148
|
const parsed = InputSchema.safeParse(args);
|
|
149
149
|
if (!parsed.success) {
|
|
150
|
-
return { content: [{ type: "text", text:
|
|
150
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
151
151
|
}
|
|
152
152
|
const { startKey } = parsed.data;
|
|
153
153
|
const maxDepth = parsed.data.maxDepth ?? 8;
|
|
@@ -154,7 +154,7 @@ async function handleResearchCompare(name, args) {
|
|
|
154
154
|
return null;
|
|
155
155
|
const parsed = InputSchema.safeParse(args);
|
|
156
156
|
if (!parsed.success) {
|
|
157
|
-
return { content: [{ type: "text", text:
|
|
157
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
158
158
|
}
|
|
159
159
|
const { keyA, keyB, focus, comparison } = parsed.data;
|
|
160
160
|
const saveToVault = parsed.data.saveToVault ?? true;
|
package/dist/tools/research.js
CHANGED
|
@@ -100,7 +100,7 @@ async function handleResearchTool(name, args) {
|
|
|
100
100
|
if (name === "web_scrape") {
|
|
101
101
|
const parsed = ScrapeSchema.safeParse(args);
|
|
102
102
|
if (!parsed.success)
|
|
103
|
-
return { content: [{ type: "text", text:
|
|
103
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
104
104
|
const { url, focus } = parsed.data;
|
|
105
105
|
const unsafe = await (0, public_url_js_1.assertPublicUrl)(url);
|
|
106
106
|
if (unsafe) {
|
|
@@ -120,7 +120,7 @@ async function handleResearchTool(name, args) {
|
|
|
120
120
|
if (name === "web_search") {
|
|
121
121
|
const parsed = SearchSchema.safeParse(args);
|
|
122
122
|
if (!parsed.success)
|
|
123
|
-
return { content: [{ type: "text", text:
|
|
123
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
124
124
|
const { query, limit = 5 } = parsed.data;
|
|
125
125
|
// Priority 1: user BYOK direct path. Priority 2: Finch proxy.
|
|
126
126
|
// Same data shape so the formatting code below is identical for both.
|
package/dist/tools/rh-bridge.js
CHANGED
|
@@ -94,7 +94,7 @@ async function handleBridgeTool(name, args) {
|
|
|
94
94
|
return null;
|
|
95
95
|
const parsed = Schema.safeParse(args);
|
|
96
96
|
if (!parsed.success) {
|
|
97
|
-
return { content: [{ type: "text", text:
|
|
97
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
98
98
|
}
|
|
99
99
|
const wanted = parsed.data.ticker?.trim().toUpperCase();
|
|
100
100
|
const minGap = parsed.data.minGapPct ?? 0;
|
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;
|
|
@@ -29,6 +30,20 @@ exports.RH_RPC = process.env.ROBINHOOD_RPC_URL ??
|
|
|
29
30
|
process.env.RH_RPC_URL ??
|
|
30
31
|
process.env.FINCH_RH_RPC_URL ??
|
|
31
32
|
"https://rpc.mainnet.chain.robinhood.com";
|
|
33
|
+
/** Host only, for display in tool output - never the full RH_RPC. An
|
|
34
|
+
* operator can point ROBINHOOD_RPC_URL/RH_RPC_URL at a keyed provider
|
|
35
|
+
* (same pattern app/convex just fixed for its own Alchemy-key leak risk -
|
|
36
|
+
* a `.../v2/<key>`-style URL echoed verbatim into a tool response lands the
|
|
37
|
+
* key in chat transcripts/client logs). Falls back to the raw value only
|
|
38
|
+
* if it isn't a parseable URL, which should never happen in practice. */
|
|
39
|
+
function rhRpcDisplay() {
|
|
40
|
+
try {
|
|
41
|
+
return new URL(exports.RH_RPC).host;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return "(configured)";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
32
47
|
exports.RH_EXPLORER = "https://robinhoodchain.blockscout.com";
|
|
33
48
|
const NATIVE_ETH = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
|
|
34
49
|
const RH_PERMIT2 = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
@@ -753,7 +768,7 @@ async function fetchRhBalances(address) {
|
|
|
753
768
|
else
|
|
754
769
|
for (const h of holdings)
|
|
755
770
|
lines.push(`- ${h.symbol}: ${h.bal} \`${h.address}\``);
|
|
756
|
-
lines.push(``, `_RPC: ${
|
|
771
|
+
lines.push(``, `_RPC: ${rhRpcDisplay()}_`, `_Explorer: ${exports.RH_EXPLORER}/address/${address}_`);
|
|
757
772
|
return lines.join("\n");
|
|
758
773
|
}
|
|
759
774
|
// ─── Onchain safety scan (Blockscout + DexScreener; free, no LLM) ─────────────
|
|
@@ -990,7 +1005,16 @@ async function quoteRh(args) {
|
|
|
990
1005
|
throw new Error("RH swaps route ETH ↔ token. Buy a token with ETH, or sell a token for ETH.");
|
|
991
1006
|
}
|
|
992
1007
|
const sellAmount = parseHumanToWei(args.amount, from.decimals);
|
|
993
|
-
const
|
|
1008
|
+
const slippagePct = args.maxSlippagePct ?? 2.0;
|
|
1009
|
+
// Same bound defi.ts's SwapSchema already enforces for Base swaps
|
|
1010
|
+
// (.positive().max(50)) - this file had no equivalent check anywhere, so a
|
|
1011
|
+
// negative or absurd value flowed straight through to the backend as
|
|
1012
|
+
// slippageBps, both for direct rh_mcp_swap calls and for every unattended
|
|
1013
|
+
// rh_orders_tick execution of a DCA/bracket order created with a bad value.
|
|
1014
|
+
if (!(slippagePct > 0) || slippagePct > 50) {
|
|
1015
|
+
throw new Error(`maxSlippagePct must be greater than 0 and at most 50 (got ${args.maxSlippagePct}).`);
|
|
1016
|
+
}
|
|
1017
|
+
const slippageBps = Math.round(slippagePct * 100);
|
|
994
1018
|
const result = await (0, convex_js_1.callConvex)("/mcp/rh/quote", "POST", {
|
|
995
1019
|
sellToken: from.address,
|
|
996
1020
|
buyToken: to.address,
|
|
@@ -999,7 +1023,7 @@ async function quoteRh(args) {
|
|
|
999
1023
|
slippageBps,
|
|
1000
1024
|
fromSymbol: from.symbol,
|
|
1001
1025
|
toSymbol: to.symbol,
|
|
1002
|
-
}, "rh_mcp_estimate");
|
|
1026
|
+
}, args.toolName ?? "rh_mcp_estimate");
|
|
1003
1027
|
if (result.error)
|
|
1004
1028
|
throw new Error(result.error);
|
|
1005
1029
|
return { ...result, from, to, sellAmount, slippageBps };
|
|
@@ -1167,7 +1191,7 @@ async function handleRhMcpTool(name, args) {
|
|
|
1167
1191
|
"",
|
|
1168
1192
|
`**Wallet**: \`${addr ?? "(not configured — run finch login / first tool)"}\``,
|
|
1169
1193
|
`**ETH on RH (gas)**: ${eth}`,
|
|
1170
|
-
`**RPC**: ${
|
|
1194
|
+
`**RPC**: ${rhRpcDisplay()}`,
|
|
1171
1195
|
`**Explorer**: ${exports.RH_EXPLORER}`,
|
|
1172
1196
|
`**Catalog**: ${exports.RH_STOCKS.length} stocks (AAPL…USAR) + any RH-chain crypto via DexScreener`,
|
|
1173
1197
|
"",
|
|
@@ -1269,6 +1293,7 @@ async function handleRhMcpTool(name, args) {
|
|
|
1269
1293
|
amount: String(a.amount),
|
|
1270
1294
|
maxSlippagePct: a.maxSlippagePct,
|
|
1271
1295
|
taker: wallet.address,
|
|
1296
|
+
toolName: "rh_mcp_swap",
|
|
1272
1297
|
});
|
|
1273
1298
|
const quote = q.quote ?? q;
|
|
1274
1299
|
const tx = quote.transaction ?? {
|