@finchagentic/mcp 4.7.2 → 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 (50) 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/local-vault.js +5 -12
  17. package/dist/output-schemas.js +3 -12
  18. package/dist/project.js +4 -13
  19. package/dist/server.js +13 -35
  20. package/dist/tool-filter.js +4 -13
  21. package/dist/tools/_solidity-scan.js +5 -14
  22. package/dist/tools/agents.js +14 -26
  23. package/dist/tools/deep-research-constants.js +33 -0
  24. package/dist/tools/deep-research-firecrawl.js +88 -0
  25. package/dist/tools/deep-research-planning.js +249 -0
  26. package/dist/tools/deep-research-synthesis.js +343 -0
  27. package/dist/tools/deep-research-text.js +158 -0
  28. package/dist/tools/deep-research-tools.js +90 -0
  29. package/dist/tools/deep-research.js +47 -938
  30. package/dist/tools/defi.js +4 -3
  31. package/dist/tools/insider.js +4 -14
  32. package/dist/tools/insight.js +7 -6
  33. package/dist/tools/market.js +16 -15
  34. package/dist/tools/memory.js +43 -61
  35. package/dist/tools/monitor.js +7 -6
  36. package/dist/tools/os.js +7 -85
  37. package/dist/tools/research.js +7 -6
  38. package/dist/tools/rh-mcp-constants.js +65 -0
  39. package/dist/tools/rh-mcp-dex.js +107 -0
  40. package/dist/tools/rh-mcp-provider.js +127 -0
  41. package/dist/tools/rh-mcp-resolve.js +137 -0
  42. package/dist/tools/rh-mcp-risk.js +230 -0
  43. package/dist/tools/rh-mcp-safety.js +234 -0
  44. package/dist/tools/rh-mcp-swap.js +181 -0
  45. package/dist/tools/rh-mcp-tools.js +137 -0
  46. package/dist/tools/rh-mcp.js +75 -1191
  47. package/dist/tools/scanner.js +10 -9
  48. package/dist/tools/stake.js +7 -20
  49. package/dist/tools/vault.js +52 -59
  50. 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
@@ -197,7 +198,9 @@ async function syncToSupermemory(content, metadata, sourceUrl) {
197
198
  // problem is the token/key was rejected. Only genuinely transient errors
198
199
  // (network hiccup, the other RRF side still succeeding) degrade silently
199
200
  // to an empty contribution - an auth failure is rethrown so it reaches the
200
- // user instead of being swallowed.
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.
201
204
  function isAuthFailure(err) {
202
205
  return err instanceof Error && /Authentication required/i.test(err.message);
203
206
  }
@@ -315,7 +318,7 @@ exports.MEMORY_TOOLS = [
315
318
  "Unlike vault_save, memory_add is instant: no versioning, no type required. " +
316
319
  "Use for notes, decisions, preferences, or anything you want to find later. " +
317
320
  "Pass sourceUrl to fetch and index any web page, GitHub repo, or Notion page automatically - " +
318
- "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 - " +
319
322
  "'what did I say about ETH yield?' finds notes containing those words or close variants, " +
320
323
  "not unrelated phrasing with the same meaning. " +
321
324
  "Auto-deduplicates: identical content in your recent 50 memories is skipped (override with force:true). " +
@@ -337,11 +340,11 @@ exports.MEMORY_TOOLS = [
337
340
  },
338
341
  {
339
342
  name: "memory_search",
340
- description: "Search your stored memories. Hybrid retrieval: semantic embeddings fused with keyword " +
341
- "search (Reciprocal Rank Fusion), reranked by recency - notes lose ~30% relevance per quarter - " +
342
- "with pinned notes immune to decay. Understands meaning, not just words: 'low risk crypto yield' " +
343
- "matches a note phrased as 'conservative DeFi strategies'. Exact-token lookups (env var names, " +
344
- "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.",
345
348
  inputSchema: {
346
349
  type: "object",
347
350
  properties: {
@@ -355,7 +358,7 @@ exports.MEMORY_TOOLS = [
355
358
  name: "memory_context",
356
359
  description: "Retrieve the most relevant memories for a topic, formatted as AI-ready context. " +
357
360
  "Use at the start of research tasks to prime with everything stored about a topic. " +
358
- "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 " +
359
362
  "you expect were actually used when the memory was saved.",
360
363
  inputSchema: {
361
364
  type: "object",
@@ -535,9 +538,9 @@ function buildMemoryList(tag, results) {
535
538
  async function handleMemoryTool(name, args) {
536
539
  switch (name) {
537
540
  case "memory_add": {
538
- const parsed = AddSchema.safeParse(args);
539
- if (!parsed.success)
540
- 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;
541
544
  const { content, title, tags, sourceUrl, force } = parsed.data;
542
545
  // `sourceUrl` is not stored, it is fetched and indexed — by the local
543
546
  // memory server on the user's own machine when local mode is on. An
@@ -581,18 +584,9 @@ async function handleMemoryTool(name, args) {
581
584
  // dedupes even before supermemory has indexed the first one.
582
585
  if (!sourceUrl)
583
586
  rememberRecentHash(hash, data?.id ?? "saved", title);
584
- // ─── Conflict hint (not a proof) ──────────────────────────────────
585
- // Finch never runs its own LLM call to judge this - "two-pass, no API
586
- // key needed" is deliberate (see this file's header comment), and an
587
- // autonomous contradiction call would break that. Instead this just
588
- // resurfaces whatever memory_search's own retrieval already ranks as
589
- // related to the content just saved, so the CALLING model - already
590
- // reasoning about this exact save, in the same turn, no extra API
591
- // cost - can judge for itself whether the two actually conflict and
592
- // decide what to do (mark the old one superseded, fold both into
593
- // memory_consolidate, or just note the new one is the current one).
594
- // Best-effort: a search hiccup here must never fail or block the save
595
- // 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.
596
590
  let conflictNote = "";
597
591
  try {
598
592
  const related = await hybridMemorySearch(content, 8);
@@ -636,9 +630,9 @@ async function handleMemoryTool(name, args) {
636
630
  };
637
631
  }
638
632
  case "memory_search": {
639
- const parsed = SearchSchema.safeParse(args);
640
- if (!parsed.success)
641
- 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;
642
636
  const { query, limit = 10 } = parsed.data;
643
637
  // Over-fetch so post-decay ranking still has enough material.
644
638
  const overfetch = Math.min(50, Math.max(limit * 2, 20));
@@ -718,9 +712,9 @@ async function handleMemoryTool(name, args) {
718
712
  return { content: [{ type: "text", text: [header, "", ...rows, promotionHint].filter(Boolean).join("\n") }], structuredContent: buildMemorySearch(query, decayed) };
719
713
  }
720
714
  case "memory_context": {
721
- const parsed = ContextSchema.safeParse(args);
722
- if (!parsed.success)
723
- 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;
724
718
  const { topic, limit = 8 } = parsed.data;
725
719
  // v3.25.1: use hybrid retrieval so context loading picks up exact-token
726
720
  // matches (env var names, model IDs, contract addresses) that semantic-
@@ -771,16 +765,16 @@ async function handleMemoryTool(name, args) {
771
765
  `• memory_add (URL indexing) - ✅`,
772
766
  `• Google Drive / Gmail / Notion - connect at finchagentic.com`,
773
767
  ``,
774
- `**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.`,
775
769
  ].join("\n"),
776
770
  }],
777
771
  structuredContent: buildMemoryProfile(data),
778
772
  };
779
773
  }
780
774
  case "memory_list": {
781
- const parsed = ListSchema.safeParse(args ?? {});
782
- if (!parsed.success)
783
- 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;
784
778
  const { limit = 20, tag } = parsed.data;
785
779
  const localList = (0, local_memory_js_1.getLocalMemoryConfig)();
786
780
  let results;
@@ -809,9 +803,9 @@ async function handleMemoryTool(name, args) {
809
803
  return { content: [{ type: "text", text: [header, "", ...rows].join("\n") }], structuredContent: buildMemoryList(tag, results) };
810
804
  }
811
805
  case "memory_delete": {
812
- const parsed = DeleteMemSchema.safeParse(args);
813
- if (!parsed.success)
814
- 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;
815
809
  if (args?.confirm !== true) {
816
810
  return {
817
811
  content: [{
@@ -840,9 +834,9 @@ async function handleMemoryTool(name, args) {
840
834
  return { content: [{ type: "text", text: `🗑️ Memory deleted: \`${parsed.data.id}\`` }] };
841
835
  }
842
836
  case "memory_insight": {
843
- const parsed = InsightSchema.safeParse(args);
844
- if (!parsed.success)
845
- 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;
846
840
  const { topic, depth = "standard" } = parsed.data;
847
841
  const memLimit = depth === "deep" ? 15 : depth === "quick" ? 5 : 8;
848
842
  // v3.25.1: hybrid retrieval for memory side (was semantic-only) so the
@@ -928,9 +922,9 @@ async function handleMemoryTool(name, args) {
928
922
  return { content: [{ type: "text", text: lines.filter(l => l !== undefined).join("\n") }] };
929
923
  }
930
924
  case "memory_extract": {
931
- const parsed = ExtractSchema.safeParse(args);
932
- if (!parsed.success)
933
- 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;
934
928
  const { text, facts, source = "extract" } = parsed.data;
935
929
  // ── PASS 1: hand the text back with the extraction rubric ───────────
936
930
  // Deciding what counts as a fact is judgement about *this user's*
@@ -985,9 +979,9 @@ async function handleMemoryTool(name, args) {
985
979
  };
986
980
  }
987
981
  case "memory_consolidate": {
988
- const parsed = ConsolidateSchema.safeParse(args);
989
- if (!parsed.success)
990
- 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;
991
985
  const { topic, limit = 12, summary } = parsed.data;
992
986
  const localConsolidateCfg = (0, local_memory_js_1.getLocalMemoryConfig)();
993
987
  // ── PASS 2: save the caller's merged version ────────────────────────
@@ -1050,22 +1044,10 @@ async function handleMemoryTool(name, args) {
1050
1044
  }],
1051
1045
  };
1052
1046
  }
1053
- // memory_publish was removed - it was broken two levels deep. The tool
1054
- // called POST /vault/save with isPublic/authorName fields that the
1055
- // handler silently dropped (only type/title/content/key/contentType/
1056
- // agentId/tags/commitMsg/metadata are forwarded to vault.saveEntry - see
1057
- // app/convex/http.ts), so `published` was never actually set true; the
1058
- // real publishEntry mutation (POST /vault/publish) existed but this tool
1059
- // never called it. And even a correctly-wired publish would have done
1060
- // nothing observable: nothing anywhere reads the `published` field for
1061
- // cross-user browsing - no /vault/community route (dangling comment
1062
- // only, same pattern as the other removed routes), no backend query for
1063
- // it, no "Memory Marketplace" page in app/src. The tool asked users to
1064
- // accept an "IRREVERSIBLE, PUBLIC" risk for a marketplace that doesn't
1065
- // exist at any layer. Re-add only alongside building the actual
1066
- // discovery path: a community-browse query + route + UI that reads
1067
- // `published: true` entries. vault_unpublish is left in place - it's a
1068
- // 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.
1069
1051
  default:
1070
1052
  return null;
1071
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.
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ // Robinhood Chain (chainId 4663) constants shared across the rh-mcp-* modules.
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.RH_DEX_CHAIN = exports.DEXSCREENER = exports.RH_BLOCKSCOUT_V2 = exports.RH_STOCKS = exports.USDG_DECIMALS = exports.USDG_ADDRESS = exports.RH_UNIVERSAL_ROUTER = exports.RH_PERMIT2 = exports.NATIVE_ETH = exports.RH_EXPLORER = exports.RH_RPC = exports.RH_CHAIN_ID = void 0;
5
+ exports.rhRpcDisplay = rhRpcDisplay;
6
+ exports.RH_CHAIN_ID = 4663;
7
+ exports.RH_RPC = process.env.ROBINHOOD_RPC_URL ??
8
+ process.env.RH_RPC_URL ??
9
+ process.env.FINCH_RH_RPC_URL ??
10
+ "https://rpc.mainnet.chain.robinhood.com";
11
+ /** Host only, for display in tool output - never the full RH_RPC. An
12
+ * operator can point ROBINHOOD_RPC_URL/RH_RPC_URL at a keyed provider
13
+ * (same pattern app/convex just fixed for its own Alchemy-key leak risk -
14
+ * a `.../v2/<key>`-style URL echoed verbatim into a tool response lands the
15
+ * key in chat transcripts/client logs). Falls back to the raw value only
16
+ * if it isn't a parseable URL, which should never happen in practice. */
17
+ function rhRpcDisplay() {
18
+ try {
19
+ return new URL(exports.RH_RPC).host;
20
+ }
21
+ catch {
22
+ return "(configured)";
23
+ }
24
+ }
25
+ exports.RH_EXPLORER = "https://robinhoodchain.blockscout.com";
26
+ exports.NATIVE_ETH = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
27
+ exports.RH_PERMIT2 = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
28
+ exports.RH_UNIVERSAL_ROUTER = "0x8876789976decbfcbbbe364623c63652db8c0904";
29
+ /**
30
+ * Canonical Global Dollar (USDG) — RH chain's settlement stablecoin (same
31
+ * address as app/convex/_settlement.ts). Hardcoded rather than resolved via
32
+ * DexScreener ticker search: "USDG" is the exact symbol an imposter contract
33
+ * would spoof, and dexSearchByTicker ranks by pool liquidity, not authenticity
34
+ * — a fake pool with inflated liquidity could otherwise outrank the real token.
35
+ */
36
+ exports.USDG_ADDRESS = "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168";
37
+ exports.USDG_DECIMALS = 6;
38
+ /** ClawHood / Finch catalog (22 tokenized stocks — official "Robinhood Token" contracts). */
39
+ exports.RH_STOCKS = [
40
+ { address: "0xaf3d76f1834a1d425780943c99ea8a608f8a93f9", symbol: "AAPL", name: "Apple" },
41
+ { address: "0x86923f96303d656e4aa86d9d42d1e57ad2023fdc", symbol: "AMD", name: "AMD" },
42
+ { address: "0x12f190a9f9d7d37a250758b26824b97ce941bf54", symbol: "AMZN", name: "Amazon" },
43
+ { address: "0x822cc93ffd030293e9842c30bbd678f530701867", symbol: "BE", name: "Bloom Energy" },
44
+ { address: "0x6330d8c3178a418788df01a47479c0ce7ccf450b", symbol: "COIN", name: "Coinbase" },
45
+ { address: "0x5f10a1c971b69e47e059e1dc91901b59b3fb49c3", symbol: "CRWV", name: "CoreWeave" },
46
+ { address: "0x1b0e319c6a659f002271b69db8a7df2f911c153e", symbol: "GME", name: "GameStop" },
47
+ { address: "0x2e0847e8910a9732eb3fb1bb4b70a580adad4fe3", symbol: "GOOGL", name: "Alphabet" },
48
+ { address: "0xc72b96e0e48ecd4dc75e1e45396e26300bc39681", symbol: "INTC", name: "Intel" },
49
+ { address: "0xc0d6457c16cc70d6790dd43521c899c87ce02f35", symbol: "META", name: "Meta" },
50
+ { address: "0xe93237c50d904957cf27e7b1133b510c669c2e74", symbol: "MSFT", name: "Microsoft" },
51
+ { address: "0xff080c8ce2e5feadaca0da81314ae59d232d4afd", symbol: "MU", name: "Micron" },
52
+ { address: "0xe0444ef8bf4ed74f74fd73686e2ddf4c1c5591e8", symbol: "NFLX", name: "Netflix" },
53
+ { address: "0xd0601ce157db5bdc3162bbac2a2c8af5320d9eec", symbol: "NVDA", name: "NVIDIA" },
54
+ { address: "0xb0992820e760d836549ba69bc7598b4af75dee03", symbol: "ORCL", name: "Oracle" },
55
+ { address: "0x894e1ec2d74ffe5aef8dc8a9e84686accb964f2a", symbol: "PLTR", name: "Palantir" },
56
+ { address: "0xd5f3879160bc7c32ebb4dc785f8a4f505888de68", symbol: "QQQ", name: "Invesco QQQ" },
57
+ { address: "0xb90a19ff0af67f7779aff50a882a9cff42446400", symbol: "SNDK", name: "Sandisk" },
58
+ { address: "0x4a0e65a3eccec6dbe60ae065f2e7bb85fae35eea", symbol: "SPCX", name: "SPACEX" },
59
+ { address: "0x117cc2133c37b721f49de2a7a74833232b3b4c0c", symbol: "SPY", name: "SPDR S&P 500" },
60
+ { address: "0x322f0929c4625ed5bad873c95208d54e1c003b2d", symbol: "TSLA", name: "Tesla" },
61
+ { address: "0xd917b029c761d264c6a312bbbcda868658ef86a6", symbol: "USAR", name: "USA Rare Earth" },
62
+ ];
63
+ exports.RH_BLOCKSCOUT_V2 = "https://robinhoodchain.blockscout.com/api/v2";
64
+ exports.DEXSCREENER = "https://api.dexscreener.com";
65
+ exports.RH_DEX_CHAIN = "robinhood";