@finchagentic/mcp 4.4.1 → 4.6.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 CHANGED
@@ -29,13 +29,13 @@ Always pin the version. Never use `@latest`.
29
29
 
30
30
  ```bash
31
31
  # One-command installer (detects common MCP clients)
32
- npx -y -p @finchagentic/mcp@4.4.1 finch install
32
+ npx -y -p @finchagentic/mcp@4.6.0 finch install
33
33
  ```
34
34
 
35
35
  ### Claude Code
36
36
 
37
37
  ```bash
38
- claude mcp add finch -s user -- npx -y -p @finchagentic/mcp@4.4.1 finch-mcp
38
+ claude mcp add finch -s user -- npx -y -p @finchagentic/mcp@4.6.0 finch-mcp
39
39
  ```
40
40
 
41
41
  ### Cursor / Windsurf / Claude Desktop
@@ -45,7 +45,7 @@ claude mcp add finch -s user -- npx -y -p @finchagentic/mcp@4.4.1 finch-mcp
45
45
  "mcpServers": {
46
46
  "finch": {
47
47
  "command": "npx",
48
- "args": ["-y", "-p", "@finchagentic/mcp@4.4.1", "finch-mcp"]
48
+ "args": ["-y", "-p", "@finchagentic/mcp@4.6.0", "finch-mcp"]
49
49
  }
50
50
  }
51
51
  }
@@ -59,7 +59,7 @@ claude mcp add finch -s user -- npx -y -p @finchagentic/mcp@4.4.1 finch-mcp
59
59
  "finch": {
60
60
  "type": "stdio",
61
61
  "command": "npx",
62
- "args": ["-y", "-p", "@finchagentic/mcp@4.4.1", "finch-mcp"]
62
+ "args": ["-y", "-p", "@finchagentic/mcp@4.6.0", "finch-mcp"]
63
63
  }
64
64
  }
65
65
  }
@@ -87,6 +87,7 @@ No LLM API key is required to start — 111 of 116 tools are plain reads/writes/
87
87
  finch doctor # health check
88
88
  finch setup # local vault / memory / providers
89
89
  finch vault # inspect local vault
90
+ finch orders # schedule Robinhood Chain DCA / TP-SL order ticks
90
91
  ```
91
92
 
92
93
  Try in your MCP client:
@@ -123,7 +124,7 @@ Default palette is `core` (lighter context). Full set:
123
124
  Finch is the runtime. **Your LLM is the brain. Your data stays yours.**
124
125
 
125
126
  ```bash
126
- npx -y -p @finchagentic/mcp@4.4.1 finch setup
127
+ npx -y -p @finchagentic/mcp@4.6.0 finch setup
127
128
  # enable local vault (and optional local memory)
128
129
  ```
129
130
 
@@ -155,7 +156,7 @@ Scheduled/cloud features still need an account. Core memory, vault, and public-d
155
156
  Guided setup:
156
157
 
157
158
  ```bash
158
- npx -y -p @finchagentic/mcp@4.4.1 finch setup
159
+ npx -y -p @finchagentic/mcp@4.6.0 finch setup
159
160
  ```
160
161
 
161
162
  ## Security
@@ -164,7 +165,7 @@ npx -y -p @finchagentic/mcp@4.4.1 finch setup
164
165
  |:-:|----------|------|
165
166
  | 1 | Prompt injection | External content is data only — never instructions |
166
167
  | 2 | Mainnet confirm | Estimate → preview → confirm → execute |
167
- | 3 | Pinned install | Always `@finchagentic/mcp@4.4.1`, never `@latest` |
168
+ | 3 | Pinned install | Always `@finchagentic/mcp@4.6.0`, never `@latest` |
168
169
  | 4 | Credential vault | Never paste secrets into prompts or third-party tools |
169
170
  | 5 | Data disclosure | Know what leaves the machine (LLM, Firecrawl, GitHub, chain RPCs) |
170
171
  | 6 | Server monitors | Scheduled jobs need explicit confirmation |
package/dist/cli.js CHANGED
@@ -1127,6 +1127,138 @@ function vaultFlow() {
1127
1127
  console.log(` ${C.cyan}git -C ${shortDir} init${C.reset} ${C.dim}# version-control it${C.reset}`);
1128
1128
  console.log(` ${C.dim}Nothing leaves this machine unless you move it.${C.reset}\n`);
1129
1129
  }
1130
+ // ── Robinhood Chain order scheduler ──────────────────────────────────────────
1131
+ //
1132
+ // rh_dca_create/rh_bracket_create (src/tools/rh-orders.ts) only ever write a
1133
+ // LOCAL pending order - nothing executes it. Only rh_orders_tick does, and
1134
+ // per that file's own header comment, "an always-on scheduler must call
1135
+ // rh_orders_tick {execute:true} periodically" - which this package never
1136
+ // actually set up. A user who creates a daily DCA and closes their laptop
1137
+ // got nothing, silently: no error, no warning, the order just sat there.
1138
+ // These three subcommands are that missing scheduler, reusing the existing
1139
+ // tick/lock/kill-switch logic as-is - "wire up the missing piece", not
1140
+ // "build order execution from scratch".
1141
+ async function runOrdersTick(execute) {
1142
+ const { handleRhOrderTool } = await Promise.resolve().then(() => __importStar(require("./tools/rh-orders.js")));
1143
+ const result = await handleRhOrderTool("rh_orders_tick", { execute });
1144
+ const text = result?.content?.[0]?.type === "text" ? result.content[0].text : "(no output)";
1145
+ return { text, isError: !!result?.isError };
1146
+ }
1147
+ function installOrdersScheduler() {
1148
+ // Version-pinned npx invocation, not a hardcoded path to this process's
1149
+ // own binary - matches this package's own "always pin the version, never
1150
+ // @latest" security guidance (README), and survives regardless of whether
1151
+ // the user got here via a global install or a one-off `npx -p`, whose
1152
+ // resolved path may not exist by the time the scheduler fires later.
1153
+ const invocation = `npx -y -p @finchagentic/mcp@${PKG_VERSION} finch orders tick --execute`;
1154
+ if (process.platform === "win32") {
1155
+ const taskName = "FinchOrdersTick";
1156
+ try {
1157
+ child_process.execSync(`schtasks /Create /TN "${taskName}" /TR "${invocation}" /SC MINUTE /MO 15 /F`, { stdio: "pipe" });
1158
+ console.log(`\n ${C.green}✓${C.reset} Registered Windows Task Scheduler task "${taskName}" - ticks every 15 min, survives reboots.`);
1159
+ console.log(` ${C.dim}Remove it:${C.reset} schtasks /Delete /TN "${taskName}" /F`);
1160
+ }
1161
+ catch (err) {
1162
+ console.error(` ${C.red}✗ schtasks failed: ${err.message}${C.reset}`);
1163
+ console.error(` ${C.dim}Try running as Administrator, or use \`finch orders daemon\` instead.${C.reset}\n`);
1164
+ process.exit(1);
1165
+ }
1166
+ }
1167
+ else {
1168
+ const logFile = path.join(os.homedir(), ".finch", "orders-tick.log");
1169
+ const line = `*/15 * * * * ${invocation} >> ${logFile} 2>&1`;
1170
+ try {
1171
+ const existing = (() => {
1172
+ try {
1173
+ return child_process.execSync("crontab -l", { stdio: ["pipe", "pipe", "ignore"] }).toString();
1174
+ }
1175
+ catch {
1176
+ return "";
1177
+ } // no crontab yet for this user - not an error
1178
+ })();
1179
+ if (existing.includes("finch orders tick")) {
1180
+ console.log(`\n ${C.yellow}○${C.reset} A finch orders tick entry already exists in crontab - not adding a duplicate.\n`);
1181
+ return;
1182
+ }
1183
+ const next = existing.trim().length > 0 ? `${existing.trim()}\n${line}\n` : `${line}\n`;
1184
+ child_process.execSync("crontab -", { input: next });
1185
+ console.log(`\n ${C.green}✓${C.reset} Added a crontab entry - ticks every 15 min, survives reboots.`);
1186
+ console.log(` ${C.dim}Remove it:${C.reset} crontab -e ${C.dim}(delete the finch orders line)${C.reset}`);
1187
+ console.log(` ${C.dim}Logs:${C.reset} ${logFile}`);
1188
+ }
1189
+ catch (err) {
1190
+ console.error(` ${C.red}✗ crontab failed: ${err.message}${C.reset}`);
1191
+ console.error(` ${C.dim}Try \`finch orders daemon\` instead.${C.reset}\n`);
1192
+ process.exit(1);
1193
+ }
1194
+ }
1195
+ console.log(` ${C.dim}Only the local wallet's own default key is needed for signing - set FINCH_WALLET_PASSPHRASE as a persistent system env var (not just your shell rc file) if you use one, since scheduled tasks don't inherit an interactive shell's environment.${C.reset}\n`);
1196
+ }
1197
+ async function ordersFlow() {
1198
+ const sub = process.argv[3];
1199
+ if (sub === "tick") {
1200
+ const execute = process.argv.includes("--execute");
1201
+ const { text, isError } = await runOrdersTick(execute);
1202
+ console.log(`\n${text}\n`);
1203
+ if (isError)
1204
+ process.exit(1);
1205
+ return;
1206
+ }
1207
+ if (sub === "daemon") {
1208
+ const intervalFlag = process.argv.indexOf("--interval");
1209
+ const intervalMinutes = intervalFlag !== -1 ? Number(process.argv[intervalFlag + 1]) : 15;
1210
+ if (!(intervalMinutes > 0)) {
1211
+ console.error(` ${C.red}✗ --interval must be a positive number of minutes${C.reset}\n`);
1212
+ process.exit(1);
1213
+ }
1214
+ console.log(`\n ${C.cyan}${C.bold}finch orders daemon${C.reset} ${C.dim}- ticking every ${intervalMinutes}m, Ctrl+C to stop${C.reset}`);
1215
+ console.log(` ${C.dim}Kill-switch: RH_ORDERS_DISABLED=1 or create ~/.finch/rh-orders.OFF${C.reset}`);
1216
+ console.log(` ${C.dim}Note: this only runs while this process stays alive - for something that${C.reset}`);
1217
+ console.log(` ${C.dim}survives reboots/terminal closure, use \`finch orders install-scheduler\` instead.${C.reset}\n`);
1218
+ let running = false; // skip a tick if the previous one is still in flight (slow RPC), don't queue up
1219
+ const tick = async () => {
1220
+ if (running)
1221
+ return;
1222
+ running = true;
1223
+ const ts = new Date().toISOString();
1224
+ try {
1225
+ const { text } = await runOrdersTick(true);
1226
+ console.log(` ${C.dim}[${ts}]${C.reset}`);
1227
+ console.log(text.split("\n").map((l) => ` ${l}`).join("\n") + "\n");
1228
+ }
1229
+ catch (err) {
1230
+ console.error(` ${C.red}[${ts}] tick error: ${err.message ?? err}${C.reset}\n`);
1231
+ }
1232
+ finally {
1233
+ running = false;
1234
+ }
1235
+ };
1236
+ await tick();
1237
+ const timer = setInterval(tick, intervalMinutes * 60000);
1238
+ const shutdown = () => {
1239
+ clearInterval(timer);
1240
+ console.log(`\n ${C.dim}Stopped.${C.reset}\n`);
1241
+ process.exit(0);
1242
+ };
1243
+ process.on("SIGINT", shutdown);
1244
+ process.on("SIGTERM", shutdown);
1245
+ return; // process stays alive via the interval timer
1246
+ }
1247
+ if (sub === "install-scheduler") {
1248
+ installOrdersScheduler();
1249
+ return;
1250
+ }
1251
+ console.log(`
1252
+ ${C.cyan}${C.bold}finch orders${C.reset} ${C.dim}- Robinhood Chain DCA/TP-SL orders (rh_dca_create/rh_bracket_create)${C.reset}
1253
+ ${C.dim}do nothing on their own until one of these is running - creating an order${C.reset}
1254
+ ${C.dim}only ever writes it locally, nothing executes it automatically by itself.${C.reset}
1255
+
1256
+ ${C.cyan}Commands:${C.reset}
1257
+ finch orders tick [--execute] Run one tick now and exit (preview by default)
1258
+ finch orders daemon [--interval N] Run in the foreground, ticking every N min (default 15)
1259
+ finch orders install-scheduler Register an OS-level recurring task (Task Scheduler / cron)
1260
+ `);
1261
+ }
1130
1262
  // ── Entry point ───────────────────────────────────────────────────────────────
1131
1263
  const cmd = process.argv[2];
1132
1264
  if (cmd === "install") {
@@ -1158,6 +1290,12 @@ else if (cmd === "setup") {
1158
1290
  else if (cmd === "vault") {
1159
1291
  vaultFlow();
1160
1292
  }
1293
+ else if (cmd === "orders") {
1294
+ ordersFlow().catch((err) => {
1295
+ console.error(` ${C.red}✗ orders error: ${err.message}${C.reset}`);
1296
+ process.exit(1);
1297
+ });
1298
+ }
1161
1299
  else if (cmd === "logout") {
1162
1300
  const cfg = (0, config_js_1.readConfig)();
1163
1301
  if (!cfg.sessionToken) {
@@ -1239,6 +1377,7 @@ else if (cmd === "help" || cmd === "--help" || cmd === "-h") {
1239
1377
  finch doctor Run a full health check + suggest fixes
1240
1378
  finch setup Configure your own LLM key and/or local memory + vault
1241
1379
  finch vault Show your local vault (location, contents, backup)
1380
+ finch orders Run/schedule Robinhood Chain DCA & TP-SL order ticks
1242
1381
  finch help Show this help
1243
1382
 
1244
1383
  ${C.dim}Claude Code / Cursor / Windsurf / Codex / Aeon / Antigravity / Zed — anywhere MCP runs.${C.reset}
package/dist/convex.js CHANGED
@@ -28,7 +28,17 @@ async function attemptConvex(url, method, headers, body, timeoutMs = 30000) {
28
28
  signal: AbortSignal.timeout(timeoutMs),
29
29
  });
30
30
  }
31
- async function callConvex(path, method, body, toolName = "unknown", timeoutMs = 30000) {
31
+ async function callConvex(path, method, body, toolName = "unknown", timeoutMs = 30000,
32
+ // Fund-moving mutations (stake, unstake, claim rewards, a real - non-
33
+ // dryRun - automation trigger) are NOT idempotent server-side: there is
34
+ // no request-id dedup on the backend, so silently retrying a POST whose
35
+ // response was merely lost (a network blip or client-side timeout AFTER
36
+ // the server already processed it) can double-execute a real transfer.
37
+ // Pass true here for any such call - it trades the convenience of an
38
+ // automatic retry for never risking a duplicate execution; the caller
39
+ // sees a clear "may have already gone through" error instead and can
40
+ // check status before deciding to retry by hand.
41
+ noRetry = false) {
32
42
  const url = `${exports.CONVEX_SITE}${path}`;
33
43
  const headers = { "Content-Type": "application/json" };
34
44
  const apiKey = process.env.FINCH_API_KEY;
@@ -66,7 +76,8 @@ async function callConvex(path, method, body, toolName = "unknown", timeoutMs =
66
76
  if (process.env.BANKR_API_KEY)
67
77
  headers["X-User-Bankr-Key"] = process.env.BANKR_API_KEY;
68
78
  let lastError = null;
69
- for (let attempt = 0; attempt < RETRY_DELAYS.length; attempt++) {
79
+ const attempts = noRetry ? 1 : RETRY_DELAYS.length;
80
+ for (let attempt = 0; attempt < attempts; attempt++) {
70
81
  if (attempt > 0) {
71
82
  await new Promise((r) => setTimeout(r, RETRY_DELAYS[attempt - 1]));
72
83
  }
@@ -75,7 +86,14 @@ async function callConvex(path, method, body, toolName = "unknown", timeoutMs =
75
86
  res = await attemptConvex(url, method, headers, body, timeoutMs);
76
87
  }
77
88
  catch (err) {
78
- lastError = err;
89
+ // Ambiguous: the request may never have reached the server, or it may
90
+ // have been received and even processed before the connection died -
91
+ // there's no way to tell from here. Safe to retry for read-only/
92
+ // idempotent calls; for a fund-moving one, retrying risks resending a
93
+ // mutation that already landed.
94
+ lastError = noRetry
95
+ ? new Error(`${err?.message ?? err} - request may or may not have completed (no response received). Check status before retrying manually.`)
96
+ : err;
79
97
  continue;
80
98
  }
81
99
  if (res.status === 402) {
@@ -89,7 +107,7 @@ async function callConvex(path, method, body, toolName = "unknown", timeoutMs =
89
107
  `Hint: ${b.hint || 'Add FINCH_SESSION_TOKEN=… to the env block in your MCP config'}\n\n` +
90
108
  `${b.alternative ? `Alternative: ${b.alternative}` : ""}`);
91
109
  }
92
- if (RETRY_STATUSES.has(res.status) && attempt < RETRY_DELAYS.length) {
110
+ if (RETRY_STATUSES.has(res.status) && attempt < attempts - 1) {
93
111
  // Capture the actual body so a deterministic error (e.g. "unknown
94
112
  // token") that happens to come back on a 500 still surfaces its real
95
113
  // message if retries exhaust - previously this discarded the body
@@ -99,6 +117,15 @@ async function callConvex(path, method, body, toolName = "unknown", timeoutMs =
99
117
  lastError = new Error(`Finch API error ${res.status}: ${bodyText.slice(0, 300) || "(no body)"}`);
100
118
  continue;
101
119
  }
120
+ // noRetry + a retryable status on the one and only attempt: the server
121
+ // definitely received this one (unlike the network-exception case
122
+ // above), so it may have partially or fully processed it before
123
+ // erroring - same "check before retrying" caution, worded for the case
124
+ // where we know a response did come back.
125
+ if (RETRY_STATUSES.has(res.status) && noRetry) {
126
+ const bodyText = await res.text().catch(() => "");
127
+ throw new Error(`Finch API error ${res.status}: ${bodyText.slice(0, 300) || "(no body)"} - the server received this request and may have processed it before erroring. Check status before retrying manually.`);
128
+ }
102
129
  if (!res.ok)
103
130
  throw new Error(`Finch API error: ${res.status} ${await res.text()}`);
104
131
  return res.json();
@@ -43,7 +43,7 @@ async function extractLearning(goal, priorUpdates, priorLearnings, latest) {
43
43
  // the catch below swallows that into a silent skip. This early return just
44
44
  // avoids building the prompt for a call we already know will fail.
45
45
  const hasLLM = !!(process.env.BANKR_API_KEY || process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY || process.env.GROK_API_KEY);
46
- if (!hasLLM && !process.env.FINCH_SESSION_TOKEN) {
46
+ if (!hasLLM) {
47
47
  return null;
48
48
  }
49
49
  // Build the user prompt with all the context the model needs.
@@ -301,7 +301,11 @@ async function handleAutomationTool(name, args) {
301
301
  if (!parsed.success)
302
302
  return { content: [{ type: "text", text: `Invalid input: automationId ${parsed.error.issues[0].message}` }], isError: true };
303
303
  const { automationId, dryRun } = parsed.data;
304
- const data = await (0, convex_js_1.callConvex)("/automations/run", "POST", { automationId, dryRun: !!dryRun }, "run_automation");
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);
305
309
  if (data.error) {
306
310
  const cat = categorizeError(data.error);
307
311
  return {
@@ -304,7 +304,11 @@ exports.MEMORY_TOOLS = [
304
304
  "searchable in ~30s. Retrieval is full-text (keyword) search, not embeddings - " +
305
305
  "'what did I say about ETH yield?' finds notes containing those words or close variants, " +
306
306
  "not unrelated phrasing with the same meaning. " +
307
- "Auto-deduplicates: identical content in your recent 50 memories is skipped (override with force:true).",
307
+ "Auto-deduplicates: identical content in your recent 50 memories is skipped (override with force:true). " +
308
+ "Also surfaces up to 3 existing memories that share real keyword overlap with what you just saved (a " +
309
+ "possible-conflict HINT, not a verdict - Finch doesn't call an LLM to judge this). When that shows up, " +
310
+ "read them and decide yourself whether the new one supersedes an old preference/fact; if so, say so in a " +
311
+ "follow-up memory_add, or fold both into one with memory_consolidate.",
308
312
  inputSchema: {
309
313
  type: "object",
310
314
  properties: {
@@ -563,6 +567,32 @@ async function handleMemoryTool(name, args) {
563
567
  // dedupes even before supermemory has indexed the first one.
564
568
  if (!sourceUrl)
565
569
  rememberRecentHash(hash, data?.id ?? "saved", title);
570
+ // ─── Conflict hint (not a proof) ──────────────────────────────────
571
+ // Finch never runs its own LLM call to judge this - "two-pass, no API
572
+ // key needed" is deliberate (see this file's header comment), and an
573
+ // autonomous contradiction call would break that. Instead this just
574
+ // resurfaces whatever memory_search's own retrieval already ranks as
575
+ // related to the content just saved, so the CALLING model - already
576
+ // reasoning about this exact save, in the same turn, no extra API
577
+ // cost - can judge for itself whether the two actually conflict and
578
+ // decide what to do (mark the old one superseded, fold both into
579
+ // memory_consolidate, or just note the new one is the current one).
580
+ // Best-effort: a search hiccup here must never fail or block the save
581
+ // that already succeeded above.
582
+ let conflictNote = "";
583
+ try {
584
+ const related = await hybridMemorySearch(content, 6);
585
+ const candidates = related
586
+ .filter((r) => r.id !== data?.id && contentHash(r.content) !== hash)
587
+ .slice(0, 3);
588
+ if (candidates.length) {
589
+ conflictNote = "\n\n⚠️ **Possibly related existing memories** - skim these for a conflict with what you just saved (e.g. an old preference this replaces):\n" +
590
+ candidates.map((r) => `- \`${r.id}\`: ${r.content.slice(0, 100)}${r.content.length > 100 ? "…" : ""}`).join("\n");
591
+ }
592
+ }
593
+ catch {
594
+ // best-effort only
595
+ }
566
596
  return {
567
597
  content: [{
568
598
  type: "text",
@@ -573,7 +603,7 @@ async function handleMemoryTool(name, args) {
573
603
  tags?.length ? `Tags: ${tags.join(", ")}` : "",
574
604
  ``,
575
605
  `Find it with: \`memory_search query: "${(title ?? content).slice(0, 40)}"\``,
576
- ].filter(Boolean).join("\n"),
606
+ ].filter(Boolean).join("\n") + conflictNote,
577
607
  }],
578
608
  };
579
609
  }
@@ -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 the " +
147
- "scheduler runs it via rh_orders_tick. Always confirm the resolved contract address first.",
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 scheduler sells sellPct% of your " +
169
- "current balance for ETH via rh_orders_tick. Provide at least one of tpPriceUsd / slPriceUsd. " +
170
- "Only ever sells tokens already in the wallet never borrows or shorts.",
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). This is the entry point an always-on scheduler calls.",
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: {
@@ -261,7 +261,7 @@ async function handleStakeTool(name, args) {
261
261
  isError: true,
262
262
  };
263
263
  }
264
- const result = await (0, convex_js_1.callConvex)("/mcp/stake/stake", "POST", { amountWei: amountWei.toString(), lockTier }, "stake_finch");
264
+ const result = await (0, convex_js_1.callConvex)("/mcp/stake/stake", "POST", { amountWei: amountWei.toString(), lockTier }, "stake_finch", 30000, true);
265
265
  if (result.error)
266
266
  return { content: [{ type: "text", text: `Stake failed: ${result.error}` }], isError: true };
267
267
  const tier = STAKING_TIERS[lockTier ?? 7];
@@ -309,7 +309,7 @@ async function handleStakeTool(name, args) {
309
309
  }
310
310
  const lines = [];
311
311
  for (const id of targetIds) {
312
- const result = await (0, convex_js_1.callConvex)("/mcp/stake/unstake", "POST", { stakeId: id }, "unstake_finch");
312
+ const result = await (0, convex_js_1.callConvex)("/mcp/stake/unstake", "POST", { stakeId: id }, "unstake_finch", 30000, true);
313
313
  if (result.error) {
314
314
  lines.push(`🔴 \`${id}\`: ${result.error}`);
315
315
  }
@@ -330,7 +330,7 @@ async function handleStakeTool(name, args) {
330
330
  const login = requireLogin();
331
331
  if (!login)
332
332
  return { content: [{ type: "text", text: NOT_LOGGED_IN_MSG }], isError: true };
333
- const result = await (0, convex_js_1.callConvex)("/mcp/stake/claim-rewards", "POST", {}, "claim_vested_rewards");
333
+ const result = await (0, convex_js_1.callConvex)("/mcp/stake/claim-rewards", "POST", {}, "claim_vested_rewards", 30000, true);
334
334
  if (result.error)
335
335
  return { content: [{ type: "text", text: `Claim failed: ${result.error}` }], isError: true };
336
336
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finchagentic/mcp",
3
- "version": "4.4.1",
3
+ "version": "4.6.0",
4
4
  "description": "The runtime layer for Agentic AI. Persistent memory, autonomous agents, and workflows that survive every session.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {