@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.
@@ -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 Noel-Vault. Same key = update (git-style: prior version snapshotted, patched to v+1). " +
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 Noel-Vault entry by its key. Returns full content, version, tags, and any linked entries.",
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 Noel-Vault entries. Filter by type, agent, or pinned status. Returns previews, not full content.",
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 Noel-Vault using semantic AI search (powered by Supermemory) when available, " +
62
- "with automatic fallback to full-text search. Semantic search understands meaning - " +
63
- "'low risk DeFi yield' matches 'conservative staking strategies' without exact keywords. " +
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 - natural language works best with semantic mode" },
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 Noel-Vault entry - like git log. " +
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 Noel-Vault entry - like git diff. " +
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 Noel-Vault or a specific type as a structured bundle. " +
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 Noel-Vault entry. Pinned entries always appear first in vault_list and are " +
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 Noel-Vault entry private again, removing it from the public community " +
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 Noel-Vault entry and ALL of its version history — this cannot be undone. " +
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 Noel-Vault entry without modifying its content. " +
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 Noel-Vault entries - building a knowledge graph. " +
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 Noel-Vault knowledge graph - get all entries linked to a given entry. " +
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 semantic memory (fire-and-forget). Skip when running a local
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 semantic memory` : (localVault ? `💾 Stored locally at ~/.finch/vault` : ""),
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}"\` — semantic search across all entries`,
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 = `📚 **Noel-Vault** (${entries.length} entries)`;
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
- // Try semantic search first (proxied through Convex). Large vault
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
- // If a passphrase is set, use it as the primary secret for stronger encryption.
71
- // Without it, the key is derived from public machine info only - this is
72
- // convenience encryption (prevents casual reads), not security against
73
- // an attacker who has read access to both the file and system info.
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 (err) {
105
- // A wallet file already exists but couldn't be decrypted - this almost
106
- // always means FINCH_WALLET_PASSPHRASE (or the machine info the key
107
- // is derived from) doesn't match what encrypted it. Silently creating
108
- // a fresh wallet here would overwrite the existing encrypted file,
109
- // orphaning it and any funds it controls. Refuse instead.
110
- throw new Error(`Could not decrypt existing wallet at ${WALLET_FILE}: ${err?.message ?? "unknown error"}\n\n` +
111
- `This usually means FINCH_WALLET_PASSPHRASE doesn't match the passphrase ` +
112
- `used when this wallet was encrypted (or this is a different machine). ` +
113
- `Refusing to auto-create a replacement wallet, since that would silently ` +
114
- `orphan the existing one and any funds it holds.\n\n` +
115
- `If you're sure this wallet should be abandoned, move or delete ${WALLET_FILE} manually first.`);
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.0.0",
4
- "description": "The runtime layer for Agentic AI. 121 MCP tools: persistent memory, autonomous agents, vault storage, scheduled workflows, DeFi on Base (base_mcp_*), Robinhood Chain tokenized stocks and arbitrary crypto with V3/V4 smart routing (rh_*), live market data, deep research, and Finch Terminal. Runs fully local \u2014 self-hosted vault + memory on your own disk, no account, your LLM does the thinking. Works in Claude Code, Cursor, Windsurf, and any MCP client.",
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
- "autonomous-agents",
28
+ "agentic",
30
29
  "persistent-memory",
31
- "memory-layer",
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
- "research",
41
- "automation",
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://github.com/finchagentic/mcp#readme",
42
+ "homepage": "https://docs.finch.com",
55
43
  "bugs": {
56
44
  "url": "https://github.com/finchagentic/mcp/issues"
57
45
  },