@finchagentic/mcp 4.2.0 β 4.5.0
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 +17 -12
- package/dist/annotations.js +9 -1
- package/dist/cli.js +139 -0
- package/dist/convex.js +31 -4
- package/dist/index.js +11 -8
- package/dist/project.js +36 -0
- package/dist/server.js +14 -6
- package/dist/tool-filter.js +5 -5
- package/dist/tools/agents.js +46 -6
- package/dist/tools/automation.js +47 -7
- package/dist/tools/coder.js +1 -1
- package/dist/tools/deep-research.js +1 -1
- package/dist/tools/defi.js +4 -4
- package/dist/tools/equity.js +1 -1
- 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 +2 -2
- package/dist/tools/market.js +5 -5
- package/dist/tools/memory.js +8 -8
- package/dist/tools/miroshark.js +8 -1
- package/dist/tools/monitor.js +2 -2
- package/dist/tools/os.js +9 -3
- 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 +16 -2
- package/dist/tools/rh-orders.js +13 -6
- package/dist/tools/scanner.js +3 -3
- package/dist/tools/stake.js +43 -3
- package/dist/tools/vault.js +195 -19
- package/package.json +3 -4
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",
|
|
@@ -265,7 +301,11 @@ async function handleAutomationTool(name, args) {
|
|
|
265
301
|
if (!parsed.success)
|
|
266
302
|
return { content: [{ type: "text", text: `Invalid input: automationId ${parsed.error.issues[0].message}` }], isError: true };
|
|
267
303
|
const { automationId, dryRun } = parsed.data;
|
|
268
|
-
|
|
304
|
+
// A real (non-dryRun) trigger moves funds if the automation is a
|
|
305
|
+
// swap/send - noRetry so a lost response never risks re-sending the
|
|
306
|
+
// same trigger. A dryRun call does nothing server-side, so retrying
|
|
307
|
+
// it is harmless and stays on the default retry behavior.
|
|
308
|
+
const data = await (0, convex_js_1.callConvex)("/automations/run", "POST", { automationId, dryRun: !!dryRun }, "run_automation", 30000, !dryRun);
|
|
269
309
|
if (data.error) {
|
|
270
310
|
const cat = categorizeError(data.error);
|
|
271
311
|
return {
|
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
|
@@ -99,7 +99,7 @@ async function handleDefiTool(name, args) {
|
|
|
99
99
|
case "estimate_swap": {
|
|
100
100
|
const parsed = SwapSchema.safeParse(args);
|
|
101
101
|
if (!parsed.success)
|
|
102
|
-
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 };
|
|
103
103
|
const { fromToken, toToken, amount, maxSlippagePct, maxPriceImpactPct } = parsed.data;
|
|
104
104
|
const slippageLimit = maxSlippagePct ?? DEFAULT_MAX_SLIPPAGE_PCT;
|
|
105
105
|
const impactLimit = maxPriceImpactPct ?? DEFAULT_MAX_PRICE_IMPACT_PCT;
|
|
@@ -137,7 +137,7 @@ async function handleDefiTool(name, args) {
|
|
|
137
137
|
case "swap_tokens": {
|
|
138
138
|
const parsed = SwapSchema.safeParse(args);
|
|
139
139
|
if (!parsed.success)
|
|
140
|
-
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 };
|
|
141
141
|
const { fromToken, toToken, amount, maxSlippagePct, maxPriceImpactPct } = parsed.data;
|
|
142
142
|
const slippageLimit = maxSlippagePct ?? DEFAULT_MAX_SLIPPAGE_PCT;
|
|
143
143
|
const impactLimit = maxPriceImpactPct ?? DEFAULT_MAX_PRICE_IMPACT_PCT;
|
|
@@ -190,7 +190,7 @@ async function handleDefiTool(name, args) {
|
|
|
190
190
|
case "send_token": {
|
|
191
191
|
const parsed = SendSchema.safeParse(args);
|
|
192
192
|
if (!parsed.success)
|
|
193
|
-
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 };
|
|
194
194
|
const { token, toAddress, amount } = parsed.data;
|
|
195
195
|
const wallet = await (0, wallet_js_1.getOrCreateWallet)();
|
|
196
196
|
const result = await (0, convex_js_1.callConvex)("/mcp/defi/send", "POST", parsed.data, "send_token");
|
|
@@ -225,7 +225,7 @@ async function handleDefiTool(name, args) {
|
|
|
225
225
|
case "get_defi_yields": {
|
|
226
226
|
const parsed = DefiYieldsSchema.safeParse(args ?? {});
|
|
227
227
|
if (!parsed.success)
|
|
228
|
-
return { content: [{ type: "text", text:
|
|
228
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
229
229
|
const { token, minApy = 1, limit = 20 } = parsed.data;
|
|
230
230
|
let pools;
|
|
231
231
|
try {
|
package/dist/tools/equity.js
CHANGED
|
@@ -183,7 +183,7 @@ async function handleEquityTool(name, args) {
|
|
|
183
183
|
return null;
|
|
184
184
|
const parsed = Schema.safeParse(args);
|
|
185
185
|
if (!parsed.success) {
|
|
186
|
-
return { content: [{ type: "text", text:
|
|
186
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
187
187
|
}
|
|
188
188
|
const ticker = parsed.data.ticker.trim().toUpperCase();
|
|
189
189
|
const periods = parsed.data.periods ?? 6;
|
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
|
|
@@ -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
|
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),
|
package/dist/tools/memory.js
CHANGED
|
@@ -519,7 +519,7 @@ async function handleMemoryTool(name, args) {
|
|
|
519
519
|
case "memory_add": {
|
|
520
520
|
const parsed = AddSchema.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 { content, title, tags, sourceUrl, force } = parsed.data;
|
|
524
524
|
// `sourceUrl` is not stored, it is fetched and indexed β by the local
|
|
525
525
|
// memory server on the user's own machine when local mode is on. An
|
|
@@ -580,7 +580,7 @@ async function handleMemoryTool(name, args) {
|
|
|
580
580
|
case "memory_search": {
|
|
581
581
|
const parsed = SearchSchema.safeParse(args);
|
|
582
582
|
if (!parsed.success)
|
|
583
|
-
return { content: [{ type: "text", text:
|
|
583
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
584
584
|
const { query, limit = 10 } = parsed.data;
|
|
585
585
|
// Over-fetch so post-decay ranking still has enough material.
|
|
586
586
|
const overfetch = Math.min(50, Math.max(limit * 2, 20));
|
|
@@ -662,7 +662,7 @@ async function handleMemoryTool(name, args) {
|
|
|
662
662
|
case "memory_context": {
|
|
663
663
|
const parsed = ContextSchema.safeParse(args);
|
|
664
664
|
if (!parsed.success)
|
|
665
|
-
return { content: [{ type: "text", text:
|
|
665
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
666
666
|
const { topic, limit = 8 } = parsed.data;
|
|
667
667
|
// v3.25.1: use hybrid retrieval so context loading picks up exact-token
|
|
668
668
|
// matches (env var names, model IDs, contract addresses) that semantic-
|
|
@@ -722,7 +722,7 @@ async function handleMemoryTool(name, args) {
|
|
|
722
722
|
case "memory_list": {
|
|
723
723
|
const parsed = ListSchema.safeParse(args ?? {});
|
|
724
724
|
if (!parsed.success)
|
|
725
|
-
return { content: [{ type: "text", text:
|
|
725
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
726
726
|
const { limit = 20, tag } = parsed.data;
|
|
727
727
|
const localList = (0, local_memory_js_1.getLocalMemoryConfig)();
|
|
728
728
|
let results;
|
|
@@ -753,7 +753,7 @@ async function handleMemoryTool(name, args) {
|
|
|
753
753
|
case "memory_delete": {
|
|
754
754
|
const parsed = DeleteMemSchema.safeParse(args);
|
|
755
755
|
if (!parsed.success)
|
|
756
|
-
return { content: [{ type: "text", text:
|
|
756
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
757
757
|
if (args?.confirm !== true) {
|
|
758
758
|
return {
|
|
759
759
|
content: [{
|
|
@@ -784,7 +784,7 @@ async function handleMemoryTool(name, args) {
|
|
|
784
784
|
case "memory_insight": {
|
|
785
785
|
const parsed = InsightSchema.safeParse(args);
|
|
786
786
|
if (!parsed.success)
|
|
787
|
-
return { content: [{ type: "text", text:
|
|
787
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
788
788
|
const { topic, depth = "standard" } = parsed.data;
|
|
789
789
|
const memLimit = depth === "deep" ? 15 : depth === "quick" ? 5 : 8;
|
|
790
790
|
// v3.25.1: hybrid retrieval for memory side (was semantic-only) so the
|
|
@@ -872,7 +872,7 @@ async function handleMemoryTool(name, args) {
|
|
|
872
872
|
case "memory_extract": {
|
|
873
873
|
const parsed = ExtractSchema.safeParse(args);
|
|
874
874
|
if (!parsed.success)
|
|
875
|
-
return { content: [{ type: "text", text:
|
|
875
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
876
876
|
const { text, facts, source = "extract" } = parsed.data;
|
|
877
877
|
// ββ PASS 1: hand the text back with the extraction rubric βββββββββββ
|
|
878
878
|
// Deciding what counts as a fact is judgement about *this user's*
|
|
@@ -929,7 +929,7 @@ async function handleMemoryTool(name, args) {
|
|
|
929
929
|
case "memory_consolidate": {
|
|
930
930
|
const parsed = ConsolidateSchema.safeParse(args);
|
|
931
931
|
if (!parsed.success)
|
|
932
|
-
return { content: [{ type: "text", text:
|
|
932
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
933
933
|
const { topic, limit = 12, summary } = parsed.data;
|
|
934
934
|
const localConsolidateCfg = (0, local_memory_js_1.getLocalMemoryConfig)();
|
|
935
935
|
// ββ PASS 2: save the caller's merged version ββββββββββββββββββββββββ
|
package/dist/tools/miroshark.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MIROSHARK_TOOLS = void 0;
|
|
4
4
|
exports.handleMirosharkTool = handleMirosharkTool;
|
|
5
|
+
const config_js_1 = require("../config.js");
|
|
5
6
|
const CONVEX_SITE = process.env.FINCH_CONVEX_URL ?? "https://befitting-porcupine-276.convex.site";
|
|
6
7
|
exports.MIROSHARK_TOOLS = [
|
|
7
8
|
{
|
|
@@ -50,7 +51,13 @@ exports.MIROSHARK_TOOLS = [
|
|
|
50
51
|
];
|
|
51
52
|
// ββ HTTP helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
52
53
|
function authHeaders() {
|
|
53
|
-
|
|
54
|
+
// Bug fix: this used to read only process.env.FINCH_API_KEY/
|
|
55
|
+
// FINCH_SESSION_TOKEN directly, bypassing getSavedToken()'s env-var-then-
|
|
56
|
+
// saved-config fallback that every other tool goes through (via
|
|
57
|
+
// callConvex/callConvexRaw in convex.ts). A user authenticated via
|
|
58
|
+
// `finch login` (writes to ~/.finch/config.json, not an env var) got a
|
|
59
|
+
// silent, unauthenticated request here while every other tool worked.
|
|
60
|
+
const key = process.env.FINCH_API_KEY ?? (0, config_js_1.getSavedToken)();
|
|
54
61
|
return key ? { Authorization: `Bearer ${key}` } : {};
|
|
55
62
|
}
|
|
56
63
|
async function miroJson(path, method, body, timeoutMs = 90000) {
|
package/dist/tools/monitor.js
CHANGED
|
@@ -106,7 +106,7 @@ async function handleMonitorTool(name, args) {
|
|
|
106
106
|
if (name === "schedule_research") {
|
|
107
107
|
const parsed = CreateSchema.safeParse(args);
|
|
108
108
|
if (!parsed.success)
|
|
109
|
-
return { content: [{ type: "text", text:
|
|
109
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
110
110
|
const key = getKey();
|
|
111
111
|
if (!key)
|
|
112
112
|
return noKeyMsg();
|
|
@@ -284,7 +284,7 @@ async function handleMonitorTool(name, args) {
|
|
|
284
284
|
if (name === "cancel_monitor") {
|
|
285
285
|
const parsed = CancelSchema.safeParse(args);
|
|
286
286
|
if (!parsed.success)
|
|
287
|
-
return { content: [{ type: "text", text:
|
|
287
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
288
288
|
if (args?.confirm !== true) {
|
|
289
289
|
return {
|
|
290
290
|
content: [{
|
package/dist/tools/os.js
CHANGED
|
@@ -6,6 +6,7 @@ const convex_js_1 = require("../convex.js");
|
|
|
6
6
|
const token_gate_js_1 = require("../token-gate.js");
|
|
7
7
|
const wallet_js_1 = require("../wallet.js");
|
|
8
8
|
const local_memory_js_1 = require("../local-memory.js");
|
|
9
|
+
const config_js_1 = require("../config.js");
|
|
9
10
|
const CONVEX_SITE = process.env.FINCH_CONVEX_URL ?? "https://befitting-porcupine-276.convex.site";
|
|
10
11
|
exports.OS_TOOLS = [
|
|
11
12
|
{
|
|
@@ -199,13 +200,18 @@ async function handleOsTool(name, args) {
|
|
|
199
200
|
if (!message)
|
|
200
201
|
return { content: [{ type: "text", text: "Error: message is required" }] };
|
|
201
202
|
try {
|
|
202
|
-
|
|
203
|
+
// Bug fix: this used to read only process.env.FINCH_SESSION_TOKEN/
|
|
204
|
+
// FINCH_API_KEY directly, bypassing getSavedToken()'s fallback to
|
|
205
|
+
// ~/.finch/config.json - a user authenticated via `finch login`
|
|
206
|
+
// (not an env var) silently sent an empty Authorization header here
|
|
207
|
+
// while every other tool (which goes through callConvex) worked.
|
|
208
|
+
const res = await fetch(`${CONVEX_SITE}/finch/shell/chat`, {
|
|
203
209
|
method: "POST",
|
|
204
210
|
headers: {
|
|
205
211
|
"Content-Type": "application/json",
|
|
206
|
-
"Authorization": `Bearer ${
|
|
212
|
+
"Authorization": `Bearer ${(0, config_js_1.getSavedToken)() ?? process.env.FINCH_API_KEY ?? ""}`,
|
|
207
213
|
},
|
|
208
|
-
body: JSON.stringify({ message, agentId: agent_id ?? "
|
|
214
|
+
body: JSON.stringify({ message, agentId: agent_id ?? "finch-default" }),
|
|
209
215
|
signal: AbortSignal.timeout(60000),
|
|
210
216
|
});
|
|
211
217
|
if (!res.ok) {
|
|
@@ -147,7 +147,7 @@ async function handleResearchChain(name, args) {
|
|
|
147
147
|
return null;
|
|
148
148
|
const parsed = InputSchema.safeParse(args);
|
|
149
149
|
if (!parsed.success) {
|
|
150
|
-
return { content: [{ type: "text", text:
|
|
150
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
151
151
|
}
|
|
152
152
|
const { startKey } = parsed.data;
|
|
153
153
|
const maxDepth = parsed.data.maxDepth ?? 8;
|
|
@@ -154,7 +154,7 @@ async function handleResearchCompare(name, args) {
|
|
|
154
154
|
return null;
|
|
155
155
|
const parsed = InputSchema.safeParse(args);
|
|
156
156
|
if (!parsed.success) {
|
|
157
|
-
return { content: [{ type: "text", text:
|
|
157
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
158
158
|
}
|
|
159
159
|
const { keyA, keyB, focus, comparison } = parsed.data;
|
|
160
160
|
const saveToVault = parsed.data.saveToVault ?? true;
|
package/dist/tools/research.js
CHANGED
|
@@ -100,7 +100,7 @@ async function handleResearchTool(name, args) {
|
|
|
100
100
|
if (name === "web_scrape") {
|
|
101
101
|
const parsed = ScrapeSchema.safeParse(args);
|
|
102
102
|
if (!parsed.success)
|
|
103
|
-
return { content: [{ type: "text", text:
|
|
103
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
104
104
|
const { url, focus } = parsed.data;
|
|
105
105
|
const unsafe = await (0, public_url_js_1.assertPublicUrl)(url);
|
|
106
106
|
if (unsafe) {
|
|
@@ -120,7 +120,7 @@ async function handleResearchTool(name, args) {
|
|
|
120
120
|
if (name === "web_search") {
|
|
121
121
|
const parsed = SearchSchema.safeParse(args);
|
|
122
122
|
if (!parsed.success)
|
|
123
|
-
return { content: [{ type: "text", text:
|
|
123
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
124
124
|
const { query, limit = 5 } = parsed.data;
|
|
125
125
|
// Priority 1: user BYOK direct path. Priority 2: Finch proxy.
|
|
126
126
|
// Same data shape so the formatting code below is identical for both.
|
package/dist/tools/rh-bridge.js
CHANGED
|
@@ -94,7 +94,7 @@ async function handleBridgeTool(name, args) {
|
|
|
94
94
|
return null;
|
|
95
95
|
const parsed = Schema.safeParse(args);
|
|
96
96
|
if (!parsed.success) {
|
|
97
|
-
return { content: [{ type: "text", text:
|
|
97
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
98
98
|
}
|
|
99
99
|
const wanted = parsed.data.ticker?.trim().toUpperCase();
|
|
100
100
|
const minGap = parsed.data.minGapPct ?? 0;
|
package/dist/tools/rh-mcp.js
CHANGED
|
@@ -30,6 +30,20 @@ exports.RH_RPC = process.env.ROBINHOOD_RPC_URL ??
|
|
|
30
30
|
process.env.RH_RPC_URL ??
|
|
31
31
|
process.env.FINCH_RH_RPC_URL ??
|
|
32
32
|
"https://rpc.mainnet.chain.robinhood.com";
|
|
33
|
+
/** Host only, for display in tool output - never the full RH_RPC. An
|
|
34
|
+
* operator can point ROBINHOOD_RPC_URL/RH_RPC_URL at a keyed provider
|
|
35
|
+
* (same pattern app/convex just fixed for its own Alchemy-key leak risk -
|
|
36
|
+
* a `.../v2/<key>`-style URL echoed verbatim into a tool response lands the
|
|
37
|
+
* key in chat transcripts/client logs). Falls back to the raw value only
|
|
38
|
+
* if it isn't a parseable URL, which should never happen in practice. */
|
|
39
|
+
function rhRpcDisplay() {
|
|
40
|
+
try {
|
|
41
|
+
return new URL(exports.RH_RPC).host;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return "(configured)";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
33
47
|
exports.RH_EXPLORER = "https://robinhoodchain.blockscout.com";
|
|
34
48
|
const NATIVE_ETH = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
|
|
35
49
|
const RH_PERMIT2 = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
@@ -754,7 +768,7 @@ async function fetchRhBalances(address) {
|
|
|
754
768
|
else
|
|
755
769
|
for (const h of holdings)
|
|
756
770
|
lines.push(`- ${h.symbol}: ${h.bal} \`${h.address}\``);
|
|
757
|
-
lines.push(``, `_RPC: ${
|
|
771
|
+
lines.push(``, `_RPC: ${rhRpcDisplay()}_`, `_Explorer: ${exports.RH_EXPLORER}/address/${address}_`);
|
|
758
772
|
return lines.join("\n");
|
|
759
773
|
}
|
|
760
774
|
// βββ Onchain safety scan (Blockscout + DexScreener; free, no LLM) βββββββββββββ
|
|
@@ -1177,7 +1191,7 @@ async function handleRhMcpTool(name, args) {
|
|
|
1177
1191
|
"",
|
|
1178
1192
|
`**Wallet**: \`${addr ?? "(not configured β run finch login / first tool)"}\``,
|
|
1179
1193
|
`**ETH on RH (gas)**: ${eth}`,
|
|
1180
|
-
`**RPC**: ${
|
|
1194
|
+
`**RPC**: ${rhRpcDisplay()}`,
|
|
1181
1195
|
`**Explorer**: ${exports.RH_EXPLORER}`,
|
|
1182
1196
|
`**Catalog**: ${exports.RH_STOCKS.length} stocks (AAPLβ¦USAR) + any RH-chain crypto via DexScreener`,
|
|
1183
1197
|
"",
|
package/dist/tools/rh-orders.js
CHANGED
|
@@ -143,8 +143,11 @@ exports.RH_ORDER_TOOLS = [
|
|
|
143
143
|
name: "rh_dca_create",
|
|
144
144
|
description: "Robinhood Chain β create a DCA plan: buy a fixed ETH amount of a token every N hours, " +
|
|
145
145
|
"up to a total number of buys, capped by maxSpendEth. Token by catalog symbol, crypto " +
|
|
146
|
-
"ticker, or 0x contract address (resolved via DexScreener). Does NOT execute now
|
|
147
|
-
"
|
|
146
|
+
"ticker, or 0x contract address (resolved via DexScreener). Does NOT execute now and does " +
|
|
147
|
+
"NOT run itself automatically β this only saves the plan. It fires only when something calls " +
|
|
148
|
+
"rh_orders_tick {execute:true}, which nothing does on its own. Tell the user to run " +
|
|
149
|
+
"`finch orders install-scheduler` (or `finch orders daemon`) after creating this, or the " +
|
|
150
|
+
"order will just sit there forever. Always confirm the resolved contract address first.",
|
|
148
151
|
inputSchema: {
|
|
149
152
|
type: "object",
|
|
150
153
|
properties: {
|
|
@@ -165,9 +168,11 @@ exports.RH_ORDER_TOOLS = [
|
|
|
165
168
|
{
|
|
166
169
|
name: "rh_bracket_create",
|
|
167
170
|
description: "Robinhood Chain β set take-profit and/or stop-loss on a token you HOLD. When DexScreener " +
|
|
168
|
-
"price crosses tpPriceUsd (β₯) or slPriceUsd (β€), the
|
|
169
|
-
"
|
|
170
|
-
"
|
|
171
|
+
"price crosses tpPriceUsd (β₯) or slPriceUsd (β€), sellPct% of the current balance sells for " +
|
|
172
|
+
"ETH β but only when something calls rh_orders_tick {execute:true}, which nothing does on " +
|
|
173
|
+
"its own by default. Tell the user to run `finch orders install-scheduler` (or `finch orders " +
|
|
174
|
+
"daemon`) after creating this, or the trigger will never actually fire. Provide at least one " +
|
|
175
|
+
"of tpPriceUsd / slPriceUsd. Only ever sells tokens already in the wallet β never borrows or shorts.",
|
|
171
176
|
inputSchema: {
|
|
172
177
|
type: "object",
|
|
173
178
|
properties: {
|
|
@@ -209,7 +214,9 @@ exports.RH_ORDER_TOOLS = [
|
|
|
209
214
|
description: "Robinhood Chain β evaluate all active orders and act on any that are due (DCA interval " +
|
|
210
215
|
"reached) or triggered (TP/SL price crossed). PREVIEW by default; pass execute:true to " +
|
|
211
216
|
"broadcast real swaps. Refuses to execute when the kill-switch is set (RH_ORDERS_DISABLED=1 " +
|
|
212
|
-
"or ~/.finch/rh-orders.OFF).
|
|
217
|
+
"or ~/.finch/rh-orders.OFF). Nothing calls this automatically β run `finch orders install-scheduler` " +
|
|
218
|
+
"(OS-level recurring task) or `finch orders daemon` (foreground loop) from a terminal to make " +
|
|
219
|
+
"orders actually fire unattended; calling this tool by hand only ticks once.",
|
|
213
220
|
inputSchema: {
|
|
214
221
|
type: "object",
|
|
215
222
|
properties: {
|
package/dist/tools/scanner.js
CHANGED
|
@@ -345,7 +345,7 @@ async function handleScannerTool(name, args) {
|
|
|
345
345
|
if (name === "score_token") {
|
|
346
346
|
const parsed = ScoreTokenSchema.safeParse(args);
|
|
347
347
|
if (!parsed.success)
|
|
348
|
-
return { content: [{ type: "text", text:
|
|
348
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
349
349
|
const { address, minLiquidity = DEFAULT_MIN_LIQ } = parsed.data;
|
|
350
350
|
const data = await fetchJson(`https://api.dexscreener.com/latest/dex/tokens/${address}`);
|
|
351
351
|
const pair = (data.pairs ?? [])
|
|
@@ -414,7 +414,7 @@ async function handleScannerTool(name, args) {
|
|
|
414
414
|
if (name === "check_token") {
|
|
415
415
|
const parsed = CheckTokenSchema.safeParse(args);
|
|
416
416
|
if (!parsed.success)
|
|
417
|
-
return { content: [{ type: "text", text:
|
|
417
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
418
418
|
const { address } = parsed.data;
|
|
419
419
|
// GoPlusLabs - chain 8453 = Base mainnet
|
|
420
420
|
const data = await fetchJson(`https://api.gopluslabs.io/api/v1/token_security/8453?contract_addresses=${address}`);
|
|
@@ -476,7 +476,7 @@ async function handleScannerTool(name, args) {
|
|
|
476
476
|
if (name === "scan_market") {
|
|
477
477
|
const parsed = ScanDipsSchema.safeParse(args ?? {});
|
|
478
478
|
if (!parsed.success)
|
|
479
|
-
return { content: [{ type: "text", text:
|
|
479
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
480
480
|
const input = args;
|
|
481
481
|
const mode = input?.mode === "momentum" ? "momentum" : "dips";
|
|
482
482
|
const { minScore = DEFAULT_MIN_SCORE, minLiquidity = DEFAULT_MIN_LIQ, limit = 40 } = parsed.data;
|