@finchagentic/mcp 4.2.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 +16 -12
- package/dist/annotations.js +9 -1
- 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 +45 -5
- package/dist/tools/automation.js +42 -6
- 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/scanner.js +3 -3
- package/dist/tools/stake.js +41 -1
- package/dist/tools/vault.js +195 -19
- package/package.json +3 -4
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.
|
|
32
|
+
npx -y -p @finchagentic/mcp@4.4.1 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.
|
|
38
|
+
claude mcp add finch -s user -- npx -y -p @finchagentic/mcp@4.4.1 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.2.0 finch-mcp
|
|
|
45
45
|
"mcpServers": {
|
|
46
46
|
"finch": {
|
|
47
47
|
"command": "npx",
|
|
48
|
-
"args": ["-y", "-p", "@finchagentic/mcp@4.
|
|
48
|
+
"args": ["-y", "-p", "@finchagentic/mcp@4.4.1", "finch-mcp"]
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
}
|
|
@@ -59,7 +59,7 @@ claude mcp add finch -s user -- npx -y -p @finchagentic/mcp@4.2.0 finch-mcp
|
|
|
59
59
|
"finch": {
|
|
60
60
|
"type": "stdio",
|
|
61
61
|
"command": "npx",
|
|
62
|
-
"args": ["-y", "-p", "@finchagentic/mcp@4.
|
|
62
|
+
"args": ["-y", "-p", "@finchagentic/mcp@4.4.1", "finch-mcp"]
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
}
|
|
@@ -79,7 +79,7 @@ claude mcp add finch -s user -- npx -y -p @finchagentic/mcp@4.2.0 finch-mcp
|
|
|
79
79
|
|
|
80
80
|
</details>
|
|
81
81
|
|
|
82
|
-
No LLM API key is required to start —
|
|
82
|
+
No LLM API key is required to start — 111 of 116 tools are plain reads/writes/on-chain calls that your MCP client's own model already drives; only 5 (`ask_finch`, `deep_research`, and scheduled agent learning) do their own multi-step reasoning server-side and need a key (see [Configuration](#configuration)). Tools load on first use.
|
|
83
83
|
|
|
84
84
|
## Quick start
|
|
85
85
|
|
|
@@ -99,15 +99,19 @@ save this thesis to vault
|
|
|
99
99
|
|
|
100
100
|
## What you get
|
|
101
101
|
|
|
102
|
-
**
|
|
102
|
+
**116 tools** across four pillars:
|
|
103
103
|
|
|
104
104
|
| Pillar | What it does |
|
|
105
105
|
|--------|----------------|
|
|
106
106
|
| **Memory** | Full-text searchable memory + versioned vault + chronicle |
|
|
107
|
-
| **Agents** | Spawn, recall, update named agents |
|
|
108
|
-
| **Workflows** | Automations, monitors, packets, deep research |
|
|
107
|
+
| **Agents** | Spawn, recall, update named agents — `agent_recall` also pulls related memory/vault context matching the agent's goal, not just its own logged updates |
|
|
108
|
+
| **Workflows** | Automations, monitors, packets, deep research (auto-saves reports + auto-links related past research) |
|
|
109
109
|
| **Execution** | Base DeFi, Robinhood Chain, market data, web, GitHub |
|
|
110
110
|
|
|
111
|
+
Coding and research sessions persist the same way: `deep_research` auto-saves its report to vault and links it to related past reports; `code_session_save` does the same for coding/debugging sessions (`vault_save type=code`, versioned per project, auto-linked). Both exist so the next session — yours or another agent's — starts with real context instead of cold.
|
|
112
|
+
|
|
113
|
+
`vault_save` and `agent_spawn` also take an optional `workspaceProject` - the same named Projects a user organizes their Agents/vault content into on the webapp's Agents page. Pass a name and it's matched case-insensitively or created automatically (`list_projects` to browse what exists first). Hosted vault only - local-vault mode has no project concept.
|
|
114
|
+
|
|
111
115
|
Default palette is `core` (lighter context). Full set:
|
|
112
116
|
|
|
113
117
|
```json
|
|
@@ -119,7 +123,7 @@ Default palette is `core` (lighter context). Full set:
|
|
|
119
123
|
Finch is the runtime. **Your LLM is the brain. Your data stays yours.**
|
|
120
124
|
|
|
121
125
|
```bash
|
|
122
|
-
npx -y -p @finchagentic/mcp@4.
|
|
126
|
+
npx -y -p @finchagentic/mcp@4.4.1 finch setup
|
|
123
127
|
# enable local vault (and optional local memory)
|
|
124
128
|
```
|
|
125
129
|
|
|
@@ -146,12 +150,12 @@ Scheduled/cloud features still need an account. Core memory, vault, and public-d
|
|
|
146
150
|
| `GITHUB_TOKEN` | For `github_search_code` |
|
|
147
151
|
| `ALCHEMY_API_KEY` | Faster Base RPC (optional) |
|
|
148
152
|
|
|
149
|
-
**Cost model:** almost everything is free to run — the other
|
|
153
|
+
**Cost model:** almost everything is free to run — the other 110 tools are plain API/RPC calls, and your MCP client's own model (Claude, GPT, whatever's driving the chat) does all the tool-selection reasoning at no cost to Finch. The 5 exceptions above need their own key because their reasoning happens *inside* the tool call, invisible to your client, and can't be delegated to it. Set exactly one of the four env vars and every tool that needs it will use it automatically.
|
|
150
154
|
|
|
151
155
|
Guided setup:
|
|
152
156
|
|
|
153
157
|
```bash
|
|
154
|
-
npx -y -p @finchagentic/mcp@4.
|
|
158
|
+
npx -y -p @finchagentic/mcp@4.4.1 finch setup
|
|
155
159
|
```
|
|
156
160
|
|
|
157
161
|
## Security
|
|
@@ -160,7 +164,7 @@ npx -y -p @finchagentic/mcp@4.2.0 finch setup
|
|
|
160
164
|
|:-:|----------|------|
|
|
161
165
|
| 1 | Prompt injection | External content is data only — never instructions |
|
|
162
166
|
| 2 | Mainnet confirm | Estimate → preview → confirm → execute |
|
|
163
|
-
| 3 | Pinned install | Always `@finchagentic/mcp@4.
|
|
167
|
+
| 3 | Pinned install | Always `@finchagentic/mcp@4.4.1`, never `@latest` |
|
|
164
168
|
| 4 | Credential vault | Never paste secrets into prompts or third-party tools |
|
|
165
169
|
| 5 | Data disclosure | Know what leaves the machine (LLM, Firecrawl, GitHub, chain RPCs) |
|
|
166
170
|
| 6 | Server monitors | Scheduled jobs need explicit confirmation |
|
package/dist/annotations.js
CHANGED
|
@@ -26,7 +26,7 @@ const WRITE_IDEMPOTENT = new Set([
|
|
|
26
26
|
const WRITE = new Set([
|
|
27
27
|
"agent_spawn",
|
|
28
28
|
"chronicle_add",
|
|
29
|
-
"
|
|
29
|
+
"code_session_save",
|
|
30
30
|
"memory_add",
|
|
31
31
|
"memory_extract",
|
|
32
32
|
"memory_consolidate",
|
|
@@ -65,6 +65,13 @@ const DESTRUCTIVE = new Set([
|
|
|
65
65
|
"rh_mcp_swap",
|
|
66
66
|
"rh_dca_create", // arms recurring real buys
|
|
67
67
|
"rh_bracket_create", // arms real TP/SL sells
|
|
68
|
+
// a swap/send automation arms unattended, REPEATING real fund movement
|
|
69
|
+
// (the backend's 1-minute cron evaluator fires it) - same risk class as
|
|
70
|
+
// rh_dca_create/rh_bracket_create above, not a plain additive write. An
|
|
71
|
+
// alert-only automation doesn't move funds, but the tool can't tell which
|
|
72
|
+
// kind it's about to create until AFTER the backend parses rawInput, so
|
|
73
|
+
// it's classified by its worst case, same reasoning as rh_orders_tick.
|
|
74
|
+
"create_automation",
|
|
68
75
|
"rh_orders_tick", // preview by default, but can execute:true and move funds
|
|
69
76
|
// executors that run other (possibly fund-moving) tools
|
|
70
77
|
"run_automation",
|
|
@@ -73,6 +80,7 @@ const DESTRUCTIVE = new Set([
|
|
|
73
80
|
// for a fixed period; unstake moves it (plus rewards) back
|
|
74
81
|
"stake_finch",
|
|
75
82
|
"unstake_finch",
|
|
83
|
+
"claim_vested_rewards", // treasury -> custodial wallet USDG transfer
|
|
76
84
|
]);
|
|
77
85
|
function annotationsFor(name) {
|
|
78
86
|
if (DESTRUCTIVE.has(name)) {
|
package/dist/index.js
CHANGED
|
@@ -66,14 +66,17 @@ const C = {
|
|
|
66
66
|
reset: "\x1b[0m",
|
|
67
67
|
bold: "\x1b[1m",
|
|
68
68
|
};
|
|
69
|
+
// Was still spelling out the pre-rebrand "NOELCLAW" wordmark in ASCII art -
|
|
70
|
+
// leftover from before the Finch rebrand, never caught because nothing
|
|
71
|
+
// visually diffs a banner string.
|
|
69
72
|
const BANNER = `
|
|
70
73
|
${C.cyan}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
██║
|
|
76
|
-
╚═╝ ╚═══╝
|
|
74
|
+
███████╗██╗███╗ ██╗ ██████╗██╗ ██╗
|
|
75
|
+
██╔════╝██║████╗ ██║██╔════╝██║ ██║
|
|
76
|
+
█████╗ ██║██╔██╗ ██║██║ ███████║
|
|
77
|
+
██╔══╝ ██║██║╚██╗██║██║ ██╔══██║
|
|
78
|
+
██║ ██║██║ ╚████║╚██████╗██║ ██║
|
|
79
|
+
╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝╚═╝ ╚═╝
|
|
77
80
|
${C.reset}`;
|
|
78
81
|
function line(label, value, color = C.cyan) {
|
|
79
82
|
const pad = " ".repeat(Math.max(0, 12 - label.length));
|
|
@@ -114,9 +117,9 @@ async function main() {
|
|
|
114
117
|
{ label: "Base MCP", match: n => n.startsWith("base_mcp_") },
|
|
115
118
|
{ label: "RH MCP", match: n => n.startsWith("rh_") },
|
|
116
119
|
{ label: "Automation", match: n => /^(create_automation|list_automations|pause_automation|delete_automation|get_automation_runs|run_automation)$/.test(n) },
|
|
117
|
-
{ label: "Vault", match: n => n.startsWith("vault_") },
|
|
120
|
+
{ label: "Vault", match: n => n.startsWith("vault_") || n === "code_session_save" || n === "list_projects" },
|
|
118
121
|
{ label: "Wallet", match: n => /^(get_wallet_address|get_wallet_balance|wallet_sign_message)$/.test(n) },
|
|
119
|
-
{ label: "Staking", match: n => /^(stake_finch|unstake_finch|stake_finch_status|stake_auto_restake)$/.test(n) },
|
|
122
|
+
{ label: "Staking", match: n => /^(stake_finch|unstake_finch|stake_finch_status|stake_auto_restake|claim_vested_rewards)$/.test(n) },
|
|
120
123
|
{ label: "MiroShark", match: n => n.startsWith("miroshark_") },
|
|
121
124
|
{ label: "Scanner", match: n => /^(scan_market|score_token|check_token)$/.test(n) },
|
|
122
125
|
{ label: "Agents", match: n => n.startsWith("agent_") },
|
package/dist/project.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isLocalVaultActive = isLocalVaultActive;
|
|
4
|
+
exports.resolveProjectId = resolveProjectId;
|
|
5
|
+
const convex_js_1 = require("./convex.js");
|
|
6
|
+
const local_vault_js_1 = require("./local-vault.js");
|
|
7
|
+
// MCP has no persistent "active project" UI state the way the webapp Sidebar
|
|
8
|
+
// switcher does - a caller (vault_save, agent_spawn) names the project it
|
|
9
|
+
// wants by string on each call instead. Resolution + auto-create both happen
|
|
10
|
+
// server-side (POST /projects/resolve, see convex/http.ts +
|
|
11
|
+
// convex/projects.ts's resolveOrCreateProjectForUser) so the match/dedup
|
|
12
|
+
// logic exists in exactly one place, shared with the webapp's own project
|
|
13
|
+
// picker.
|
|
14
|
+
//
|
|
15
|
+
// Local-vault mode (vaultBackend: "local") has no project concept at all -
|
|
16
|
+
// it's a flat, unauthenticated file store on the user's own machine with no
|
|
17
|
+
// server to resolve a name against. Callers should check
|
|
18
|
+
// isLocalVaultActive() first and skip resolution entirely rather than call
|
|
19
|
+
// this and get a confusing network-style failure.
|
|
20
|
+
function isLocalVaultActive() {
|
|
21
|
+
return !!(0, local_vault_js_1.getLocalVaultConfig)();
|
|
22
|
+
}
|
|
23
|
+
async function resolveProjectId(name) {
|
|
24
|
+
const trimmed = name.trim();
|
|
25
|
+
if (!trimmed)
|
|
26
|
+
return null;
|
|
27
|
+
try {
|
|
28
|
+
return await (0, convex_js_1.callConvex)("/projects/resolve", "POST", { name: trimmed }, "vault_save");
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// Resolution is best-effort - a save should never fail just because the
|
|
32
|
+
// project lookup did. Falls back to unassigned, same as the webapp's
|
|
33
|
+
// agent_spawn membership check when a stale/bad id doesn't match.
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -115,7 +115,7 @@ exports.ALL_TOOLS = [
|
|
|
115
115
|
// SWARM_TOOLS removed v3.19 - multi-agent research is now built into
|
|
116
116
|
// deep_research (depth=standard|deep). Handler fully removed v3.21.
|
|
117
117
|
// FRAMEWORK_TOOLS removed - list_playbooks/run_playbook/get_finch_ledger backend routes never existed, see tools/ import comment above.
|
|
118
|
-
...vault_js_1.VAULT_TOOLS, //
|
|
118
|
+
...vault_js_1.VAULT_TOOLS, // 17 - save, code_session_save, read, list, search, history, diff, export, pin, unpublish, tag, delete, link, related, store_credential, get_credential, list_projects
|
|
119
119
|
...wallet_js_1.WALLET_TOOLS, // 3 - get_wallet_address, get_wallet_balance, wallet_sign_message
|
|
120
120
|
...miroshark_js_1.MIROSHARK_TOOLS, // 3 - simulate, status, stop
|
|
121
121
|
...agents_js_1.AGENT_TOOLS, // 4 - agent_spawn, agent_recall, agent_update, agent_ledger (vault-backed, all working). list_agents/hire_agent and the autonomous-schedule tools (agent_identity/agent_schedule/agent_unschedule/agent_pause/agent_resume/agent_runs) removed - their backend routes were never implemented, see tools/agents.ts
|
|
@@ -139,14 +139,22 @@ exports.ALL_TOOLS = [
|
|
|
139
139
|
...github_js_1.GITHUB_TOOLS, // 8 - list_repos, list_prs, get_pr, list_issues, get_issue, get_file, get_commits, search_code
|
|
140
140
|
...chronicle_js_1.CHRONICLE_TOOLS, // 4 - chronicle_add, chronicle_list, chronicle_search, chronicle_stats
|
|
141
141
|
...packets_js_1.PACKET_TOOLS, // 4 - packet_create, packet_run, packet_list, packet_share
|
|
142
|
-
...stake_js_1.STAKE_TOOLS, //
|
|
143
|
-
// total:
|
|
142
|
+
...stake_js_1.STAKE_TOOLS, // 5 - stake_finch_status, stake_finch, unstake_finch, claim_vested_rewards, stake_auto_restake (custodial wallet; requires `finch login`)
|
|
143
|
+
// total: 116 tools as measured by ALL_TOOLS.length - do not hand-maintain a
|
|
144
144
|
// count in this comment (drifted stale multiple times already: before
|
|
145
145
|
// staking was added, after the P1 audit removed 9 dead framework/agent-
|
|
146
146
|
// schedule tools, after memory_publish was removed for promising a
|
|
147
|
-
// marketplace that doesn't exist,
|
|
148
|
-
//
|
|
149
|
-
//
|
|
147
|
+
// marketplace that doesn't exist, after stake_auto_restake was added,
|
|
148
|
+
// after claim_vested_rewards was added to close the gap where the
|
|
149
|
+
// stake-lifecycle notification told users to "run claimVestedRewards" but
|
|
150
|
+
// no MCP tool by that name existed, after code_session_save was added so
|
|
151
|
+
// coding sessions persist the same way deep_research already auto-saves
|
|
152
|
+
// research, and after list_projects was added alongside `workspaceProject`
|
|
153
|
+
// support on vault_save/agent_spawn - MCP tools can now file into the same
|
|
154
|
+
// Projects the webapp Agents page organizes by, resolved/auto-created
|
|
155
|
+
// server-side via POST /projects/resolve; ALL_TOOLS.length is the only
|
|
156
|
+
// number that can't lie). Per-category counts above are best-effort
|
|
157
|
+
// documentation, not load-bearing anywhere.
|
|
150
158
|
];
|
|
151
159
|
exports.HANDLER_MAP = new Map([
|
|
152
160
|
...market_js_1.MARKET_TOOLS.map(t => [t.name, market_js_1.handleMarketTool]),
|
package/dist/tool-filter.js
CHANGED
|
@@ -27,11 +27,11 @@ exports.filterTools = filterTools;
|
|
|
27
27
|
// through that same preset). New tools MUST match a preset or the test fails
|
|
28
28
|
// the build, instead of quietly vanishing like these did.
|
|
29
29
|
const PRESETS = {
|
|
30
|
-
core: /^(memory_|vault_|agent_|ask_finch|finch_status|finch_diagnostics|finch_shell_chat|get_wallet_address|get_wallet_balance|wallet_sign_message|chronicle_|packet_)/,
|
|
31
|
-
defi: /^(get_market_data|get_token_data|compare_tokens|market_overview|token_history|get_base_token_data|stock_fundamentals|stock_insider|stock_events|market_thesis|trade_plan|base_mcp_|rh_|base_|get_defi_yields|score_token|check_token|scan_market|get_wallet_balance|wallet_sign_message|create_automation|list_automations|pause_automation|delete_automation|get_automation_runs|run_automation|miroshark_|stake_|unstake_finch)/,
|
|
32
|
-
research: /^(memory_|vault_|deep_research|research_compare|research_chain|web_search|web_scrape|schedule_research|list_monitors|cancel_monitor|ask_finch|stock_fundamentals|stock_insider|stock_events)/,
|
|
33
|
-
memory: /^(memory_|vault_|agent_|chronicle_)/,
|
|
34
|
-
coder: /^(audit_contract|github_)/,
|
|
30
|
+
core: /^(memory_|vault_|code_session_save|list_projects|agent_|ask_finch|finch_status|finch_diagnostics|finch_shell_chat|get_wallet_address|get_wallet_balance|wallet_sign_message|chronicle_|packet_)/,
|
|
31
|
+
defi: /^(get_market_data|get_token_data|compare_tokens|market_overview|token_history|get_base_token_data|stock_fundamentals|stock_insider|stock_events|market_thesis|trade_plan|base_mcp_|rh_|base_|get_defi_yields|score_token|check_token|scan_market|get_wallet_balance|wallet_sign_message|create_automation|list_automations|pause_automation|delete_automation|get_automation_runs|run_automation|miroshark_|stake_|unstake_finch|claim_vested_rewards)/,
|
|
32
|
+
research: /^(memory_|vault_|code_session_save|list_projects|deep_research|research_compare|research_chain|web_search|web_scrape|schedule_research|list_monitors|cancel_monitor|ask_finch|stock_fundamentals|stock_insider|stock_events)/,
|
|
33
|
+
memory: /^(memory_|vault_|code_session_save|list_projects|agent_|chronicle_)/,
|
|
34
|
+
coder: /^(audit_contract|github_|code_session_save)/,
|
|
35
35
|
};
|
|
36
36
|
function filterTools(allTools) {
|
|
37
37
|
// Default is "core" - keeps LLM context cost low while everything
|
package/dist/tools/agents.js
CHANGED
|
@@ -7,6 +7,8 @@ const zod_1 = require("zod");
|
|
|
7
7
|
const convex_js_1 = require("../convex.js");
|
|
8
8
|
const llm_js_1 = require("../llm.js");
|
|
9
9
|
const local_vault_js_1 = require("../local-vault.js");
|
|
10
|
+
const memory_js_1 = require("./memory.js");
|
|
11
|
+
const project_js_1 = require("../project.js");
|
|
10
12
|
// ─── Agent Learning Memory (v3.25) ──────────────────────────────────────────
|
|
11
13
|
// After every agent_update, an LLM reviews the new progress in context of the
|
|
12
14
|
// agent's goal + prior learnings to extract a single repeatable insight. The
|
|
@@ -133,6 +135,12 @@ exports.AGENT_TOOLS = [
|
|
|
133
135
|
name: { type: "string", description: "Unique agent name (e.g. 'market-researcher', 'onboarding-helper')" },
|
|
134
136
|
goal: { type: "string", description: "What this agent is trying to accomplish" },
|
|
135
137
|
context: { type: "string", description: "Optional starting context, data, or notes for the agent" },
|
|
138
|
+
workspaceProject: {
|
|
139
|
+
type: "string",
|
|
140
|
+
description: "Optional: file this agent into a named Finch workspace project (visible on the Agents page's " +
|
|
141
|
+
"project switcher). Matched case-insensitively by name; created automatically if it doesn't exist " +
|
|
142
|
+
"yet. Hosted vault only (no effect in local-vault mode).",
|
|
143
|
+
},
|
|
136
144
|
},
|
|
137
145
|
required: ["name", "goal"],
|
|
138
146
|
},
|
|
@@ -140,6 +148,8 @@ exports.AGENT_TOOLS = [
|
|
|
140
148
|
{
|
|
141
149
|
name: "agent_recall",
|
|
142
150
|
description: "Recall a persistent agent by name - loads its goal, current progress, findings, full history, and accumulated learnings (patterns the agent extracted from past runs). " +
|
|
151
|
+
"Also pulls related context from memory/vault (code_session_save entries, deep_research reports, notes) matching the agent's goal, " +
|
|
152
|
+
"so recall reflects everything relevant to the goal - not just what agent_update explicitly logged. " +
|
|
143
153
|
"Use this to resume a long-running task, check what an agent last did, or hand context to a fresh LLM session. " +
|
|
144
154
|
"Learnings compound over time - the more an agent runs, the smarter recall becomes.",
|
|
145
155
|
inputSchema: {
|
|
@@ -192,6 +202,7 @@ const SpawnAgentSchema = zod_1.z.object({
|
|
|
192
202
|
name: zod_1.z.string().min(1).max(60).regex(/^[a-z0-9-]+$/, "name must be lowercase alphanumeric with hyphens"),
|
|
193
203
|
goal: zod_1.z.string().min(1),
|
|
194
204
|
context: zod_1.z.string().optional(),
|
|
205
|
+
workspaceProject: zod_1.z.string().optional(),
|
|
195
206
|
});
|
|
196
207
|
const RecallAgentSchema = zod_1.z.object({ name: zod_1.z.string().min(1) });
|
|
197
208
|
const UpdateAgentSchema = zod_1.z.object({
|
|
@@ -217,8 +228,8 @@ async function handleAgentTool(name, args) {
|
|
|
217
228
|
if (name === "agent_spawn") {
|
|
218
229
|
const parsed = SpawnAgentSchema.safeParse(args);
|
|
219
230
|
if (!parsed.success)
|
|
220
|
-
return { content: [{ type: "text", text:
|
|
221
|
-
const { name: agentName, goal, context } = parsed.data;
|
|
231
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
232
|
+
const { name: agentName, goal, context, workspaceProject } = parsed.data;
|
|
222
233
|
const content = JSON.stringify({
|
|
223
234
|
goal,
|
|
224
235
|
status: "active",
|
|
@@ -237,19 +248,30 @@ async function handleAgentTool(name, args) {
|
|
|
237
248
|
commitMsg: "spawned",
|
|
238
249
|
};
|
|
239
250
|
const localVault = (0, local_vault_js_1.getLocalVaultConfig)();
|
|
251
|
+
// Resolve the project name -> id server-side (auto-creates on first use).
|
|
252
|
+
// No-op in local-vault mode - there is no project concept there.
|
|
253
|
+
let resolvedProjectName = null;
|
|
254
|
+
if (workspaceProject && !localVault) {
|
|
255
|
+
const resolved = await (0, project_js_1.resolveProjectId)(workspaceProject);
|
|
256
|
+
if (resolved) {
|
|
257
|
+
savePayload.projectId = resolved.projectId;
|
|
258
|
+
resolvedProjectName = resolved.name;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
240
261
|
const data = localVault
|
|
241
262
|
? (0, local_vault_js_1.localVaultSave)(localVault, savePayload)
|
|
242
263
|
: await (0, convex_js_1.callConvex)("/vault/save", "POST", savePayload, "vault_save");
|
|
243
264
|
if (data.error)
|
|
244
265
|
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
266
|
+
const projectLine = resolvedProjectName ? `\n**Project:** ${resolvedProjectName}` : "";
|
|
245
267
|
return {
|
|
246
|
-
content: [{ type: "text", text: `🤖 Agent **${agentName}** spawned${localVault ? " locally" : ""}.\n\n**Goal:** ${goal}\n\nRecall with \`agent_recall\` · Update progress with \`agent_update\`` }],
|
|
268
|
+
content: [{ type: "text", text: `🤖 Agent **${agentName}** spawned${localVault ? " locally" : ""}.\n\n**Goal:** ${goal}${projectLine}\n\nRecall with \`agent_recall\` · Update progress with \`agent_update\`` }],
|
|
247
269
|
};
|
|
248
270
|
}
|
|
249
271
|
if (name === "agent_recall") {
|
|
250
272
|
const parsed = RecallAgentSchema.safeParse(args);
|
|
251
273
|
if (!parsed.success)
|
|
252
|
-
return { content: [{ type: "text", text:
|
|
274
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
253
275
|
const localVault = (0, local_vault_js_1.getLocalVaultConfig)();
|
|
254
276
|
let data;
|
|
255
277
|
try {
|
|
@@ -291,12 +313,30 @@ async function handleAgentTool(name, args) {
|
|
|
291
313
|
lines.push(`\n**Recent updates:**\n${updates.join("\n")}`);
|
|
292
314
|
if (state.nextStep)
|
|
293
315
|
lines.push(`\n**Next step:** ${state.nextStep}`);
|
|
316
|
+
// Related context - best-effort pull of relevant memory/vault knowledge
|
|
317
|
+
// (code_session_save entries, deep_research reports, manual notes) so the
|
|
318
|
+
// agent isn't blind to work done on its goal outside its own update log.
|
|
319
|
+
// This is what makes recall "continuously have context" rather than only
|
|
320
|
+
// ever knowing what agent_update explicitly logged.
|
|
321
|
+
if (state.goal) {
|
|
322
|
+
try {
|
|
323
|
+
const related = await (0, memory_js_1.hybridMemorySearch)(state.goal, 4);
|
|
324
|
+
if (related.length) {
|
|
325
|
+
lines.push(`\n**📎 Related context (${related.length}):**`);
|
|
326
|
+
related.forEach((r) => {
|
|
327
|
+
const title = r.metadata?.title ?? r.content.slice(0, 70).replace(/\n/g, " ");
|
|
328
|
+
lines.push(` • ${title}`);
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
catch { /* best-effort - recall must never fail because of this */ }
|
|
333
|
+
}
|
|
294
334
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
295
335
|
}
|
|
296
336
|
if (name === "agent_update") {
|
|
297
337
|
const parsed = UpdateAgentSchema.safeParse(args);
|
|
298
338
|
if (!parsed.success)
|
|
299
|
-
return { content: [{ type: "text", text:
|
|
339
|
+
return { content: [{ type: "text", text: `${parsed.error.issues[0].message}` }], isError: true };
|
|
300
340
|
const { name: agentName, progress, findings, status = "active", nextStep } = parsed.data;
|
|
301
341
|
return withAgentLock(agentName, async () => {
|
|
302
342
|
const localVault = (0, local_vault_js_1.getLocalVaultConfig)();
|
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/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
|