@finchagentic/mcp 4.1.0 ā 4.4.1
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/README.md +19 -15
- package/dist/agent-loop.js +75 -72
- package/dist/annotations.js +23 -14
- package/dist/convex.js +15 -18
- package/dist/index.js +14 -12
- package/dist/llm.js +12 -1
- package/dist/local-memory-file.js +14 -1
- package/dist/local-memory.js +13 -0
- package/dist/output-schemas.js +71 -17
- package/dist/project.js +36 -0
- package/dist/resources.js +6 -11
- package/dist/server.js +35 -13
- package/dist/token-gate.js +2 -2
- package/dist/tool-filter.js +14 -5
- package/dist/tools/agents.js +106 -394
- package/dist/tools/automation.js +42 -6
- package/dist/tools/base-mcp.js +3 -15
- package/dist/tools/base.js +34 -20
- package/dist/tools/coder.js +1 -1
- package/dist/tools/deep-research.js +1 -1
- package/dist/tools/defi.js +14 -34
- package/dist/tools/equity.js +10 -2
- package/dist/tools/events.js +1 -1
- package/dist/tools/github.js +51 -1
- package/dist/tools/insider.js +1 -1
- package/dist/tools/insight.js +4 -4
- package/dist/tools/market.js +5 -5
- package/dist/tools/memory.js +52 -88
- package/dist/tools/miroshark.js +8 -1
- package/dist/tools/monitor.js +8 -8
- package/dist/tools/os.js +9 -4
- package/dist/tools/packets.js +2 -2
- package/dist/tools/research-chain.js +1 -1
- package/dist/tools/research-compare.js +1 -1
- package/dist/tools/research.js +2 -2
- package/dist/tools/rh-bridge.js +1 -1
- package/dist/tools/rh-mcp.js +29 -4
- package/dist/tools/rh-orders.js +201 -123
- package/dist/tools/scanner.js +33 -3
- package/dist/tools/stake.js +369 -0
- package/dist/tools/vault.js +294 -40
- package/dist/wallet.js +130 -19
- package/package.json +4 -5
- package/dist/tools/framework.js +0 -150
package/dist/tools/automation.js
CHANGED
|
@@ -44,10 +44,17 @@ function categorizeError(err) {
|
|
|
44
44
|
exports.AUTOMATION_TOOLS = [
|
|
45
45
|
{
|
|
46
46
|
name: "create_automation",
|
|
47
|
-
description: "Create an automation in plain English. Supports DCA, price alerts, conditional buys/sells, and recurring market updates."
|
|
47
|
+
description: "Create an automation in plain English. Supports DCA, price alerts, conditional buys/sells, and recurring market updates. " +
|
|
48
|
+
"A swap/send automation arms UNATTENDED, REPEATING real fund movement (a backend cron fires it going forward, not just once) - " +
|
|
49
|
+
"requires two calls. First call WITHOUT confirm: returns a preview of the parsed trigger/action, nothing is created yet. " +
|
|
50
|
+
"Show that preview to the user in plain language and get explicit confirmation. Only then call again with the SAME rawInput " +
|
|
51
|
+
"and confirm: true to actually create it. Never pass confirm: true on the first call.",
|
|
48
52
|
inputSchema: {
|
|
49
53
|
type: "object",
|
|
50
|
-
properties: {
|
|
54
|
+
properties: {
|
|
55
|
+
rawInput: { type: "string", description: "Plain English description of the automation" },
|
|
56
|
+
confirm: { type: "boolean", description: "Must be true to actually create. Omit or false to get a preview only - nothing is created." },
|
|
57
|
+
},
|
|
51
58
|
required: ["rawInput"],
|
|
52
59
|
},
|
|
53
60
|
},
|
|
@@ -108,7 +115,7 @@ exports.AUTOMATION_TOOLS = [
|
|
|
108
115
|
},
|
|
109
116
|
},
|
|
110
117
|
];
|
|
111
|
-
const CreateAutomationSchema = zod_1.z.object({ rawInput: zod_1.z.string().min(1) });
|
|
118
|
+
const CreateAutomationSchema = zod_1.z.object({ rawInput: zod_1.z.string().min(1), confirm: zod_1.z.boolean().optional() });
|
|
112
119
|
const AutomationIdSchema = zod_1.z.object({ automationId: zod_1.z.string().min(1) });
|
|
113
120
|
const RunAutomationSchema = zod_1.z.object({ automationId: zod_1.z.string().min(1), dryRun: zod_1.z.boolean().optional() });
|
|
114
121
|
const RunsSchema = zod_1.z.object({ automationId: zod_1.z.string().min(1), limit: zod_1.z.number().int().min(1).max(100).optional() });
|
|
@@ -148,15 +155,44 @@ async function handleAutomationTool(name, args) {
|
|
|
148
155
|
const parsed = CreateAutomationSchema.safeParse(args);
|
|
149
156
|
if (!parsed.success)
|
|
150
157
|
return { content: [{ type: "text", text: `Invalid input: rawInput ${parsed.error.issues[0].message}` }], isError: true };
|
|
151
|
-
const data = await (0, convex_js_1.callConvex)("/automations/create", "POST", { rawInput: parsed.data.rawInput }, "create_automation");
|
|
152
|
-
if (!data.success)
|
|
153
|
-
return { content: [{ type: "text", text: `Failed: ${data.error}` }], isError: true };
|
|
154
158
|
const triggerLabel = {
|
|
155
159
|
schedule: "ā° Schedule", price_drop_pct: "š Price Drop %", price_rise_pct: "š Price Rise %",
|
|
156
160
|
price_below: "ā¬ļø Price Below", price_above: "ā¬ļø Price Above",
|
|
157
161
|
dominance_below: "š Dominance Below", dominance_above: "š Dominance Above",
|
|
158
162
|
};
|
|
159
163
|
const actionLabel = { swap: "š± Swap", send: "š¤ Send", alert: "š Alert" };
|
|
164
|
+
// Not confirmed yet - dry-run only (backend validates/parses but does
|
|
165
|
+
// NOT create anything - see http.ts's /automations/create dryRun
|
|
166
|
+
// branch). A swap/send automation arms unattended, repeating real
|
|
167
|
+
// fund movement, so it needs a real preview + explicit confirm, same
|
|
168
|
+
// shape as base_mcp_swap/send - not created blind on the first call.
|
|
169
|
+
if (parsed.data.confirm !== true) {
|
|
170
|
+
const preview = await (0, convex_js_1.callConvex)("/automations/create", "POST", { rawInput: parsed.data.rawInput, dryRun: true }, "create_automation");
|
|
171
|
+
if (!preview.success)
|
|
172
|
+
return { content: [{ type: "text", text: `Could not parse this automation: ${preview.error}` }], isError: true };
|
|
173
|
+
const moneyMoving = preview.actionType === "swap" || preview.actionType === "send";
|
|
174
|
+
return {
|
|
175
|
+
content: [{
|
|
176
|
+
type: "text",
|
|
177
|
+
text: [
|
|
178
|
+
`**Preview - nothing created yet**`, ``,
|
|
179
|
+
`**Trigger:** ${triggerLabel[preview.triggerType] ?? preview.triggerType}`,
|
|
180
|
+
`**Action:** ${actionLabel[preview.actionType] ?? preview.actionType}`,
|
|
181
|
+
preview.fromToken && preview.toToken ? `**Swap:** ${preview.amountUsd ? `$${preview.amountUsd}` : `${preview.amountPct}%`} ${preview.fromToken} ā ${preview.toToken}` : ``,
|
|
182
|
+
preview.toAddress ? `**Send:** ${preview.amountUsd ? `$${preview.amountUsd}` : ""} ${preview.fromToken ?? ""} to \`${preview.toAddress}\`` : ``,
|
|
183
|
+
preview.priceToken ? `**Price condition:** ${preview.priceToken} ${preview.triggerType?.includes("above") ? "above" : "below"} ${preview.priceThreshold}` : ``,
|
|
184
|
+
preview.priceBaselineUsd ? `**Baseline price:** $${Number(preview.priceBaselineUsd).toLocaleString()}` : ``,
|
|
185
|
+
moneyMoving
|
|
186
|
+
? `\nā ļø This will move real funds, repeatedly, unattended, until paused or deleted. Show this preview to the user and get explicit confirmation.`
|
|
187
|
+
: `\nThis is alert-only - it will not move funds.`,
|
|
188
|
+
`\nIf this is correct, call create_automation again with the same rawInput and confirm: true.`,
|
|
189
|
+
].filter(Boolean).join("\n"),
|
|
190
|
+
}],
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
const data = await (0, convex_js_1.callConvex)("/automations/create", "POST", { rawInput: parsed.data.rawInput }, "create_automation");
|
|
194
|
+
if (!data.success)
|
|
195
|
+
return { content: [{ type: "text", text: `Failed: ${data.error}` }], isError: true };
|
|
160
196
|
return {
|
|
161
197
|
content: [{
|
|
162
198
|
type: "text",
|
package/dist/tools/base-mcp.js
CHANGED
|
@@ -306,9 +306,9 @@ async function resolveBasename(input) {
|
|
|
306
306
|
*
|
|
307
307
|
* `base_mcp_send` and `base_mcp_swap` sign and broadcast from the user's wallet
|
|
308
308
|
* on the first call ā an on-chain transfer that cannot be recalled. The chain's
|
|
309
|
-
* own swap tool (`rh_mcp_swap`) already required confirmation, as
|
|
310
|
-
* `vault_delete
|
|
311
|
-
* funds with less friction than
|
|
309
|
+
* own swap tool (`rh_mcp_swap`) already required confirmation, as does
|
|
310
|
+
* `vault_delete`; these two were the outliers, moving real
|
|
311
|
+
* funds with less friction than an irreversible delete. The guard also gives the
|
|
312
312
|
* model somewhere to stop when a destination address came from a scraped page
|
|
313
313
|
* or tool output rather than from the user.
|
|
314
314
|
*/
|
|
@@ -460,18 +460,6 @@ async function handleBaseMcpTool(name, args) {
|
|
|
460
460
|
structuredContent: buildBasenameResolution(a.name, result),
|
|
461
461
|
};
|
|
462
462
|
}
|
|
463
|
-
case "base_mcp_analyze": {
|
|
464
|
-
// Resolve basename if input is a name
|
|
465
|
-
let address = a.address;
|
|
466
|
-
if (typeof a.address === "string" && !/^0x[a-fA-F0-9]{40}$/.test(a.address)) {
|
|
467
|
-
const resolved = await resolveBasename(a.address);
|
|
468
|
-
if (resolved.error || !resolved.address) {
|
|
469
|
-
return { content: [{ type: "text", text: `Could not resolve "${a.address}": ${resolved.error ?? "unknown"}` }], isError: true };
|
|
470
|
-
}
|
|
471
|
-
address = resolved.address;
|
|
472
|
-
}
|
|
473
|
-
return (0, defi_js_1.handleDefiTool)("analyze_wallet", { address, label: a.label });
|
|
474
|
-
}
|
|
475
463
|
default:
|
|
476
464
|
return null;
|
|
477
465
|
}
|
package/dist/tools/base.js
CHANGED
|
@@ -4,10 +4,35 @@ exports.BASE_TOOLS = void 0;
|
|
|
4
4
|
exports.handleBaseTool = handleBaseTool;
|
|
5
5
|
const MORPHO_API = "https://blue-api.morpho.org/graphql";
|
|
6
6
|
const MOONWELL_API = "https://api.moonwell.fi/v1/markets";
|
|
7
|
+
/** Morpho's public API occasionally 403s transiently (rate-limit/WAF blip,
|
|
8
|
+
* confirmed by hand: identical requests succeed moments later) - one retry
|
|
9
|
+
* after a short delay clears most of these without adding real latency to
|
|
10
|
+
* the common case. */
|
|
11
|
+
async function fetchMorphoGql(query) {
|
|
12
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
13
|
+
if (attempt > 0)
|
|
14
|
+
await new Promise((r) => setTimeout(r, 800));
|
|
15
|
+
const res = await fetch(MORPHO_API, {
|
|
16
|
+
method: "POST",
|
|
17
|
+
headers: { "Content-Type": "application/json" },
|
|
18
|
+
body: JSON.stringify({ query }),
|
|
19
|
+
signal: AbortSignal.timeout(15000),
|
|
20
|
+
});
|
|
21
|
+
if (res.ok)
|
|
22
|
+
return res.json();
|
|
23
|
+
if (attempt === 1)
|
|
24
|
+
throw new Error(`Morpho API error: ${res.status} (after retry)`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
7
27
|
exports.BASE_TOOLS = [
|
|
8
28
|
{
|
|
9
29
|
name: "base_mcp_yield_vaults",
|
|
10
|
-
description: "Find the best yield/earning opportunities on Base chain using Morpho vaults
|
|
30
|
+
description: "Find the best yield/earning opportunities on Base chain using Morpho vaults - queries Morpho's own " +
|
|
31
|
+
"API directly, so numbers are fresher than get_defi_yields' DeFiLlama-aggregated Morpho figures, " +
|
|
32
|
+
"but this tool only covers Morpho (not Aerodrome/Uniswap/other protocols - use get_defi_yields for " +
|
|
33
|
+
"cross-protocol comparison). Returns all vaults ranked by APY ā use this when the user asks about " +
|
|
34
|
+
"yield farming, APY, best rates to earn, where to put USDC, or passive income on Base. For yield on " +
|
|
35
|
+
"a specific token, also see base_mcp_lend.",
|
|
11
36
|
inputSchema: {
|
|
12
37
|
type: "object",
|
|
13
38
|
properties: {
|
|
@@ -25,7 +50,12 @@ exports.BASE_TOOLS = [
|
|
|
25
50
|
},
|
|
26
51
|
{
|
|
27
52
|
name: "base_mcp_lending_rates",
|
|
28
|
-
description: "Get lending and borrowing rates across all Moonwell markets on Base
|
|
53
|
+
description: "Get lending and borrowing rates across all Moonwell markets on Base - queries Moonwell's own API " +
|
|
54
|
+
"directly, so numbers are fresher than get_defi_yields' DeFiLlama-aggregated Moonwell figures, but " +
|
|
55
|
+
"this tool only covers Moonwell (use get_defi_yields for cross-protocol comparison, or " +
|
|
56
|
+
"base_mcp_yield_vaults for Morpho specifically). Returns supply APY, borrow APY, liquidity, and " +
|
|
57
|
+
"utilization per asset. Use when the user asks about borrow rates, lending rates, interest rates, " +
|
|
58
|
+
"or supply APY on Base.",
|
|
29
59
|
inputSchema: {
|
|
30
60
|
type: "object",
|
|
31
61
|
properties: {
|
|
@@ -99,15 +129,7 @@ async function fetchMorphoVaults(asset, limit = 10) {
|
|
|
99
129
|
}
|
|
100
130
|
}
|
|
101
131
|
}`;
|
|
102
|
-
const
|
|
103
|
-
method: "POST",
|
|
104
|
-
headers: { "Content-Type": "application/json" },
|
|
105
|
-
body: JSON.stringify({ query: gql }),
|
|
106
|
-
signal: AbortSignal.timeout(15000),
|
|
107
|
-
});
|
|
108
|
-
if (!res.ok)
|
|
109
|
-
throw new Error(`Morpho API error: ${res.status}`);
|
|
110
|
-
const data = await res.json();
|
|
132
|
+
const data = await fetchMorphoGql(gql);
|
|
111
133
|
let vaults = data?.data?.vaults?.items ?? [];
|
|
112
134
|
// Filter out test/spam vaults: min $10k TVL, max 500% APY
|
|
113
135
|
vaults = vaults.filter((v) => {
|
|
@@ -199,15 +221,7 @@ async function prepareDeposit(vaultName, asset, amount) {
|
|
|
199
221
|
}
|
|
200
222
|
}
|
|
201
223
|
}`;
|
|
202
|
-
const
|
|
203
|
-
method: "POST",
|
|
204
|
-
headers: { "Content-Type": "application/json" },
|
|
205
|
-
body: JSON.stringify({ query: gql }),
|
|
206
|
-
signal: AbortSignal.timeout(15000),
|
|
207
|
-
});
|
|
208
|
-
if (!res.ok)
|
|
209
|
-
throw new Error(`Morpho API error: ${res.status}`);
|
|
210
|
-
const data = await res.json();
|
|
224
|
+
const data = await fetchMorphoGql(gql);
|
|
211
225
|
let vaults = data?.data?.vaults?.items ?? [];
|
|
212
226
|
// Filter by asset first
|
|
213
227
|
vaults = vaults.filter((v) => v.asset?.symbol?.toLowerCase() === asset.toLowerCase());
|
package/dist/tools/coder.js
CHANGED
|
@@ -52,7 +52,7 @@ async function handleCoderTool(name, args) {
|
|
|
52
52
|
return null;
|
|
53
53
|
const p = AuditSchema.safeParse(args);
|
|
54
54
|
if (!p.success)
|
|
55
|
-
return err(
|
|
55
|
+
return err(p.error.issues[0].message);
|
|
56
56
|
const { code, focus = [] } = p.data;
|
|
57
57
|
// The pattern scan grounds the review. Without it an audit is pure model
|
|
58
58
|
// opinion ā someone might trust "looks safe" with nothing behind it.
|
|
@@ -915,7 +915,7 @@ async function handleDeepResearch(name, args, onProgress) {
|
|
|
915
915
|
return null;
|
|
916
916
|
const parsed = InputSchema.safeParse(args);
|
|
917
917
|
if (!parsed.success) {
|
|
918
|
-
return { content: [{ type: "text", text:
|
|
918
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
919
919
|
}
|
|
920
920
|
const { query, focus, continueFrom } = parsed.data;
|
|
921
921
|
const clientQueries = parsed.data.queries ?? [];
|
package/dist/tools/defi.js
CHANGED
|
@@ -11,7 +11,11 @@ exports.DEFI_TOOLS = [
|
|
|
11
11
|
{
|
|
12
12
|
name: "get_defi_yields",
|
|
13
13
|
description: "Fetch top DeFi yield opportunities on Base - Morpho, Moonwell, Aerodrome, Uniswap, and more. " +
|
|
14
|
-
"Returns
|
|
14
|
+
"Returns APY, TVL, and pool info aggregated from DeFiLlama (no API key required) - broadest " +
|
|
15
|
+
"protocol coverage, but DeFiLlama's numbers can lag the protocol's own API by hours. Good first " +
|
|
16
|
+
"stop for comparing across protocols. For the freshest Morpho-vault-specific numbers use " +
|
|
17
|
+
"base_mcp_yield_vaults instead; for the freshest Moonwell supply/borrow rates use " +
|
|
18
|
+
"base_mcp_lending_rates instead - both query the protocol directly. " +
|
|
15
19
|
"Filter by token or minimum APY. Use before depositing to find the best rates.",
|
|
16
20
|
inputSchema: {
|
|
17
21
|
type: "object",
|
|
@@ -34,7 +38,6 @@ const SwapSchema = zod_1.z.object({
|
|
|
34
38
|
const DEFAULT_MAX_SLIPPAGE_PCT = 1.0;
|
|
35
39
|
const DEFAULT_MAX_PRICE_IMPACT_PCT = 3.0;
|
|
36
40
|
const SendSchema = zod_1.z.object({ token: zod_1.z.string().min(1), toAddress: zod_1.z.string().regex(/^0x[0-9a-fA-F]{40}$/, "must be a valid 0x address"), amount: zod_1.z.string().min(1) });
|
|
37
|
-
const AnalyzeWalletSchema = zod_1.z.object({ address: zod_1.z.string().regex(/^0x[0-9a-fA-F]{40}$/, "must be a valid 0x address"), label: zod_1.z.string().optional() });
|
|
38
41
|
const DefiYieldsSchema = zod_1.z.object({
|
|
39
42
|
token: zod_1.z.string().optional(),
|
|
40
43
|
minApy: zod_1.z.number().optional(),
|
|
@@ -96,7 +99,7 @@ async function handleDefiTool(name, args) {
|
|
|
96
99
|
case "estimate_swap": {
|
|
97
100
|
const parsed = SwapSchema.safeParse(args);
|
|
98
101
|
if (!parsed.success)
|
|
99
|
-
return { content: [{ type: "text", text:
|
|
102
|
+
return { content: [{ type: "text", text: `${String(parsed.error.issues[0].path[0])}: ${parsed.error.issues[0].message}` }], isError: true };
|
|
100
103
|
const { fromToken, toToken, amount, maxSlippagePct, maxPriceImpactPct } = parsed.data;
|
|
101
104
|
const slippageLimit = maxSlippagePct ?? DEFAULT_MAX_SLIPPAGE_PCT;
|
|
102
105
|
const impactLimit = maxPriceImpactPct ?? DEFAULT_MAX_PRICE_IMPACT_PCT;
|
|
@@ -134,7 +137,7 @@ async function handleDefiTool(name, args) {
|
|
|
134
137
|
case "swap_tokens": {
|
|
135
138
|
const parsed = SwapSchema.safeParse(args);
|
|
136
139
|
if (!parsed.success)
|
|
137
|
-
return { content: [{ type: "text", text:
|
|
140
|
+
return { content: [{ type: "text", text: `${String(parsed.error.issues[0].path[0])}: ${parsed.error.issues[0].message}` }], isError: true };
|
|
138
141
|
const { fromToken, toToken, amount, maxSlippagePct, maxPriceImpactPct } = parsed.data;
|
|
139
142
|
const slippageLimit = maxSlippagePct ?? DEFAULT_MAX_SLIPPAGE_PCT;
|
|
140
143
|
const impactLimit = maxPriceImpactPct ?? DEFAULT_MAX_PRICE_IMPACT_PCT;
|
|
@@ -187,7 +190,7 @@ async function handleDefiTool(name, args) {
|
|
|
187
190
|
case "send_token": {
|
|
188
191
|
const parsed = SendSchema.safeParse(args);
|
|
189
192
|
if (!parsed.success)
|
|
190
|
-
return { content: [{ type: "text", text:
|
|
193
|
+
return { content: [{ type: "text", text: `${String(parsed.error.issues[0].path[0])}: ${parsed.error.issues[0].message}` }], isError: true };
|
|
191
194
|
const { token, toAddress, amount } = parsed.data;
|
|
192
195
|
const wallet = await (0, wallet_js_1.getOrCreateWallet)();
|
|
193
196
|
const result = await (0, convex_js_1.callConvex)("/mcp/defi/send", "POST", parsed.data, "send_token");
|
|
@@ -214,38 +217,15 @@ async function handleDefiTool(name, args) {
|
|
|
214
217
|
isError: receipt.mined && !receipt.ok,
|
|
215
218
|
};
|
|
216
219
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const data = await (0, convex_js_1.callConvex)("/wallet/analyze", "POST", { address, label }, "analyze_wallet");
|
|
223
|
-
if (data.error)
|
|
224
|
-
return { content: [{ type: "text", text: `Wallet analysis failed: ${data.error}` }], isError: true };
|
|
225
|
-
const total = (data.totalUsd ?? 0).toFixed(2);
|
|
226
|
-
const walletLabel = label ? ` - ${label}` : "";
|
|
227
|
-
const topHoldings = (data.holdings ?? [])
|
|
228
|
-
.slice(0, 8)
|
|
229
|
-
.map(h => `⢠**${h.token}**: $${(h.valueUsd ?? 0).toFixed(2)}${h.pct != null ? ` (${h.pct}%)` : ""}`)
|
|
230
|
-
.join("\n");
|
|
231
|
-
const profileLine = data.profile ? `**Profile:** ${data.profile}\n` : "";
|
|
232
|
-
const header = [
|
|
233
|
-
`**Wallet Analysis**${walletLabel}`,
|
|
234
|
-
`\`${address}\``,
|
|
235
|
-
`**Portfolio value:** $${total}`,
|
|
236
|
-
``,
|
|
237
|
-
profileLine,
|
|
238
|
-
`**Holdings:**`,
|
|
239
|
-
topHoldings || "No token holdings found.",
|
|
240
|
-
``,
|
|
241
|
-
].join("\n");
|
|
242
|
-
const body = data.analysis ?? (data.analysisError ? `*AI analysis unavailable: ${data.analysisError}*` : "*AI analysis not available*");
|
|
243
|
-
return { content: [{ type: "text", text: header + body }] };
|
|
244
|
-
}
|
|
220
|
+
// analyze_wallet was removed - it called POST /wallet/analyze, which was
|
|
221
|
+
// never registered in app/convex/http.ts (only a dangling section-header
|
|
222
|
+
// comment exists there). The tool was never in the exported DEFI_TOOLS
|
|
223
|
+
// list either, so this case was already unreachable dead code - no Tool
|
|
224
|
+
// registers the name "analyze_wallet" for the MCP dispatcher to route to.
|
|
245
225
|
case "get_defi_yields": {
|
|
246
226
|
const parsed = DefiYieldsSchema.safeParse(args ?? {});
|
|
247
227
|
if (!parsed.success)
|
|
248
|
-
return { content: [{ type: "text", text:
|
|
228
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
249
229
|
const { token, minApy = 1, limit = 20 } = parsed.data;
|
|
250
230
|
let pools;
|
|
251
231
|
try {
|
package/dist/tools/equity.js
CHANGED
|
@@ -11,8 +11,13 @@
|
|
|
11
11
|
// and is a stronger analyst than anything this module could embed.
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
13
|
exports.EQUITY_TOOLS = void 0;
|
|
14
|
+
exports.buildStockFundamentals = buildStockFundamentals;
|
|
14
15
|
exports.handleEquityTool = handleEquityTool;
|
|
15
16
|
const zod_1 = require("zod");
|
|
17
|
+
// āā Structured output builder (schema in output-schemas.ts) āāāāāāāāāāāāāāāāā
|
|
18
|
+
function buildStockFundamentals(ticker, companyName, cik, quote, quarterly, annual) {
|
|
19
|
+
return { ticker, companyName, cik, quote, quarterly, annual };
|
|
20
|
+
}
|
|
16
21
|
const SEC_UA = "Finch MCP research (contact: support@finchagentic.com)";
|
|
17
22
|
const SEC_TICKERS = "https://www.sec.gov/files/company_tickers.json";
|
|
18
23
|
const SEC_CONCEPT = (cik, tag) => `https://data.sec.gov/api/xbrl/companyconcept/CIK${cik}/us-gaap/${tag}.json`;
|
|
@@ -178,7 +183,7 @@ async function handleEquityTool(name, args) {
|
|
|
178
183
|
return null;
|
|
179
184
|
const parsed = Schema.safeParse(args);
|
|
180
185
|
if (!parsed.success) {
|
|
181
|
-
return { content: [{ type: "text", text:
|
|
186
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
182
187
|
}
|
|
183
188
|
const ticker = parsed.data.ticker.trim().toUpperCase();
|
|
184
189
|
const periods = parsed.data.periods ?? 6;
|
|
@@ -360,5 +365,8 @@ async function handleEquityTool(name, args) {
|
|
|
360
365
|
// Issuers tag the same concept differently, so naming the tag behind each
|
|
361
366
|
// series makes the figures checkable against EDGAR rather than trusted.
|
|
362
367
|
`_XBRL concepts used: ${usedTags.join(" Ā· ")}_`, ``, `_Figures are as filed with the SEC. Not investment advice._`);
|
|
363
|
-
return {
|
|
368
|
+
return {
|
|
369
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
370
|
+
structuredContent: buildStockFundamentals(ticker, company.name ?? null, company.cik ?? null, quote, quarterly, annual),
|
|
371
|
+
};
|
|
364
372
|
}
|
package/dist/tools/events.js
CHANGED
|
@@ -97,7 +97,7 @@ async function handleEventTool(name, args) {
|
|
|
97
97
|
return null;
|
|
98
98
|
const parsed = Schema.safeParse(args);
|
|
99
99
|
if (!parsed.success) {
|
|
100
|
-
return { content: [{ type: "text", text:
|
|
100
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
101
101
|
}
|
|
102
102
|
const ticker = parsed.data.ticker.trim().toUpperCase();
|
|
103
103
|
const limit = parsed.data.limit ?? 15;
|
package/dist/tools/github.js
CHANGED
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.GITHUB_TOOLS = void 0;
|
|
4
37
|
exports.buildRepoList = buildRepoList;
|
|
@@ -9,13 +42,30 @@ exports.buildIssueDetail = buildIssueDetail;
|
|
|
9
42
|
exports.buildCommitList = buildCommitList;
|
|
10
43
|
exports.buildCodeSearch = buildCodeSearch;
|
|
11
44
|
exports.handleGithubTool = handleGithubTool;
|
|
45
|
+
const fs = __importStar(require("fs"));
|
|
46
|
+
const path = __importStar(require("path"));
|
|
12
47
|
const GH_BASE = "https://api.github.com";
|
|
48
|
+
// Was hardcoded "finch-mcp/3.28.0" - stale the moment package.json's version
|
|
49
|
+
// moved on (already 4.4.0 locally as of this fix), unlike server.ts/cli.ts/
|
|
50
|
+
// index.ts which all read it from package.json at runtime per this repo's
|
|
51
|
+
// own stated policy (see CLAUDE.md's mcp-server note). __dirname here is
|
|
52
|
+
// dist/tools/ once built, so two levels up reaches the package root - one
|
|
53
|
+
// level deeper than server.ts's own copy of this pattern (dist/).
|
|
54
|
+
const PKG_VERSION = (() => {
|
|
55
|
+
try {
|
|
56
|
+
const raw = fs.readFileSync(path.join(__dirname, "..", "..", "package.json"), "utf8");
|
|
57
|
+
return JSON.parse(raw).version ?? "unknown";
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return "unknown";
|
|
61
|
+
}
|
|
62
|
+
})();
|
|
13
63
|
function ghHeaders() {
|
|
14
64
|
const token = process.env.GITHUB_TOKEN;
|
|
15
65
|
const h = {
|
|
16
66
|
Accept: "application/vnd.github.v3+json",
|
|
17
67
|
"X-GitHub-Api-Version": "2022-11-28",
|
|
18
|
-
"User-Agent":
|
|
68
|
+
"User-Agent": `finch-mcp/${PKG_VERSION}`,
|
|
19
69
|
};
|
|
20
70
|
if (token)
|
|
21
71
|
h.Authorization = `Bearer ${token}`;
|
package/dist/tools/insider.js
CHANGED
|
@@ -116,7 +116,7 @@ async function handleInsiderTool(name, args) {
|
|
|
116
116
|
return null;
|
|
117
117
|
const parsed = Schema.safeParse(args);
|
|
118
118
|
if (!parsed.success) {
|
|
119
|
-
return { content: [{ type: "text", text:
|
|
119
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
120
120
|
}
|
|
121
121
|
const ticker = parsed.data.ticker.trim().toUpperCase();
|
|
122
122
|
const limit = parsed.data.limit ?? 15;
|
package/dist/tools/insight.js
CHANGED
|
@@ -451,7 +451,7 @@ async function handleInsightTool(name, args) {
|
|
|
451
451
|
if (name === "market_thesis") {
|
|
452
452
|
const parsed = MarketThesisSchema.safeParse(args);
|
|
453
453
|
if (!parsed.success)
|
|
454
|
-
return { content: [{ type: "text", text:
|
|
454
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
455
455
|
const { token, context } = parsed.data;
|
|
456
456
|
const priceData = await fetchVerifiedPrice(token);
|
|
457
457
|
// Hard guard: without a verified live price, refuse to generate. Better
|
|
@@ -481,7 +481,7 @@ async function handleInsightTool(name, args) {
|
|
|
481
481
|
// thesis is model work ā and the caller is a model that can also weigh the
|
|
482
482
|
// user's stated context. Hand over grounded data plus the structure.
|
|
483
483
|
const suggest = process.env.TRIGGER_SECRET_KEY
|
|
484
|
-
? `\n\n---\nš” Use \`
|
|
484
|
+
? `\n\n---\nš” Use \`schedule_research\` for scheduled briefings on ${token.toUpperCase()}.`
|
|
485
485
|
: "";
|
|
486
486
|
return {
|
|
487
487
|
content: [{
|
|
@@ -519,7 +519,7 @@ async function handleInsightTool(name, args) {
|
|
|
519
519
|
if (name === "trade_plan") {
|
|
520
520
|
const parsed = TradePlanSchema.safeParse(args);
|
|
521
521
|
if (!parsed.success)
|
|
522
|
-
return { content: [{ type: "text", text:
|
|
522
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
523
523
|
const { token, side = "long", portfolioSize, riskTolerance = "moderate", timeframe } = parsed.data;
|
|
524
524
|
const priceData = await fetchVerifiedPrice(token);
|
|
525
525
|
// Hard guard: trade plans without verified live price = entry/SL/TP
|
|
@@ -575,7 +575,7 @@ async function handleInsightTool(name, args) {
|
|
|
575
575
|
]
|
|
576
576
|
: [`**Max position (${riskTolerance}):** ${band.label} ā pass \`portfolioSize\` for USD figures.`];
|
|
577
577
|
const suggest = process.env.TRIGGER_SECRET_KEY
|
|
578
|
-
? `\n\n---\nš” Use \`
|
|
578
|
+
? `\n\n---\nš” Use \`schedule_research\` for scheduled briefings on ${token.toUpperCase()}.`
|
|
579
579
|
: "";
|
|
580
580
|
return {
|
|
581
581
|
content: [{
|
package/dist/tools/market.js
CHANGED
|
@@ -293,7 +293,7 @@ async function handleMarketTool(name, args) {
|
|
|
293
293
|
case "get_market_data": {
|
|
294
294
|
const parsed = GetMarketDataSchema.safeParse(args ?? {});
|
|
295
295
|
if (!parsed.success)
|
|
296
|
-
return { content: [{ type: "text", text:
|
|
296
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
297
297
|
const { token } = parsed.data;
|
|
298
298
|
if (token) {
|
|
299
299
|
const resolved = await resolveTokenId(token);
|
|
@@ -349,7 +349,7 @@ async function handleMarketTool(name, args) {
|
|
|
349
349
|
case "get_token_data": {
|
|
350
350
|
const parsed = GetTokenDataSchema.safeParse(args);
|
|
351
351
|
if (!parsed.success)
|
|
352
|
-
return { content: [{ type: "text", text:
|
|
352
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
353
353
|
const q = parsed.data.question;
|
|
354
354
|
// Try to extract a known symbol first, then fall back to search
|
|
355
355
|
const upperQ = q.toUpperCase();
|
|
@@ -381,7 +381,7 @@ async function handleMarketTool(name, args) {
|
|
|
381
381
|
case "compare_tokens": {
|
|
382
382
|
const parsed = CompareTokensSchema.safeParse(args);
|
|
383
383
|
if (!parsed.success)
|
|
384
|
-
return { content: [{ type: "text", text:
|
|
384
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
385
385
|
const syms = parsed.data.tokens.map(t => t.toUpperCase());
|
|
386
386
|
const ids = syms.map(s => SYMBOL_TO_ID[s]).filter(Boolean);
|
|
387
387
|
const unknown = syms.filter(s => !SYMBOL_TO_ID[s]);
|
|
@@ -449,7 +449,7 @@ async function handleMarketTool(name, args) {
|
|
|
449
449
|
case "token_history": {
|
|
450
450
|
const parsed = TokenHistorySchema.safeParse(args);
|
|
451
451
|
if (!parsed.success)
|
|
452
|
-
return { content: [{ type: "text", text:
|
|
452
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
453
453
|
const days = parsed.data.days ?? 7;
|
|
454
454
|
const resolved = await resolveTokenId(parsed.data.token);
|
|
455
455
|
if (!resolved)
|
|
@@ -498,7 +498,7 @@ async function handleMarketTool(name, args) {
|
|
|
498
498
|
case "get_base_token_data": {
|
|
499
499
|
const parsed = GetBaseTokenDataSchema.safeParse(args);
|
|
500
500
|
if (!parsed.success)
|
|
501
|
-
return { content: [{ type: "text", text:
|
|
501
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
502
502
|
const address = parsed.data.tokenAddress.toLowerCase();
|
|
503
503
|
const [pair, coingeckoId] = await Promise.all([
|
|
504
504
|
fetchDexscreenerBaseToken(address),
|