@finchagentic/mcp 4.6.1 → 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.
- package/package.json +5 -6
- package/dist/_http-cache.js +0 -96
- package/dist/_text-search.js +0 -39
- package/dist/agent-loop.js +0 -301
- package/dist/annotations.js +0 -122
- package/dist/cli.js +0 -1391
- package/dist/clink-input.js +0 -15
- package/dist/config.js +0 -132
- package/dist/convex.js +0 -175
- package/dist/dex-pair.js +0 -54
- package/dist/enrichment-router.js +0 -315
- package/dist/index.js +0 -258
- package/dist/llm.js +0 -298
- package/dist/local-memory-file.js +0 -150
- package/dist/local-memory.js +0 -135
- package/dist/local-vault.js +0 -456
- package/dist/output-schemas.js +0 -605
- package/dist/project.js +0 -36
- package/dist/prompts.js +0 -111
- package/dist/public-url.js +0 -107
- package/dist/resources.js +0 -111
- package/dist/server.js +0 -322
- package/dist/signal-gate.js +0 -57
- package/dist/token-decimals.js +0 -26
- package/dist/token-gate.js +0 -88
- package/dist/tool-filter.js +0 -53
- package/dist/tools/_solidity-scan.js +0 -313
- package/dist/tools/agents.js +0 -441
- package/dist/tools/automation.js +0 -354
- package/dist/tools/base-mcp.js +0 -466
- package/dist/tools/base.js +0 -283
- package/dist/tools/chronicle.js +0 -268
- package/dist/tools/coder.js +0 -94
- package/dist/tools/deep-research.js +0 -1421
- package/dist/tools/defi.js +0 -292
- package/dist/tools/equity.js +0 -372
- package/dist/tools/events.js +0 -182
- package/dist/tools/github.js +0 -564
- package/dist/tools/insider.js +0 -264
- package/dist/tools/insight.js +0 -630
- package/dist/tools/market.js +0 -555
- package/dist/tools/memory.js +0 -1059
- package/dist/tools/miroshark.js +0 -350
- package/dist/tools/monitor.js +0 -319
- package/dist/tools/os.js +0 -236
- package/dist/tools/packets.js +0 -296
- package/dist/tools/research-chain.js +0 -226
- package/dist/tools/research-compare.js +0 -280
- package/dist/tools/research.js +0 -188
- package/dist/tools/rh-bridge.js +0 -148
- package/dist/tools/rh-mcp.js +0 -1448
- package/dist/tools/rh-orders.js +0 -556
- package/dist/tools/scanner.js +0 -564
- package/dist/tools/stake.js +0 -369
- package/dist/tools/vault.js +0 -1020
- package/dist/tools/wallet.js +0 -200
- package/dist/types.js +0 -2
- package/dist/wallet.js +0 -372
|
@@ -1,315 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// Generalized enrichment router for deep_research synthesis.
|
|
3
|
-
//
|
|
4
|
-
// Old behavior: Firecrawl scraped 5–10 pages and the LLM synthesized. When
|
|
5
|
-
// pages were thin or outdated, the LLM filled gaps with plausible fiction.
|
|
6
|
-
//
|
|
7
|
-
// New behavior: detect the topic domain and hit primary-source APIs in
|
|
8
|
-
// parallel before synthesis. Inject results as "AUTHORITATIVE LIVE DATA"
|
|
9
|
-
// the LLM is instructed to lead with.
|
|
10
|
-
//
|
|
11
|
-
// All APIs used here are free, public, no auth required:
|
|
12
|
-
// - DefiLlama, CoinGecko (crypto)
|
|
13
|
-
// - HackerNews Algolia (tech news, ~real-time)
|
|
14
|
-
// - GitHub search (repos, no auth needed for public read)
|
|
15
|
-
// - arXiv (academic papers)
|
|
16
|
-
// - Wikipedia REST (foundational facts)
|
|
17
|
-
//
|
|
18
|
-
// All calls are best-effort with short timeouts - if any fail, the
|
|
19
|
-
// synthesis runs without that block.
|
|
20
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
-
exports.enrichQuery = enrichQuery;
|
|
22
|
-
exports.todayContext = todayContext;
|
|
23
|
-
// ─── Topic detection ──────────────────────────────────────────────────────────
|
|
24
|
-
const CRYPTO_RE = /\b(tvl|apy|yield|vault|defi|stablecoin|usdc|usdt|eth|btc|sol|bitcoin|ethereum|solana|base|aerodrome|uniswap|morpho|moonwell|aave|lido|ethena|pendle|curve|gauntlet|steakhouse|base chain|arbitrum|optimism|polygon|onchain|on-chain|wallet|swap|amm|perpetual|perp|liquid staking)\b/i;
|
|
25
|
-
const TECH_RE = /\b(ai|llm|gpt|claude|anthropic|openai|gemini|machine learning|deep learning|neural|transformer|frontier model|agent|mcp|api|framework|library|sdk|github|repo|repository|startup|y combinator|yc|saas|vc|funding round|seed|series [a-d]|launches|launched|release|shipped|build|launch)\b/i;
|
|
26
|
-
const ACADEMIC_RE = /\b(paper|research paper|arxiv|study|publication|preprint|peer.review|citation|abstract|methodology|hypothesis|empirical|experiment|finding[s]?|literature review|systematic review|meta.analysis|theorem|proof)\b/i;
|
|
27
|
-
const FACTUAL_RE = /\b(history of|founded|established|definition|what is|who is|when did|where is|biography|encyclopedia|background|origin|first invented)\b/i;
|
|
28
|
-
function detectDomains(query) {
|
|
29
|
-
const q = query.toLowerCase();
|
|
30
|
-
const domains = [];
|
|
31
|
-
if (CRYPTO_RE.test(q))
|
|
32
|
-
domains.push("crypto");
|
|
33
|
-
if (TECH_RE.test(q))
|
|
34
|
-
domains.push("tech");
|
|
35
|
-
if (ACADEMIC_RE.test(q))
|
|
36
|
-
domains.push("academic");
|
|
37
|
-
if (FACTUAL_RE.test(q))
|
|
38
|
-
domains.push("general");
|
|
39
|
-
return domains;
|
|
40
|
-
}
|
|
41
|
-
// ─── Formatting helpers ──────────────────────────────────────────────────────
|
|
42
|
-
function formatUsd(n) {
|
|
43
|
-
if (typeof n !== "number" || !isFinite(n))
|
|
44
|
-
return "n/a";
|
|
45
|
-
if (n >= 1e9)
|
|
46
|
-
return `$${(n / 1e9).toFixed(2)}B`;
|
|
47
|
-
if (n >= 1e6)
|
|
48
|
-
return `$${(n / 1e6).toFixed(2)}M`;
|
|
49
|
-
if (n >= 1e3)
|
|
50
|
-
return `$${(n / 1e3).toFixed(2)}K`;
|
|
51
|
-
return `$${n.toFixed(2)}`;
|
|
52
|
-
}
|
|
53
|
-
function clipText(s, n) {
|
|
54
|
-
return s.length > n ? s.slice(0, n - 1) + "…" : s;
|
|
55
|
-
}
|
|
56
|
-
function ageInDays(iso) {
|
|
57
|
-
return Math.floor((Date.now() - new Date(iso).getTime()) / 86400000);
|
|
58
|
-
}
|
|
59
|
-
// ─── Crypto enrichment (DefiLlama + CoinGecko) ────────────────────────────────
|
|
60
|
-
const DEFI_PROTOCOLS = [
|
|
61
|
-
"aerodrome", "uniswap", "morpho", "moonwell", "aave", "compound", "lido",
|
|
62
|
-
"rocket pool", "rocketpool", "ethena", "pendle", "curve", "balancer",
|
|
63
|
-
"convex", "frax", "spark", "fluid", "kamino", "marginfi", "jupiter",
|
|
64
|
-
"raydium", "orca", "drift", "gmx", "synthetix", "dydx", "vertex",
|
|
65
|
-
"hyperliquid", "yearn", "gauntlet", "steakhouse",
|
|
66
|
-
];
|
|
67
|
-
const CHAIN_KEYWORDS = [
|
|
68
|
-
"ethereum", "base", "arbitrum", "optimism", "polygon", "solana", "avalanche",
|
|
69
|
-
"blast", "sonic", "linea", "scroll", "zksync", "berachain", "monad",
|
|
70
|
-
];
|
|
71
|
-
const TOP_TOKENS = [
|
|
72
|
-
"btc", "bitcoin", "eth", "ethereum", "usdc", "usdt", "dai", "weth",
|
|
73
|
-
"sol", "solana", "matic", "avax", "link", "uni", "aave", "ldo",
|
|
74
|
-
];
|
|
75
|
-
async function cryptoEnrich(query) {
|
|
76
|
-
const q = query.toLowerCase();
|
|
77
|
-
const protocols = DEFI_PROTOCOLS.filter((p) => q.includes(p));
|
|
78
|
-
const chains = CHAIN_KEYWORDS.filter((c) => q.includes(c));
|
|
79
|
-
const tokens = TOP_TOKENS.filter((t) => new RegExp(`\\b${t}\\b`, "i").test(q));
|
|
80
|
-
const blocks = [];
|
|
81
|
-
// Protocol TVL
|
|
82
|
-
if (protocols.length > 0) {
|
|
83
|
-
try {
|
|
84
|
-
const slug = protocols[0].toLowerCase().replace(/\s+/g, "-");
|
|
85
|
-
const res = await fetch(`https://api.llama.fi/protocol/${slug}`, { signal: AbortSignal.timeout(8000) });
|
|
86
|
-
if (res.ok) {
|
|
87
|
-
const d = (await res.json());
|
|
88
|
-
if (d?.name) {
|
|
89
|
-
const chainBreakdown = d.currentChainTvls
|
|
90
|
-
? Object.entries(d.currentChainTvls)
|
|
91
|
-
.sort(([, a], [, b]) => b - a)
|
|
92
|
-
.slice(0, 5)
|
|
93
|
-
.map(([c, v]) => `${c}: ${formatUsd(v)}`)
|
|
94
|
-
.join(" · ")
|
|
95
|
-
: "";
|
|
96
|
-
blocks.push([
|
|
97
|
-
`### 📊 DefiLlama - ${d.name}`,
|
|
98
|
-
`- TVL: **${formatUsd(d.tvl)}** | 24h: ${d.change_1d?.toFixed(2) ?? "n/a"}% | 7d: ${d.change_7d?.toFixed(2) ?? "n/a"}%`,
|
|
99
|
-
d.mcap ? `- Market cap: ${formatUsd(d.mcap)}` : "",
|
|
100
|
-
chainBreakdown ? `- Top chains: ${chainBreakdown}` : "",
|
|
101
|
-
`- Source: https://defillama.com/protocol/${slug}`,
|
|
102
|
-
].filter(Boolean).join("\n"));
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
catch { /* ignore */ }
|
|
107
|
-
}
|
|
108
|
-
// Yields
|
|
109
|
-
if (q.match(/\b(yield|vault|apy|lp)\b/)) {
|
|
110
|
-
try {
|
|
111
|
-
const res = await fetch("https://yields.llama.fi/pools", { signal: AbortSignal.timeout(8000) });
|
|
112
|
-
if (res.ok) {
|
|
113
|
-
const d = (await res.json());
|
|
114
|
-
let pools = (d?.data ?? []);
|
|
115
|
-
if (chains[0])
|
|
116
|
-
pools = pools.filter((p) => (p.chain ?? "").toLowerCase() === chains[0]);
|
|
117
|
-
if (tokens[0])
|
|
118
|
-
pools = pools.filter((p) => (p.symbol ?? "").toLowerCase().includes(tokens[0]));
|
|
119
|
-
pools = pools.filter((p) => (p.tvlUsd ?? 0) > 1000000 && (p.apy ?? 0) < 100);
|
|
120
|
-
pools = pools.sort((a, b) => (b.tvlUsd ?? 0) - (a.tvlUsd ?? 0)).slice(0, 8);
|
|
121
|
-
if (pools.length > 0) {
|
|
122
|
-
const rows = pools.map((p) => `- **${p.project}** ${p.symbol} on ${p.chain}: ${(p.apy ?? 0).toFixed(2)}% APY · TVL ${formatUsd(p.tvlUsd)}`).join("\n");
|
|
123
|
-
blocks.push(`### 💰 DefiLlama Yields (TVL > $1M)\n${rows}\n- Source: https://yields.llama.fi/`);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
catch { /* ignore */ }
|
|
128
|
-
}
|
|
129
|
-
// Prices
|
|
130
|
-
if (tokens.length > 0) {
|
|
131
|
-
try {
|
|
132
|
-
const idMap = {
|
|
133
|
-
btc: "bitcoin", eth: "ethereum", sol: "solana", matic: "matic-network",
|
|
134
|
-
usdc: "usd-coin", usdt: "tether", dai: "dai", weth: "weth",
|
|
135
|
-
avax: "avalanche-2", link: "chainlink", uni: "uniswap", aave: "aave",
|
|
136
|
-
ldo: "lido-dao",
|
|
137
|
-
};
|
|
138
|
-
const ids = tokens.map((t) => idMap[t.toLowerCase()] ?? t.toLowerCase()).join(",");
|
|
139
|
-
const res = await fetch(`https://api.coingecko.com/api/v3/simple/price?ids=${ids}&vs_currencies=usd&include_24hr_change=true&include_market_cap=true`, { signal: AbortSignal.timeout(8000) });
|
|
140
|
-
if (res.ok) {
|
|
141
|
-
const d = (await res.json());
|
|
142
|
-
const rows = Object.entries(d).map(([id, p]) => {
|
|
143
|
-
const change = typeof p.usd_24h_change === "number"
|
|
144
|
-
? ` (${p.usd_24h_change >= 0 ? "+" : ""}${p.usd_24h_change.toFixed(2)}% 24h)`
|
|
145
|
-
: "";
|
|
146
|
-
const mcap = p.usd_market_cap ? ` · mcap ${formatUsd(p.usd_market_cap)}` : "";
|
|
147
|
-
return `- **${id}**: $${(p.usd ?? 0).toLocaleString()}${change}${mcap}`;
|
|
148
|
-
}).join("\n");
|
|
149
|
-
if (rows)
|
|
150
|
-
blocks.push(`### 💵 CoinGecko\n${rows}\n- Source: https://www.coingecko.com/`);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
catch { /* ignore */ }
|
|
154
|
-
}
|
|
155
|
-
return blocks.length > 0 ? blocks.join("\n\n") : null;
|
|
156
|
-
}
|
|
157
|
-
// ─── Tech enrichment (HackerNews Algolia + GitHub search) ────────────────────
|
|
158
|
-
async function techEnrich(query) {
|
|
159
|
-
const blocks = [];
|
|
160
|
-
// HackerNews - last 30 days, sorted by relevance + popularity
|
|
161
|
-
try {
|
|
162
|
-
const since = Math.floor((Date.now() - 30 * 86400000) / 1000);
|
|
163
|
-
const url = `https://hn.algolia.com/api/v1/search?query=${encodeURIComponent(query)}&numericFilters=created_at_i>${since}&hitsPerPage=8`;
|
|
164
|
-
const res = await fetch(url, { signal: AbortSignal.timeout(8000) });
|
|
165
|
-
if (res.ok) {
|
|
166
|
-
const d = (await res.json());
|
|
167
|
-
const hits = (d?.hits ?? []);
|
|
168
|
-
if (hits.length > 0) {
|
|
169
|
-
const rows = hits.slice(0, 6).map((h) => {
|
|
170
|
-
const title = h.title ?? h.story_title ?? "(no title)";
|
|
171
|
-
const points = h.points != null ? `${h.points} pts` : "";
|
|
172
|
-
const comments = h.num_comments != null ? `${h.num_comments} comments` : "";
|
|
173
|
-
const age = h.created_at ? `${ageInDays(h.created_at)}d ago` : "";
|
|
174
|
-
const meta = [points, comments, age].filter(Boolean).join(" · ");
|
|
175
|
-
const link = h.url ?? `https://news.ycombinator.com/item?id=${h.objectID}`;
|
|
176
|
-
return `- **${clipText(title, 140)}** · ${meta}\n ${link}`;
|
|
177
|
-
}).join("\n");
|
|
178
|
-
blocks.push(`### 📰 HackerNews (last 30d, top hits)\n${rows}\n- Source: https://hn.algolia.com/`);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
catch { /* ignore */ }
|
|
183
|
-
// GitHub search - public repos matching query
|
|
184
|
-
try {
|
|
185
|
-
const url = `https://api.github.com/search/repositories?q=${encodeURIComponent(query)}&sort=stars&order=desc&per_page=6`;
|
|
186
|
-
const res = await fetch(url, {
|
|
187
|
-
signal: AbortSignal.timeout(8000),
|
|
188
|
-
headers: { Accept: "application/vnd.github+json", "User-Agent": "finch-mcp" },
|
|
189
|
-
});
|
|
190
|
-
if (res.ok) {
|
|
191
|
-
const d = (await res.json());
|
|
192
|
-
const repos = (d?.items ?? []);
|
|
193
|
-
if (repos.length > 0) {
|
|
194
|
-
const rows = repos.slice(0, 5).map((r) => {
|
|
195
|
-
const stars = r.stargazers_count?.toLocaleString() ?? "?";
|
|
196
|
-
const desc = clipText(r.description ?? "", 100);
|
|
197
|
-
return `- **${r.full_name}** (★${stars}) - ${desc}\n ${r.html_url}`;
|
|
198
|
-
}).join("\n");
|
|
199
|
-
blocks.push(`### 🐙 GitHub (top repos by stars)\n${rows}\n- Source: https://github.com/search?q=${encodeURIComponent(query)}`);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
catch { /* ignore */ }
|
|
204
|
-
return blocks.length > 0 ? blocks.join("\n\n") : null;
|
|
205
|
-
}
|
|
206
|
-
// ─── Academic enrichment (arXiv) ─────────────────────────────────────────────
|
|
207
|
-
async function academicEnrich(query) {
|
|
208
|
-
try {
|
|
209
|
-
const url = `http://export.arxiv.org/api/query?search_query=all:${encodeURIComponent(query)}&start=0&max_results=5&sortBy=submittedDate&sortOrder=descending`;
|
|
210
|
-
const res = await fetch(url, { signal: AbortSignal.timeout(8000) });
|
|
211
|
-
if (!res.ok)
|
|
212
|
-
return null;
|
|
213
|
-
const xml = await res.text();
|
|
214
|
-
const entries = xml.match(/<entry>[\s\S]*?<\/entry>/g) ?? [];
|
|
215
|
-
if (entries.length === 0)
|
|
216
|
-
return null;
|
|
217
|
-
const rows = entries.slice(0, 5).map((e) => {
|
|
218
|
-
const title = (e.match(/<title>([\s\S]*?)<\/title>/)?.[1] ?? "").replace(/\s+/g, " ").trim();
|
|
219
|
-
const summary = (e.match(/<summary>([\s\S]*?)<\/summary>/)?.[1] ?? "").replace(/\s+/g, " ").trim();
|
|
220
|
-
const published = e.match(/<published>([\s\S]*?)<\/published>/)?.[1] ?? "";
|
|
221
|
-
const link = e.match(/<id>([\s\S]*?)<\/id>/)?.[1] ?? "";
|
|
222
|
-
const date = published ? `${published.slice(0, 10)} (${ageInDays(published)}d ago)` : "";
|
|
223
|
-
return `- **${clipText(title, 140)}** · ${date}\n ${clipText(summary, 200)}\n ${link}`;
|
|
224
|
-
}).join("\n\n");
|
|
225
|
-
return `### 📚 arXiv (recent papers)\n${rows}\n- Source: https://arxiv.org/`;
|
|
226
|
-
}
|
|
227
|
-
catch {
|
|
228
|
-
return null;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
// ─── General enrichment (Wikipedia REST) ─────────────────────────────────────
|
|
232
|
-
async function generalEnrich(query) {
|
|
233
|
-
try {
|
|
234
|
-
// Wikipedia search - find best matching article
|
|
235
|
-
const searchRes = await fetch(`https://en.wikipedia.org/w/api.php?action=opensearch&format=json&search=${encodeURIComponent(query)}&limit=3`, { signal: AbortSignal.timeout(6000) });
|
|
236
|
-
if (!searchRes.ok)
|
|
237
|
-
return null;
|
|
238
|
-
const [, titles, descs, urls] = (await searchRes.json());
|
|
239
|
-
if (!titles || titles.length === 0)
|
|
240
|
-
return null;
|
|
241
|
-
// Fetch summary for top hit
|
|
242
|
-
const top = titles[0];
|
|
243
|
-
const sumRes = await fetch(`https://en.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(top)}`, { signal: AbortSignal.timeout(6000) });
|
|
244
|
-
if (!sumRes.ok)
|
|
245
|
-
return null;
|
|
246
|
-
const summary = (await sumRes.json());
|
|
247
|
-
const lines = [
|
|
248
|
-
`### 🌐 Wikipedia - ${summary.title ?? top}`,
|
|
249
|
-
summary.extract ? clipText(summary.extract, 600) : descs[0] ?? "",
|
|
250
|
-
`- Source: ${urls[0] ?? `https://en.wikipedia.org/wiki/${encodeURIComponent(top)}`}`,
|
|
251
|
-
];
|
|
252
|
-
return lines.filter(Boolean).join("\n");
|
|
253
|
-
}
|
|
254
|
-
catch {
|
|
255
|
-
return null;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
// ─── Public entry point ──────────────────────────────────────────────────────
|
|
259
|
-
/**
|
|
260
|
-
* Run enrichment in parallel for all detected domains. Pure best-effort: if
|
|
261
|
-
* no domain matches or all APIs fail, returns hasData=false and synthesis
|
|
262
|
-
* runs normally.
|
|
263
|
-
*/
|
|
264
|
-
async function enrichQuery(query) {
|
|
265
|
-
const domains = detectDomains(query);
|
|
266
|
-
if (domains.length === 0) {
|
|
267
|
-
return { context: "", authoritative: [], hasData: false, domains: [] };
|
|
268
|
-
}
|
|
269
|
-
const promises = [];
|
|
270
|
-
if (domains.includes("crypto"))
|
|
271
|
-
promises.push(cryptoEnrich(query));
|
|
272
|
-
if (domains.includes("tech"))
|
|
273
|
-
promises.push(techEnrich(query));
|
|
274
|
-
if (domains.includes("academic"))
|
|
275
|
-
promises.push(academicEnrich(query));
|
|
276
|
-
if (domains.includes("general"))
|
|
277
|
-
promises.push(generalEnrich(query));
|
|
278
|
-
const results = await Promise.all(promises);
|
|
279
|
-
const blocks = results.filter((b) => !!b);
|
|
280
|
-
if (blocks.length === 0) {
|
|
281
|
-
return { context: "", authoritative: [], hasData: false, domains };
|
|
282
|
-
}
|
|
283
|
-
const authoritative = [];
|
|
284
|
-
if (domains.includes("crypto"))
|
|
285
|
-
authoritative.push("defillama.com", "coingecko.com");
|
|
286
|
-
if (domains.includes("tech"))
|
|
287
|
-
authoritative.push("news.ycombinator.com", "github.com");
|
|
288
|
-
if (domains.includes("academic"))
|
|
289
|
-
authoritative.push("arxiv.org");
|
|
290
|
-
if (domains.includes("general"))
|
|
291
|
-
authoritative.push("wikipedia.org");
|
|
292
|
-
const todayISO = new Date().toISOString().slice(0, 10);
|
|
293
|
-
const context = [
|
|
294
|
-
`---`,
|
|
295
|
-
`## 🔒 AUTHORITATIVE LIVE DATA - fetched ${todayISO}`,
|
|
296
|
-
``,
|
|
297
|
-
`The blocks below come from primary-source APIs called at query time. Treat as ground truth. When figures here conflict with the scraped sources below, prefer these and cite them by source name (e.g. \`[DefiLlama]\`, \`[HackerNews]\`, \`[arXiv]\`, \`[GitHub]\`, \`[Wikipedia]\`).`,
|
|
298
|
-
``,
|
|
299
|
-
blocks.join("\n\n"),
|
|
300
|
-
`---`,
|
|
301
|
-
].join("\n");
|
|
302
|
-
return { context, authoritative, hasData: true, domains };
|
|
303
|
-
}
|
|
304
|
-
/**
|
|
305
|
-
* Returns the current date in ISO format. Used by the synthesis prompt to
|
|
306
|
-
* anchor the LLM in time - prevents "Q2 2026 catalysts in July" style
|
|
307
|
-
* calendar hallucinations.
|
|
308
|
-
*/
|
|
309
|
-
function todayContext() {
|
|
310
|
-
const now = new Date();
|
|
311
|
-
const iso = now.toISOString().slice(0, 10);
|
|
312
|
-
const weekday = now.toLocaleDateString("en-US", { weekday: "long" });
|
|
313
|
-
const quarter = `Q${Math.floor(now.getUTCMonth() / 3) + 1}`;
|
|
314
|
-
return `Today is ${weekday}, ${iso}. This is ${quarter} ${now.getUTCFullYear()}. Treat any date after today as a future/projected event, not a confirmed one.`;
|
|
315
|
-
}
|
package/dist/index.js
DELETED
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
-
if (k2 === undefined) k2 = k;
|
|
5
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
-
}
|
|
9
|
-
Object.defineProperty(o, k2, desc);
|
|
10
|
-
}) : (function(o, m, k, k2) {
|
|
11
|
-
if (k2 === undefined) k2 = k;
|
|
12
|
-
o[k2] = m[k];
|
|
13
|
-
}));
|
|
14
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
-
}) : function(o, v) {
|
|
17
|
-
o["default"] = v;
|
|
18
|
-
});
|
|
19
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
-
var ownKeys = function(o) {
|
|
21
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
-
var ar = [];
|
|
23
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
-
return ar;
|
|
25
|
-
};
|
|
26
|
-
return ownKeys(o);
|
|
27
|
-
};
|
|
28
|
-
return function (mod) {
|
|
29
|
-
if (mod && mod.__esModule) return mod;
|
|
30
|
-
var result = {};
|
|
31
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
-
__setModuleDefault(result, mod);
|
|
33
|
-
return result;
|
|
34
|
-
};
|
|
35
|
-
})();
|
|
36
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
-
const server_js_1 = require("./server.js");
|
|
38
|
-
const tool_filter_js_1 = require("./tool-filter.js");
|
|
39
|
-
const wallet_js_1 = require("./wallet.js");
|
|
40
|
-
const config_js_1 = require("./config.js");
|
|
41
|
-
const clink_input_js_1 = require("./clink-input.js");
|
|
42
|
-
const readline = __importStar(require("readline"));
|
|
43
|
-
const fs = __importStar(require("fs"));
|
|
44
|
-
const path = __importStar(require("path"));
|
|
45
|
-
// Always read the version from package.json so banner + boot strings stay in
|
|
46
|
-
// sync after every npm publish - no more hand-edits in three places.
|
|
47
|
-
const PKG_VERSION = (() => {
|
|
48
|
-
try {
|
|
49
|
-
// dist/index.js -> ../package.json (CJS so __dirname is available)
|
|
50
|
-
const raw = fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8");
|
|
51
|
-
return JSON.parse(raw).version ?? "unknown";
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
return "unknown";
|
|
55
|
-
}
|
|
56
|
-
})();
|
|
57
|
-
const CONVEX_SITE = process.env.FINCH_CONVEX_URL ?? "https://befitting-porcupine-276.convex.site";
|
|
58
|
-
// ── ANSI helpers ──────────────────────────────────────────────────────────────
|
|
59
|
-
const C = {
|
|
60
|
-
cyan: "\x1b[36m",
|
|
61
|
-
dim: "\x1b[90m",
|
|
62
|
-
white: "\x1b[97m",
|
|
63
|
-
green: "\x1b[32m",
|
|
64
|
-
yellow: "\x1b[33m",
|
|
65
|
-
red: "\x1b[31m",
|
|
66
|
-
reset: "\x1b[0m",
|
|
67
|
-
bold: "\x1b[1m",
|
|
68
|
-
};
|
|
69
|
-
// Was still spelling out the pre-rebrand "NOELCLAW" wordmark in ASCII art -
|
|
70
|
-
// leftover from before the Finch rebrand, never caught because nothing
|
|
71
|
-
// visually diffs a banner string.
|
|
72
|
-
const BANNER = `
|
|
73
|
-
${C.cyan}
|
|
74
|
-
███████╗██╗███╗ ██╗ ██████╗██╗ ██╗
|
|
75
|
-
██╔════╝██║████╗ ██║██╔════╝██║ ██║
|
|
76
|
-
█████╗ ██║██╔██╗ ██║██║ ███████║
|
|
77
|
-
██╔══╝ ██║██║╚██╗██║██║ ██╔══██║
|
|
78
|
-
██║ ██║██║ ╚████║╚██████╗██║ ██║
|
|
79
|
-
╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝╚═╝ ╚═╝
|
|
80
|
-
${C.reset}`;
|
|
81
|
-
function line(label, value, color = C.cyan) {
|
|
82
|
-
const pad = " ".repeat(Math.max(0, 12 - label.length));
|
|
83
|
-
process.stderr.write(` ${color}▸ ${label}${C.reset}${pad}${value}\n`);
|
|
84
|
-
}
|
|
85
|
-
function divider() {
|
|
86
|
-
process.stderr.write(` ${C.dim}${"─".repeat(58)}${C.reset}\n`);
|
|
87
|
-
}
|
|
88
|
-
async function checkForUpdate(current) {
|
|
89
|
-
try {
|
|
90
|
-
const res = await fetch("https://registry.npmjs.org/@finchagentic/mcp/latest", {
|
|
91
|
-
signal: AbortSignal.timeout(5000),
|
|
92
|
-
});
|
|
93
|
-
if (!res.ok)
|
|
94
|
-
return;
|
|
95
|
-
const data = await res.json();
|
|
96
|
-
const latest = data.version;
|
|
97
|
-
if (!latest || latest === current)
|
|
98
|
-
return;
|
|
99
|
-
// Show update notice to stderr - visible in Claude Desktop logs and terminal.
|
|
100
|
-
const sep = ` ${"─".repeat(58)}`;
|
|
101
|
-
process.stderr.write(`\n${sep}\n` +
|
|
102
|
-
` ${C.yellow}⚠${C.reset} Update available: ${C.yellow}v${current}${C.reset} → ${C.cyan}v${latest}${C.reset}\n` +
|
|
103
|
-
` ${C.dim}npm install -g @finchagentic/mcp@${latest}${C.reset} ${C.dim}or restart your MCP client${C.reset}\n` +
|
|
104
|
-
`${sep}\n\n`);
|
|
105
|
-
}
|
|
106
|
-
catch {
|
|
107
|
-
// Non-blocking - silently ignore network errors
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
async function main() {
|
|
111
|
-
(0, config_js_1.hydrateEnvFromConfig)();
|
|
112
|
-
process.stderr.write(BANNER);
|
|
113
|
-
const CAT_RULES = [
|
|
114
|
-
{ label: "Market", match: n => /^(get_market_data|get_token_data|compare_tokens|market_overview|token_history|get_base_token_data|stock_fundamentals|stock_insider|stock_events)$/.test(n) },
|
|
115
|
-
{ label: "Insight", match: n => /^(ask_finch|market_thesis|trade_plan)$/.test(n) },
|
|
116
|
-
{ label: "DeFi", match: n => n === "get_defi_yields" },
|
|
117
|
-
{ label: "Base MCP", match: n => n.startsWith("base_mcp_") },
|
|
118
|
-
{ label: "RH MCP", match: n => n.startsWith("rh_") },
|
|
119
|
-
{ label: "Automation", match: n => /^(create_automation|list_automations|pause_automation|delete_automation|get_automation_runs|run_automation)$/.test(n) },
|
|
120
|
-
{ label: "Vault", match: n => n.startsWith("vault_") || n === "code_session_save" || n === "list_projects" },
|
|
121
|
-
{ label: "Wallet", match: n => /^(get_wallet_address|get_wallet_balance|wallet_sign_message)$/.test(n) },
|
|
122
|
-
{ label: "Staking", match: n => /^(stake_finch|unstake_finch|stake_finch_status|stake_auto_restake|claim_vested_rewards)$/.test(n) },
|
|
123
|
-
{ label: "MiroShark", match: n => n.startsWith("miroshark_") },
|
|
124
|
-
{ label: "Scanner", match: n => /^(scan_market|score_token|check_token)$/.test(n) },
|
|
125
|
-
{ label: "Agents", match: n => n.startsWith("agent_") },
|
|
126
|
-
{ label: "Coder", match: n => n === "audit_contract" },
|
|
127
|
-
{ label: "Memory", match: n => n.startsWith("memory_") },
|
|
128
|
-
{ label: "OS", match: n => /^(finch_status|finch_diagnostics|finch_shell_chat)$/.test(n) },
|
|
129
|
-
{ label: "Research", match: n => /^(web_scrape|web_search|deep_research|research_compare|research_chain)$/.test(n) },
|
|
130
|
-
{ label: "Monitor", match: n => /^(schedule_research|list_monitors|cancel_monitor)$/.test(n) },
|
|
131
|
-
{ label: "GitHub", match: n => n.startsWith("github_") },
|
|
132
|
-
{ label: "Chronicle", match: n => n.startsWith("chronicle_") },
|
|
133
|
-
{ label: "Packets", match: n => n.startsWith("packet_") },
|
|
134
|
-
];
|
|
135
|
-
const categories = CAT_RULES
|
|
136
|
-
.map(rule => {
|
|
137
|
-
const names = server_js_1.ALL_TOOLS.map(t => t.name).filter(rule.match);
|
|
138
|
-
return { label: rule.label, count: names.length, tools: names.join(" · ") };
|
|
139
|
-
})
|
|
140
|
-
.filter(c => c.count > 0);
|
|
141
|
-
const total = server_js_1.ALL_TOOLS.length;
|
|
142
|
-
// What the connected client actually sees - the LIST response is filtered by
|
|
143
|
-
// FINCH_TOOLS (default "core"). Showing this next to the registered total
|
|
144
|
-
// stops the banner from claiming 121 while the client only sees the core set.
|
|
145
|
-
const exposed = (0, tool_filter_js_1.filterTools)(server_js_1.ALL_TOOLS).length;
|
|
146
|
-
const toolMode = process.env.FINCH_TOOLS ?? "core";
|
|
147
|
-
divider();
|
|
148
|
-
process.stderr.write(`\n`);
|
|
149
|
-
// Tools don't run inference - the connected client's model does. A provider
|
|
150
|
-
// is shown only because the CLI agent loop and scheduled agents use one; its
|
|
151
|
-
// absence is the normal, fully-working state for an MCP client.
|
|
152
|
-
const model = process.env.FINCH_MODEL ?? "claude-haiku-4-5-20251001";
|
|
153
|
-
const aiMode = process.env.BANKR_API_KEY
|
|
154
|
-
? `Bankr ${C.dim}${model}${C.reset}`
|
|
155
|
-
: process.env.ANTHROPIC_API_KEY
|
|
156
|
-
? `Anthropic ${C.dim}${model}${C.reset}`
|
|
157
|
-
: process.env.OPENAI_API_KEY
|
|
158
|
-
? `OpenAI ${C.dim}${model}${C.reset}`
|
|
159
|
-
: `your client's model ${C.dim}no key needed${C.reset}`;
|
|
160
|
-
line("version", `v${PKG_VERSION}`);
|
|
161
|
-
line("ai", aiMode);
|
|
162
|
-
const toolDetail = exposed === total
|
|
163
|
-
? `${C.white}${C.bold}${total} tools exposed${C.reset} ${C.dim}across ${categories.length} categories${C.reset}`
|
|
164
|
-
: `${C.white}${C.bold}${exposed} tools exposed${C.reset} ${C.dim}(${toolMode} of ${total} registered · set FINCH_TOOLS=all for every tool)${C.reset}`;
|
|
165
|
-
line("tools", toolDetail);
|
|
166
|
-
process.stderr.write(`\n`);
|
|
167
|
-
divider();
|
|
168
|
-
process.stderr.write(`\n`);
|
|
169
|
-
// ── Categories grid ────────────────────────────────────────────────────────
|
|
170
|
-
for (const cat of categories) {
|
|
171
|
-
const countStr = `${cat.count}`.padStart(2);
|
|
172
|
-
process.stderr.write(` ${C.dim}│${C.reset} ${C.cyan}${cat.label.padEnd(11)}${C.reset} ${C.dim}${countStr}x${C.reset} ${C.dim}${cat.tools}${C.reset}\n`);
|
|
173
|
-
}
|
|
174
|
-
process.stderr.write(`\n`);
|
|
175
|
-
divider();
|
|
176
|
-
// ── Wallet + start ─────────────────────────────────────────────────────────
|
|
177
|
-
await (0, server_js_1.startServer)();
|
|
178
|
-
const hasAuth = !!(0, config_js_1.getSavedToken)();
|
|
179
|
-
try {
|
|
180
|
-
const wallet = await (0, wallet_js_1.getOrCreateWallet)();
|
|
181
|
-
process.stderr.write(`\n`);
|
|
182
|
-
line("wallet", wallet.address);
|
|
183
|
-
if (hasAuth) {
|
|
184
|
-
line("auth", `${C.green}signed in${C.reset} ${C.dim}all ${server_js_1.ALL_TOOLS.length} tools unlocked${C.reset}`, C.green);
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
line("auth", `${C.yellow}not signed in${C.reset} ${C.dim}run 'finch login' to unlock premium tools${C.reset}`, C.yellow);
|
|
188
|
-
}
|
|
189
|
-
line("status", `${C.green}ready${C.reset} ${C.dim}waiting for MCP client...${C.reset}`, C.green);
|
|
190
|
-
process.stderr.write(`\n`);
|
|
191
|
-
}
|
|
192
|
-
catch {
|
|
193
|
-
process.stderr.write(`\n`);
|
|
194
|
-
line("wallet", `${C.yellow}not configured${C.reset} ${C.dim}run 'finch login' to set up${C.reset}`, C.yellow);
|
|
195
|
-
line("status", `${C.green}ready${C.reset} ${C.dim}wallet tools require setup${C.reset}`, C.green);
|
|
196
|
-
process.stderr.write(`\n`);
|
|
197
|
-
}
|
|
198
|
-
// Check for updates async - fires 3s after startup so it doesn't delay boot
|
|
199
|
-
setTimeout(() => { checkForUpdate(PKG_VERSION).catch(() => { }); }, 3000);
|
|
200
|
-
}
|
|
201
|
-
// Interactive sign-in for the `finch-mcp login` path. Kept identical to the
|
|
202
|
-
// canonical `finch login` (cli.ts): both accept a Finch API key
|
|
203
|
-
// (finch_sk_...) and exchange it for a session token at /auth/apikey/login.
|
|
204
|
-
// Earlier this asked for a raw "session token" against /auth/me - a different
|
|
205
|
-
// credential users never actually generate, so the two bins disagreed on what
|
|
206
|
-
// "login" meant.
|
|
207
|
-
async function loginFlow() {
|
|
208
|
-
process.stderr.write(`\n ${C.cyan}▸ finch login${C.reset}\n\n`);
|
|
209
|
-
process.stderr.write(` Generate an API key at ${C.cyan}app.finchagentic.com${C.reset} → Settings → API Keys\n\n`);
|
|
210
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
211
|
-
let apiKey = await new Promise((resolve) => {
|
|
212
|
-
rl.question(` API key (finch_sk_... / noel_sk_...): `, (answer) => {
|
|
213
|
-
rl.close();
|
|
214
|
-
resolve(answer.trim());
|
|
215
|
-
});
|
|
216
|
-
});
|
|
217
|
-
// Deduplicate doubled input from a known Clink terminal bug + strip non-ASCII.
|
|
218
|
-
apiKey = (0, clink_input_js_1.dedupClinkInput)(apiKey).replace(/[^\x20-\x7E]/g, "").trim();
|
|
219
|
-
if (!(0, config_js_1.isApiKey)(apiKey)) {
|
|
220
|
-
process.stderr.write(`\n ${C.yellow}✗ Invalid key — should start with finch_sk_ or noel_sk_${C.reset}\n\n`);
|
|
221
|
-
process.exit(1);
|
|
222
|
-
}
|
|
223
|
-
try {
|
|
224
|
-
const res = await fetch(`${CONVEX_SITE}/auth/apikey/login`, {
|
|
225
|
-
method: "POST",
|
|
226
|
-
headers: { "Content-Type": "application/json" },
|
|
227
|
-
body: JSON.stringify({ apiKey }),
|
|
228
|
-
signal: AbortSignal.timeout(8000),
|
|
229
|
-
});
|
|
230
|
-
const data = await res.json();
|
|
231
|
-
if (!res.ok || !data.token) {
|
|
232
|
-
process.stderr.write(`\n ${C.yellow}✗ ${data.error ?? "Invalid API key"} — check it at app.finchagentic.com${C.reset}\n\n`);
|
|
233
|
-
process.exit(1);
|
|
234
|
-
}
|
|
235
|
-
(0, config_js_1.writeConfig)({ sessionToken: data.token, email: data.email, name: data.displayName ?? undefined });
|
|
236
|
-
process.stderr.write(`\n ${C.green}✓ Signed in as ${data.email ?? data.displayName ?? "user"}${C.reset}\n`);
|
|
237
|
-
process.stderr.write(` ${C.dim}Token saved to ~/.finch/config.json${C.reset}\n`);
|
|
238
|
-
process.stderr.write(` ${C.dim}All ${server_js_1.ALL_TOOLS.length} tools now unlocked.${C.reset}\n\n`);
|
|
239
|
-
}
|
|
240
|
-
catch (err) {
|
|
241
|
-
process.stderr.write(`\n ${C.red}✗ Login failed: ${err.message}${C.reset}\n\n`);
|
|
242
|
-
process.exit(1);
|
|
243
|
-
}
|
|
244
|
-
process.exit(0);
|
|
245
|
-
}
|
|
246
|
-
const cmd = process.argv[2];
|
|
247
|
-
if (cmd === "login") {
|
|
248
|
-
loginFlow().catch((err) => {
|
|
249
|
-
process.stderr.write(`[finch] login error: ${err}\n`);
|
|
250
|
-
process.exit(1);
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
else {
|
|
254
|
-
main().catch((err) => {
|
|
255
|
-
process.stderr.write(`[finch] fatal: ${err}\n`);
|
|
256
|
-
process.exit(1);
|
|
257
|
-
});
|
|
258
|
-
}
|