@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
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ // Shared constants for the deep_research pipeline (deep-research-*.ts).
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.STOPWORDS = exports.FRESH_TRIGGER_RE = exports.NEWS_DOMAIN_BOOST_RE = exports.DOMAIN_TIER_BONUS = exports.EXCERPT_TOP_CHUNKS = exports.EXCERPT_CHARS_PER_CHUNK = exports.MAX_PER_DOMAIN = exports.FC_BASE = void 0;
5
+ exports.FC_BASE = "https://api.firecrawl.dev/v1";
6
+ exports.MAX_PER_DOMAIN = 2; // source diversity - cap hits per domain
7
+ exports.EXCERPT_CHARS_PER_CHUNK = 600;
8
+ exports.EXCERPT_TOP_CHUNKS = 4; // pick top N relevant chunks per source
9
+ // Quality scoring - higher = more trustworthy primary source
10
+ exports.DOMAIN_TIER_BONUS = [
11
+ [/\.gov(\b|\/|$)/i, 4],
12
+ [/\.edu(\b|\/|$)/i, 3],
13
+ [/(?:nature|science|nih|arxiv|acm|ieee|sciencedirect)\.(?:org|com)/i, 3],
14
+ // Crypto primary-source boost - these are the authoritative data sources
15
+ // for protocol TVL, yields, prices, and on-chain analytics. Rank above
16
+ // generic news for crypto queries.
17
+ [/(?:defillama|tokenterminal|coingecko|coinmarketcap|dune|messari|artemis)\.(?:com|fi)/i, 3],
18
+ [/(?:etherscan|basescan|arbiscan|solscan|polygonscan|optimistic\.etherscan)\.(?:io|com)/i, 2],
19
+ [/(?:reuters|apnews|bbc|economist|ft|wsj|bloomberg)\.com/i, 2],
20
+ [/(?:coindesk|theblock|cointelegraph|decrypt|theinformation)\.(?:co|com|io)/i, 1],
21
+ [/(?:wikipedia|github|stackoverflow)\.(?:org|com)/i, 1],
22
+ [/(?:medium|substack|reddit|twitter|x)\.com/i, -1],
23
+ ];
24
+ // News-domain bonus - applied additionally in fresh mode
25
+ exports.NEWS_DOMAIN_BOOST_RE = /(?:reuters|apnews|bbc|economist|ft|wsj|bloomberg|cnbc|theverge|techcrunch|axios|coindesk|theinformation|nytimes|guardian|aljazeera)\.com/i;
26
+ // Keywords that signal a time-sensitive query - trigger fresh mode auto.
27
+ exports.FRESH_TRIGGER_RE = /\b(today|tonight|tomorrow|yesterday|this week|last week|past week|latest|breaking|just now|recent|currently|now|live|happening|this month|last month|past month|past \d+ days?|last \d+ days?|q[1-4]|h[12]|202[6-9])\b/i;
28
+ exports.STOPWORDS = new Set([
29
+ "the", "and", "for", "with", "that", "this", "what", "when", "where", "how", "why",
30
+ "have", "has", "had", "are", "was", "were", "will", "would", "could", "should", "does",
31
+ "did", "being", "been", "from", "into", "over", "under", "about", "into", "than", "then",
32
+ "your", "yours", "their", "they", "them", "there", "here", "just", "also", "more", "most",
33
+ ]);
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ // Firecrawl search/scrape - BYOK direct call with a Finch backend-proxy
3
+ // fallback for signed-in users without their own key.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.normalizeSearchHits = normalizeSearchHits;
6
+ exports.fcSearch = fcSearch;
7
+ exports.fcScrape = fcScrape;
8
+ const convex_js_1 = require("../convex.js");
9
+ const deep_research_constants_js_1 = require("./deep-research-constants.js");
10
+ /**
11
+ * Firecrawl returns either a flat array or a keyed object of result groups
12
+ * (`{ web: [...], news: [...] }`). The proxy path assumed the flat shape and
13
+ * handed an object to `.forEach`, which threw — invisible until now because
14
+ * without an API key the run always died at the planner first.
15
+ */
16
+ function normalizeSearchHits(raw) {
17
+ if (Array.isArray(raw))
18
+ return raw;
19
+ if (raw && typeof raw === "object") {
20
+ const out = [];
21
+ for (const group of Object.values(raw)) {
22
+ if (Array.isArray(group))
23
+ out.push(...group);
24
+ }
25
+ return out;
26
+ }
27
+ return [];
28
+ }
29
+ async function fcSearch(query, limit) {
30
+ // BYOK path - direct call to Firecrawl with user's key. Fastest, no proxy hop.
31
+ const key = process.env.FIRECRAWL_API_KEY;
32
+ if (key) {
33
+ try {
34
+ const res = await fetch(`${deep_research_constants_js_1.FC_BASE}/search`, {
35
+ method: "POST",
36
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
37
+ body: JSON.stringify({ query, limit }),
38
+ signal: AbortSignal.timeout(15000),
39
+ });
40
+ if (res.ok) {
41
+ const data = (await res.json());
42
+ return normalizeSearchHits(data.data);
43
+ }
44
+ }
45
+ catch { /* fall through */ }
46
+ }
47
+ // Backend-proxy path - session-authed; Finch covers Firecrawl cost.
48
+ try {
49
+ const data = await (0, convex_js_1.callConvex)("/research/firecrawl-search", "POST", { query, limit }, "web_search", 20000);
50
+ return normalizeSearchHits(data?.results);
51
+ }
52
+ catch {
53
+ return [];
54
+ }
55
+ }
56
+ async function fcScrape(url) {
57
+ const key = process.env.FIRECRAWL_API_KEY;
58
+ if (key) {
59
+ try {
60
+ const res = await fetch(`${deep_research_constants_js_1.FC_BASE}/scrape`, {
61
+ method: "POST",
62
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
63
+ body: JSON.stringify({ url, formats: ["markdown"], onlyMainContent: true }),
64
+ signal: AbortSignal.timeout(20000),
65
+ });
66
+ if (res.ok) {
67
+ const data = (await res.json());
68
+ const md = data.data?.markdown;
69
+ if (md) {
70
+ const meta = data.data?.metadata;
71
+ const publishedAt = meta?.publishedAt ?? meta?.ogPublishedTime ?? meta?.["article:published_time"];
72
+ return { markdown: md, publishedAt };
73
+ }
74
+ }
75
+ }
76
+ catch { /* fall through */ }
77
+ }
78
+ // Backend-proxy path. Returns markdown only - no metadata extraction yet,
79
+ // which means continueFrom can't auto-date proxied scrapes. Acceptable
80
+ // tradeoff for now; markdown is the primary signal.
81
+ try {
82
+ const data = await (0, convex_js_1.callConvex)("/research/firecrawl-scrape", "POST", { url }, "web_scrape", 25000);
83
+ if (data?.markdown)
84
+ return { markdown: data.markdown };
85
+ }
86
+ catch { /* swallow */ }
87
+ return null;
88
+ }
@@ -0,0 +1,249 @@
1
+ "use strict";
2
+ // Query planning: sub-query derivation, LLM planners (flat + multi-angle),
3
+ // the deep-mode adversarial critic, and gap-finding reflection.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.deriveSubQueries = deriveSubQueries;
6
+ exports.planQueries = planQueries;
7
+ exports.planAngles = planAngles;
8
+ exports.runCritic = runCritic;
9
+ exports.reflectAndExtend = reflectAndExtend;
10
+ const llm_js_1 = require("../llm.js");
11
+ const deep_research_text_js_1 = require("./deep-research-text.js");
12
+ /**
13
+ * Sub-queries without an LLM.
14
+ *
15
+ * The caller should normally pass `queries` — it is a model and plans better
16
+ * than any heuristic. This exists so the evidence-pack path still works with
17
+ * zero configuration, rather than failing when no API key is set.
18
+ */
19
+ function deriveSubQueries(query, n, focus, freshMode) {
20
+ const base = query.trim().replace(/\?+$/, "");
21
+ const recency = freshMode ? ` ${new Date().getFullYear()} latest` : "";
22
+ const lenses = focus
23
+ ? [focus, `${focus} risks`, `${focus} data`]
24
+ : ["overview", "latest developments", "risks and criticism", "data and numbers", "expert analysis"];
25
+ const out = [`${base}${recency}`];
26
+ for (const lens of lenses) {
27
+ if (out.length >= Math.max(2, n))
28
+ break;
29
+ out.push(`${base} ${lens}${recency}`);
30
+ }
31
+ return out;
32
+ }
33
+ async function planQueries(query, n, focus, priorContext, freshMode) {
34
+ const focusNote = focus ? ` Focus angle: ${focus}.` : "";
35
+ const sys = "You are a research planner. Output strict JSON only - no preamble, no markdown.";
36
+ const freshNote = freshMode
37
+ ? `
38
+
39
+ ⏱ FRESH MODE - last ${freshMode.days} days:
40
+ - Add a recency hint to most sub-questions: "in ${(0, deep_research_text_js_1.currentYearMonth)()}", "last ${freshMode.days} days", "this week", "as of ${(0, deep_research_text_js_1.todayISO)()}", etc.
41
+ - Prefer queries that surface news / press releases / X posts over evergreen background.
42
+ - Skip generic background - the user wants what's CURRENT, not historical.
43
+ - At least 70% of sub-questions must include a date or recency token.`
44
+ : "";
45
+ const continuationNote = priorContext
46
+ ? `
47
+
48
+ ⚠️ CONTINUATION MODE - there is a PRIOR research report on this topic:
49
+
50
+ """
51
+ ${priorContext.slice(0, 2500)}
52
+ """
53
+
54
+ Your sub-questions must focus on:
55
+ 1. UPDATES - what has changed since the prior report (new releases, news, data revisions)
56
+ 2. GAPS - angles the prior report explicitly listed as open questions or follow-ups
57
+ 3. NEW developments - entities/events the prior report doesn't mention
58
+ 4. VERIFICATION - claims the prior report flagged as low-confidence or single-source
59
+
60
+ DO NOT re-tread material already well-covered in the prior report. The user already has those answers.`
61
+ : "";
62
+ const user = `Decompose this research question into ${n} sub-questions that together cover the topic from different angles.${focusNote}${continuationNote}${freshNote}
63
+
64
+ Rules:
65
+ - Each sub-question must be a standalone web search query, under 90 chars.
66
+ - Cover different facets: definition, current state, key actors, comparisons, counterarguments, recent news, forward outlook.
67
+ - No duplicates, no near-paraphrases.
68
+
69
+ ENTITY-HUNTING - at least HALF of your sub-questions must target queries likely to surface:
70
+ - Specific company / product / framework names (e.g., "LangGraph adoption stats", "Manus orchestration funding")
71
+ - Dollar amounts (acquisitions, funding rounds, revenue, ARR, market size)
72
+ - Benchmark numbers (% adoption, latency ms, accuracy scores, MMLU/HumanEval/SWE-bench results)
73
+ - Specific dates and timeline events (when X launched, when Y reached scale)
74
+ - Named studies / surveys / reports (e.g., "Anthropic Economic Index 2026", "a16z AI infrastructure report")
75
+
76
+ Bad: "what is X" → too generic, returns Wikipedia
77
+ Good: "X adoption rate enterprise 2026 survey" → returns concrete stats
78
+
79
+ Question: "${query}"
80
+
81
+ Return: {"queries": ["...", "..."]} - exactly ${n} items.`;
82
+ let raw = "";
83
+ try {
84
+ raw = await (0, llm_js_1.callLLM)(sys, user, 500, [], 30000);
85
+ }
86
+ catch {
87
+ return [query];
88
+ }
89
+ const parsed = (0, deep_research_text_js_1.safeParseJson)(raw, {});
90
+ if (!parsed.queries || !Array.isArray(parsed.queries))
91
+ return [query];
92
+ const queries = parsed.queries
93
+ .filter((s) => typeof s === "string")
94
+ .map((s) => s.trim())
95
+ .filter((s) => s.length > 0 && s.length <= 200)
96
+ .slice(0, n);
97
+ return queries.length > 0 ? queries : [query];
98
+ }
99
+ async function planAngles(query, n, focus, priorContext, freshMode) {
100
+ const focusNote = focus ? ` Focus angle: ${focus}.` : "";
101
+ const freshNote = freshMode
102
+ ? `\n\n⏱ FRESH MODE - last ${freshMode.days} days: bias each angle's queries toward news/recent press/dated reports.`
103
+ : "";
104
+ const continuationNote = priorContext
105
+ ? `\n\n⚠️ CONTINUATION - prior report exists. Angles must focus on UPDATES, GAPS, NEW developments since:\n"""\n${priorContext.slice(0, 1500)}\n"""`
106
+ : "";
107
+ const sys = "You are a research planner that decomposes topics into specialist angles. Output strict JSON only.";
108
+ const user = `Break this research topic into ${n} DIFFERENT specialist angles. Each angle gets its own 2-3 search queries.${focusNote}${continuationNote}${freshNote}
109
+
110
+ Topic: "${query}"
111
+
112
+ Pick angles that are GENUINELY different - not paraphrases of the same question. Good angle diversity examples:
113
+ - data / quantitative metrics
114
+ - competitive landscape
115
+ - team / governance / actors
116
+ - recent news / catalysts
117
+ - counterarguments / risks / criticism
118
+ - forward outlook / projections
119
+ - historical context / origins
120
+ - regulatory / policy angle
121
+ - technical / mechanism
122
+ - ecosystem partners
123
+
124
+ Pick the ${n} angles that most fit THIS specific topic. Each angle should have a label (3-6 words) and rationale (1 sentence). Queries must be standalone, ≤90 chars, entity-rich (specific names, dollar amounts, dates, benchmark numbers - NOT generic background).
125
+
126
+ Return strict JSON:
127
+ {
128
+ "angles": [
129
+ {
130
+ "label": "...",
131
+ "rationale": "...",
132
+ "queries": ["...", "..."]
133
+ }
134
+ ]
135
+ }
136
+
137
+ Exactly ${n} angles. 2-3 queries per angle. No duplicate queries across angles.`;
138
+ let raw = "";
139
+ try {
140
+ raw = await (0, llm_js_1.callLLM)(sys, user, 1200, [], 30000);
141
+ }
142
+ catch {
143
+ return [];
144
+ }
145
+ const parsed = (0, deep_research_text_js_1.safeParseJson)(raw, {});
146
+ if (!parsed.angles || !Array.isArray(parsed.angles))
147
+ return [];
148
+ const angles = [];
149
+ for (const a of parsed.angles) {
150
+ if (typeof a !== "object" || a === null)
151
+ continue;
152
+ const o = a;
153
+ if (typeof o.label !== "string" || typeof o.rationale !== "string")
154
+ continue;
155
+ if (!Array.isArray(o.queries))
156
+ continue;
157
+ const queries = o.queries
158
+ .filter((q) => typeof q === "string")
159
+ .map((q) => q.trim())
160
+ .filter((q) => q.length > 0 && q.length <= 200)
161
+ .slice(0, 3);
162
+ if (queries.length === 0)
163
+ continue;
164
+ angles.push({
165
+ label: o.label.trim().slice(0, 80),
166
+ rationale: o.rationale.trim().slice(0, 200),
167
+ queries,
168
+ });
169
+ if (angles.length >= n)
170
+ break;
171
+ }
172
+ return angles;
173
+ }
174
+ // Adversarial critic - only runs for depth=deep. Reads the draft + sources
175
+ // already gathered and produces a structured challenge block: single-source
176
+ // claims, contradictions across angles, speculation framed as fact. The
177
+ // final synthesizer is told to incorporate or refute these challenges
178
+ // explicitly, lifting the quality floor.
179
+ async function runCritic(query, draft, angles, sourceCount) {
180
+ const sys = "You are an adversarial research critic. Be terse, specific, and unsparing. Output markdown.";
181
+ const angleLabels = angles.map((a, i) => `${i + 1}. ${a.label}`).join("\n");
182
+ const user = `Original question: "${query}"
183
+
184
+ Specialist angles investigated:
185
+ ${angleLabels}
186
+
187
+ Total sources gathered: ${sourceCount}
188
+
189
+ Draft report:
190
+ """
191
+ ${draft.slice(0, 5000)}
192
+ """
193
+
194
+ Audit the draft for quality problems. Be specific and quote spans where possible.
195
+
196
+ Identify:
197
+ 1. **Single-source claims** - major assertions resting on one [N] citation that aren't widely corroborated
198
+ 2. **Contradictions** - places where different sources disagree but the draft doesn't surface the disagreement
199
+ 3. **Speculation framed as fact** - confident statements about future/intent/cause without evidence
200
+ 4. **Coverage gaps** - angles from the list above that got shallow treatment
201
+ 5. **Stale or weak sources** - dated material treated as current, blog posts cited as primary data
202
+
203
+ Output format:
204
+ ## Critic notes
205
+ - **[type]**: specific issue + relevant quote or claim. (1-2 sentences each)
206
+ - Skip categories with no findings - don't pad.
207
+
208
+ End with one line: "Net recommendation: [accept|revise|reject]"
209
+
210
+ If the draft is solid, say so plainly - don't manufacture issues.`;
211
+ try {
212
+ const notes = await (0, llm_js_1.callLLM)(sys, user, 1500, [], 60000);
213
+ return notes.trim();
214
+ }
215
+ catch {
216
+ return ""; // critic failure shouldn't block final synth
217
+ }
218
+ }
219
+ async function reflectAndExtend(query, draft, existingQueries, n) {
220
+ const sys = "You are a research auditor. Find gaps in a draft report and propose follow-up search queries. Output strict JSON.";
221
+ const user = `Original question: "${query}"
222
+
223
+ Queries already run:
224
+ ${existingQueries.map((q, i) => `${i + 1}. ${q}`).join("\n")}
225
+
226
+ Draft report:
227
+ """
228
+ ${draft.slice(0, 4000)}
229
+ """
230
+
231
+ Identify ${n} GAPS in the draft - angles missing, claims that need verification, counter-perspectives not represented, or recent developments not covered. For each gap, give ONE web search query (≤90 chars) that would fill it.
232
+
233
+ Return: {"gap_queries": ["...", "..."]} - exactly ${n} items, no duplicates of existing queries.`;
234
+ let raw = "";
235
+ try {
236
+ raw = await (0, llm_js_1.callLLM)(sys, user, 500, [], 30000);
237
+ }
238
+ catch {
239
+ return [];
240
+ }
241
+ const parsed = (0, deep_research_text_js_1.safeParseJson)(raw, {});
242
+ if (!parsed.gap_queries || !Array.isArray(parsed.gap_queries))
243
+ return [];
244
+ return parsed.gap_queries
245
+ .filter((s) => typeof s === "string")
246
+ .map((s) => s.trim())
247
+ .filter((s) => s.length > 0 && s.length <= 200)
248
+ .slice(0, n);
249
+ }