@finchagentic/mcp 4.0.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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +345 -0
  3. package/dist/_http-cache.js +96 -0
  4. package/dist/agent-loop.js +231 -0
  5. package/dist/annotations.js +113 -0
  6. package/dist/cli.js +1195 -0
  7. package/dist/clink-input.js +15 -0
  8. package/dist/config.js +132 -0
  9. package/dist/convex.js +151 -0
  10. package/dist/dex-pair.js +54 -0
  11. package/dist/enrichment-router.js +315 -0
  12. package/dist/index.js +256 -0
  13. package/dist/llm.js +323 -0
  14. package/dist/local-memory.js +102 -0
  15. package/dist/local-vault.js +454 -0
  16. package/dist/output-schemas.js +551 -0
  17. package/dist/prompts.js +111 -0
  18. package/dist/public-url.js +107 -0
  19. package/dist/resources.js +116 -0
  20. package/dist/server.js +300 -0
  21. package/dist/signal-gate.js +57 -0
  22. package/dist/token-decimals.js +26 -0
  23. package/dist/token-gate.js +88 -0
  24. package/dist/tool-filter.js +44 -0
  25. package/dist/tools/_solidity-scan.js +313 -0
  26. package/dist/tools/agents.js +729 -0
  27. package/dist/tools/automation.js +314 -0
  28. package/dist/tools/base-mcp.js +478 -0
  29. package/dist/tools/base.js +269 -0
  30. package/dist/tools/chronicle.js +268 -0
  31. package/dist/tools/coder.js +94 -0
  32. package/dist/tools/deep-research.js +1416 -0
  33. package/dist/tools/defi.js +291 -0
  34. package/dist/tools/equity.js +364 -0
  35. package/dist/tools/events.js +182 -0
  36. package/dist/tools/framework.js +150 -0
  37. package/dist/tools/github.js +514 -0
  38. package/dist/tools/insider.js +264 -0
  39. package/dist/tools/insight.js +634 -0
  40. package/dist/tools/market.js +555 -0
  41. package/dist/tools/memory.js +1046 -0
  42. package/dist/tools/miroshark.js +343 -0
  43. package/dist/tools/monitor.js +319 -0
  44. package/dist/tools/os.js +226 -0
  45. package/dist/tools/packets.js +296 -0
  46. package/dist/tools/research-chain.js +226 -0
  47. package/dist/tools/research-compare.js +280 -0
  48. package/dist/tools/research.js +188 -0
  49. package/dist/tools/rh-bridge.js +148 -0
  50. package/dist/tools/rh-mcp.js +1411 -0
  51. package/dist/tools/rh-orders.js +471 -0
  52. package/dist/tools/scanner.js +534 -0
  53. package/dist/tools/vault.js +764 -0
  54. package/dist/tools/wallet.js +200 -0
  55. package/dist/types.js +2 -0
  56. package/dist/wallet.js +184 -0
  57. package/package.json +87 -0
@@ -0,0 +1,231 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runAgent = runAgent;
4
+ const server_js_1 = require("./server.js");
5
+ const llm_js_1 = require("./llm.js");
6
+ const convex_js_1 = require("./convex.js");
7
+ const SYSTEM_PROMPT = [
8
+ `You are Finch, the runtime layer for Agentic AI - with ${server_js_1.ALL_TOOLS.length} tools spanning memory, vault, deep research, persistent agents, code, automations, DeFi, and GitHub.`,
9
+ "Be direct and concise. Pick the right tool - don't narrate the choice. Summarize tool results in plain English.",
10
+ "",
11
+ "CRITICAL - on-chain / financial actions (swap, send, transfer, buy, sell, bridge, lend, deposit, withdraw, balance):",
12
+ "- You MUST call the corresponding tool. NEVER write 'Sent', 'Swapped', 'Tx confirmed', or any execution claim without an actual tool call returning a result first.",
13
+ "- Tx hash and basescan/explorer URL from the tool output are MANDATORY in your reply - show them verbatim, do not omit or paraphrase.",
14
+ "- NEVER fabricate a post-transaction balance with arithmetic. If user wants the new balance, call the balance tool again - do not compute it from a prior balance + amount.",
15
+ "- For ALL Base chain operations, you MUST use the base_mcp_* family: base_mcp_swap (NOT swap_tokens), base_mcp_send (NOT send_token), base_mcp_balance (NOT get_portfolio), base_mcp_estimate, base_mcp_resolve, base_mcp_lend, base_mcp_status. This is non-negotiable - Base operations go through the Base MCP skill, period.",
16
+ "- For Robinhood Chain tokenized stocks (chainId 4663, NVDA/AAPL/etc on Uniswap V4), you MUST use rh_mcp_*: rh_mcp_status, rh_mcp_list_stocks, rh_mcp_balance, rh_mcp_estimate, rh_mcp_swap. Never use base_mcp_* or 0x for RH stocks. rh_mcp_swap requires confirm:true. Explorer = robinhoodchain.blockscout.com. This is NOT Robinhood Agentic brokerage (agent.robinhood.com).",
17
+ "- Do NOT claim an address belongs to the user (e.g. 'your own address') unless you have verified ownership. Resolving a basename returns whoever owns that name - usually NOT the caller.",
18
+ "",
19
+ "For deep research: prefer deep_research (multi-stage, saves to vault). Use continueFrom when extending prior reports.",
20
+ "For live web info: use web_search. For market questions: use get_market_data or market_thesis.",
21
+ "Save substantive findings to vault; do not save thin or empty outputs.",
22
+ ].join("\n");
23
+ async function runAgent(userMessage, history, onToolCall) {
24
+ const bankrKey = process.env.BANKR_API_KEY;
25
+ const anthropicKey = process.env.ANTHROPIC_API_KEY;
26
+ const openaiKey = process.env.OPENAI_API_KEY;
27
+ if (bankrKey)
28
+ return runBankrLoop(bankrKey, userMessage, history, onToolCall);
29
+ if (anthropicKey)
30
+ return runAnthropicLoop(anthropicKey, userMessage, history, onToolCall);
31
+ if (openaiKey)
32
+ return runOpenAILoop(openaiKey, userMessage, history, onToolCall);
33
+ // No direct key - proxy through Finch backend. Wallet auto-creates at ~/.finch/wallet.json
34
+ // on first use and signs requests transparently. No account or config needed.
35
+ try {
36
+ return await runConvexProxiedLoop(userMessage, history, onToolCall);
37
+ }
38
+ catch {
39
+ // Network down or backend unavailable - plain chat fallback
40
+ const text = await (0, llm_js_1.callLLM)(SYSTEM_PROMPT, userMessage, 1024, history);
41
+ return { text, toolCalls: [] };
42
+ }
43
+ }
44
+ // ── Anthropic agent loop ─────────────────────────────────────────────────────
45
+ function toAnthropicTool(tool) {
46
+ return {
47
+ name: tool.name,
48
+ description: tool.description ?? "",
49
+ input_schema: tool.inputSchema ?? { type: "object", properties: {} },
50
+ };
51
+ }
52
+ async function runAnthropicLoop(apiKey, userMessage, history, onToolCall) {
53
+ const model = process.env.FINCH_MODEL ?? process.env.ANTHROPIC_MODEL ?? "claude-haiku-4-5-20251001";
54
+ const tools = server_js_1.ALL_TOOLS.map(toAnthropicTool);
55
+ const toolCalls = [];
56
+ const messages = [
57
+ ...history.map(h => ({ role: h.role, content: h.content })),
58
+ { role: "user", content: userMessage },
59
+ ];
60
+ for (let turn = 0; turn < 10; turn++) {
61
+ const res = await fetch("https://api.anthropic.com/v1/messages", {
62
+ method: "POST",
63
+ headers: {
64
+ "Content-Type": "application/json",
65
+ "x-api-key": apiKey,
66
+ "anthropic-version": "2023-06-01",
67
+ },
68
+ body: JSON.stringify({ model, max_tokens: 2048, system: SYSTEM_PROMPT, tools, messages }),
69
+ signal: AbortSignal.timeout(90000),
70
+ });
71
+ if (!res.ok) {
72
+ const body = await res.text().catch(() => "");
73
+ throw new Error(`Anthropic ${res.status}: ${body.slice(0, 300)}`);
74
+ }
75
+ const data = await res.json();
76
+ messages.push({ role: "assistant", content: data.content });
77
+ if (data.stop_reason !== "tool_use") {
78
+ const text = data.content
79
+ .filter(b => b.type === "text")
80
+ .map(b => b.text)
81
+ .join("");
82
+ return { text, toolCalls };
83
+ }
84
+ // Execute all tool_use blocks
85
+ const toolResults = [];
86
+ for (const block of data.content) {
87
+ if (block.type !== "tool_use")
88
+ continue;
89
+ onToolCall(block.name);
90
+ toolCalls.push({ name: block.name });
91
+ let resultText;
92
+ try {
93
+ const handler = server_js_1.HANDLER_MAP.get(block.name);
94
+ if (!handler)
95
+ throw new Error(`Unknown tool: ${block.name}`);
96
+ const result = await handler(block.name, block.input ?? {});
97
+ resultText = result?.content?.[0]?.text ?? "Done.";
98
+ }
99
+ catch (err) {
100
+ resultText = `Error: ${err.message}`;
101
+ }
102
+ toolResults.push({ type: "tool_result", tool_use_id: block.id, content: resultText });
103
+ }
104
+ messages.push({ role: "user", content: toolResults });
105
+ }
106
+ return { text: "Reached max tool iterations.", toolCalls };
107
+ }
108
+ // ── Convex-proxied Anthropic loop (session token only - platform covers LLM) ──
109
+ async function runConvexProxiedLoop(userMessage, history, onToolCall) {
110
+ const model = process.env.FINCH_MODEL ?? process.env.ANTHROPIC_MODEL ?? "claude-haiku-4-5-20251001";
111
+ const tools = server_js_1.ALL_TOOLS.map(toAnthropicTool);
112
+ const toolCalls = [];
113
+ const messages = [
114
+ ...history.map(h => ({ role: h.role, content: h.content })),
115
+ { role: "user", content: userMessage },
116
+ ];
117
+ for (let turn = 0; turn < 10; turn++) {
118
+ // callConvex handles wallet/session auth automatically; 90s timeout matches the proxy endpoint
119
+ const data = await (0, convex_js_1.callConvex)("/llm/complete", "POST", {
120
+ model,
121
+ max_tokens: 2048,
122
+ system: SYSTEM_PROMPT,
123
+ tools,
124
+ messages,
125
+ }, "llm_complete", 90000);
126
+ messages.push({ role: "assistant", content: data.content });
127
+ if (data.stop_reason !== "tool_use") {
128
+ const text = data.content
129
+ .filter(b => b.type === "text")
130
+ .map(b => b.text)
131
+ .join("");
132
+ return { text, toolCalls };
133
+ }
134
+ const toolResults = [];
135
+ for (const block of data.content) {
136
+ if (block.type !== "tool_use")
137
+ continue;
138
+ onToolCall(block.name);
139
+ toolCalls.push({ name: block.name });
140
+ let resultText;
141
+ try {
142
+ const handler = server_js_1.HANDLER_MAP.get(block.name);
143
+ if (!handler)
144
+ throw new Error(`Unknown tool: ${block.name}`);
145
+ const result = await handler(block.name, block.input ?? {});
146
+ resultText = result?.content?.[0]?.text ?? "Done.";
147
+ }
148
+ catch (err) {
149
+ resultText = `Error: ${err.message}`;
150
+ }
151
+ toolResults.push({ type: "tool_result", tool_use_id: block.id, content: resultText });
152
+ }
153
+ messages.push({ role: "user", content: toolResults });
154
+ }
155
+ return { text: "Reached max tool iterations.", toolCalls };
156
+ }
157
+ // ── Bankr (OpenAI-compatible) agent loop ─────────────────────────────────────
158
+ function toBankrTool(tool) {
159
+ return {
160
+ type: "function",
161
+ function: {
162
+ name: tool.name,
163
+ description: tool.description ?? "",
164
+ parameters: tool.inputSchema ?? { type: "object", properties: {} },
165
+ },
166
+ };
167
+ }
168
+ // Shared tool-calling loop for any OpenAI Chat Completions-compatible
169
+ // endpoint (Bankr's LLM gateway and OpenAI itself both speak this format).
170
+ // Only the URL, auth header, and model differ per provider.
171
+ async function runOpenAICompatibleLoop(url, authHeaders, model, providerLabel, userMessage, history, onToolCall) {
172
+ const tools = server_js_1.ALL_TOOLS.map(toBankrTool);
173
+ const toolCalls = [];
174
+ const messages = [
175
+ { role: "system", content: SYSTEM_PROMPT },
176
+ ...history.map(h => ({ role: h.role, content: h.content })),
177
+ { role: "user", content: userMessage },
178
+ ];
179
+ for (let turn = 0; turn < 10; turn++) {
180
+ const res = await fetch(url, {
181
+ method: "POST",
182
+ headers: { "Content-Type": "application/json", ...authHeaders },
183
+ body: JSON.stringify({ model, messages, tools, max_tokens: 2048 }),
184
+ signal: AbortSignal.timeout(90000),
185
+ });
186
+ if (!res.ok) {
187
+ const body = await res.text().catch(() => "");
188
+ throw new Error(`${providerLabel} ${res.status}: ${body.slice(0, 300)}`);
189
+ }
190
+ const data = await res.json();
191
+ const choice = data.choices?.[0]?.message;
192
+ if (!choice)
193
+ throw new Error(`Empty response from ${providerLabel}`);
194
+ messages.push(choice);
195
+ if (!choice.tool_calls?.length) {
196
+ return { text: choice.content ?? "", toolCalls };
197
+ }
198
+ for (const call of choice.tool_calls) {
199
+ onToolCall(call.function.name);
200
+ toolCalls.push({ name: call.function.name });
201
+ let resultText;
202
+ try {
203
+ const args = JSON.parse(call.function.arguments ?? "{}");
204
+ const handler = server_js_1.HANDLER_MAP.get(call.function.name);
205
+ if (!handler)
206
+ throw new Error(`Unknown tool: ${call.function.name}`);
207
+ const result = await handler(call.function.name, args);
208
+ resultText = result?.content?.[0]?.text ?? "Done.";
209
+ }
210
+ catch (err) {
211
+ resultText = `Error: ${err.message}`;
212
+ }
213
+ messages.push({ role: "tool", tool_call_id: call.id, content: resultText });
214
+ }
215
+ }
216
+ return { text: "Reached max tool iterations.", toolCalls };
217
+ }
218
+ async function runBankrLoop(apiKey, userMessage, history, onToolCall) {
219
+ const model = process.env.FINCH_MODEL ?? process.env.BANKR_MODEL ?? "claude-haiku-4-5-20251001";
220
+ return runOpenAICompatibleLoop("https://llm.bankr.bot/v1/chat/completions", { "X-API-Key": apiKey }, model, "Bankr", userMessage, history, onToolCall);
221
+ }
222
+ // Same OPENAI_BASE_URL override as llm.ts's callOpenAI - lets tool-calling
223
+ // route to a self-hosted OpenAI-compatible gateway too.
224
+ function openAiChatUrl() {
225
+ const base = process.env.OPENAI_BASE_URL?.replace(/\/+$/, "");
226
+ return base ? `${base}/chat/completions` : "https://api.openai.com/v1/chat/completions";
227
+ }
228
+ async function runOpenAILoop(apiKey, userMessage, history, onToolCall) {
229
+ const model = process.env.FINCH_MODEL ?? process.env.OPENAI_MODEL ?? "gpt-4o-mini";
230
+ return runOpenAICompatibleLoop(openAiChatUrl(), { Authorization: `Bearer ${apiKey}` }, model, "OpenAI", userMessage, history, onToolCall);
231
+ }
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MUTATING_TOOL_NAMES = void 0;
4
+ exports.annotationsFor = annotationsFor;
5
+ exports.withAnnotations = withAnnotations;
6
+ const output_schemas_js_1 = require("./output-schemas.js");
7
+ // Read-only AND no network - a purely local computation (static analysis).
8
+ // Read-only but open-world is the default, so this set only exists to flip
9
+ // openWorldHint off for the rare offline tool.
10
+ const READ_ONLY_LOCAL = new Set([
11
+ "audit_contract", // deterministic static Solidity scan, no backend call
12
+ ]);
13
+ // readOnly=false, destructive=false, idempotent=true.
14
+ // Toggles and upserts: re-running with the same args lands in the same state.
15
+ const WRITE_IDEMPOTENT = new Set([
16
+ "agent_update",
17
+ "agent_schedule",
18
+ "agent_pause",
19
+ "agent_resume",
20
+ "agent_unschedule",
21
+ "pause_automation",
22
+ "vault_link",
23
+ "vault_pin",
24
+ "vault_tag",
25
+ "vault_unpublish",
26
+ "wallet_sign_message", // signing is not itself a fund move; re-sign = same sig
27
+ ]);
28
+ // readOnly=false, destructive=false.
29
+ // Additive writes / new resources: they create or append, they don't destroy.
30
+ // (hire_agent is deliberately NOT here: it only returns a specialist persona
31
+ // scoped to the caller's task - it reads, it does not write - so it stays in
32
+ // the read-only default.)
33
+ const WRITE = new Set([
34
+ "agent_spawn",
35
+ "chronicle_add",
36
+ "create_automation",
37
+ "create_monitor",
38
+ "memory_add",
39
+ "memory_extract",
40
+ "memory_consolidate",
41
+ "packet_create",
42
+ "packet_share",
43
+ "schedule_research",
44
+ "vault_save",
45
+ "vault_store_credential",
46
+ "miroshark_simulate",
47
+ "finch_shell_chat", // orchestrator: can spawn/save/create via delegated tools
48
+ ]);
49
+ // readOnly=false, destructive=true.
50
+ // Deletes, cancels, irreversible publishes, and anything that moves real funds
51
+ // (or arms an order engine that will). Clients should confirm before running.
52
+ const DESTRUCTIVE = new Set([
53
+ // removals / cancels
54
+ "cancel_monitor",
55
+ "delete_automation",
56
+ "memory_delete",
57
+ "miroshark_stop",
58
+ "rh_order_cancel",
59
+ "vault_delete",
60
+ // irreversible public exposure
61
+ "memory_publish", // "IRREVERSIBLE, PUBLIC" per its own description
62
+ // money movement (Base)
63
+ "base_mcp_send",
64
+ "base_mcp_swap",
65
+ "base_mcp_lend",
66
+ // money movement (Robinhood Chain)
67
+ "rh_mcp_swap",
68
+ "rh_dca_create", // arms recurring real buys
69
+ "rh_bracket_create", // arms real TP/SL sells
70
+ "rh_orders_tick", // preview by default, but can execute:true and move funds
71
+ // executors that run other (possibly fund-moving) tools
72
+ "run_automation",
73
+ "run_playbook",
74
+ "packet_run",
75
+ ]);
76
+ function annotationsFor(name) {
77
+ if (DESTRUCTIVE.has(name)) {
78
+ return { readOnlyHint: false, destructiveHint: true, openWorldHint: true };
79
+ }
80
+ if (WRITE_IDEMPOTENT.has(name)) {
81
+ return { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true };
82
+ }
83
+ if (WRITE.has(name)) {
84
+ return { readOnlyHint: false, destructiveHint: false, openWorldHint: true };
85
+ }
86
+ if (READ_ONLY_LOCAL.has(name)) {
87
+ return { readOnlyHint: true, openWorldHint: false };
88
+ }
89
+ return { readOnlyHint: true, openWorldHint: true };
90
+ }
91
+ // Decorate a tool list with MCP metadata: behavioural annotations
92
+ // (readOnly/destructive/etc) and, for tools registered in OUTPUT_SCHEMAS, a
93
+ // machine-readable `outputSchema`. A value already present on the tool is left
94
+ // untouched, so a module can always override either.
95
+ function withAnnotations(tools) {
96
+ return tools.map((t) => {
97
+ const patch = {};
98
+ if (!t.annotations)
99
+ patch.annotations = annotationsFor(t.name);
100
+ if (!t.outputSchema && output_schemas_js_1.OUTPUT_SCHEMAS[t.name]) {
101
+ patch.outputSchema = output_schemas_js_1.OUTPUT_SCHEMAS[t.name];
102
+ }
103
+ return Object.keys(patch).length ? { ...t, ...patch } : t;
104
+ });
105
+ }
106
+ // Exported for the test suite to assert the classification only references real
107
+ // tool names (catches typos / tools renamed out from under a set).
108
+ exports.MUTATING_TOOL_NAMES = [
109
+ ...WRITE_IDEMPOTENT,
110
+ ...WRITE,
111
+ ...DESTRUCTIVE,
112
+ ...READ_ONLY_LOCAL,
113
+ ];