@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/vault.js
CHANGED
|
@@ -11,7 +11,7 @@ const VAULT_TYPES = ["research", "execution", "workflow", "prompt", "file", "mem
|
|
|
11
11
|
exports.VAULT_TOOLS = [
|
|
12
12
|
{
|
|
13
13
|
name: "vault_save",
|
|
14
|
-
description: "Save or update a versioned artifact in
|
|
14
|
+
description: "Save or update a versioned artifact in Finch Vault. Same key = update (git-style: prior version snapshotted, patched to v+1). " +
|
|
15
15
|
"Types: research | execution | workflow | prompt | file | memory. " +
|
|
16
16
|
"Entries up to 10MB - content over 600KB auto-offloads to blob storage. " +
|
|
17
17
|
"For quick unstructured notes, use memory_add instead.",
|
|
@@ -33,7 +33,7 @@ exports.VAULT_TOOLS = [
|
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
name: "vault_read",
|
|
36
|
-
description: "Read a
|
|
36
|
+
description: "Read a Finch Vault entry by its key. Returns full content, version, tags, and any linked entries.",
|
|
37
37
|
inputSchema: {
|
|
38
38
|
type: "object",
|
|
39
39
|
properties: {
|
|
@@ -44,7 +44,7 @@ exports.VAULT_TOOLS = [
|
|
|
44
44
|
},
|
|
45
45
|
{
|
|
46
46
|
name: "vault_list",
|
|
47
|
-
description: "List
|
|
47
|
+
description: "List Finch Vault entries. Filter by type, agent, or pinned status. Returns previews, not full content.",
|
|
48
48
|
inputSchema: {
|
|
49
49
|
type: "object",
|
|
50
50
|
properties: {
|
|
@@ -58,14 +58,14 @@ exports.VAULT_TOOLS = [
|
|
|
58
58
|
},
|
|
59
59
|
{
|
|
60
60
|
name: "vault_search",
|
|
61
|
-
description: "Search
|
|
62
|
-
"
|
|
63
|
-
"
|
|
61
|
+
description: "Search Finch Vault using full-text (keyword) search over titles, content, and tags. " +
|
|
62
|
+
"This is lexical matching, not embeddings - exact and near words in your query rank " +
|
|
63
|
+
"highest, so short specific phrases work better than long abstract descriptions. " +
|
|
64
64
|
"Optionally filter by type. Returns ranked results with previews.",
|
|
65
65
|
inputSchema: {
|
|
66
66
|
type: "object",
|
|
67
67
|
properties: {
|
|
68
|
-
query: { type: "string", description: "Search query -
|
|
68
|
+
query: { type: "string", description: "Search query - specific keywords work better than abstract phrasing (this is full-text search, not semantic)" },
|
|
69
69
|
type: { type: "string", enum: [...VAULT_TYPES], description: "Narrow search to a specific type" },
|
|
70
70
|
limit: { type: "number", description: "Max results (default 20)" },
|
|
71
71
|
},
|
|
@@ -74,7 +74,7 @@ exports.VAULT_TOOLS = [
|
|
|
74
74
|
},
|
|
75
75
|
{
|
|
76
76
|
name: "vault_history",
|
|
77
|
-
description: "Get the full version history of a
|
|
77
|
+
description: "Get the full version history of a Finch Vault entry - like git log. " +
|
|
78
78
|
"Shows each version with its commit message, author agent, size, and timestamp.",
|
|
79
79
|
inputSchema: {
|
|
80
80
|
type: "object",
|
|
@@ -86,7 +86,7 @@ exports.VAULT_TOOLS = [
|
|
|
86
86
|
},
|
|
87
87
|
{
|
|
88
88
|
name: "vault_diff",
|
|
89
|
-
description: "Compare two versions of a
|
|
89
|
+
description: "Compare two versions of a Finch Vault entry - like git diff. " +
|
|
90
90
|
"Shows lines added (+) and removed (-) between fromVersion and toVersion.",
|
|
91
91
|
inputSchema: {
|
|
92
92
|
type: "object",
|
|
@@ -100,7 +100,7 @@ exports.VAULT_TOOLS = [
|
|
|
100
100
|
},
|
|
101
101
|
{
|
|
102
102
|
name: "vault_export",
|
|
103
|
-
description: "Export your entire
|
|
103
|
+
description: "Export your entire Finch Vault or a specific type as a structured bundle. " +
|
|
104
104
|
"Useful for archiving, syncing to GitHub, or passing context to another agent.",
|
|
105
105
|
inputSchema: {
|
|
106
106
|
type: "object",
|
|
@@ -139,7 +139,7 @@ exports.VAULT_TOOLS = [
|
|
|
139
139
|
},
|
|
140
140
|
{
|
|
141
141
|
name: "vault_pin",
|
|
142
|
-
description: "Pin or unpin a
|
|
142
|
+
description: "Pin or unpin a Finch Vault entry. Pinned entries always appear first in vault_list and are " +
|
|
143
143
|
"prioritized in memory_context and search results. Use for your most important research, key prompts, or canonical references.",
|
|
144
144
|
inputSchema: {
|
|
145
145
|
type: "object",
|
|
@@ -152,7 +152,7 @@ exports.VAULT_TOOLS = [
|
|
|
152
152
|
},
|
|
153
153
|
{
|
|
154
154
|
name: "vault_unpublish",
|
|
155
|
-
description: "Make a previously shared
|
|
155
|
+
description: "Make a previously shared Finch Vault entry private again, removing it from the public community " +
|
|
156
156
|
"listing. Use this to reverse vault publishing or packet_share. Note that anyone who already " +
|
|
157
157
|
"copied the content while it was public still has it — unpublishing stops future discovery, " +
|
|
158
158
|
"it does not retract what was taken.",
|
|
@@ -166,7 +166,7 @@ exports.VAULT_TOOLS = [
|
|
|
166
166
|
},
|
|
167
167
|
{
|
|
168
168
|
name: "vault_delete",
|
|
169
|
-
description: "PERMANENT. Delete a
|
|
169
|
+
description: "PERMANENT. Delete a Finch Vault entry and ALL of its version history — this cannot be undone. " +
|
|
170
170
|
"Requires confirm: true. Use vault_list to browse first, and show the user the exact entry " +
|
|
171
171
|
"(key + title) you are about to destroy before confirming.",
|
|
172
172
|
inputSchema: {
|
|
@@ -180,7 +180,7 @@ exports.VAULT_TOOLS = [
|
|
|
180
180
|
},
|
|
181
181
|
{
|
|
182
182
|
name: "vault_tag",
|
|
183
|
-
description: "Add or replace tags on an existing
|
|
183
|
+
description: "Add or replace tags on an existing Finch Vault entry without modifying its content. " +
|
|
184
184
|
"Useful for organizing entries retroactively. Set replace=true to overwrite all existing tags.",
|
|
185
185
|
inputSchema: {
|
|
186
186
|
type: "object",
|
|
@@ -194,7 +194,7 @@ exports.VAULT_TOOLS = [
|
|
|
194
194
|
},
|
|
195
195
|
{
|
|
196
196
|
name: "vault_link",
|
|
197
|
-
description: "Create a semantic relationship between two
|
|
197
|
+
description: "Create a semantic relationship between two Finch Vault entries - building a knowledge graph. " +
|
|
198
198
|
"Relations: references | derived_from | supersedes | related | continues. " +
|
|
199
199
|
"Example: link a synthesis entry as 'derived_from' several research entries, or mark a newer analysis as 'supersedes' an older one. " +
|
|
200
200
|
"Duplicate links are updated in-place.",
|
|
@@ -214,7 +214,7 @@ exports.VAULT_TOOLS = [
|
|
|
214
214
|
},
|
|
215
215
|
{
|
|
216
216
|
name: "vault_related",
|
|
217
|
-
description: "Traverse the
|
|
217
|
+
description: "Traverse the Finch Vault knowledge graph - get all entries linked to a given entry. " +
|
|
218
218
|
"Returns both outbound links (entries this entry references) and inbound links (entries that reference this one). " +
|
|
219
219
|
"Filter by relation type to find only derived entries, superseded versions, continuations, etc.",
|
|
220
220
|
inputSchema: {
|
|
@@ -302,7 +302,7 @@ async function handleVaultTool(name, args) {
|
|
|
302
302
|
if (data.error)
|
|
303
303
|
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
304
304
|
const { key, version, changed } = data;
|
|
305
|
-
// Mirror to
|
|
305
|
+
// Mirror to searchable memory (fire-and-forget). Skip when running a local
|
|
306
306
|
// vault WITHOUT local memory - otherwise the mirror would phone home to
|
|
307
307
|
// the hosted proxy (needs auth the local-only user doesn't have, and
|
|
308
308
|
// ships their data off-machine, defeating the whole point). vault_search
|
|
@@ -335,7 +335,7 @@ async function handleVaultTool(name, args) {
|
|
|
335
335
|
`Key: \`${key}\``,
|
|
336
336
|
`Version: v${version}`,
|
|
337
337
|
changed && version > 1 ? `Previous version auto-snapshotted.` : "",
|
|
338
|
-
mirrorToMemory ? `🧠 Synced to
|
|
338
|
+
mirrorToMemory ? `🧠 Synced to searchable memory` : (localVault ? `💾 Stored locally at ~/.finch/vault` : ""),
|
|
339
339
|
...linkSummary,
|
|
340
340
|
``,
|
|
341
341
|
`Use \`vault_read\` to retrieve, \`vault_history\` to see all versions.`,
|
|
@@ -361,7 +361,7 @@ async function handleVaultTool(name, args) {
|
|
|
361
361
|
`vault_read: entry \`${vaultReadKey}\` not found.`,
|
|
362
362
|
``,
|
|
363
363
|
`Try searching for it:`,
|
|
364
|
-
`- \`vault_search query="${searchTerms}"\` —
|
|
364
|
+
`- \`vault_search query="${searchTerms}"\` — full-text search across all entries`,
|
|
365
365
|
`- \`vault_list\` — browse all entries`,
|
|
366
366
|
`- \`vault_search query="${vaultReadKey.split("/")[0]}"\` — search by type prefix`,
|
|
367
367
|
].join("\n") }], isError: true };
|
|
@@ -435,7 +435,7 @@ async function handleVaultTool(name, args) {
|
|
|
435
435
|
const entries = data.entries ?? [];
|
|
436
436
|
if (!entries.length)
|
|
437
437
|
return { content: [{ type: "text", text: `No vault entries found${parsed.data.type ? ` of type '${parsed.data.type}'` : ""}.` }] };
|
|
438
|
-
const header = `📚 **
|
|
438
|
+
const header = `📚 **Finch Vault** (${entries.length} entries)`;
|
|
439
439
|
const rows = entries.map((e) => `${e.isPinned ? "📌 " : ""}[\`${e.key}\`] ${e.title} - v${e.version} · ${e.type} · ${formatBytes(e.size)} · ${formatDate(e.updatedAt)}`);
|
|
440
440
|
return { content: [{ type: "text", text: [header, "", ...rows].join("\n") }] };
|
|
441
441
|
}
|
|
@@ -443,7 +443,9 @@ async function handleVaultTool(name, args) {
|
|
|
443
443
|
const parsed = SearchSchema.safeParse(args);
|
|
444
444
|
if (!parsed.success)
|
|
445
445
|
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
446
|
-
//
|
|
446
|
+
// Full-text search, proxied through Convex (searchSupermemory is a
|
|
447
|
+
// legacy name - it calls Finch's own /memory/search endpoint, not a
|
|
448
|
+
// third-party semantic service; there is no embedding step here). Large vault
|
|
447
449
|
// entries are indexed as multiple chunks tagged with isVaultChunk +
|
|
448
450
|
// vaultKey - group chunks back to their parent entry so the result
|
|
449
451
|
// list shows one row per entry, not one row per chunk.
|
package/dist/wallet.js
CHANGED
|
@@ -36,8 +36,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.BASE_CHAIN_ID = exports.MEV_PROTECT_ENABLED = exports.BROADCAST_RPC = exports.BASE_RPC = void 0;
|
|
37
37
|
exports.clearWalletCache = clearWalletCache;
|
|
38
38
|
exports.getMachineKey = getMachineKey;
|
|
39
|
+
exports.warnIfNoPassphrase = warnIfNoPassphrase;
|
|
39
40
|
exports.getOrCreateWallet = getOrCreateWallet;
|
|
40
41
|
exports.signRequest = signRequest;
|
|
42
|
+
exports.waitForReceipt = waitForReceipt;
|
|
41
43
|
exports.signAndBroadcast = signAndBroadcast;
|
|
42
44
|
const ethers_1 = require("ethers");
|
|
43
45
|
const fs = __importStar(require("fs"));
|
|
@@ -67,10 +69,30 @@ const WALLET_FILE = path.join(WALLET_DIR, "wallet.json");
|
|
|
67
69
|
let _cachedWallet = null;
|
|
68
70
|
function clearWalletCache() { _cachedWallet = null; }
|
|
69
71
|
function getMachineKey() {
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
//
|
|
72
|
+
// A passphrase is meant to make the wallet portable (move the encrypted
|
|
73
|
+
// file + set the same passphrase elsewhere and it still decrypts) - so when
|
|
74
|
+
// one is set, derive the key from ONLY the passphrase, no machine binding.
|
|
75
|
+
// Without a passphrase, fall back to machine info as convenience-only
|
|
76
|
+
// encryption (prevents casual reads, not security against an attacker who
|
|
77
|
+
// has both the file and the system info).
|
|
78
|
+
const passphrase = process.env.FINCH_WALLET_PASSPHRASE ?? "";
|
|
79
|
+
if (passphrase) {
|
|
80
|
+
return crypto.createHash("sha256").update(passphrase).digest("hex").slice(0, 32);
|
|
81
|
+
}
|
|
82
|
+
return crypto
|
|
83
|
+
.createHash("sha256")
|
|
84
|
+
.update(os.hostname() + os.platform() + os.arch())
|
|
85
|
+
.digest("hex")
|
|
86
|
+
.slice(0, 32);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Pre-fix key derivation: always folded in machine info even when a
|
|
90
|
+
* passphrase was set, so a wallet encrypted before this fix can only ever be
|
|
91
|
+
* decrypted on the exact machine that created it - never portable. Kept
|
|
92
|
+
* solely so those existing wallets still open; getOrCreateWallet migrates
|
|
93
|
+
* them to the portable scheme in place on first successful decrypt.
|
|
94
|
+
*/
|
|
95
|
+
function getLegacyMachineKey() {
|
|
74
96
|
const passphrase = process.env.FINCH_WALLET_PASSPHRASE ?? "";
|
|
75
97
|
return crypto
|
|
76
98
|
.createHash("sha256")
|
|
@@ -101,18 +123,43 @@ async function getOrCreateWallet() {
|
|
|
101
123
|
_cachedWallet = wallet;
|
|
102
124
|
return wallet;
|
|
103
125
|
}
|
|
104
|
-
catch (
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
// a
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
126
|
+
catch (currentErr) {
|
|
127
|
+
// Fall back to the pre-fix machine-bound scheme, for wallets encrypted
|
|
128
|
+
// before passphrases became portable. Only relevant on the ORIGINAL
|
|
129
|
+
// machine (it still needs that machine's hostname/platform/arch) - it
|
|
130
|
+
// can't rescue a wallet file copied to a new machine from before this
|
|
131
|
+
// fix; there was no passphrase-only secret saved anywhere to recover.
|
|
132
|
+
try {
|
|
133
|
+
const wallet = await ethers_1.ethers.Wallet.fromEncryptedJson(encrypted, getLegacyMachineKey());
|
|
134
|
+
_cachedWallet = wallet;
|
|
135
|
+
// Migrate in place to the portable scheme now that we've proven we
|
|
136
|
+
// hold the right key, so this only ever needs to happen once.
|
|
137
|
+
try {
|
|
138
|
+
const migrated = await wallet.encrypt(getMachineKey());
|
|
139
|
+
fs.writeFileSync(WALLET_FILE, migrated, { mode: 0o600 });
|
|
140
|
+
process.stderr.write(`\nMigrated ${WALLET_FILE} to the portable passphrase scheme.\n\n`);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
/* migration is best-effort - the legacy key still works next run either way */
|
|
144
|
+
}
|
|
145
|
+
return wallet;
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// A wallet file already exists but couldn't be decrypted under either
|
|
149
|
+
// scheme - this almost always means FINCH_WALLET_PASSPHRASE doesn't
|
|
150
|
+
// match what encrypted it, or this is a different machine and no
|
|
151
|
+
// passphrase was ever set. Silently creating a fresh wallet here
|
|
152
|
+
// would overwrite the existing encrypted file, orphaning it and any
|
|
153
|
+
// funds it controls. Refuse instead.
|
|
154
|
+
throw new Error(`Could not decrypt existing wallet at ${WALLET_FILE}: ${currentErr?.message ?? "unknown error"}\n\n` +
|
|
155
|
+
`This usually means FINCH_WALLET_PASSPHRASE doesn't match the passphrase ` +
|
|
156
|
+
`used when this wallet was encrypted, or this file was copied from a ` +
|
|
157
|
+
`different machine and no passphrase was set when it was created (in ` +
|
|
158
|
+
`which case the key was machine-bound and cannot be recovered elsewhere). ` +
|
|
159
|
+
`Refusing to auto-create a replacement wallet, since that would silently ` +
|
|
160
|
+
`orphan the existing one and any funds it holds.\n\n` +
|
|
161
|
+
`If you're sure this wallet should be abandoned, move or delete ${WALLET_FILE} manually first.`);
|
|
162
|
+
}
|
|
116
163
|
}
|
|
117
164
|
}
|
|
118
165
|
const wallet = ethers_1.ethers.Wallet.createRandom();
|
|
@@ -161,6 +208,36 @@ async function broadcastTx(signedTx) {
|
|
|
161
208
|
throw new Error(`broadcast failed: ${data.error.message}`);
|
|
162
209
|
return data.result;
|
|
163
210
|
}
|
|
211
|
+
/**
|
|
212
|
+
* Poll for the mined receipt and report the ACTUAL on-chain outcome.
|
|
213
|
+
*
|
|
214
|
+
* eth_sendRawTransaction only confirms the mempool accepted the tx, not that
|
|
215
|
+
* it succeeded - a stale quote, slippage beyond the router's own guard, or an
|
|
216
|
+
* allowance edge case can all revert on-chain while still broadcasting fine.
|
|
217
|
+
* Callers must gate their success/failure message on this, not on
|
|
218
|
+
* signAndBroadcast() returning a hash.
|
|
219
|
+
*/
|
|
220
|
+
async function waitForReceipt(txHash, timeoutMs = 60000, pollMs = 2000) {
|
|
221
|
+
const deadline = Date.now() + timeoutMs;
|
|
222
|
+
while (Date.now() < deadline) {
|
|
223
|
+
try {
|
|
224
|
+
const receipt = await rpcPost("eth_getTransactionReceipt", [txHash]);
|
|
225
|
+
if (receipt) {
|
|
226
|
+
return {
|
|
227
|
+
mined: true,
|
|
228
|
+
ok: parseInt(receipt.status, 16) === 1,
|
|
229
|
+
blockNumber: parseInt(receipt.blockNumber, 16),
|
|
230
|
+
gasUsed: receipt.gasUsed ? BigInt(receipt.gasUsed).toString() : undefined,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
/* transient RPC error - keep polling until timeout */
|
|
236
|
+
}
|
|
237
|
+
await new Promise((r) => setTimeout(r, pollMs));
|
|
238
|
+
}
|
|
239
|
+
return { mined: false };
|
|
240
|
+
}
|
|
164
241
|
async function signAndBroadcast(wallet, txData) {
|
|
165
242
|
let data = txData.data || "0x";
|
|
166
243
|
if (txData.permit2?.eip712) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@finchagentic/mcp",
|
|
3
|
-
"version": "4.
|
|
4
|
-
"description": "The runtime layer for Agentic AI.
|
|
3
|
+
"version": "4.1.0",
|
|
4
|
+
"description": "The runtime layer for Agentic AI. Persistent memory, autonomous agents, and workflows that survive every session.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"finch": "dist/cli.js",
|
|
@@ -23,35 +23,23 @@
|
|
|
23
23
|
},
|
|
24
24
|
"keywords": [
|
|
25
25
|
"mcp",
|
|
26
|
-
"mcp-server",
|
|
27
26
|
"model-context-protocol",
|
|
28
27
|
"ai-agent",
|
|
29
|
-
"
|
|
28
|
+
"agentic",
|
|
30
29
|
"persistent-memory",
|
|
31
|
-
"
|
|
30
|
+
"autonomous-agents",
|
|
31
|
+
"workflows",
|
|
32
32
|
"vault",
|
|
33
|
-
"agentic",
|
|
34
|
-
"workflow-automation",
|
|
35
|
-
"claude-code",
|
|
36
|
-
"cursor",
|
|
37
|
-
"windsurf",
|
|
38
33
|
"finch",
|
|
39
34
|
"finchagentic",
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"defi",
|
|
43
|
-
"base-chain",
|
|
44
|
-
"tool-calling",
|
|
45
|
-
"finch-terminal",
|
|
46
|
-
"ai-terminal",
|
|
47
|
-
"knowledge-graph",
|
|
48
|
-
"semantic-search"
|
|
35
|
+
"claude-code",
|
|
36
|
+
"cursor"
|
|
49
37
|
],
|
|
50
38
|
"repository": {
|
|
51
39
|
"type": "git",
|
|
52
40
|
"url": "git+https://github.com/finchagentic/mcp.git"
|
|
53
41
|
},
|
|
54
|
-
"homepage": "https://
|
|
42
|
+
"homepage": "https://docs.finch.com",
|
|
55
43
|
"bugs": {
|
|
56
44
|
"url": "https://github.com/finchagentic/mcp/issues"
|
|
57
45
|
},
|