@finchagentic/mcp 4.6.0 → 4.6.2

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 (58) hide show
  1. package/README.md +7 -7
  2. package/package.json +5 -6
  3. package/dist/_http-cache.js +0 -96
  4. package/dist/agent-loop.js +0 -301
  5. package/dist/annotations.js +0 -122
  6. package/dist/cli.js +0 -1391
  7. package/dist/clink-input.js +0 -15
  8. package/dist/config.js +0 -132
  9. package/dist/convex.js +0 -175
  10. package/dist/dex-pair.js +0 -54
  11. package/dist/enrichment-router.js +0 -315
  12. package/dist/index.js +0 -258
  13. package/dist/llm.js +0 -298
  14. package/dist/local-memory-file.js +0 -147
  15. package/dist/local-memory.js +0 -135
  16. package/dist/local-vault.js +0 -454
  17. package/dist/output-schemas.js +0 -605
  18. package/dist/project.js +0 -36
  19. package/dist/prompts.js +0 -111
  20. package/dist/public-url.js +0 -107
  21. package/dist/resources.js +0 -111
  22. package/dist/server.js +0 -322
  23. package/dist/signal-gate.js +0 -57
  24. package/dist/token-decimals.js +0 -26
  25. package/dist/token-gate.js +0 -88
  26. package/dist/tool-filter.js +0 -53
  27. package/dist/tools/_solidity-scan.js +0 -313
  28. package/dist/tools/agents.js +0 -441
  29. package/dist/tools/automation.js +0 -354
  30. package/dist/tools/base-mcp.js +0 -466
  31. package/dist/tools/base.js +0 -283
  32. package/dist/tools/chronicle.js +0 -268
  33. package/dist/tools/coder.js +0 -94
  34. package/dist/tools/deep-research.js +0 -1421
  35. package/dist/tools/defi.js +0 -292
  36. package/dist/tools/equity.js +0 -372
  37. package/dist/tools/events.js +0 -182
  38. package/dist/tools/github.js +0 -564
  39. package/dist/tools/insider.js +0 -264
  40. package/dist/tools/insight.js +0 -630
  41. package/dist/tools/market.js +0 -555
  42. package/dist/tools/memory.js +0 -1044
  43. package/dist/tools/miroshark.js +0 -350
  44. package/dist/tools/monitor.js +0 -319
  45. package/dist/tools/os.js +0 -236
  46. package/dist/tools/packets.js +0 -296
  47. package/dist/tools/research-chain.js +0 -226
  48. package/dist/tools/research-compare.js +0 -280
  49. package/dist/tools/research.js +0 -188
  50. package/dist/tools/rh-bridge.js +0 -148
  51. package/dist/tools/rh-mcp.js +0 -1448
  52. package/dist/tools/rh-orders.js +0 -556
  53. package/dist/tools/scanner.js +0 -564
  54. package/dist/tools/stake.js +0 -369
  55. package/dist/tools/vault.js +0 -1020
  56. package/dist/tools/wallet.js +0 -200
  57. package/dist/types.js +0 -2
  58. package/dist/wallet.js +0 -372
@@ -1,188 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RESEARCH_TOOLS = void 0;
4
- exports.handleResearchTool = handleResearchTool;
5
- const zod_1 = require("zod");
6
- const convex_js_1 = require("../convex.js");
7
- const public_url_js_1 = require("../public-url.js");
8
- const FC_BASE = "https://api.firecrawl.dev/v1";
9
- exports.RESEARCH_TOOLS = [
10
- {
11
- name: "web_scrape",
12
- description: "Fetch and extract clean readable content from any URL - returns markdown. " +
13
- "Use when an agent needs to read an article, docs page, GitHub repo, or any web page. " +
14
- "Set FIRECRAWL_API_KEY for best quality (firecrawl.dev). Falls back to basic fetch if not set.",
15
- inputSchema: {
16
- type: "object",
17
- properties: {
18
- url: { type: "string", description: "URL to fetch" },
19
- focus: { type: "string", description: "Optional: specific topic or section to extract from the page" },
20
- },
21
- required: ["url"],
22
- },
23
- },
24
- {
25
- name: "web_search",
26
- description: "Search the web and return raw results: titles, URLs, and snippets. No synthesis or analysis — " +
27
- "use this for quick lookups, finding sources, or fetching recent news. " +
28
- "For multi-source research with LLM synthesis, use deep_research instead. " +
29
- "Requires FIRECRAWL_API_KEY (firecrawl.dev).",
30
- inputSchema: {
31
- type: "object",
32
- properties: {
33
- query: { type: "string", description: "Search query" },
34
- limit: { type: "number", description: "Max results (default 5, max 10)" },
35
- },
36
- required: ["query"],
37
- },
38
- },
39
- ];
40
- const ScrapeSchema = zod_1.z.object({
41
- url: zod_1.z.string().url(),
42
- focus: zod_1.z.string().optional(),
43
- });
44
- const SearchSchema = zod_1.z.object({
45
- query: zod_1.z.string().min(1),
46
- limit: zod_1.z.number().int().min(1).max(10).optional(),
47
- });
48
- async function firecrawlScrape(url) {
49
- // Priority 1: user's own FIRECRAWL_API_KEY (BYOK fast-path, zero network detour).
50
- const key = process.env.FIRECRAWL_API_KEY;
51
- if (key) {
52
- try {
53
- const res = await fetch(`${FC_BASE}/scrape`, {
54
- method: "POST",
55
- headers: { "Content-Type": "application/json", "Authorization": `Bearer ${key}` },
56
- body: JSON.stringify({ url, formats: ["markdown"], onlyMainContent: true }),
57
- signal: AbortSignal.timeout(20000),
58
- });
59
- if (res.ok) {
60
- const data = await res.json();
61
- return data.data?.markdown ?? null;
62
- }
63
- }
64
- catch { /* fall through to backend proxy */ }
65
- }
66
- // Priority 2: route through Finch backend (session-token authed, backend
67
- // pays for the Firecrawl call). The backend returns 503 if it doesn't have
68
- // FIRECRAWL_API_KEY set either, in which case web_scrape silently falls
69
- // through to basicFetch below.
70
- try {
71
- const data = await (0, convex_js_1.callConvex)("/research/firecrawl-scrape", "POST", { url }, "web_scrape", 25000);
72
- if (data?.markdown)
73
- return data.markdown;
74
- }
75
- catch { /* fall through */ }
76
- return null;
77
- }
78
- async function basicFetch(url) {
79
- try {
80
- const res = await fetch(url, {
81
- headers: { "User-Agent": "Mozilla/5.0 (compatible; FinchBot/1.0)" },
82
- signal: AbortSignal.timeout(10000),
83
- });
84
- if (!res.ok)
85
- return null;
86
- const html = await res.text();
87
- return html
88
- .replace(/<script[\s\S]*?<\/script>/gi, "")
89
- .replace(/<style[\s\S]*?<\/style>/gi, "")
90
- .replace(/<[^>]+>/g, " ")
91
- .replace(/\s+/g, " ")
92
- .trim()
93
- .slice(0, 4000);
94
- }
95
- catch {
96
- return null;
97
- }
98
- }
99
- async function handleResearchTool(name, args) {
100
- 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 };
104
- const { url, focus } = parsed.data;
105
- const unsafe = await (0, public_url_js_1.assertPublicUrl)(url);
106
- if (unsafe) {
107
- return { content: [{ type: "text", text: (0, public_url_js_1.refuseUrlText)(url, unsafe) }], isError: true };
108
- }
109
- let content = await firecrawlScrape(url);
110
- const source = content ? "Firecrawl" : "basic fetch";
111
- if (!content)
112
- content = await basicFetch(url);
113
- if (!content) {
114
- return { content: [{ type: "text", text: `Could not fetch ${url} - page may require JavaScript or block crawlers.` }], isError: true };
115
- }
116
- const focusNote = focus ? `\n\n_Focus: ${focus}_\n\n` : "\n\n";
117
- const body = content.length > 6000 ? content.slice(0, 6000) + "\n\n…(truncated)" : content;
118
- return { content: [{ type: "text", text: `**${url}**${focusNote}${body}\n\n_Source: ${source}_` }] };
119
- }
120
- 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 };
124
- const { query, limit = 5 } = parsed.data;
125
- // Priority 1: user BYOK direct path. Priority 2: Finch proxy.
126
- // Same data shape so the formatting code below is identical for both.
127
- const key = process.env.FIRECRAWL_API_KEY;
128
- let results = null;
129
- let lastErr = null;
130
- if (key) {
131
- try {
132
- const res = await fetch(`${FC_BASE}/search`, {
133
- method: "POST",
134
- headers: { "Content-Type": "application/json", "Authorization": `Bearer ${key}` },
135
- body: JSON.stringify({ query, limit }),
136
- signal: AbortSignal.timeout(15000),
137
- });
138
- if (res.ok) {
139
- const data = await res.json();
140
- results = data.data ?? [];
141
- }
142
- else {
143
- lastErr = `${res.status}: ${(await res.text()).slice(0, 200)}`;
144
- }
145
- }
146
- catch (err) {
147
- lastErr = err?.message ?? "BYOK search failed";
148
- }
149
- }
150
- if (!results) {
151
- try {
152
- const data = await (0, convex_js_1.callConvex)("/research/firecrawl-search", "POST", { query, limit }, "web_search", 30000);
153
- results = data?.results ?? [];
154
- }
155
- catch (err) {
156
- const msg = err?.message ?? String(err);
157
- return {
158
- content: [{
159
- type: "text",
160
- text: [
161
- `web_search failed.`,
162
- ``,
163
- `Tried backend proxy: ${msg.slice(0, 200)}`,
164
- lastErr ? `Direct call also failed: ${lastErr}` : "",
165
- ``,
166
- `Fix: either run \`/login\` so the Finch backend can pay, or set FIRECRAWL_API_KEY in your MCP env block.`,
167
- ].filter(Boolean).join("\n"),
168
- }],
169
- isError: true,
170
- };
171
- }
172
- }
173
- const safeResults = results ?? [];
174
- if (!safeResults.length) {
175
- return { content: [{ type: "text", text: `No results found for: "${query}"` }] };
176
- }
177
- const lines = [`🔍 **Web Search: "${query}"** - ${safeResults.length} results\n`];
178
- for (const r of safeResults) {
179
- lines.push(`**${r.title ?? r.url}**`);
180
- lines.push(r.url);
181
- if (r.description)
182
- lines.push(r.description.slice(0, 200));
183
- lines.push("");
184
- }
185
- return { content: [{ type: "text", text: lines.join("\n") }] };
186
- }
187
- return null;
188
- }
@@ -1,148 +0,0 @@
1
- "use strict";
2
- // Tokenized stock ↔ real equity comparison on Robinhood Chain.
3
- //
4
- // Robinhood Chain exists to trade equities 24/7 on-chain, but the two prices
5
- // drift: the token trades continuously against a shallow pool while the real
6
- // share only prices during market hours. That gap is the whole point of the
7
- // bridge — and it matters to both sides. An equity trader wants to know whether
8
- // after-hours exposure is fairly priced; an on-chain trader wants to know when
9
- // the token has detached from the asset it represents.
10
- //
11
- // The gap alone is not an opportunity: a 20% premium on a $100k pool you cannot
12
- // exit is a trap, so pool depth is reported next to every quote.
13
- Object.defineProperty(exports, "__esModule", { value: true });
14
- exports.BRIDGE_TOOLS = void 0;
15
- exports.handleBridgeTool = handleBridgeTool;
16
- const zod_1 = require("zod");
17
- const rh_mcp_js_1 = require("./rh-mcp.js");
18
- exports.BRIDGE_TOOLS = [
19
- {
20
- name: "rh_stock_bridge",
21
- description: "Compare a tokenized stock on Robinhood Chain (4663) against the real US equity: " +
22
- "on-chain price vs live share price, the premium/discount between them, pool depth, " +
23
- "and whether the US market is currently open. Covers the 22 tokenized tickers " +
24
- "(NVDA, TSLA, AAPL, COIN…). Omit `ticker` to scan all of them at once. " +
25
- "No API key needed. Reports the gap and the depth — it does not tell you to trade it.",
26
- inputSchema: {
27
- type: "object",
28
- properties: {
29
- ticker: {
30
- type: "string",
31
- description: "Tokenized stock symbol, e.g. 'NVDA'. Omit to scan every tokenized stock.",
32
- },
33
- minGapPct: {
34
- type: "number",
35
- description: "When scanning, only show names whose gap exceeds this (default 0 = show all)",
36
- },
37
- },
38
- required: [],
39
- },
40
- },
41
- ];
42
- const Schema = zod_1.z.object({
43
- ticker: zod_1.z.string().min(1).max(10).optional(),
44
- minGapPct: zod_1.z.number().min(0).max(100).optional(),
45
- });
46
- async function realQuote(symbol) {
47
- try {
48
- const res = await fetch(`https://query1.finance.yahoo.com/v8/finance/chart/${encodeURIComponent(symbol)}?interval=1d&range=1d`, { headers: { "User-Agent": "Mozilla/5.0" }, signal: AbortSignal.timeout(12000) });
49
- if (!res.ok)
50
- return null;
51
- const j = await res.json();
52
- const m = j?.chart?.result?.[0]?.meta;
53
- if (!m?.regularMarketPrice)
54
- return null;
55
- // The chart endpoint carries no `marketState`, so derive it: during the
56
- // regular session `regularMarketTime` keeps advancing, and once the bell
57
- // rings it freezes at the close. A recent timestamp means live pricing.
58
- const ts = Number(m.regularMarketTime) * 1000;
59
- const ageMin = isFinite(ts) ? (Date.now() - ts) / 60000 : Infinity;
60
- return { price: m.regularMarketPrice, state: ageMin <= 20 ? "REGULAR" : "CLOSED" };
61
- }
62
- catch {
63
- return null;
64
- }
65
- }
66
- async function buildRow(s) {
67
- const [dex, rq] = await Promise.all([(0, rh_mcp_js_1.dexTokenBest)(s.address), realQuote(s.symbol)]);
68
- const onchain = dex?.priceUsd != null ? Number(dex.priceUsd) : null;
69
- const real = rq?.price ?? null;
70
- const gapPct = onchain != null && real != null && real > 0 ? ((onchain - real) / real) * 100 : null;
71
- return {
72
- symbol: s.symbol,
73
- name: s.name,
74
- address: s.address,
75
- onchain: onchain != null && isFinite(onchain) ? onchain : null,
76
- real,
77
- gapPct,
78
- liquidityUsd: dex?.liquidity?.usd ?? 0,
79
- marketState: rq?.state,
80
- };
81
- }
82
- function gapTag(gap, liq) {
83
- const a = Math.abs(gap);
84
- if (a < 1)
85
- return "🟢 tracking";
86
- if (a < 3)
87
- return "🟡 minor drift";
88
- if (liq < 50000)
89
- return "🟠 wide — but the pool is thin";
90
- return "🔴 wide";
91
- }
92
- async function handleBridgeTool(name, args) {
93
- if (name !== "rh_stock_bridge")
94
- return null;
95
- const parsed = Schema.safeParse(args);
96
- if (!parsed.success) {
97
- return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
98
- }
99
- const wanted = parsed.data.ticker?.trim().toUpperCase();
100
- const minGap = parsed.data.minGapPct ?? 0;
101
- const targets = wanted ? rh_mcp_js_1.RH_STOCKS.filter((s) => s.symbol === wanted) : rh_mcp_js_1.RH_STOCKS;
102
- if (wanted && targets.length === 0) {
103
- return {
104
- content: [{
105
- type: "text",
106
- text: `**${wanted}** is not a tokenized stock on Robinhood Chain.\n\n` +
107
- `Available: ${rh_mcp_js_1.RH_STOCKS.map((s) => s.symbol).join(", ")}`,
108
- }],
109
- isError: true,
110
- };
111
- }
112
- const rows = await Promise.all(targets.map(buildRow));
113
- const usable = rows.filter((r) => r.gapPct != null);
114
- const marketState = usable.find((r) => r.marketState)?.marketState ?? "UNKNOWN";
115
- const marketOpen = marketState === "REGULAR";
116
- const lines = [
117
- `# Tokenized stock ↔ real equity — Robinhood Chain (4663)`,
118
- ``,
119
- `**US market: ${marketOpen ? "🟢 OPEN" : "🔴 CLOSED"}**` +
120
- (marketOpen
121
- ? ` — both sides are pricing live, so a gap is a genuine dislocation.`
122
- : ` — the share price is frozen at the last close while the token keeps trading. ` +
123
- `A gap here is largely expected and is NOT by itself a mispricing.`),
124
- ``,
125
- ];
126
- // Single-ticker detail view.
127
- if (wanted && usable.length === 1) {
128
- const r = usable[0];
129
- lines.push(`## ${r.symbol} — ${r.name}`, ``, `| | |`, `|---|---|`, `| On-chain (token) | **$${r.onchain.toFixed(4)}** |`, `| Real share | **$${r.real.toFixed(2)}** |`, `| Gap | **${r.gapPct >= 0 ? "+" : ""}${r.gapPct.toFixed(2)}%** ${gapTag(r.gapPct, r.liquidityUsd)} |`, `| Pool depth | $${Math.round(r.liquidityUsd).toLocaleString()} |`, `| Token | \`${r.address}\` |`, ``, `${rh_mcp_js_1.RH_EXPLORER}/token/${r.address}`, ``);
130
- }
131
- else {
132
- const shown = usable
133
- .filter((r) => Math.abs(r.gapPct) >= minGap)
134
- .sort((a, b) => Math.abs(b.gapPct) - Math.abs(a.gapPct));
135
- lines.push(`| Ticker | On-chain | Real | Gap | Pool depth |`, `|---|---|---|---|---|`, ...shown.map((r) => `| **${r.symbol}** | $${r.onchain.toFixed(2)} | $${r.real.toFixed(2)} | ` +
136
- `${r.gapPct >= 0 ? "+" : ""}${r.gapPct.toFixed(2)}% ${gapTag(r.gapPct, r.liquidityUsd)} | ` +
137
- `$${Math.round(r.liquidityUsd).toLocaleString()} |`), ``);
138
- const missing = rows.filter((r) => r.gapPct == null);
139
- if (missing.length) {
140
- lines.push(`_No usable quote for: ${missing.map((m) => m.symbol).join(", ")}._`, ``);
141
- }
142
- }
143
- lines.push(`---`, ``, `**Reading this**`, ``, `- A gap is only tradeable to the depth of the pool. A 20% premium on a $100k pool is not ` +
144
- `a 20% opportunity — size it against the depth column, then check the real fill with \`rh_mcp_estimate\`.`, `- While the US market is closed the token is the only live price. Drift is expected, and it ` +
145
- `often reflects overnight news the share price has not opened to yet.`, `- Buying the token is exposure to the token, not ownership of the share. Redemption terms, ` +
146
- `custody and counterparty are Robinhood's, not this tool's — and are not verified here.`, ``, `_Prices: on-chain via DexScreener, share via Yahoo Finance. Not investment advice._`);
147
- return { content: [{ type: "text", text: lines.join("\n") }] };
148
- }