@finchagentic/mcp 4.2.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 CHANGED
@@ -29,13 +29,13 @@ Always pin the version. Never use `@latest`.
29
29
 
30
30
  ```bash
31
31
  # One-command installer (detects common MCP clients)
32
- npx -y -p @finchagentic/mcp@4.2.0 finch install
32
+ npx -y -p @finchagentic/mcp@4.4.1 finch install
33
33
  ```
34
34
 
35
35
  ### Claude Code
36
36
 
37
37
  ```bash
38
- claude mcp add finch -s user -- npx -y -p @finchagentic/mcp@4.2.0 finch-mcp
38
+ claude mcp add finch -s user -- npx -y -p @finchagentic/mcp@4.4.1 finch-mcp
39
39
  ```
40
40
 
41
41
  ### Cursor / Windsurf / Claude Desktop
@@ -45,7 +45,7 @@ claude mcp add finch -s user -- npx -y -p @finchagentic/mcp@4.2.0 finch-mcp
45
45
  "mcpServers": {
46
46
  "finch": {
47
47
  "command": "npx",
48
- "args": ["-y", "-p", "@finchagentic/mcp@4.2.0", "finch-mcp"]
48
+ "args": ["-y", "-p", "@finchagentic/mcp@4.4.1", "finch-mcp"]
49
49
  }
50
50
  }
51
51
  }
@@ -59,7 +59,7 @@ claude mcp add finch -s user -- npx -y -p @finchagentic/mcp@4.2.0 finch-mcp
59
59
  "finch": {
60
60
  "type": "stdio",
61
61
  "command": "npx",
62
- "args": ["-y", "-p", "@finchagentic/mcp@4.2.0", "finch-mcp"]
62
+ "args": ["-y", "-p", "@finchagentic/mcp@4.4.1", "finch-mcp"]
63
63
  }
64
64
  }
65
65
  }
@@ -79,7 +79,7 @@ claude mcp add finch -s user -- npx -y -p @finchagentic/mcp@4.2.0 finch-mcp
79
79
 
80
80
  </details>
81
81
 
82
- No LLM API key is required to start — 108 of 113 tools are plain reads/writes/on-chain calls that your MCP client's own model already drives; only 5 (`ask_finch`, `deep_research`, and scheduled agent learning) do their own multi-step reasoning server-side and need a key (see [Configuration](#configuration)). Tools load on first use.
82
+ No LLM API key is required to start — 111 of 116 tools are plain reads/writes/on-chain calls that your MCP client's own model already drives; only 5 (`ask_finch`, `deep_research`, and scheduled agent learning) do their own multi-step reasoning server-side and need a key (see [Configuration](#configuration)). Tools load on first use.
83
83
 
84
84
  ## Quick start
85
85
 
@@ -99,15 +99,19 @@ save this thesis to vault
99
99
 
100
100
  ## What you get
101
101
 
102
- **113 tools** across four pillars:
102
+ **116 tools** across four pillars:
103
103
 
104
104
  | Pillar | What it does |
105
105
  |--------|----------------|
106
106
  | **Memory** | Full-text searchable memory + versioned vault + chronicle |
107
- | **Agents** | Spawn, recall, update named agents |
108
- | **Workflows** | Automations, monitors, packets, deep research |
107
+ | **Agents** | Spawn, recall, update named agents — `agent_recall` also pulls related memory/vault context matching the agent's goal, not just its own logged updates |
108
+ | **Workflows** | Automations, monitors, packets, deep research (auto-saves reports + auto-links related past research) |
109
109
  | **Execution** | Base DeFi, Robinhood Chain, market data, web, GitHub |
110
110
 
111
+ Coding and research sessions persist the same way: `deep_research` auto-saves its report to vault and links it to related past reports; `code_session_save` does the same for coding/debugging sessions (`vault_save type=code`, versioned per project, auto-linked). Both exist so the next session — yours or another agent's — starts with real context instead of cold.
112
+
113
+ `vault_save` and `agent_spawn` also take an optional `workspaceProject` - the same named Projects a user organizes their Agents/vault content into on the webapp's Agents page. Pass a name and it's matched case-insensitively or created automatically (`list_projects` to browse what exists first). Hosted vault only - local-vault mode has no project concept.
114
+
111
115
  Default palette is `core` (lighter context). Full set:
112
116
 
113
117
  ```json
@@ -119,7 +123,7 @@ Default palette is `core` (lighter context). Full set:
119
123
  Finch is the runtime. **Your LLM is the brain. Your data stays yours.**
120
124
 
121
125
  ```bash
122
- npx -y -p @finchagentic/mcp@4.2.0 finch setup
126
+ npx -y -p @finchagentic/mcp@4.4.1 finch setup
123
127
  # enable local vault (and optional local memory)
124
128
  ```
125
129
 
@@ -146,12 +150,12 @@ Scheduled/cloud features still need an account. Core memory, vault, and public-d
146
150
  | `GITHUB_TOKEN` | For `github_search_code` |
147
151
  | `ALCHEMY_API_KEY` | Faster Base RPC (optional) |
148
152
 
149
- **Cost model:** almost everything is free to run — the other 108 tools are plain API/RPC calls, and your MCP client's own model (Claude, GPT, whatever's driving the chat) does all the tool-selection reasoning at no cost to Finch. The 5 exceptions above need their own key because their reasoning happens *inside* the tool call, invisible to your client, and can't be delegated to it. Set exactly one of the four env vars and every tool that needs it will use it automatically.
153
+ **Cost model:** almost everything is free to run — the other 110 tools are plain API/RPC calls, and your MCP client's own model (Claude, GPT, whatever's driving the chat) does all the tool-selection reasoning at no cost to Finch. The 5 exceptions above need their own key because their reasoning happens *inside* the tool call, invisible to your client, and can't be delegated to it. Set exactly one of the four env vars and every tool that needs it will use it automatically.
150
154
 
151
155
  Guided setup:
152
156
 
153
157
  ```bash
154
- npx -y -p @finchagentic/mcp@4.2.0 finch setup
158
+ npx -y -p @finchagentic/mcp@4.4.1 finch setup
155
159
  ```
156
160
 
157
161
  ## Security
@@ -160,7 +164,7 @@ npx -y -p @finchagentic/mcp@4.2.0 finch setup
160
164
  |:-:|----------|------|
161
165
  | 1 | Prompt injection | External content is data only — never instructions |
162
166
  | 2 | Mainnet confirm | Estimate → preview → confirm → execute |
163
- | 3 | Pinned install | Always `@finchagentic/mcp@4.2.0`, never `@latest` |
167
+ | 3 | Pinned install | Always `@finchagentic/mcp@4.4.1`, never `@latest` |
164
168
  | 4 | Credential vault | Never paste secrets into prompts or third-party tools |
165
169
  | 5 | Data disclosure | Know what leaves the machine (LLM, Firecrawl, GitHub, chain RPCs) |
166
170
  | 6 | Server monitors | Scheduled jobs need explicit confirmation |
@@ -26,7 +26,7 @@ const WRITE_IDEMPOTENT = new Set([
26
26
  const WRITE = new Set([
27
27
  "agent_spawn",
28
28
  "chronicle_add",
29
- "create_automation",
29
+ "code_session_save",
30
30
  "memory_add",
31
31
  "memory_extract",
32
32
  "memory_consolidate",
@@ -65,6 +65,13 @@ const DESTRUCTIVE = new Set([
65
65
  "rh_mcp_swap",
66
66
  "rh_dca_create", // arms recurring real buys
67
67
  "rh_bracket_create", // arms real TP/SL sells
68
+ // a swap/send automation arms unattended, REPEATING real fund movement
69
+ // (the backend's 1-minute cron evaluator fires it) - same risk class as
70
+ // rh_dca_create/rh_bracket_create above, not a plain additive write. An
71
+ // alert-only automation doesn't move funds, but the tool can't tell which
72
+ // kind it's about to create until AFTER the backend parses rawInput, so
73
+ // it's classified by its worst case, same reasoning as rh_orders_tick.
74
+ "create_automation",
68
75
  "rh_orders_tick", // preview by default, but can execute:true and move funds
69
76
  // executors that run other (possibly fund-moving) tools
70
77
  "run_automation",
@@ -73,6 +80,7 @@ const DESTRUCTIVE = new Set([
73
80
  // for a fixed period; unstake moves it (plus rewards) back
74
81
  "stake_finch",
75
82
  "unstake_finch",
83
+ "claim_vested_rewards", // treasury -> custodial wallet USDG transfer
76
84
  ]);
77
85
  function annotationsFor(name) {
78
86
  if (DESTRUCTIVE.has(name)) {
package/dist/index.js CHANGED
@@ -66,14 +66,17 @@ const C = {
66
66
  reset: "\x1b[0m",
67
67
  bold: "\x1b[1m",
68
68
  };
69
+ // Was still spelling out the pre-rebrand "NOELCLAW" wordmark in ASCII art -
70
+ // leftover from before the Finch rebrand, never caught because nothing
71
+ // visually diffs a banner string.
69
72
  const BANNER = `
70
73
  ${C.cyan}
71
- ███╗ ██╗ ██████╗ ███████╗██╗ ██████╗██╗ █████╗ ██╗ ██╗
72
- ████╗ ██║██╔═══██╗██╔════╝██║ ██╔════╝██║ ██╔══██╗██║ ██║
73
- ██╔██╗ ██║██║ ██║█████╗ ██║ ██║ ██║ ███████║██║ █╗ ██║
74
- ██║╚██╗██║██║ ██║██╔══╝ ██║ ██║ ██║ ██╔══██║██║███╗██║
75
- ██║ ╚████║╚██████╔╝███████╗███████╗╚██████╗███████╗██║ ██║╚███╔███╔╝
76
- ╚═╝ ╚═══╝ ╚═════╝ ╚══════╝╚══════╝ ╚═════╝╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝
74
+ ███████╗██╗███╗ ██╗ ██████╗██╗ ██╗
75
+ ██╔════╝██║████╗ ██║██╔════╝██║ ██║
76
+ █████╗ ██║██╔██╗ ██║██║ ███████║
77
+ ██╔══╝ ██║██║╚██╗██║██║ ██╔══██║
78
+ ██║ ██║██║ ╚████║╚██████╗██║ ██║
79
+ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝╚═╝ ╚═╝
77
80
  ${C.reset}`;
78
81
  function line(label, value, color = C.cyan) {
79
82
  const pad = " ".repeat(Math.max(0, 12 - label.length));
@@ -114,9 +117,9 @@ async function main() {
114
117
  { label: "Base MCP", match: n => n.startsWith("base_mcp_") },
115
118
  { label: "RH MCP", match: n => n.startsWith("rh_") },
116
119
  { label: "Automation", match: n => /^(create_automation|list_automations|pause_automation|delete_automation|get_automation_runs|run_automation)$/.test(n) },
117
- { label: "Vault", match: n => n.startsWith("vault_") },
120
+ { label: "Vault", match: n => n.startsWith("vault_") || n === "code_session_save" || n === "list_projects" },
118
121
  { label: "Wallet", match: n => /^(get_wallet_address|get_wallet_balance|wallet_sign_message)$/.test(n) },
119
- { label: "Staking", match: n => /^(stake_finch|unstake_finch|stake_finch_status|stake_auto_restake)$/.test(n) },
122
+ { label: "Staking", match: n => /^(stake_finch|unstake_finch|stake_finch_status|stake_auto_restake|claim_vested_rewards)$/.test(n) },
120
123
  { label: "MiroShark", match: n => n.startsWith("miroshark_") },
121
124
  { label: "Scanner", match: n => /^(scan_market|score_token|check_token)$/.test(n) },
122
125
  { label: "Agents", match: n => n.startsWith("agent_") },
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isLocalVaultActive = isLocalVaultActive;
4
+ exports.resolveProjectId = resolveProjectId;
5
+ const convex_js_1 = require("./convex.js");
6
+ const local_vault_js_1 = require("./local-vault.js");
7
+ // MCP has no persistent "active project" UI state the way the webapp Sidebar
8
+ // switcher does - a caller (vault_save, agent_spawn) names the project it
9
+ // wants by string on each call instead. Resolution + auto-create both happen
10
+ // server-side (POST /projects/resolve, see convex/http.ts +
11
+ // convex/projects.ts's resolveOrCreateProjectForUser) so the match/dedup
12
+ // logic exists in exactly one place, shared with the webapp's own project
13
+ // picker.
14
+ //
15
+ // Local-vault mode (vaultBackend: "local") has no project concept at all -
16
+ // it's a flat, unauthenticated file store on the user's own machine with no
17
+ // server to resolve a name against. Callers should check
18
+ // isLocalVaultActive() first and skip resolution entirely rather than call
19
+ // this and get a confusing network-style failure.
20
+ function isLocalVaultActive() {
21
+ return !!(0, local_vault_js_1.getLocalVaultConfig)();
22
+ }
23
+ async function resolveProjectId(name) {
24
+ const trimmed = name.trim();
25
+ if (!trimmed)
26
+ return null;
27
+ try {
28
+ return await (0, convex_js_1.callConvex)("/projects/resolve", "POST", { name: trimmed }, "vault_save");
29
+ }
30
+ catch {
31
+ // Resolution is best-effort - a save should never fail just because the
32
+ // project lookup did. Falls back to unassigned, same as the webapp's
33
+ // agent_spawn membership check when a stale/bad id doesn't match.
34
+ return null;
35
+ }
36
+ }
package/dist/server.js CHANGED
@@ -115,7 +115,7 @@ exports.ALL_TOOLS = [
115
115
  // SWARM_TOOLS removed v3.19 - multi-agent research is now built into
116
116
  // deep_research (depth=standard|deep). Handler fully removed v3.21.
117
117
  // FRAMEWORK_TOOLS removed - list_playbooks/run_playbook/get_finch_ledger backend routes never existed, see tools/ import comment above.
118
- ...vault_js_1.VAULT_TOOLS, // 15 - save, read, list, search, history, diff, export, pin, unpublish, tag, delete, link, related, store_credential, get_credential
118
+ ...vault_js_1.VAULT_TOOLS, // 17 - save, code_session_save, read, list, search, history, diff, export, pin, unpublish, tag, delete, link, related, store_credential, get_credential, list_projects
119
119
  ...wallet_js_1.WALLET_TOOLS, // 3 - get_wallet_address, get_wallet_balance, wallet_sign_message
120
120
  ...miroshark_js_1.MIROSHARK_TOOLS, // 3 - simulate, status, stop
121
121
  ...agents_js_1.AGENT_TOOLS, // 4 - agent_spawn, agent_recall, agent_update, agent_ledger (vault-backed, all working). list_agents/hire_agent and the autonomous-schedule tools (agent_identity/agent_schedule/agent_unschedule/agent_pause/agent_resume/agent_runs) removed - their backend routes were never implemented, see tools/agents.ts
@@ -139,14 +139,22 @@ exports.ALL_TOOLS = [
139
139
  ...github_js_1.GITHUB_TOOLS, // 8 - list_repos, list_prs, get_pr, list_issues, get_issue, get_file, get_commits, search_code
140
140
  ...chronicle_js_1.CHRONICLE_TOOLS, // 4 - chronicle_add, chronicle_list, chronicle_search, chronicle_stats
141
141
  ...packets_js_1.PACKET_TOOLS, // 4 - packet_create, packet_run, packet_list, packet_share
142
- ...stake_js_1.STAKE_TOOLS, // 4 - stake_finch_status, stake_finch, unstake_finch, stake_auto_restake (custodial wallet; requires `finch login`)
143
- // total: 113 tools as measured by ALL_TOOLS.length - do not hand-maintain a
142
+ ...stake_js_1.STAKE_TOOLS, // 5 - stake_finch_status, stake_finch, unstake_finch, claim_vested_rewards, stake_auto_restake (custodial wallet; requires `finch login`)
143
+ // total: 116 tools as measured by ALL_TOOLS.length - do not hand-maintain a
144
144
  // count in this comment (drifted stale multiple times already: before
145
145
  // staking was added, after the P1 audit removed 9 dead framework/agent-
146
146
  // schedule tools, after memory_publish was removed for promising a
147
- // marketplace that doesn't exist, and after stake_auto_restake was added;
148
- // ALL_TOOLS.length is the only number that can't lie). Per-category counts
149
- // above are best-effort documentation, not load-bearing anywhere.
147
+ // marketplace that doesn't exist, after stake_auto_restake was added,
148
+ // after claim_vested_rewards was added to close the gap where the
149
+ // stake-lifecycle notification told users to "run claimVestedRewards" but
150
+ // no MCP tool by that name existed, after code_session_save was added so
151
+ // coding sessions persist the same way deep_research already auto-saves
152
+ // research, and after list_projects was added alongside `workspaceProject`
153
+ // support on vault_save/agent_spawn - MCP tools can now file into the same
154
+ // Projects the webapp Agents page organizes by, resolved/auto-created
155
+ // server-side via POST /projects/resolve; ALL_TOOLS.length is the only
156
+ // number that can't lie). Per-category counts above are best-effort
157
+ // documentation, not load-bearing anywhere.
150
158
  ];
151
159
  exports.HANDLER_MAP = new Map([
152
160
  ...market_js_1.MARKET_TOOLS.map(t => [t.name, market_js_1.handleMarketTool]),
@@ -27,11 +27,11 @@ exports.filterTools = filterTools;
27
27
  // through that same preset). New tools MUST match a preset or the test fails
28
28
  // the build, instead of quietly vanishing like these did.
29
29
  const PRESETS = {
30
- core: /^(memory_|vault_|agent_|ask_finch|finch_status|finch_diagnostics|finch_shell_chat|get_wallet_address|get_wallet_balance|wallet_sign_message|chronicle_|packet_)/,
31
- defi: /^(get_market_data|get_token_data|compare_tokens|market_overview|token_history|get_base_token_data|stock_fundamentals|stock_insider|stock_events|market_thesis|trade_plan|base_mcp_|rh_|base_|get_defi_yields|score_token|check_token|scan_market|get_wallet_balance|wallet_sign_message|create_automation|list_automations|pause_automation|delete_automation|get_automation_runs|run_automation|miroshark_|stake_|unstake_finch)/,
32
- research: /^(memory_|vault_|deep_research|research_compare|research_chain|web_search|web_scrape|schedule_research|list_monitors|cancel_monitor|ask_finch|stock_fundamentals|stock_insider|stock_events)/,
33
- memory: /^(memory_|vault_|agent_|chronicle_)/,
34
- coder: /^(audit_contract|github_)/,
30
+ core: /^(memory_|vault_|code_session_save|list_projects|agent_|ask_finch|finch_status|finch_diagnostics|finch_shell_chat|get_wallet_address|get_wallet_balance|wallet_sign_message|chronicle_|packet_)/,
31
+ defi: /^(get_market_data|get_token_data|compare_tokens|market_overview|token_history|get_base_token_data|stock_fundamentals|stock_insider|stock_events|market_thesis|trade_plan|base_mcp_|rh_|base_|get_defi_yields|score_token|check_token|scan_market|get_wallet_balance|wallet_sign_message|create_automation|list_automations|pause_automation|delete_automation|get_automation_runs|run_automation|miroshark_|stake_|unstake_finch|claim_vested_rewards)/,
32
+ research: /^(memory_|vault_|code_session_save|list_projects|deep_research|research_compare|research_chain|web_search|web_scrape|schedule_research|list_monitors|cancel_monitor|ask_finch|stock_fundamentals|stock_insider|stock_events)/,
33
+ memory: /^(memory_|vault_|code_session_save|list_projects|agent_|chronicle_)/,
34
+ coder: /^(audit_contract|github_|code_session_save)/,
35
35
  };
36
36
  function filterTools(allTools) {
37
37
  // Default is "core" - keeps LLM context cost low while everything
@@ -7,6 +7,8 @@ const zod_1 = require("zod");
7
7
  const convex_js_1 = require("../convex.js");
8
8
  const llm_js_1 = require("../llm.js");
9
9
  const local_vault_js_1 = require("../local-vault.js");
10
+ const memory_js_1 = require("./memory.js");
11
+ const project_js_1 = require("../project.js");
10
12
  // ─── Agent Learning Memory (v3.25) ──────────────────────────────────────────
11
13
  // After every agent_update, an LLM reviews the new progress in context of the
12
14
  // agent's goal + prior learnings to extract a single repeatable insight. The
@@ -133,6 +135,12 @@ exports.AGENT_TOOLS = [
133
135
  name: { type: "string", description: "Unique agent name (e.g. 'market-researcher', 'onboarding-helper')" },
134
136
  goal: { type: "string", description: "What this agent is trying to accomplish" },
135
137
  context: { type: "string", description: "Optional starting context, data, or notes for the agent" },
138
+ workspaceProject: {
139
+ type: "string",
140
+ description: "Optional: file this agent into a named Finch workspace project (visible on the Agents page's " +
141
+ "project switcher). Matched case-insensitively by name; created automatically if it doesn't exist " +
142
+ "yet. Hosted vault only (no effect in local-vault mode).",
143
+ },
136
144
  },
137
145
  required: ["name", "goal"],
138
146
  },
@@ -140,6 +148,8 @@ exports.AGENT_TOOLS = [
140
148
  {
141
149
  name: "agent_recall",
142
150
  description: "Recall a persistent agent by name - loads its goal, current progress, findings, full history, and accumulated learnings (patterns the agent extracted from past runs). " +
151
+ "Also pulls related context from memory/vault (code_session_save entries, deep_research reports, notes) matching the agent's goal, " +
152
+ "so recall reflects everything relevant to the goal - not just what agent_update explicitly logged. " +
143
153
  "Use this to resume a long-running task, check what an agent last did, or hand context to a fresh LLM session. " +
144
154
  "Learnings compound over time - the more an agent runs, the smarter recall becomes.",
145
155
  inputSchema: {
@@ -192,6 +202,7 @@ const SpawnAgentSchema = zod_1.z.object({
192
202
  name: zod_1.z.string().min(1).max(60).regex(/^[a-z0-9-]+$/, "name must be lowercase alphanumeric with hyphens"),
193
203
  goal: zod_1.z.string().min(1),
194
204
  context: zod_1.z.string().optional(),
205
+ workspaceProject: zod_1.z.string().optional(),
195
206
  });
196
207
  const RecallAgentSchema = zod_1.z.object({ name: zod_1.z.string().min(1) });
197
208
  const UpdateAgentSchema = zod_1.z.object({
@@ -217,8 +228,8 @@ async function handleAgentTool(name, args) {
217
228
  if (name === "agent_spawn") {
218
229
  const parsed = SpawnAgentSchema.safeParse(args);
219
230
  if (!parsed.success)
220
- return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
221
- const { name: agentName, goal, context } = parsed.data;
231
+ return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
232
+ const { name: agentName, goal, context, workspaceProject } = parsed.data;
222
233
  const content = JSON.stringify({
223
234
  goal,
224
235
  status: "active",
@@ -237,19 +248,30 @@ async function handleAgentTool(name, args) {
237
248
  commitMsg: "spawned",
238
249
  };
239
250
  const localVault = (0, local_vault_js_1.getLocalVaultConfig)();
251
+ // Resolve the project name -> id server-side (auto-creates on first use).
252
+ // No-op in local-vault mode - there is no project concept there.
253
+ let resolvedProjectName = null;
254
+ if (workspaceProject && !localVault) {
255
+ const resolved = await (0, project_js_1.resolveProjectId)(workspaceProject);
256
+ if (resolved) {
257
+ savePayload.projectId = resolved.projectId;
258
+ resolvedProjectName = resolved.name;
259
+ }
260
+ }
240
261
  const data = localVault
241
262
  ? (0, local_vault_js_1.localVaultSave)(localVault, savePayload)
242
263
  : await (0, convex_js_1.callConvex)("/vault/save", "POST", savePayload, "vault_save");
243
264
  if (data.error)
244
265
  return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
266
+ const projectLine = resolvedProjectName ? `\n**Project:** ${resolvedProjectName}` : "";
245
267
  return {
246
- content: [{ type: "text", text: `🤖 Agent **${agentName}** spawned${localVault ? " locally" : ""}.\n\n**Goal:** ${goal}\n\nRecall with \`agent_recall\` · Update progress with \`agent_update\`` }],
268
+ content: [{ type: "text", text: `🤖 Agent **${agentName}** spawned${localVault ? " locally" : ""}.\n\n**Goal:** ${goal}${projectLine}\n\nRecall with \`agent_recall\` · Update progress with \`agent_update\`` }],
247
269
  };
248
270
  }
249
271
  if (name === "agent_recall") {
250
272
  const parsed = RecallAgentSchema.safeParse(args);
251
273
  if (!parsed.success)
252
- return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
274
+ return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
253
275
  const localVault = (0, local_vault_js_1.getLocalVaultConfig)();
254
276
  let data;
255
277
  try {
@@ -291,12 +313,30 @@ async function handleAgentTool(name, args) {
291
313
  lines.push(`\n**Recent updates:**\n${updates.join("\n")}`);
292
314
  if (state.nextStep)
293
315
  lines.push(`\n**Next step:** ${state.nextStep}`);
316
+ // Related context - best-effort pull of relevant memory/vault knowledge
317
+ // (code_session_save entries, deep_research reports, manual notes) so the
318
+ // agent isn't blind to work done on its goal outside its own update log.
319
+ // This is what makes recall "continuously have context" rather than only
320
+ // ever knowing what agent_update explicitly logged.
321
+ if (state.goal) {
322
+ try {
323
+ const related = await (0, memory_js_1.hybridMemorySearch)(state.goal, 4);
324
+ if (related.length) {
325
+ lines.push(`\n**📎 Related context (${related.length}):**`);
326
+ related.forEach((r) => {
327
+ const title = r.metadata?.title ?? r.content.slice(0, 70).replace(/\n/g, " ");
328
+ lines.push(` • ${title}`);
329
+ });
330
+ }
331
+ }
332
+ catch { /* best-effort - recall must never fail because of this */ }
333
+ }
294
334
  return { content: [{ type: "text", text: lines.join("\n") }] };
295
335
  }
296
336
  if (name === "agent_update") {
297
337
  const parsed = UpdateAgentSchema.safeParse(args);
298
338
  if (!parsed.success)
299
- return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
339
+ return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
300
340
  const { name: agentName, progress, findings, status = "active", nextStep } = parsed.data;
301
341
  return withAgentLock(agentName, async () => {
302
342
  const localVault = (0, local_vault_js_1.getLocalVaultConfig)();
@@ -44,10 +44,17 @@ function categorizeError(err) {
44
44
  exports.AUTOMATION_TOOLS = [
45
45
  {
46
46
  name: "create_automation",
47
- description: "Create an automation in plain English. Supports DCA, price alerts, conditional buys/sells, and recurring market updates.",
47
+ description: "Create an automation in plain English. Supports DCA, price alerts, conditional buys/sells, and recurring market updates. " +
48
+ "A swap/send automation arms UNATTENDED, REPEATING real fund movement (a backend cron fires it going forward, not just once) - " +
49
+ "requires two calls. First call WITHOUT confirm: returns a preview of the parsed trigger/action, nothing is created yet. " +
50
+ "Show that preview to the user in plain language and get explicit confirmation. Only then call again with the SAME rawInput " +
51
+ "and confirm: true to actually create it. Never pass confirm: true on the first call.",
48
52
  inputSchema: {
49
53
  type: "object",
50
- properties: { rawInput: { type: "string", description: "Plain English description of the automation" } },
54
+ properties: {
55
+ rawInput: { type: "string", description: "Plain English description of the automation" },
56
+ confirm: { type: "boolean", description: "Must be true to actually create. Omit or false to get a preview only - nothing is created." },
57
+ },
51
58
  required: ["rawInput"],
52
59
  },
53
60
  },
@@ -108,7 +115,7 @@ exports.AUTOMATION_TOOLS = [
108
115
  },
109
116
  },
110
117
  ];
111
- const CreateAutomationSchema = zod_1.z.object({ rawInput: zod_1.z.string().min(1) });
118
+ const CreateAutomationSchema = zod_1.z.object({ rawInput: zod_1.z.string().min(1), confirm: zod_1.z.boolean().optional() });
112
119
  const AutomationIdSchema = zod_1.z.object({ automationId: zod_1.z.string().min(1) });
113
120
  const RunAutomationSchema = zod_1.z.object({ automationId: zod_1.z.string().min(1), dryRun: zod_1.z.boolean().optional() });
114
121
  const RunsSchema = zod_1.z.object({ automationId: zod_1.z.string().min(1), limit: zod_1.z.number().int().min(1).max(100).optional() });
@@ -148,15 +155,44 @@ async function handleAutomationTool(name, args) {
148
155
  const parsed = CreateAutomationSchema.safeParse(args);
149
156
  if (!parsed.success)
150
157
  return { content: [{ type: "text", text: `Invalid input: rawInput ${parsed.error.issues[0].message}` }], isError: true };
151
- const data = await (0, convex_js_1.callConvex)("/automations/create", "POST", { rawInput: parsed.data.rawInput }, "create_automation");
152
- if (!data.success)
153
- return { content: [{ type: "text", text: `Failed: ${data.error}` }], isError: true };
154
158
  const triggerLabel = {
155
159
  schedule: "⏰ Schedule", price_drop_pct: "📉 Price Drop %", price_rise_pct: "📈 Price Rise %",
156
160
  price_below: "⬇️ Price Below", price_above: "⬆️ Price Above",
157
161
  dominance_below: "📊 Dominance Below", dominance_above: "📊 Dominance Above",
158
162
  };
159
163
  const actionLabel = { swap: "💱 Swap", send: "📤 Send", alert: "🔔 Alert" };
164
+ // Not confirmed yet - dry-run only (backend validates/parses but does
165
+ // NOT create anything - see http.ts's /automations/create dryRun
166
+ // branch). A swap/send automation arms unattended, repeating real
167
+ // fund movement, so it needs a real preview + explicit confirm, same
168
+ // shape as base_mcp_swap/send - not created blind on the first call.
169
+ if (parsed.data.confirm !== true) {
170
+ const preview = await (0, convex_js_1.callConvex)("/automations/create", "POST", { rawInput: parsed.data.rawInput, dryRun: true }, "create_automation");
171
+ if (!preview.success)
172
+ return { content: [{ type: "text", text: `Could not parse this automation: ${preview.error}` }], isError: true };
173
+ const moneyMoving = preview.actionType === "swap" || preview.actionType === "send";
174
+ return {
175
+ content: [{
176
+ type: "text",
177
+ text: [
178
+ `**Preview - nothing created yet**`, ``,
179
+ `**Trigger:** ${triggerLabel[preview.triggerType] ?? preview.triggerType}`,
180
+ `**Action:** ${actionLabel[preview.actionType] ?? preview.actionType}`,
181
+ preview.fromToken && preview.toToken ? `**Swap:** ${preview.amountUsd ? `$${preview.amountUsd}` : `${preview.amountPct}%`} ${preview.fromToken} → ${preview.toToken}` : ``,
182
+ preview.toAddress ? `**Send:** ${preview.amountUsd ? `$${preview.amountUsd}` : ""} ${preview.fromToken ?? ""} to \`${preview.toAddress}\`` : ``,
183
+ preview.priceToken ? `**Price condition:** ${preview.priceToken} ${preview.triggerType?.includes("above") ? "above" : "below"} ${preview.priceThreshold}` : ``,
184
+ preview.priceBaselineUsd ? `**Baseline price:** $${Number(preview.priceBaselineUsd).toLocaleString()}` : ``,
185
+ moneyMoving
186
+ ? `\n⚠️ This will move real funds, repeatedly, unattended, until paused or deleted. Show this preview to the user and get explicit confirmation.`
187
+ : `\nThis is alert-only - it will not move funds.`,
188
+ `\nIf this is correct, call create_automation again with the same rawInput and confirm: true.`,
189
+ ].filter(Boolean).join("\n"),
190
+ }],
191
+ };
192
+ }
193
+ const data = await (0, convex_js_1.callConvex)("/automations/create", "POST", { rawInput: parsed.data.rawInput }, "create_automation");
194
+ if (!data.success)
195
+ return { content: [{ type: "text", text: `Failed: ${data.error}` }], isError: true };
160
196
  return {
161
197
  content: [{
162
198
  type: "text",
@@ -52,7 +52,7 @@ async function handleCoderTool(name, args) {
52
52
  return null;
53
53
  const p = AuditSchema.safeParse(args);
54
54
  if (!p.success)
55
- return err(`Invalid input: ${p.error.message}`);
55
+ return err(p.error.issues[0].message);
56
56
  const { code, focus = [] } = p.data;
57
57
  // The pattern scan grounds the review. Without it an audit is pure model
58
58
  // opinion — someone might trust "looks safe" with nothing behind it.
@@ -915,7 +915,7 @@ async function handleDeepResearch(name, args, onProgress) {
915
915
  return null;
916
916
  const parsed = InputSchema.safeParse(args);
917
917
  if (!parsed.success) {
918
- return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
918
+ return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
919
919
  }
920
920
  const { query, focus, continueFrom } = parsed.data;
921
921
  const clientQueries = parsed.data.queries ?? [];
@@ -99,7 +99,7 @@ async function handleDefiTool(name, args) {
99
99
  case "estimate_swap": {
100
100
  const parsed = SwapSchema.safeParse(args);
101
101
  if (!parsed.success)
102
- return { content: [{ type: "text", text: `Invalid input: ${String(parsed.error.issues[0].path[0])} ${parsed.error.issues[0].message}` }], isError: true };
102
+ return { content: [{ type: "text", text: `${String(parsed.error.issues[0].path[0])}: ${parsed.error.issues[0].message}` }], isError: true };
103
103
  const { fromToken, toToken, amount, maxSlippagePct, maxPriceImpactPct } = parsed.data;
104
104
  const slippageLimit = maxSlippagePct ?? DEFAULT_MAX_SLIPPAGE_PCT;
105
105
  const impactLimit = maxPriceImpactPct ?? DEFAULT_MAX_PRICE_IMPACT_PCT;
@@ -137,7 +137,7 @@ async function handleDefiTool(name, args) {
137
137
  case "swap_tokens": {
138
138
  const parsed = SwapSchema.safeParse(args);
139
139
  if (!parsed.success)
140
- return { content: [{ type: "text", text: `Invalid input: ${String(parsed.error.issues[0].path[0])} ${parsed.error.issues[0].message}` }], isError: true };
140
+ return { content: [{ type: "text", text: `${String(parsed.error.issues[0].path[0])}: ${parsed.error.issues[0].message}` }], isError: true };
141
141
  const { fromToken, toToken, amount, maxSlippagePct, maxPriceImpactPct } = parsed.data;
142
142
  const slippageLimit = maxSlippagePct ?? DEFAULT_MAX_SLIPPAGE_PCT;
143
143
  const impactLimit = maxPriceImpactPct ?? DEFAULT_MAX_PRICE_IMPACT_PCT;
@@ -190,7 +190,7 @@ async function handleDefiTool(name, args) {
190
190
  case "send_token": {
191
191
  const parsed = SendSchema.safeParse(args);
192
192
  if (!parsed.success)
193
- return { content: [{ type: "text", text: `Invalid input: ${String(parsed.error.issues[0].path[0])} ${parsed.error.issues[0].message}` }], isError: true };
193
+ return { content: [{ type: "text", text: `${String(parsed.error.issues[0].path[0])}: ${parsed.error.issues[0].message}` }], isError: true };
194
194
  const { token, toAddress, amount } = parsed.data;
195
195
  const wallet = await (0, wallet_js_1.getOrCreateWallet)();
196
196
  const result = await (0, convex_js_1.callConvex)("/mcp/defi/send", "POST", parsed.data, "send_token");
@@ -225,7 +225,7 @@ async function handleDefiTool(name, args) {
225
225
  case "get_defi_yields": {
226
226
  const parsed = DefiYieldsSchema.safeParse(args ?? {});
227
227
  if (!parsed.success)
228
- return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
228
+ return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
229
229
  const { token, minApy = 1, limit = 20 } = parsed.data;
230
230
  let pools;
231
231
  try {
@@ -183,7 +183,7 @@ async function handleEquityTool(name, args) {
183
183
  return null;
184
184
  const parsed = Schema.safeParse(args);
185
185
  if (!parsed.success) {
186
- return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
186
+ return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
187
187
  }
188
188
  const ticker = parsed.data.ticker.trim().toUpperCase();
189
189
  const periods = parsed.data.periods ?? 6;
@@ -97,7 +97,7 @@ async function handleEventTool(name, args) {
97
97
  return null;
98
98
  const parsed = Schema.safeParse(args);
99
99
  if (!parsed.success) {
100
- return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
100
+ return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
101
101
  }
102
102
  const ticker = parsed.data.ticker.trim().toUpperCase();
103
103
  const limit = parsed.data.limit ?? 15;
@@ -1,4 +1,37 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.GITHUB_TOOLS = void 0;
4
37
  exports.buildRepoList = buildRepoList;
@@ -9,13 +42,30 @@ exports.buildIssueDetail = buildIssueDetail;
9
42
  exports.buildCommitList = buildCommitList;
10
43
  exports.buildCodeSearch = buildCodeSearch;
11
44
  exports.handleGithubTool = handleGithubTool;
45
+ const fs = __importStar(require("fs"));
46
+ const path = __importStar(require("path"));
12
47
  const GH_BASE = "https://api.github.com";
48
+ // Was hardcoded "finch-mcp/3.28.0" - stale the moment package.json's version
49
+ // moved on (already 4.4.0 locally as of this fix), unlike server.ts/cli.ts/
50
+ // index.ts which all read it from package.json at runtime per this repo's
51
+ // own stated policy (see CLAUDE.md's mcp-server note). __dirname here is
52
+ // dist/tools/ once built, so two levels up reaches the package root - one
53
+ // level deeper than server.ts's own copy of this pattern (dist/).
54
+ const PKG_VERSION = (() => {
55
+ try {
56
+ const raw = fs.readFileSync(path.join(__dirname, "..", "..", "package.json"), "utf8");
57
+ return JSON.parse(raw).version ?? "unknown";
58
+ }
59
+ catch {
60
+ return "unknown";
61
+ }
62
+ })();
13
63
  function ghHeaders() {
14
64
  const token = process.env.GITHUB_TOKEN;
15
65
  const h = {
16
66
  Accept: "application/vnd.github.v3+json",
17
67
  "X-GitHub-Api-Version": "2022-11-28",
18
- "User-Agent": "finch-mcp/3.28.0",
68
+ "User-Agent": `finch-mcp/${PKG_VERSION}`,
19
69
  };
20
70
  if (token)
21
71
  h.Authorization = `Bearer ${token}`;
@@ -116,7 +116,7 @@ async function handleInsiderTool(name, args) {
116
116
  return null;
117
117
  const parsed = Schema.safeParse(args);
118
118
  if (!parsed.success) {
119
- return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
119
+ return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
120
120
  }
121
121
  const ticker = parsed.data.ticker.trim().toUpperCase();
122
122
  const limit = parsed.data.limit ?? 15;
@@ -451,7 +451,7 @@ async function handleInsightTool(name, args) {
451
451
  if (name === "market_thesis") {
452
452
  const parsed = MarketThesisSchema.safeParse(args);
453
453
  if (!parsed.success)
454
- return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
454
+ return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
455
455
  const { token, context } = parsed.data;
456
456
  const priceData = await fetchVerifiedPrice(token);
457
457
  // Hard guard: without a verified live price, refuse to generate. Better
@@ -519,7 +519,7 @@ async function handleInsightTool(name, args) {
519
519
  if (name === "trade_plan") {
520
520
  const parsed = TradePlanSchema.safeParse(args);
521
521
  if (!parsed.success)
522
- return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
522
+ return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
523
523
  const { token, side = "long", portfolioSize, riskTolerance = "moderate", timeframe } = parsed.data;
524
524
  const priceData = await fetchVerifiedPrice(token);
525
525
  // Hard guard: trade plans without verified live price = entry/SL/TP