@finchagentic/mcp 4.7.1 → 4.7.3

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.
Files changed (51) hide show
  1. package/README.md +74 -39
  2. package/dist/_zod-helpers.js +10 -0
  3. package/dist/cli-doctor.js +300 -0
  4. package/dist/cli-env.js +91 -0
  5. package/dist/cli-install.js +236 -0
  6. package/dist/cli-login.js +101 -0
  7. package/dist/cli-orders.js +175 -0
  8. package/dist/cli-setup.js +270 -0
  9. package/dist/cli-ui.js +168 -0
  10. package/dist/cli-vault.js +75 -0
  11. package/dist/cli.js +61 -1152
  12. package/dist/config.js +3 -2
  13. package/dist/enrichment-router.js +5 -18
  14. package/dist/finch-output.js +30 -29
  15. package/dist/finch-status.js +105 -40
  16. package/dist/index.js +0 -0
  17. package/dist/local-vault.js +5 -12
  18. package/dist/output-schemas.js +3 -12
  19. package/dist/project.js +4 -13
  20. package/dist/server.js +13 -35
  21. package/dist/tool-filter.js +4 -13
  22. package/dist/tools/_solidity-scan.js +5 -14
  23. package/dist/tools/agents.js +14 -26
  24. package/dist/tools/deep-research-constants.js +33 -0
  25. package/dist/tools/deep-research-firecrawl.js +88 -0
  26. package/dist/tools/deep-research-planning.js +249 -0
  27. package/dist/tools/deep-research-synthesis.js +343 -0
  28. package/dist/tools/deep-research-text.js +158 -0
  29. package/dist/tools/deep-research-tools.js +90 -0
  30. package/dist/tools/deep-research.js +47 -938
  31. package/dist/tools/defi.js +4 -3
  32. package/dist/tools/insider.js +4 -14
  33. package/dist/tools/insight.js +7 -6
  34. package/dist/tools/market.js +16 -15
  35. package/dist/tools/memory.js +57 -62
  36. package/dist/tools/monitor.js +7 -6
  37. package/dist/tools/os.js +7 -85
  38. package/dist/tools/research.js +7 -6
  39. package/dist/tools/rh-mcp-constants.js +65 -0
  40. package/dist/tools/rh-mcp-dex.js +107 -0
  41. package/dist/tools/rh-mcp-provider.js +127 -0
  42. package/dist/tools/rh-mcp-resolve.js +137 -0
  43. package/dist/tools/rh-mcp-risk.js +230 -0
  44. package/dist/tools/rh-mcp-safety.js +234 -0
  45. package/dist/tools/rh-mcp-swap.js +181 -0
  46. package/dist/tools/rh-mcp-tools.js +137 -0
  47. package/dist/tools/rh-mcp.js +75 -1191
  48. package/dist/tools/scanner.js +10 -9
  49. package/dist/tools/stake.js +7 -20
  50. package/dist/tools/vault.js +52 -59
  51. package/package.json +12 -9
package/dist/config.js CHANGED
@@ -88,8 +88,9 @@ function writeConfig(patch) {
88
88
  fs.writeFileSync(CONFIG_FILE, JSON.stringify(updated, null, 2), { mode: 0o600 });
89
89
  }
90
90
  function getSavedToken() {
91
- // env var always wins over saved config
92
- return env("FINCH_SESSION_TOKEN", "FINCH_SESSION_TOKEN") ?? readConfig().sessionToken;
91
+ // FINCH_API_KEY (persistent finch_sk_* key) wins - MCP clients should hold
92
+ // a key, not a session token that expires and silently disconnects them.
93
+ return process.env.FINCH_API_KEY ?? env("FINCH_SESSION_TOKEN", "FINCH_SESSION_TOKEN") ?? readConfig().sessionToken;
93
94
  }
94
95
  /** Accept finch_sk_* (new) and noel_sk_* (backend still issues these). */
95
96
  function isApiKey(value) {
@@ -1,22 +1,9 @@
1
1
  "use strict";
2
- // Generalized enrichment router for deep_research synthesis.
3
- //
4
- // Old behavior: Firecrawl scraped 5–10 pages and the LLM synthesized. When
5
- // pages were thin or outdated, the LLM filled gaps with plausible fiction.
6
- //
7
- // New behavior: detect the topic domain and hit primary-source APIs in
8
- // parallel before synthesis. Inject results as "AUTHORITATIVE LIVE DATA"
9
- // the LLM is instructed to lead with.
10
- //
11
- // All APIs used here are free, public, no auth required:
12
- // - DefiLlama, CoinGecko (crypto)
13
- // - HackerNews Algolia (tech news, ~real-time)
14
- // - GitHub search (repos, no auth needed for public read)
15
- // - arXiv (academic papers)
16
- // - Wikipedia REST (foundational facts)
17
- //
18
- // All calls are best-effort with short timeouts - if any fail, the
19
- // synthesis runs without that block.
2
+ // Enrichment router for deep_research synthesis - detects topic domain and
3
+ // hits free primary-source APIs in parallel (DefiLlama/CoinGecko, HN
4
+ // Algolia, GitHub search, arXiv, Wikipedia), injected as "AUTHORITATIVE
5
+ // LIVE DATA" so the LLM leads with real data instead of filling thin
6
+ // scraped pages with plausible fiction. Best-effort, short timeouts.
20
7
  Object.defineProperty(exports, "__esModule", { value: true });
21
8
  exports.enrichQuery = enrichQuery;
22
9
  exports.todayContext = todayContext;
@@ -1,4 +1,8 @@
1
1
  "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatMoney = formatMoney;
4
+ exports.truncate = truncate;
5
+ exports.processResponse = processResponse;
2
6
  /**
3
7
  * Output quality post-processor (CallTool choke point).
4
8
  * Normalizes every tool response before it reaches the client:
@@ -10,38 +14,35 @@
10
14
  * shapes - the client renders those natively.
11
15
  */
12
16
  const MAX_LINES = 200;
13
-
14
- const MONEY_RE = /\$\s?([\d,]+\.\d{4,})/g; // $0.00012345 -> $0.00012 (4dp cap)
15
- const ZEROS_RE = /\$0\.0+\d{0,2}(?=\b)/g; // leave tiny prices alone
16
-
17
+ const MONEY_RE = /\$\s?([\d,]+\.\d{4,})/g; // $0.00012345 -> $0.00012 (4dp cap)
17
18
  function formatMoney(text) {
18
- // Cap runaway precision: $0.00345678 -> $0.00346. Keeps tokens readable.
19
- return text.replace(MONEY_RE, (m, num) => {
20
- const n = Number(num.replace(/,/g, ""));
21
- if (!Number.isFinite(n)) return m;
22
- const trimmed = n.toFixed(4).replace(/0+$/, "").replace(/\.$/, "");
23
- return `$${trimmed}`;
24
- });
19
+ // Cap runaway precision: $0.00345678 -> $0.00346. Keeps tokens readable.
20
+ return text.replace(MONEY_RE, (m, num) => {
21
+ const n = Number(num.replace(/,/g, ""));
22
+ if (!Number.isFinite(n))
23
+ return m;
24
+ const trimmed = n.toFixed(4).replace(/0+$/, "").replace(/\.$/, "");
25
+ return `$${trimmed}`;
26
+ });
25
27
  }
26
-
27
28
  function truncate(text) {
28
- const lines = text.split("\n");
29
- if (lines.length <= MAX_LINES) return text;
30
- const head = lines.slice(0, MAX_LINES).join("\n");
31
- return `${head}\n\n… ${lines.length - MAX_LINES} more lines — narrow your query (smaller limit, fewer fields) for the full output.`;
29
+ const lines = text.split("\n");
30
+ if (lines.length <= MAX_LINES)
31
+ return text;
32
+ const head = lines.slice(0, MAX_LINES).join("\n");
33
+ return `${head}\n\n… ${lines.length - MAX_LINES} more lines — narrow your query (smaller limit, fewer fields) for the full output.`;
32
34
  }
33
-
34
35
  function processResponse(response) {
35
- if (!response || response.isError) return response;
36
- const first = response.content?.[0];
37
- if (!first || first.type !== "text" || typeof first.text !== "string") return response;
38
- let text = first.text;
39
- text = formatMoney(text);
40
- text = truncate(text);
41
- if (text !== first.text) {
42
- return { ...response, content: [{ ...first, text }] };
43
- }
44
- return response;
36
+ if (!response || response.isError)
37
+ return response;
38
+ const first = response.content?.[0];
39
+ if (!first || first.type !== "text" || typeof first.text !== "string")
40
+ return response;
41
+ let text = first.text;
42
+ text = formatMoney(text);
43
+ text = truncate(text);
44
+ if (text !== first.text) {
45
+ return { ...response, content: [{ ...first, text }, ...response.content.slice(1)] };
46
+ }
47
+ return response;
45
48
  }
46
-
47
- module.exports = { processResponse, formatMoney, truncate };
@@ -1,37 +1,96 @@
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
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.FINCH_STATUS_TOOL = void 0;
37
+ exports.handleFinchStatus = handleFinchStatus;
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
2
40
  const config_js_1 = require("./config.js");
3
41
  const convex_js_1 = require("./convex.js");
42
+ // Same pattern as server.ts's own PKG_VERSION - fs.readFileSync, not
43
+ // require(), since this project's eslint config forbids require() imports.
44
+ function readPkgVersion() {
45
+ try {
46
+ const raw = fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8");
47
+ return JSON.parse(raw).version ?? "unknown";
48
+ }
49
+ catch {
50
+ return "unknown";
51
+ }
52
+ }
53
+ // Local-only, no-auth-required status check. Replaces the old finch_status
54
+ // (which lived in tools/os.ts, made 6 Promise.allSettled backend calls, and
55
+ // silently defaulted every field to zero/basic on ANY failure - including a
56
+ // 401 - producing a dashboard that read "empty but healthy" instead of
57
+ // surfacing the real problem). This version makes exactly ONE cheap authed
58
+ // call when a token is configured, and reports the REAL auth state instead.
4
59
  exports.FINCH_STATUS_TOOL = {
5
60
  name: "finch_status",
6
- description: "Check Finch MCP is working - no sign-in needed. Returns the version, how many tools are available, whether the session token is configured, and a ready-to-paste MCP client config snippet. Run this FIRST if anything feels off: it tells you instantly whether the problem is auth (missing token), network (backend unreachable), or the tool itself.",
61
+ description: "Check Finch MCP is working - no sign-in needed. Returns the version, how many tools are available, " +
62
+ "whether the session token is configured, and a ready-to-paste MCP client config snippet. Run this FIRST " +
63
+ "if anything feels off: it tells you instantly whether the problem is auth (missing token), network " +
64
+ "(backend unreachable), or the tool itself.",
7
65
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
8
66
  annotations: { title: "Finch status", readOnlyHint: true, openWorldHint: false },
9
67
  };
10
-
11
- // `finch_status` handler - never throws on auth; when a token is configured
12
- // it makes ONE cheap authed call (/memory/profile) and reports the REAL
13
- // auth state instead of a fake-healthy dashboard (the old Promise.allSettled
14
- // version swallowed 401s and showed empty-but-healthy, which hid expired
15
- // credentials entirely).
16
- exports.handleFinchStatus = async function handleFinchStatus() {
17
- const pkg = require("./../package.json");
68
+ async function handleFinchStatus(toolCount) {
69
+ const version = readPkgVersion();
18
70
  let tokenSet = false;
19
71
  let authState = "none";
20
72
  let authDetail = "";
21
73
  try {
22
74
  tokenSet = Boolean((0, config_js_1.getSavedToken)());
23
- } catch { tokenSet = false; }
75
+ }
76
+ catch {
77
+ tokenSet = false;
78
+ }
24
79
  if (tokenSet) {
25
80
  try {
26
81
  const data = await (0, convex_js_1.callConvex)("/memory/profile", "GET", undefined, "finch_status", 8000, true);
27
82
  authState = (data?.status === "ok") ? "valid" : "rejected";
28
- if (authState === "rejected") authDetail = "the session token was rejected by the backend - re-copy it from app.finchagentic.com";
29
- } catch (err) {
83
+ if (authState === "rejected") {
84
+ authDetail = "the session token was rejected by the backend - re-copy it from app.finchagentic.com";
85
+ }
86
+ }
87
+ catch (err) {
30
88
  const msg = String(err?.message ?? err);
31
89
  if (msg.includes("Authentication required")) {
32
90
  authState = "invalid";
33
91
  authDetail = "the backend rejected the token as expired or invalid - re-copy it from app.finchagentic.com";
34
- } else {
92
+ }
93
+ else {
35
94
  authState = "unreachable";
36
95
  authDetail = msg.slice(0, 120);
37
96
  }
@@ -41,41 +100,47 @@ exports.handleFinchStatus = async function handleFinchStatus() {
41
100
  mcpServers: {
42
101
  finch: {
43
102
  command: "npx",
44
- args: ["-y", "@finchagentic/mcp@" + pkg.version],
103
+ args: ["-y", `@finchagentic/mcp@${version}`],
45
104
  env: { FINCH_SESSION_TOKEN: "<your token>" },
46
105
  },
47
106
  },
48
107
  }, null, 2);
49
108
  const lines = tokenSet && authState === "valid"
50
109
  ? [
51
- "**Finch MCP v" + pkg.version + "** - runtime layer for agentic AI", "",
52
- "- Session token: \u2705 valid (verified against the backend)",
53
- "- Tools: **116** registered (surface depends on FINCH_TOOLS filter)",
110
+ `**Finch MCP v${version}** - runtime layer for agentic AI`,
111
+ "",
112
+ "- Session token: valid (verified against the backend)",
113
+ `- Tools: **${toolCount}** registered (surface depends on FINCH_TOOLS filter)`,
54
114
  "- Resources: finch://vault/<key> - Prompts: crypto-thesis and friends",
55
115
  "- Tool presets: FINCH_PRESET=core|defi|research|memory (default core) - FINCH_TOOLS=all for everything",
56
116
  "",
57
- "All set. Good first calls: `memory_add` (persist a note instantly), `get_market` (live prices), `deep_research` (multi-step reports, streaming progress).",
117
+ "All set. Good first calls: `memory_add` (persist a note instantly), `get_market_data` (live prices), `deep_research` (multi-step reports, streaming progress).",
58
118
  ]
59
119
  : tokenSet
60
- ? [
61
- "**Finch MCP v" + pkg.version + "** - runtime layer for agentic AI", "",
62
- `- Session token: \u26a0\ufe0f configured but **${authState}**`,
63
- authDetail ? `- ${authDetail}` : "",
64
- "- Tools are registered, but every backend-backed call will fail until the token is replaced.",
65
- "",
66
- "**Fix:** re-copy a fresh token from app.finchagentic.com, update FINCH_SESSION_TOKEN in your MCP config, restart the client.",
67
- ]
68
- : [
69
- "**Finch MCP v" + pkg.version + "** - runtime layer for agentic AI", "",
70
- "- Session token: \u274c not set", "",
71
- "**To sign in (60 seconds):**", "",
72
- "1. Sign in at https://app.finchagentic.com (wallet or Google)",
73
- "2. Copy your session token from the app",
74
- "3. Add to your MCP client config:",
75
- "```json",
76
- configJson,
77
- "```", "",
78
- "Once the token is set, restart the MCP client and re-run this tool.",
79
- ];
80
- return { content: [{ type: "text", text: lines.join("\n") }] };
81
- };
120
+ ? [
121
+ `**Finch MCP v${version}** - runtime layer for agentic AI`,
122
+ "",
123
+ `- Session token: ⚠️ configured but **${authState}**`,
124
+ authDetail ? `- ${authDetail}` : "",
125
+ "- Tools are registered, but every backend-backed call will fail until the token is replaced.",
126
+ "",
127
+ "**Fix:** re-copy a fresh token from app.finchagentic.com, update FINCH_SESSION_TOKEN in your MCP config, restart the client.",
128
+ ]
129
+ : [
130
+ `**Finch MCP v${version}** - runtime layer for agentic AI`,
131
+ "",
132
+ "- Session token: not set",
133
+ "",
134
+ "**To sign in (60 seconds):**",
135
+ "",
136
+ "1. Sign in at https://app.finchagentic.com (wallet or Google)",
137
+ "2. Copy your session token from the app",
138
+ "3. Add to your MCP client config:",
139
+ "```json",
140
+ configJson,
141
+ "```",
142
+ "",
143
+ "Once the token is set, restart the MCP client and re-run this tool.",
144
+ ];
145
+ return { content: [{ type: "text", text: lines.filter((l) => l !== undefined).join("\n") }] };
146
+ }
package/dist/index.js CHANGED
File without changes
@@ -54,18 +54,11 @@ const path = __importStar(require("path"));
54
54
  const crypto = __importStar(require("crypto"));
55
55
  const config_js_1 = require("./config.js");
56
56
  const _text_search_js_1 = require("./_text-search.js");
57
- // Fully-local, user-owned Noel-Vault backend. Mirrors the two-tier pattern of
58
- // local-memory.ts: when the user opts in (`vaultBackend: "local"`), the vault
59
- // tools store versioned artifacts on the user's own disk under
60
- // ~/.finch/vault/ - no Finch account, no Convex, no network, no cost to
61
- // the platform. Zero runtime dependencies (plain JSON + one content file per
62
- // version), same spirit as codebase-memory-mcp's local-first SQLite store.
63
- //
64
- // Every exported function returns objects shaped like the Convex /vault/*
65
- // responses so tools/vault.ts can swap the data source with a one-line branch
66
- // and reuse all of its existing rendering. Not-found cases THROW an Error whose
67
- // message contains "not found", matching how callConvex surfaces a 404 (the
68
- // vault handlers already catch and format that).
57
+ // Fully-local, user-owned vault backend (`vaultBackend: "local"`) - versioned
58
+ // artifacts on disk under ~/.finch/vault/, no account/Convex/network. Every
59
+ // export returns Convex /vault/*-shaped objects so tools/vault.ts can swap
60
+ // data source with a one-line branch. Not-found throws an Error containing
61
+ // "not found", matching callConvex's own 404 format.
69
62
  const VAULT_DIR = path.join(os.homedir(), ".finch", "vault");
70
63
  // Local vault is enabled purely by config - it needs no server, just the
71
64
  // filesystem. Returns null (→ "use Convex") when the user hasn't opted in.
@@ -1,17 +1,8 @@
1
1
  "use strict";
2
2
  // Central registry of MCP tool `outputSchema` declarations (spec 2025-06-18).
3
- //
4
- // A tool that declares an outputSchema SHOULD also return `structuredContent`
5
- // conforming to it (its handler does; the human-readable `content` text stays
6
- // for backward compatibility). Keeping every schema in one file - rather than
7
- // inline on each tool definition across ~25 modules - makes the machine-readable
8
- // surface reviewable in one place, mirrors how annotations.ts centralises
9
- // behavioural hints, and lets a new tool opt in by adding one entry here plus a
10
- // structuredContent return in its handler (no tool-definition edit needed).
11
- //
12
- // Attached to the listed tools by withAnnotations() (annotations.ts) at
13
- // tools/list time. Only tools whose handler genuinely emits matching
14
- // structuredContent belong here.
3
+ // One file instead of inline per tool (mirrors annotations.ts). Attached by
4
+ // withAnnotations() at tools/list time - only tools whose handler actually
5
+ // emits matching structuredContent belong here.
15
6
  Object.defineProperty(exports, "__esModule", { value: true });
16
7
  exports.OUTPUT_SCHEMAS = void 0;
17
8
  const number_null = { type: ["number", "null"] };
package/dist/project.js CHANGED
@@ -4,19 +4,10 @@ exports.isLocalVaultActive = isLocalVaultActive;
4
4
  exports.resolveProjectId = resolveProjectId;
5
5
  const convex_js_1 = require("./convex.js");
6
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.
7
+ // No persistent "active project" state here - callers name the project by
8
+ // string each call; resolution/auto-create happens server-side (POST
9
+ // /projects/resolve), shared with the webapp's own picker. Local-vault mode
10
+ // has no project concept at all - check isLocalVaultActive() first.
20
11
  function isLocalVaultActive() {
21
12
  return !!(0, local_vault_js_1.getLocalVaultConfig)();
22
13
  }
package/dist/server.js CHANGED
@@ -45,6 +45,8 @@ const resources_js_1 = require("./resources.js");
45
45
  const prompts_js_1 = require("./prompts.js");
46
46
  const tool_filter_js_1 = require("./tool-filter.js");
47
47
  const annotations_js_1 = require("./annotations.js");
48
+ const finch_status_js_1 = require("./finch-status.js");
49
+ const finch_output_js_1 = require("./finch-output.js");
48
50
  // Read version from package.json so the server announces the same version
49
51
  // MCP clients see in the npm tarball. Falls back to "unknown" if the file
50
52
  // can't be loaded - never blocks server startup.
@@ -58,8 +60,6 @@ const PKG_VERSION = (() => {
58
60
  }
59
61
  })();
60
62
  const market_js_1 = require("./tools/market.js");
61
- const finch_output_js_1 = require("./finch-output.js");
62
- const finch_status_js_1 = require("./finch-status.js");
63
63
  const defi_js_1 = require("./tools/defi.js");
64
64
  const automation_js_1 = require("./tools/automation.js");
65
65
  const insight_js_1 = require("./tools/insight.js");
@@ -132,8 +132,7 @@ exports.ALL_TOOLS = [
132
132
  ...rh_orders_js_1.RH_ORDER_TOOLS, // 5 - rh_dca_create, rh_bracket_create, rh_orders_list, rh_order_cancel, rh_orders_tick (RH automated DCA/TP/SL)
133
133
  ...rh_bridge_js_1.BRIDGE_TOOLS, // 1 - rh_stock_bridge (tokenized stock vs real equity)
134
134
  ...memory_js_1.MEMORY_TOOLS, // 9 - memory_add, memory_search, memory_context, memory_profile, memory_list, memory_delete, memory_insight, memory_extract, memory_consolidate. memory_publish removed - promised a "Memory Marketplace" that doesn't exist at any layer (no browse route, no query, no UI), see tools/memory.ts
135
- ...os_js_1.OS_TOOLS.filter((t) => t.name !== "finch_status"), // finch_status lives in finch-status.js (dedup)
136
- // 2 - finch_diagnostics, finch_shell_chat
135
+ ...os_js_1.OS_TOOLS, // 3 - finch_status, finch_diagnostics, finch_shell_chat
137
136
  ...research_js_1.RESEARCH_TOOLS, // 2 - web_scrape, web_search
138
137
  ...deep_research_js_1.DEEP_RESEARCH_TOOLS, // 1 - deep_research (search → scrape → rank → cited evidence pack; caller synthesises)
139
138
  ...research_compare_js_1.RESEARCH_COMPARE_TOOLS, // 1 - research_compare (diff two reports across time)
@@ -143,21 +142,10 @@ exports.ALL_TOOLS = [
143
142
  ...chronicle_js_1.CHRONICLE_TOOLS, // 4 - chronicle_add, chronicle_list, chronicle_search, chronicle_stats
144
143
  ...packets_js_1.PACKET_TOOLS, // 4 - packet_create, packet_run, packet_list, packet_share
145
144
  ...stake_js_1.STAKE_TOOLS, // 5 - stake_finch_status, stake_finch, unstake_finch, claim_vested_rewards, stake_auto_restake (custodial wallet; requires `finch login`)
146
- // total: 116 tools as measured by ALL_TOOLS.length - do not hand-maintain a
147
- // count in this comment (drifted stale multiple times already: before
148
- // staking was added, after the P1 audit removed 9 dead framework/agent-
149
- // schedule tools, after memory_publish was removed for promising a
150
- // marketplace that doesn't exist, after stake_auto_restake was added,
151
- // after claim_vested_rewards was added to close the gap where the
152
- // stake-lifecycle notification told users to "run claimVestedRewards" but
153
- // no MCP tool by that name existed, after code_session_save was added so
154
- // coding sessions persist the same way deep_research already auto-saves
155
- // research, and after list_projects was added alongside `workspaceProject`
156
- // support on vault_save/agent_spawn - MCP tools can now file into the same
157
- // Projects the webapp Agents page organizes by, resolved/auto-created
158
- // server-side via POST /projects/resolve; ALL_TOOLS.length is the only
159
- // number that can't lie). Per-category counts above are best-effort
160
- // documentation, not load-bearing anywhere.
145
+ // Total tool count: use ALL_TOOLS.length, not a hardcoded number here -
146
+ // this comment has drifted stale repeatedly. Per-category counts above
147
+ // are best-effort docs, not load-bearing.
148
+ finch_status_js_1.FINCH_STATUS_TOOL, // 1 - finch_status now lives in finch-status.ts, not OS_TOOLS - see that file's header for why
161
149
  ];
162
150
  exports.HANDLER_MAP = new Map([
163
151
  ...market_js_1.MARKET_TOOLS.map(t => [t.name, market_js_1.handleMarketTool]),
@@ -189,6 +177,7 @@ exports.HANDLER_MAP = new Map([
189
177
  ...chronicle_js_1.CHRONICLE_TOOLS.map(t => [t.name, (n, a) => (0, chronicle_js_1.handleChronicle)(n, a)]),
190
178
  ...packets_js_1.PACKET_TOOLS.map(t => [t.name, (n, a) => (0, packets_js_1.handlePacket)(n, a)]),
191
179
  ...stake_js_1.STAKE_TOOLS.map(t => [t.name, stake_js_1.handleStakeTool]),
180
+ [finch_status_js_1.FINCH_STATUS_TOOL.name, ((_name, _args) => (0, finch_status_js_1.handleFinchStatus)(exports.ALL_TOOLS.length))],
192
181
  ]);
193
182
  // `instructions` is returned in the initialize result - it tells the client
194
183
  // (and, through it, the model) what this server is and how to reach for it.
@@ -218,10 +207,7 @@ exports.server = new index_js_1.Server({ name: "finch", version: PKG_VERSION },
218
207
  // withAnnotations attaches MCP behavioural hints (readOnly/destructive/etc) so
219
208
  // clients can auto-run reads and prompt before destructive/fund-moving calls.
220
209
  exports.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
221
- tools: [
222
- finch_status_js_1.FINCH_STATUS_TOOL,
223
- ...(0, annotations_js_1.withAnnotations)((0, tool_filter_js_1.filterTools)(exports.ALL_TOOLS)),
224
- ],
210
+ tools: (0, annotations_js_1.withAnnotations)((0, tool_filter_js_1.filterTools)(exports.ALL_TOOLS)),
225
211
  }));
226
212
  // MCP Resources - vault entries surface as `finch://vault/<key>`.
227
213
  // Clients can pull them via the standard resource flow instead of a Tool
@@ -289,9 +275,6 @@ exports.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (reques
289
275
  return { content: [{ type: "text", text: `Error: ${err.message}` }], isError: true };
290
276
  }
291
277
  }
292
- if (name === "finch_status") {
293
- return finch_status_js_1.handleFinchStatus();
294
- }
295
278
  const handler = exports.HANDLER_MAP.get(name);
296
279
  if (!handler) {
297
280
  return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
@@ -299,20 +282,15 @@ exports.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (reques
299
282
  try {
300
283
  // Hard timeout guard: a hung tool must never wedge the client session.
301
284
  // 180s covers deep_research's multi-stage pipeline with margin.
302
- const HARD_TIMEOUT_MS = 180_000;
285
+ const HARD_TIMEOUT_MS = 180000;
303
286
  let timer;
304
287
  const timeoutPromise = new Promise((_, reject) => {
305
- timer = setTimeout(() => reject(new Error(
306
- `Tool timed out after 180s (${name}). The operation may still complete server-side - check the app before re-running.`
307
- )), HARD_TIMEOUT_MS);
288
+ timer = setTimeout(() => reject(new Error(`Tool timed out after 180s (${name}). The operation may still complete server-side - check the app before re-running.`)), HARD_TIMEOUT_MS);
308
289
  });
309
- const result = await Promise.race([
310
- handler(name, args),
311
- timeoutPromise,
312
- ]);
290
+ const result = await Promise.race([handler(name, args), timeoutPromise]);
313
291
  clearTimeout(timer);
314
292
  if (result)
315
- return (0, finch_output_js_1.processResponse)(result);
293
+ return (0, finch_output_js_1.processResponse)(result) ?? result;
316
294
  return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
317
295
  }
318
296
  catch (err) {
@@ -1,20 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.filterTools = filterTools;
4
- // Tool-subset filter. Each user keeps the full handler map (so any tool
5
- // can still be invoked by name if explicitly referenced), but the LIST
6
- // response sent to MCP clients is trimmed based on FINCH_TOOLS.
4
+ // Tool-subset filter. Full handler map stays intact (any tool can still be
5
+ // invoked by name); only the LIST response sent to clients is trimmed by
6
+ // FINCH_TOOLS, to cut LLM context cost. Default = "core".
7
7
  //
8
- // Default = "core" - runtime essentials only (memory, vault, agents,
9
- // chronicle, status). Opt-in presets let token-conscious users cut LLM
10
- // context cost while keeping the full surface accessible by name:
11
- //
12
- // FINCH_TOOLS=core runtime essentials (memory, vault, agents)
13
- // FINCH_TOOLS=defi Base + market + DeFi execution
14
- // FINCH_TOOLS=research research + memory + vault
15
- // FINCH_TOOLS=memory memory + vault + agents only
16
- // FINCH_TOOLS=memory,defi comma-separated combination
17
- // FINCH_TOOLS=all every registered tool
8
+ // FINCH_TOOLS=core|defi|research|memory|all, or comma-separated
18
9
  //
19
10
  // Unknown presets fall back to all to avoid silently hiding tools.
20
11
  // Each preset's regex is checked against every tool name in ALL_TOOLS by
@@ -1,18 +1,9 @@
1
1
  "use strict";
2
- // Static heuristic scanner for Solidity audit grounding.
3
- //
4
- // The LLM-only audit path was dangerous: a model could say "this contract
5
- // looks safe" without any structural basis, and a user might trust that
6
- // claim with real funds. This module runs a regex/pattern scan over the
7
- // source BEFORE the LLM call and forces the LLM to address each finding
8
- // (either confirm the risk or explain why it's a false positive). The
9
- // resulting report leads with "automated static checks", then "LLM review
10
- // over those findings", then a mandatory disclaimer.
11
- //
12
- // This is NOT a substitute for a professional audit. It catches obvious
13
- // antipatterns. Subtle vulnerabilities (reentrancy guarded by state ordering,
14
- // signature replay, oracle manipulation under non-obvious conditions) still
15
- // need human review or specialized tooling (Slither, Mythril, Echidna).
2
+ // Static heuristic scanner for Solidity audit grounding - runs a
3
+ // regex/pattern scan before the LLM call and forces it to address each
4
+ // finding, instead of letting it claim "looks safe" with no structural
5
+ // basis. Not a substitute for a professional audit (Slither/Mythril/
6
+ // Echidna) - catches obvious antipatterns only.
16
7
  Object.defineProperty(exports, "__esModule", { value: true });
17
8
  exports.AUDIT_DISCLAIMER = void 0;
18
9
  exports.staticScanSolidity = staticScanSolidity;
@@ -9,6 +9,7 @@ const llm_js_1 = require("../llm.js");
9
9
  const local_vault_js_1 = require("../local-vault.js");
10
10
  const memory_js_1 = require("./memory.js");
11
11
  const project_js_1 = require("../project.js");
12
+ const _zod_helpers_js_1 = require("../_zod-helpers.js");
12
13
  // ─── Agent Learning Memory (v3.25) ──────────────────────────────────────────
13
14
  // After every agent_update, an LLM reviews the new progress in context of the
14
15
  // agent's goal + prior learnings to extract a single repeatable insight. The
@@ -104,23 +105,10 @@ function withAgentLock(agentName, fn) {
104
105
  });
105
106
  return next;
106
107
  }
107
- // list_agents/hire_agent were removed (not disabled - deleted) because their
108
- // backend routes, /agents/list and /agents/hire, were never actually
109
- // registered in app/convex/http.ts - only dangling section-header comments
110
- // exist there, no matching http.route(...) call. Every call to either tool
111
- // always 404'd. Re-add them (and the matching handler blocks below) only
112
- // alongside building those two routes for real.
113
- //
114
- // Same story, same fix, applied again here: agent_identity, agent_schedule,
115
- // agent_unschedule, agent_pause, agent_resume and agent_runs called
116
- // /agents/identity, /agents/schedule, /agents/unschedule, /agents/pause and
117
- // /agents/runs - none of which exist in http.ts, and none of which have any
118
- // backing data model in schema.ts (no agent-identity table, no scheduler,
119
- // no run-history table; there isn't even a cron for it in crons.ts). This
120
- // isn't a missing route, it's an unbuilt subsystem, so the tools were
121
- // removed rather than stubbed. Re-add only alongside building that
122
- // autonomous-scheduling backend for real: identity/schedule storage, a cron
123
- // that actually wakes agents up, and run-history writes.
108
+ // list_agents/hire_agent/agent_identity/agent_schedule/agent_unschedule/
109
+ // agent_pause/agent_resume/agent_runs deleted - their backend routes never
110
+ // existed (dangling comments in http.ts, no actual route/data model). Not a
111
+ // missing route, an unbuilt subsystem. Re-add only alongside building it.
124
112
  exports.AGENT_TOOLS = [
125
113
  {
126
114
  name: "agent_spawn",
@@ -226,9 +214,9 @@ function buildAgentLedger(name, versions) {
226
214
  }
227
215
  async function handleAgentTool(name, args) {
228
216
  if (name === "agent_spawn") {
229
- const parsed = SpawnAgentSchema.safeParse(args);
230
- if (!parsed.success)
231
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
217
+ const parsed = (0, _zod_helpers_js_1.parseOrError)(SpawnAgentSchema, args);
218
+ if (!parsed.ok)
219
+ return parsed.error;
232
220
  const { name: agentName, goal, context, workspaceProject } = parsed.data;
233
221
  const content = JSON.stringify({
234
222
  goal,
@@ -269,9 +257,9 @@ async function handleAgentTool(name, args) {
269
257
  };
270
258
  }
271
259
  if (name === "agent_recall") {
272
- const parsed = RecallAgentSchema.safeParse(args);
273
- if (!parsed.success)
274
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
260
+ const parsed = (0, _zod_helpers_js_1.parseOrError)(RecallAgentSchema, args);
261
+ if (!parsed.ok)
262
+ return parsed.error;
275
263
  const localVault = (0, local_vault_js_1.getLocalVaultConfig)();
276
264
  let data;
277
265
  try {
@@ -334,9 +322,9 @@ async function handleAgentTool(name, args) {
334
322
  return { content: [{ type: "text", text: lines.join("\n") }] };
335
323
  }
336
324
  if (name === "agent_update") {
337
- const parsed = UpdateAgentSchema.safeParse(args);
338
- if (!parsed.success)
339
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
325
+ const parsed = (0, _zod_helpers_js_1.parseOrError)(UpdateAgentSchema, args);
326
+ if (!parsed.ok)
327
+ return parsed.error;
340
328
  const { name: agentName, progress, findings, status = "active", nextStep } = parsed.data;
341
329
  return withAgentLock(agentName, async () => {
342
330
  const localVault = (0, local_vault_js_1.getLocalVaultConfig)();