@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
@@ -7,6 +7,7 @@ const zod_1 = require("zod");
7
7
  const convex_js_1 = require("../convex.js");
8
8
  const wallet_js_1 = require("../wallet.js");
9
9
  const token_decimals_js_1 = require("../token-decimals.js");
10
+ const _zod_helpers_js_1 = require("../_zod-helpers.js");
10
11
  exports.DEFI_TOOLS = [
11
12
  {
12
13
  name: "get_defi_yields",
@@ -223,9 +224,9 @@ async function handleDefiTool(name, args) {
223
224
  // list either, so this case was already unreachable dead code - no Tool
224
225
  // registers the name "analyze_wallet" for the MCP dispatcher to route to.
225
226
  case "get_defi_yields": {
226
- const parsed = DefiYieldsSchema.safeParse(args ?? {});
227
- if (!parsed.success)
228
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
227
+ const parsed = (0, _zod_helpers_js_1.parseOrError)(DefiYieldsSchema, args ?? {});
228
+ if (!parsed.ok)
229
+ return parsed.error;
229
230
  const { token, minApy = 1, limit = 20 } = parsed.data;
230
231
  let pools;
231
232
  try {
@@ -1,18 +1,8 @@
1
1
  "use strict";
2
- // Insider transactions from SEC Form 4 parsed from the filings themselves.
3
- //
4
- // Two accuracy traps make naive versions of this actively misleading, and both
5
- // are handled here:
6
- //
7
- // 1. A company's filing feed contains Form 4s where that company is the
8
- // REPORTING OWNER of a stake in some other issuer. Those are not insider
9
- // trades in the ticker you asked about, so every filing is checked against
10
- // `issuerTradingSymbol` before it counts.
11
- //
12
- // 2. Most "insider selling" is transaction code F — shares withheld to cover
13
- // tax on vesting RSUs. It is automatic, not a decision, and carries no
14
- // signal. Only P (open-market purchase) and S (open-market sale) reflect a
15
- // choice, so they are reported separately from everything else.
2
+ // Insider transactions from SEC Form 4. Two traps handled: (1) filings
3
+ // where the company is REPORTING OWNER of another issuer are filtered via
4
+ // `issuerTradingSymbol`; (2) code F (tax withholding on RSU vests, no real
5
+ // signal) is separated from P/S (actual buy/sell decisions).
16
6
  Object.defineProperty(exports, "__esModule", { value: true });
17
7
  exports.INSIDER_TOOLS = void 0;
18
8
  exports.buildInsiderSummary = buildInsiderSummary;
@@ -8,6 +8,7 @@ const llm_js_1 = require("../llm.js");
8
8
  const memory_js_1 = require("./memory.js");
9
9
  const enrichment_router_js_1 = require("../enrichment-router.js");
10
10
  const signal_gate_js_1 = require("../signal-gate.js");
11
+ const _zod_helpers_js_1 = require("../_zod-helpers.js");
11
12
  exports.INSIGHT_TOOLS = [
12
13
  {
13
14
  name: "ask_finch",
@@ -449,9 +450,9 @@ async function handleInsightTool(name, args) {
449
450
  return { content: [{ type: "text", text: contextHeader + finalAnswer }] };
450
451
  }
451
452
  if (name === "market_thesis") {
452
- const parsed = MarketThesisSchema.safeParse(args);
453
- if (!parsed.success)
454
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
453
+ const parsed = (0, _zod_helpers_js_1.parseOrError)(MarketThesisSchema, args);
454
+ if (!parsed.ok)
455
+ return parsed.error;
455
456
  const { token, context } = parsed.data;
456
457
  const priceData = await fetchVerifiedPrice(token);
457
458
  // Hard guard: without a verified live price, refuse to generate. Better
@@ -517,9 +518,9 @@ async function handleInsightTool(name, args) {
517
518
  };
518
519
  }
519
520
  if (name === "trade_plan") {
520
- const parsed = TradePlanSchema.safeParse(args);
521
- if (!parsed.success)
522
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
521
+ const parsed = (0, _zod_helpers_js_1.parseOrError)(TradePlanSchema, args);
522
+ if (!parsed.ok)
523
+ return parsed.error;
523
524
  const { token, side = "long", portfolioSize, riskTolerance = "moderate", timeframe } = parsed.data;
524
525
  const priceData = await fetchVerifiedPrice(token);
525
526
  // Hard guard: trade plans without verified live price = entry/SL/TP
@@ -11,6 +11,7 @@ exports.handleMarketTool = handleMarketTool;
11
11
  const zod_1 = require("zod");
12
12
  const _http_cache_js_1 = require("../_http-cache.js");
13
13
  const dex_pair_js_1 = require("../dex-pair.js");
14
+ const _zod_helpers_js_1 = require("../_zod-helpers.js");
14
15
  const COINGECKO = "https://api.coingecko.com/api/v3";
15
16
  const SYMBOL_TO_ID = {
16
17
  BTC: "bitcoin", ETH: "ethereum", SOL: "solana", BNB: "binancecoin",
@@ -291,9 +292,9 @@ async function fetchMarketSnapshot() {
291
292
  async function handleMarketTool(name, args) {
292
293
  switch (name) {
293
294
  case "get_market_data": {
294
- const parsed = GetMarketDataSchema.safeParse(args ?? {});
295
- if (!parsed.success)
296
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
295
+ const parsed = (0, _zod_helpers_js_1.parseOrError)(GetMarketDataSchema, args ?? {});
296
+ if (!parsed.ok)
297
+ return parsed.error;
297
298
  const { token } = parsed.data;
298
299
  if (token) {
299
300
  const resolved = await resolveTokenId(token);
@@ -347,9 +348,9 @@ async function handleMarketTool(name, args) {
347
348
  return { content: [{ type: "text", text: lines.join("\n") }] };
348
349
  }
349
350
  case "get_token_data": {
350
- const parsed = GetTokenDataSchema.safeParse(args);
351
- if (!parsed.success)
352
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
351
+ const parsed = (0, _zod_helpers_js_1.parseOrError)(GetTokenDataSchema, args);
352
+ if (!parsed.ok)
353
+ return parsed.error;
353
354
  const q = parsed.data.question;
354
355
  // Try to extract a known symbol first, then fall back to search
355
356
  const upperQ = q.toUpperCase();
@@ -379,9 +380,9 @@ async function handleMarketTool(name, args) {
379
380
  };
380
381
  }
381
382
  case "compare_tokens": {
382
- const parsed = CompareTokensSchema.safeParse(args);
383
- if (!parsed.success)
384
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
383
+ const parsed = (0, _zod_helpers_js_1.parseOrError)(CompareTokensSchema, args);
384
+ if (!parsed.ok)
385
+ return parsed.error;
385
386
  const syms = parsed.data.tokens.map(t => t.toUpperCase());
386
387
  const ids = syms.map(s => SYMBOL_TO_ID[s]).filter(Boolean);
387
388
  const unknown = syms.filter(s => !SYMBOL_TO_ID[s]);
@@ -447,9 +448,9 @@ async function handleMarketTool(name, args) {
447
448
  return { content: [{ type: "text", text: lines.join("\n") }], structuredContent: buildMarketOverview(global, fg, trendCoins) };
448
449
  }
449
450
  case "token_history": {
450
- const parsed = TokenHistorySchema.safeParse(args);
451
- if (!parsed.success)
452
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
451
+ const parsed = (0, _zod_helpers_js_1.parseOrError)(TokenHistorySchema, args);
452
+ if (!parsed.ok)
453
+ return parsed.error;
453
454
  const days = parsed.data.days ?? 7;
454
455
  const resolved = await resolveTokenId(parsed.data.token);
455
456
  if (!resolved)
@@ -496,9 +497,9 @@ async function handleMarketTool(name, args) {
496
497
  };
497
498
  }
498
499
  case "get_base_token_data": {
499
- const parsed = GetBaseTokenDataSchema.safeParse(args);
500
- if (!parsed.success)
501
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
500
+ const parsed = (0, _zod_helpers_js_1.parseOrError)(GetBaseTokenDataSchema, args);
501
+ if (!parsed.ok)
502
+ return parsed.error;
502
503
  const address = parsed.data.tokenAddress.toLowerCase();
503
504
  const [pair, coingeckoId] = await Promise.all([
504
505
  fetchDexscreenerBaseToken(address),
@@ -50,6 +50,7 @@ const public_url_js_1 = require("../public-url.js");
50
50
  const local_vault_js_1 = require("../local-vault.js");
51
51
  const local_memory_js_1 = require("../local-memory.js");
52
52
  const _text_search_js_1 = require("../_text-search.js");
53
+ const _zod_helpers_js_1 = require("../_zod-helpers.js");
53
54
  // memory_extract and memory_consolidate used to run their own LLM calls here
54
55
  // (and a matching pair of Convex routes did the same server-side). Both are now
55
56
  // two-pass: the tool fetches and stores, the caller decides what the facts are
@@ -192,6 +193,17 @@ async function syncToSupermemory(content, metadata, sourceUrl) {
192
193
  console.error(`[memory] sync_failed: ${errMsg} | preview: "${preview}"`);
193
194
  });
194
195
  }
196
+ // A real auth failure must never be reported as "0 results found" - that
197
+ // reads as "your account genuinely has nothing saved" when the actual
198
+ // problem is the token/key was rejected. Only genuinely transient errors
199
+ // (network hiccup, the other RRF side still succeeding) degrade silently
200
+ // to an empty contribution - an auth failure is rethrown so it reaches the
201
+ // user instead of being swallowed. Found live: memory_search/memory_context/
202
+ // memory_insight/memory_consolidate all share this retrieval path, so a
203
+ // single fix here closes the same bug across all four tools.
204
+ function isAuthFailure(err) {
205
+ return err instanceof Error && /Authentication required/i.test(err.message);
206
+ }
195
207
  async function searchSupermemory(query, limit = 10) {
196
208
  try {
197
209
  const local = (0, local_memory_js_1.getLocalMemoryConfig)();
@@ -200,7 +212,9 @@ async function searchSupermemory(query, limit = 10) {
200
212
  const data = await (0, convex_js_1.callConvex)("/memory/search", "POST", { q: query, n: limit }, "memory_search");
201
213
  return data?.results ?? [];
202
214
  }
203
- catch {
215
+ catch (err) {
216
+ if (isAuthFailure(err))
217
+ throw err;
204
218
  return [];
205
219
  }
206
220
  }
@@ -218,7 +232,9 @@ async function lexicalSearch(query, limit = 30) {
218
232
  rank: typeof r.rank === "number" ? r.rank : idx,
219
233
  }));
220
234
  }
221
- catch {
235
+ catch (err) {
236
+ if (isAuthFailure(err))
237
+ throw err;
222
238
  return [];
223
239
  }
224
240
  }
@@ -302,7 +318,7 @@ exports.MEMORY_TOOLS = [
302
318
  "Unlike vault_save, memory_add is instant: no versioning, no type required. " +
303
319
  "Use for notes, decisions, preferences, or anything you want to find later. " +
304
320
  "Pass sourceUrl to fetch and index any web page, GitHub repo, or Notion page automatically - " +
305
- "searchable in ~30s. Retrieval is hybrid (semantic embeddings fused with keyword search, reranked by recency and pinned weight) - " +
321
+ "searchable in ~30s. Retrieval is full-text (keyword) search, not embeddings - " +
306
322
  "'what did I say about ETH yield?' finds notes containing those words or close variants, " +
307
323
  "not unrelated phrasing with the same meaning. " +
308
324
  "Auto-deduplicates: identical content in your recent 50 memories is skipped (override with force:true). " +
@@ -324,11 +340,11 @@ exports.MEMORY_TOOLS = [
324
340
  },
325
341
  {
326
342
  name: "memory_search",
327
- description: "Search your stored memories. Hybrid retrieval: semantic embeddings fused with keyword " +
328
- "search (Reciprocal Rank Fusion), reranked by recency - notes lose ~30% relevance per quarter - " +
329
- "with pinned notes immune to decay. Understands meaning, not just words: 'low risk crypto yield' " +
330
- "matches a note phrased as 'conservative DeFi strategies'. Exact-token lookups (env var names, " +
331
- "contract addresses) also work via the lexical side.",
343
+ description: "Full-text (keyword) search over your stored memories, with 90-day time-decay weighting so " +
344
+ "recent notes outrank stale ones with similar wording. Good for exact-token lookups (env var " +
345
+ "names, contract addresses, IDs, specific phrases) - it does not understand meaning, so " +
346
+ "'low risk crypto yield' will not match a note phrased as 'conservative DeFi strategies' " +
347
+ "unless the words themselves overlap.",
332
348
  inputSchema: {
333
349
  type: "object",
334
350
  properties: {
@@ -342,7 +358,7 @@ exports.MEMORY_TOOLS = [
342
358
  name: "memory_context",
343
359
  description: "Retrieve the most relevant memories for a topic, formatted as AI-ready context. " +
344
360
  "Use at the start of research tasks to prime with everything stored about a topic. " +
345
- "Hybrid retrieval: embeddings + keyword, fused and reranked - phrasing can differ from the note. " +
361
+ "Uses full-text (keyword) search, not embeddings - phrase your topic with the words " +
346
362
  "you expect were actually used when the memory was saved.",
347
363
  inputSchema: {
348
364
  type: "object",
@@ -522,9 +538,9 @@ function buildMemoryList(tag, results) {
522
538
  async function handleMemoryTool(name, args) {
523
539
  switch (name) {
524
540
  case "memory_add": {
525
- const parsed = AddSchema.safeParse(args);
526
- if (!parsed.success)
527
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
541
+ const parsed = (0, _zod_helpers_js_1.parseOrError)(AddSchema, args);
542
+ if (!parsed.ok)
543
+ return parsed.error;
528
544
  const { content, title, tags, sourceUrl, force } = parsed.data;
529
545
  // `sourceUrl` is not stored, it is fetched and indexed — by the local
530
546
  // memory server on the user's own machine when local mode is on. An
@@ -568,18 +584,9 @@ async function handleMemoryTool(name, args) {
568
584
  // dedupes even before supermemory has indexed the first one.
569
585
  if (!sourceUrl)
570
586
  rememberRecentHash(hash, data?.id ?? "saved", title);
571
- // ─── Conflict hint (not a proof) ──────────────────────────────────
572
- // Finch never runs its own LLM call to judge this - "two-pass, no API
573
- // key needed" is deliberate (see this file's header comment), and an
574
- // autonomous contradiction call would break that. Instead this just
575
- // resurfaces whatever memory_search's own retrieval already ranks as
576
- // related to the content just saved, so the CALLING model - already
577
- // reasoning about this exact save, in the same turn, no extra API
578
- // cost - can judge for itself whether the two actually conflict and
579
- // decide what to do (mark the old one superseded, fold both into
580
- // memory_consolidate, or just note the new one is the current one).
581
- // Best-effort: a search hiccup here must never fail or block the save
582
- // that already succeeded above.
587
+ // Conflict hint, not a proof - no LLM call server-side (two-pass, no
588
+ // API key). Resurfaces related existing memories so the calling
589
+ // model decides. Best-effort, must never block the save above.
583
590
  let conflictNote = "";
584
591
  try {
585
592
  const related = await hybridMemorySearch(content, 8);
@@ -623,9 +630,9 @@ async function handleMemoryTool(name, args) {
623
630
  };
624
631
  }
625
632
  case "memory_search": {
626
- const parsed = SearchSchema.safeParse(args);
627
- if (!parsed.success)
628
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
633
+ const parsed = (0, _zod_helpers_js_1.parseOrError)(SearchSchema, args);
634
+ if (!parsed.ok)
635
+ return parsed.error;
629
636
  const { query, limit = 10 } = parsed.data;
630
637
  // Over-fetch so post-decay ranking still has enough material.
631
638
  const overfetch = Math.min(50, Math.max(limit * 2, 20));
@@ -705,9 +712,9 @@ async function handleMemoryTool(name, args) {
705
712
  return { content: [{ type: "text", text: [header, "", ...rows, promotionHint].filter(Boolean).join("\n") }], structuredContent: buildMemorySearch(query, decayed) };
706
713
  }
707
714
  case "memory_context": {
708
- const parsed = ContextSchema.safeParse(args);
709
- if (!parsed.success)
710
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
715
+ const parsed = (0, _zod_helpers_js_1.parseOrError)(ContextSchema, args);
716
+ if (!parsed.ok)
717
+ return parsed.error;
711
718
  const { topic, limit = 8 } = parsed.data;
712
719
  // v3.25.1: use hybrid retrieval so context loading picks up exact-token
713
720
  // matches (env var names, model IDs, contract addresses) that semantic-
@@ -758,16 +765,16 @@ async function handleMemoryTool(name, args) {
758
765
  `• memory_add (URL indexing) - ✅`,
759
766
  `• Google Drive / Gmail / Notion - connect at finchagentic.com`,
760
767
  ``,
761
- `**Capabilities:** Hybrid retrieval (semantic + keyword, RRF fusion) with time-decay ranking; pinned notes bypass decay.`,
768
+ `**Capabilities:** Full-text (keyword) search with 90-day time-decay ranking - not embeddings, no vector search.`,
762
769
  ].join("\n"),
763
770
  }],
764
771
  structuredContent: buildMemoryProfile(data),
765
772
  };
766
773
  }
767
774
  case "memory_list": {
768
- const parsed = ListSchema.safeParse(args ?? {});
769
- if (!parsed.success)
770
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
775
+ const parsed = (0, _zod_helpers_js_1.parseOrError)(ListSchema, args ?? {});
776
+ if (!parsed.ok)
777
+ return parsed.error;
771
778
  const { limit = 20, tag } = parsed.data;
772
779
  const localList = (0, local_memory_js_1.getLocalMemoryConfig)();
773
780
  let results;
@@ -796,9 +803,9 @@ async function handleMemoryTool(name, args) {
796
803
  return { content: [{ type: "text", text: [header, "", ...rows].join("\n") }], structuredContent: buildMemoryList(tag, results) };
797
804
  }
798
805
  case "memory_delete": {
799
- const parsed = DeleteMemSchema.safeParse(args);
800
- if (!parsed.success)
801
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
806
+ const parsed = (0, _zod_helpers_js_1.parseOrError)(DeleteMemSchema, args);
807
+ if (!parsed.ok)
808
+ return parsed.error;
802
809
  if (args?.confirm !== true) {
803
810
  return {
804
811
  content: [{
@@ -827,9 +834,9 @@ async function handleMemoryTool(name, args) {
827
834
  return { content: [{ type: "text", text: `🗑️ Memory deleted: \`${parsed.data.id}\`` }] };
828
835
  }
829
836
  case "memory_insight": {
830
- const parsed = InsightSchema.safeParse(args);
831
- if (!parsed.success)
832
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
837
+ const parsed = (0, _zod_helpers_js_1.parseOrError)(InsightSchema, args);
838
+ if (!parsed.ok)
839
+ return parsed.error;
833
840
  const { topic, depth = "standard" } = parsed.data;
834
841
  const memLimit = depth === "deep" ? 15 : depth === "quick" ? 5 : 8;
835
842
  // v3.25.1: hybrid retrieval for memory side (was semantic-only) so the
@@ -915,9 +922,9 @@ async function handleMemoryTool(name, args) {
915
922
  return { content: [{ type: "text", text: lines.filter(l => l !== undefined).join("\n") }] };
916
923
  }
917
924
  case "memory_extract": {
918
- const parsed = ExtractSchema.safeParse(args);
919
- if (!parsed.success)
920
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
925
+ const parsed = (0, _zod_helpers_js_1.parseOrError)(ExtractSchema, args);
926
+ if (!parsed.ok)
927
+ return parsed.error;
921
928
  const { text, facts, source = "extract" } = parsed.data;
922
929
  // ── PASS 1: hand the text back with the extraction rubric ───────────
923
930
  // Deciding what counts as a fact is judgement about *this user's*
@@ -972,9 +979,9 @@ async function handleMemoryTool(name, args) {
972
979
  };
973
980
  }
974
981
  case "memory_consolidate": {
975
- const parsed = ConsolidateSchema.safeParse(args);
976
- if (!parsed.success)
977
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
982
+ const parsed = (0, _zod_helpers_js_1.parseOrError)(ConsolidateSchema, args);
983
+ if (!parsed.ok)
984
+ return parsed.error;
978
985
  const { topic, limit = 12, summary } = parsed.data;
979
986
  const localConsolidateCfg = (0, local_memory_js_1.getLocalMemoryConfig)();
980
987
  // ── PASS 2: save the caller's merged version ────────────────────────
@@ -1037,22 +1044,10 @@ async function handleMemoryTool(name, args) {
1037
1044
  }],
1038
1045
  };
1039
1046
  }
1040
- // memory_publish was removed - it was broken two levels deep. The tool
1041
- // called POST /vault/save with isPublic/authorName fields that the
1042
- // handler silently dropped (only type/title/content/key/contentType/
1043
- // agentId/tags/commitMsg/metadata are forwarded to vault.saveEntry - see
1044
- // app/convex/http.ts), so `published` was never actually set true; the
1045
- // real publishEntry mutation (POST /vault/publish) existed but this tool
1046
- // never called it. And even a correctly-wired publish would have done
1047
- // nothing observable: nothing anywhere reads the `published` field for
1048
- // cross-user browsing - no /vault/community route (dangling comment
1049
- // only, same pattern as the other removed routes), no backend query for
1050
- // it, no "Memory Marketplace" page in app/src. The tool asked users to
1051
- // accept an "IRREVERSIBLE, PUBLIC" risk for a marketplace that doesn't
1052
- // exist at any layer. Re-add only alongside building the actual
1053
- // discovery path: a community-browse query + route + UI that reads
1054
- // `published: true` entries. vault_unpublish is left in place - it's a
1055
- // correct, harmless no-op until then.
1047
+ // memory_publish removed - broken (silently dropped isPublic/authorName,
1048
+ // never actually set `published`) AND pointless even if fixed - no
1049
+ // Memory Marketplace page/query ever reads that field. vault_unpublish
1050
+ // stays, it's a harmless no-op. Re-add only alongside building discovery.
1056
1051
  default:
1057
1052
  return null;
1058
1053
  }
@@ -5,6 +5,7 @@ exports.buildMonitorList = buildMonitorList;
5
5
  exports.handleMonitorTool = handleMonitorTool;
6
6
  const zod_1 = require("zod");
7
7
  const convex_js_1 = require("../convex.js");
8
+ const _zod_helpers_js_1 = require("../_zod-helpers.js");
8
9
  const TRIGGER_BASE = "https://api.trigger.dev/api/v1";
9
10
  const MONITOR_TASK_ID = "finch-monitor";
10
11
  const MONITOR_INPUT_SCHEMA = {
@@ -104,9 +105,9 @@ function buildMonitorList(schedules, configs = {}) {
104
105
  }
105
106
  async function handleMonitorTool(name, args) {
106
107
  if (name === "schedule_research") {
107
- const parsed = CreateSchema.safeParse(args);
108
- if (!parsed.success)
109
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
108
+ const parsed = (0, _zod_helpers_js_1.parseOrError)(CreateSchema, args);
109
+ if (!parsed.ok)
110
+ return parsed.error;
110
111
  const key = getKey();
111
112
  if (!key)
112
113
  return noKeyMsg();
@@ -282,9 +283,9 @@ async function handleMonitorTool(name, args) {
282
283
  }
283
284
  }
284
285
  if (name === "cancel_monitor") {
285
- const parsed = CancelSchema.safeParse(args);
286
- if (!parsed.success)
287
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
286
+ const parsed = (0, _zod_helpers_js_1.parseOrError)(CancelSchema, args);
287
+ if (!parsed.ok)
288
+ return parsed.error;
288
289
  if (args?.confirm !== true) {
289
290
  return {
290
291
  content: [{
package/dist/tools/os.js CHANGED
@@ -2,20 +2,17 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.OS_TOOLS = void 0;
4
4
  exports.handleOsTool = handleOsTool;
5
- const convex_js_1 = require("../convex.js");
6
- const token_gate_js_1 = require("../token-gate.js");
7
- const wallet_js_1 = require("../wallet.js");
8
5
  const local_memory_js_1 = require("../local-memory.js");
9
6
  const config_js_1 = require("../config.js");
10
7
  const CONVEX_SITE = process.env.FINCH_CONVEX_URL ?? "https://valuable-fish-533.convex.site";
8
+ // finch_status moved to ../finch-status.ts (Sep 2026) - the old version here
9
+ // made 6 Promise.allSettled backend calls and silently defaulted every field
10
+ // to zero/basic on ANY failure (including a 401), which read as "empty but
11
+ // healthy" instead of surfacing a real auth problem. The new one makes one
12
+ // cheap authed call and reports the real state. Kept out of OS_TOOLS/this
13
+ // switch entirely (not just filtered at list time) so there's exactly one
14
+ // place this tool is defined - server.ts wires it in directly.
11
15
  exports.OS_TOOLS = [
12
- {
13
- name: "finch_status",
14
- description: "Full runtime dashboard - memory size, persistent agents, active automations, recent vault research, " +
15
- "execution scores, and your tier. Like `htop` for your AI runtime. " +
16
- "Run this to see what's running and what state your runtime is currently holding.",
17
- inputSchema: { type: "object", properties: {}, required: [] },
18
- },
19
16
  {
20
17
  name: "finch_diagnostics",
21
18
  description: "Health check for all Finch services - Convex backend, Firecrawl, Supermemory, and configured API keys. " +
@@ -38,81 +35,6 @@ exports.OS_TOOLS = [
38
35
  ];
39
36
  async function handleOsTool(name, args) {
40
37
  switch (name) {
41
- case "finch_status": {
42
- // Respect local memory mode here too - querying Convex's /memory/profile
43
- // when writes have been going to a local supermemory server instead
44
- // would report a stale/zero count (the exact bug memory_profile's own
45
- // handler was fixed to avoid).
46
- const localStatusCfg = (0, local_memory_js_1.getLocalMemoryConfig)();
47
- const memoryProfileCall = localStatusCfg
48
- ? (0, local_memory_js_1.localMemoryProfile)(localStatusCfg)
49
- : (0, convex_js_1.callConvex)("/memory/profile", "GET", undefined, "memory_profile");
50
- const [tierResult, walletResult, memRes, autoRes, vaultRes, agentsRes] = await Promise.allSettled([
51
- (0, token_gate_js_1.getTier)(),
52
- (0, wallet_js_1.getOrCreateWallet)(),
53
- memoryProfileCall,
54
- (0, convex_js_1.callConvex)("/automations/list", "GET", undefined, "list_automations"),
55
- (0, convex_js_1.callConvex)("/vault/list?type=research&limit=5", "GET", undefined, "vault_list"),
56
- (0, convex_js_1.callConvex)("/vault/list?type=memory&limit=20", "GET", undefined, "vault_list"),
57
- ]);
58
- const tier = tierResult.status === "fulfilled" ? tierResult.value : "basic";
59
- const wallet = walletResult.status === "fulfilled" ? walletResult.value : null;
60
- const mem = memRes.status === "fulfilled" ? memRes.value : null;
61
- const autos = autoRes.status === "fulfilled" ? autoRes.value : null;
62
- const vault = vaultRes.status === "fulfilled" ? vaultRes.value : null;
63
- const agents = agentsRes.status === "fulfilled" ? agentsRes.value : null;
64
- const automations = autos?.automations ?? [];
65
- const activeAutos = automations.filter((a) => a.status === "active");
66
- const vaultEntries = vault?.entries ?? [];
67
- // Persistent agents live in vault as type=memory with key prefix "agent/"
68
- const persistentAgents = (agents?.entries ?? []).filter((e) => typeof e.key === "string" && e.key.startsWith("agent/"));
69
- const memTotal = mem?.total ?? 0;
70
- const memStatus = mem?.status ?? "unknown";
71
- const tierLabel = tier === "holder"
72
- ? "\u{1F7E2} **Holder** - premium tools unlocked"
73
- : "⚪ **Basic** - hold FINCH on Base to unlock premium tools";
74
- const walletShort = wallet
75
- ? `${wallet.address.slice(0, 6)}...${wallet.address.slice(-4)}`
76
- : "not configured";
77
- const lines = [
78
- `**Finch Runtime - System Status**`,
79
- `────────────────────────────────`,
80
- ``,
81
- `🔑 **Tier** ${tierLabel}`,
82
- `👛 **Wallet** ${walletShort}`,
83
- ``,
84
- `🧠 **Memory** ${memStatus === "ok" ? "✅" : "⚠️"} ${memTotal} entries · Space: ${mem?.space ?? "-"}`,
85
- `🤖 **Agents** ${persistentAgents.length} persistent agent${persistentAgents.length === 1 ? "" : "s"} in vault`,
86
- `⚡ **Automations** ${activeAutos.length} active of ${automations.length} total`,
87
- `📚 **Vault** ${vaultEntries.length} recent research entries`,
88
- ``,
89
- ];
90
- if (activeAutos.length > 0) {
91
- lines.push(`**Active Automations:**`);
92
- for (const a of activeAutos.slice(0, 5)) {
93
- const next = a.nextRunAt ? ` · next ${new Date(a.nextRunAt).toUTCString()}` : "";
94
- lines.push(` • ${a.name} - ${a.triggerType}${next}`);
95
- }
96
- lines.push("");
97
- }
98
- if (persistentAgents.length > 0) {
99
- lines.push(`**Persistent Agents:**`);
100
- for (const a of persistentAgents.slice(0, 5)) {
101
- const name = a.key.replace(/^agent\//, "");
102
- lines.push(` • ${name} - v${a.version ?? 1}`);
103
- }
104
- lines.push("");
105
- }
106
- if (vaultEntries.length > 0) {
107
- lines.push(`**Recent Research:**`);
108
- for (const e of vaultEntries) {
109
- lines.push(` • [${e.agentId ?? "vault"}] ${e.title}`);
110
- }
111
- lines.push("");
112
- }
113
- lines.push(`💡 Run \`deep_research query: "..."\` to launch multi-agent research · \`agent_spawn\` to start a persistent agent`);
114
- return { content: [{ type: "text", text: lines.join("\n") }] };
115
- }
116
38
  case "finch_diagnostics": {
117
39
  const CONVEX_URL = process.env.FINCH_CONVEX_URL ?? "https://valuable-fish-533.convex.site";
118
40
  // Ping root of each service — any non-5xx means the host is up
@@ -5,6 +5,7 @@ exports.handleResearchTool = handleResearchTool;
5
5
  const zod_1 = require("zod");
6
6
  const convex_js_1 = require("../convex.js");
7
7
  const public_url_js_1 = require("../public-url.js");
8
+ const _zod_helpers_js_1 = require("../_zod-helpers.js");
8
9
  const FC_BASE = "https://api.firecrawl.dev/v1";
9
10
  exports.RESEARCH_TOOLS = [
10
11
  {
@@ -98,9 +99,9 @@ async function basicFetch(url) {
98
99
  }
99
100
  async function handleResearchTool(name, args) {
100
101
  if (name === "web_scrape") {
101
- const parsed = ScrapeSchema.safeParse(args);
102
- if (!parsed.success)
103
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
102
+ const parsed = (0, _zod_helpers_js_1.parseOrError)(ScrapeSchema, args);
103
+ if (!parsed.ok)
104
+ return parsed.error;
104
105
  const { url, focus } = parsed.data;
105
106
  const unsafe = await (0, public_url_js_1.assertPublicUrl)(url);
106
107
  if (unsafe) {
@@ -118,9 +119,9 @@ async function handleResearchTool(name, args) {
118
119
  return { content: [{ type: "text", text: `**${url}**${focusNote}${body}\n\n_Source: ${source}_` }] };
119
120
  }
120
121
  if (name === "web_search") {
121
- const parsed = SearchSchema.safeParse(args);
122
- if (!parsed.success)
123
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
122
+ const parsed = (0, _zod_helpers_js_1.parseOrError)(SearchSchema, args);
123
+ if (!parsed.ok)
124
+ return parsed.error;
124
125
  const { query, limit = 5 } = parsed.data;
125
126
  // Priority 1: user BYOK direct path. Priority 2: Finch proxy.
126
127
  // Same data shape so the formatting code below is identical for both.