@blockrun/mcp 0.16.0 → 0.16.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.
Files changed (2) hide show
  1. package/dist/index.js +108 -30
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  PriceClient,
16
16
  SolanaLLMClient,
17
17
  getOrCreateWallet,
18
+ getOrCreateSolanaWallet,
18
19
  loadSolanaWallet,
19
20
  getPaymentLinks,
20
21
  formatWalletCreatedMessage,
@@ -53,6 +54,30 @@ function getChain() {
53
54
  }
54
55
  return "base";
55
56
  }
57
+ var CHAIN_FILE = path.join(BLOCKRUN_DIR, ".chain");
58
+ function resetChainCaches() {
59
+ _evmClient = null;
60
+ _solanaClient = null;
61
+ _imageClient = null;
62
+ _priceClient = null;
63
+ _freePriceClient = null;
64
+ }
65
+ function setChain(chain) {
66
+ fs.mkdirSync(BLOCKRUN_DIR, { recursive: true });
67
+ fs.writeFileSync(CHAIN_FILE, chain, { mode: 384 });
68
+ resetChainCaches();
69
+ }
70
+ async function ensureBothWallets() {
71
+ const evm = ensureEvmWallet();
72
+ const sol = await getOrCreateSolanaWallet();
73
+ if (sol.isNew) {
74
+ console.error(formatWalletCreatedMessage(sol.address));
75
+ }
76
+ return {
77
+ base: { address: evm.address, isNew: evm.isNew },
78
+ solana: { address: sol.address, isNew: sol.isNew }
79
+ };
80
+ }
56
81
  function ensureEvmWallet() {
57
82
  if (!_evmWalletInfo) {
58
83
  _evmWalletInfo = getOrCreateWallet();
@@ -66,11 +91,14 @@ function getOrCreateWalletKey() {
66
91
  const info = ensureEvmWallet();
67
92
  return info.privateKey;
68
93
  }
94
+ function buildSolanaClient() {
95
+ const privateKey = process.env.SOLANA_WALLET_KEY || loadSolanaWallet() || void 0;
96
+ return new SolanaLLMClient(privateKey ? { privateKey } : void 0);
97
+ }
69
98
  function getClient() {
70
99
  if (getChain() === "solana") {
71
100
  if (!_solanaClient) {
72
- const privateKey = process.env.SOLANA_WALLET_KEY || loadSolanaWallet() || void 0;
73
- _solanaClient = new SolanaLLMClient(privateKey ? { privateKey } : void 0);
101
+ _solanaClient = buildSolanaClient();
74
102
  }
75
103
  return _solanaClient;
76
104
  }
@@ -126,15 +154,14 @@ async function getWalletInfo() {
126
154
  fundingUrl: links.blockrun
127
155
  };
128
156
  }
129
- async function getUsdcBalance(address) {
130
- if (getChain() === "solana") {
131
- try {
132
- const client = getClient();
133
- return await client.getBalance();
134
- } catch {
135
- return null;
136
- }
157
+ async function getSolanaUsdcBalance() {
158
+ try {
159
+ return await buildSolanaClient().getBalance();
160
+ } catch {
161
+ return null;
137
162
  }
163
+ }
164
+ async function getBaseUsdcBalance(address) {
138
165
  const USDC_ADDRESS2 = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
139
166
  const BASE_RPC_URLS = [
140
167
  "https://mainnet.base.org",
@@ -162,6 +189,9 @@ async function getUsdcBalance(address) {
162
189
  }
163
190
  return null;
164
191
  }
192
+ async function getChainBalance(chain, address) {
193
+ return chain === "solana" ? getSolanaUsdcBalance() : getBaseUsdcBalance(address);
194
+ }
165
195
 
166
196
  // src/tools/wallet.ts
167
197
  import { z } from "zod";
@@ -182,7 +212,7 @@ var QR_FILE = path2.join(WALLET_DIR, "qr.png");
182
212
  var USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
183
213
  var BASE_CHAIN_ID = "8453";
184
214
  var MODEL_TIERS = {
185
- fast: ["google/gemini-2.5-flash", "google/gemini-3.1-flash-lite", "openai/gpt-5-mini", "deepseek/deepseek-chat", "google/gemini-3-flash-preview"],
215
+ fast: ["google/gemini-3.5-flash", "google/gemini-2.5-flash", "google/gemini-3.1-flash-lite", "openai/gpt-5-mini", "deepseek/deepseek-chat", "google/gemini-3-flash-preview"],
186
216
  balanced: ["openai/gpt-5.4", "anthropic/claude-sonnet-4.6", "google/gemini-2.5-pro", "moonshot/kimi-k2.6", "openai/gpt-5.3", "google/gemini-3.1-pro"],
187
217
  powerful: ["openai/gpt-5.4-pro", "anthropic/claude-opus-4.7", "anthropic/claude-opus-4.6", "openai/o3", "openai/gpt-5.4"],
188
218
  cheap: ["zai/glm-5", "zai/glm-5-turbo", "nvidia/gpt-oss-120b", "nvidia/deepseek-v3.2", "google/gemini-2.5-flash", "deepseek/deepseek-chat", "openai/gpt-5.4-nano"],
@@ -324,10 +354,14 @@ Call this FIRST if any other blockrun_* tool returns a payment/balance error.
324
354
  Call this to check your current USDC balance before expensive operations.
325
355
  Call this to set spending limits before spawning child agents.
326
356
 
357
+ The server holds TWO wallets \u2014 one on Base, one on Solana \u2014 but pays on ONE
358
+ active chain at a time. status shows both addresses/balances and which is active.
359
+
327
360
  Actions:
328
- - status (default): Current wallet address, USDC balance, total session spending
329
- - setup: Get funding instructions + QR code (call this when balance is 0)
330
- - qr: Open QR code in system viewer
361
+ - status (default): Both wallet addresses + USDC balances, active chain, session spending
362
+ - setup: Get funding instructions + QR code for the ACTIVE chain (call this when balance is 0)
363
+ - qr: Open QR code (active chain) in system viewer
364
+ - chain + chain:"base"|"solana": Switch the active payment chain (omit chain: to just see the current one)
331
365
 
332
366
  Budget controls:
333
367
  - budget + budget_action:"set" + budget_amount:1.00 \u2192 Set global spend cap
@@ -345,14 +379,15 @@ Usage pattern for multi-agent systems:
345
379
 
346
380
  Do NOT call this for actual AI queries \u2014 use blockrun_chat for that.`,
347
381
  inputSchema: {
348
- action: z.enum(["status", "setup", "qr", "budget", "delegate", "revoke", "report"]).optional().default("status").describe("What to do"),
382
+ action: z.enum(["status", "setup", "qr", "chain", "budget", "delegate", "revoke", "report"]).optional().default("status").describe("What to do"),
383
+ chain: z.enum(["base", "solana"]).optional().describe("Target chain for action='chain'. Omit to view the current active chain."),
349
384
  budget_action: z.enum(["set", "check", "clear"]).optional().describe("Budget action (for action='budget')"),
350
385
  budget_amount: z.number().optional().describe("Budget limit in USD (for budget_action='set')"),
351
386
  agent_id: z.string().optional().describe("Agent identifier for delegate/revoke/report actions"),
352
387
  agent_limit: z.number().optional().describe("Budget limit in USD for this agent (required for delegate action)")
353
388
  }
354
389
  },
355
- async ({ action, budget_action, budget_amount, agent_id, agent_limit }) => {
390
+ async ({ action, chain: targetChain, budget_action, budget_amount, agent_id, agent_limit }) => {
356
391
  if (action === "budget") {
357
392
  const budgetAct = budget_action || "check";
358
393
  if (budgetAct === "set") {
@@ -428,6 +463,35 @@ Pass agent_id: "${agent_id}" in any blockrun_* tool call to track and enforce th
428
463
  structuredContent: { global: { limit: budget.limit, spent: budget.spent, calls: budget.calls }, agents: agentRows }
429
464
  };
430
465
  }
466
+ if (action === "chain") {
467
+ const both2 = await ensureBothWallets();
468
+ if (targetChain && targetChain !== getChain()) {
469
+ setChain(targetChain);
470
+ }
471
+ const active = getChain();
472
+ const activeWallet = active === "solana" ? both2.solana : both2.base;
473
+ const activeBalance2 = await getChainBalance(active, activeWallet.address);
474
+ const balStr = activeBalance2 !== null ? `$${activeBalance2.toFixed(6)} USDC` : "balance unavailable";
475
+ const switched = targetChain ? `Switched active chain \u2192 ${active.toUpperCase()}.` : `Active chain: ${active.toUpperCase()}.`;
476
+ const text2 = `${switched}
477
+
478
+ Active (${active}): ${activeWallet.address}
479
+ Balance: ${balStr}${activeBalance2 !== null && activeBalance2 < 1 ? " (low \u2014 fund this address)" : ""}
480
+ Base: ${both2.base.address}
481
+ Solana: ${both2.solana.address}
482
+
483
+ All blockrun_* calls now pay on ${active}. Note: image generation, price, video,
484
+ music and RealFace are Base-only \u2014 switch back with chain:"base" for those.`;
485
+ return {
486
+ content: [{ type: "text", text: text2 }],
487
+ structuredContent: {
488
+ activeChain: active,
489
+ base: both2.base.address,
490
+ solana: both2.solana.address,
491
+ activeBalance: activeBalance2
492
+ }
493
+ };
494
+ }
431
495
  const info = await getWalletInfo();
432
496
  const address = info.address;
433
497
  const chain = getChain();
@@ -525,24 +589,38 @@ SECURITY: Private key stored at ~/.blockrun/.session (never leaves your machine)
525
589
  ================================================================================`;
526
590
  return { content: [{ type: "text", text: text2 }] };
527
591
  }
528
- const balance = await getUsdcBalance(address);
529
- const balanceStr = balance !== null ? `$${balance.toFixed(6)} USDC` : "Unable to fetch";
530
- const lowBalance = balance !== null && balance < 1;
592
+ const both = await ensureBothWallets();
593
+ const [baseBal, solBal] = await Promise.all([
594
+ getChainBalance("base", both.base.address),
595
+ getChainBalance("solana", both.solana.address)
596
+ ]);
597
+ const activeBalance = chain === "solana" ? solBal : baseBal;
598
+ const fmt = (b) => b !== null ? `$${b.toFixed(6)} USDC` : "unavailable";
531
599
  const explorerLabel = chain === "solana" ? "Solscan" : "Basescan";
532
- const text = `Wallet: ${address}
533
- Balance: ${balanceStr}${lowBalance ? " (low - add funds)" : ""}
534
- Network: ${info.network} | View: ${info.explorerUrl}
535
- ${info.isNew ? "\nNEW WALLET - Run with action: 'setup' for funding instructions" : ""}`;
600
+ const mark = (c) => c === chain ? "\u2192" : " ";
601
+ const text = `Active chain: ${chain.toUpperCase()} (switch with action:"chain" chain:"base"|"solana")
602
+
603
+ ${mark("base")} Base: ${both.base.address}
604
+ ${fmt(baseBal)}${baseBal !== null && baseBal < 1 ? " (low)" : ""}
605
+ ${mark("solana")} Solana: ${both.solana.address}
606
+ ${fmt(solBal)}${solBal !== null && solBal < 1 ? " (low)" : ""}
607
+
608
+ Paying on ${chain} | View active: ${info.explorerUrl}${info.isNew ? "\nNEW WALLET on active chain \u2014 run action:'setup' for funding instructions" : ""}`;
536
609
  return {
537
610
  content: [{ type: "text", text }],
538
611
  structuredContent: {
612
+ activeChain: chain,
539
613
  address: info.address,
540
- balance,
614
+ balance: activeBalance,
541
615
  network: info.network,
542
616
  chainId: info.chainId,
543
617
  isNew: info.isNew,
544
618
  explorerUrl: info.explorerUrl,
545
- explorerLabel
619
+ explorerLabel,
620
+ wallets: {
621
+ base: { address: both.base.address, balance: baseBal },
622
+ solana: { address: both.solana.address, balance: solBal }
623
+ }
546
624
  }
547
625
  };
548
626
  }
@@ -865,7 +943,7 @@ Edit models: openai/gpt-image-1, openai/gpt-image-2 (default for edits)`,
865
943
  try {
866
944
  if (getChain() !== "base") {
867
945
  return {
868
- content: [{ type: "text", text: formatError("blockrun_image currently settles on Base only. Switch BlockRun to Base (for example: write 'base' to ~/.blockrun/.chain) and fund the Base wallet with USDC.") }],
946
+ content: [{ type: "text", text: formatError("blockrun_image currently settles on Base only. Switch BlockRun to Base (for example: run blockrun_wallet with action:chain chain:base) and fund the Base wallet with USDC.") }],
869
947
  isError: true
870
948
  };
871
949
  }
@@ -979,7 +1057,7 @@ Returns a time-limited CDN URL \u2014 download immediately if you need to keep t
979
1057
  try {
980
1058
  if (getChain() !== "base") {
981
1059
  return {
982
- content: [{ type: "text", text: formatError("blockrun_music currently settles on Base only. Switch BlockRun to Base (for example: write 'base' to ~/.blockrun/.chain) and fund the Base wallet with USDC.") }],
1060
+ content: [{ type: "text", text: formatError("blockrun_music currently settles on Base only. Switch BlockRun to Base (for example: run blockrun_wallet with action:chain chain:base) and fund the Base wallet with USDC.") }],
983
1061
  isError: true
984
1062
  };
985
1063
  }
@@ -1166,7 +1244,7 @@ Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GC
1166
1244
  try {
1167
1245
  if (getChain() !== "base") {
1168
1246
  return {
1169
- content: [{ type: "text", text: formatError("blockrun_video currently settles on Base only. Switch BlockRun to Base (for example: write 'base' to ~/.blockrun/.chain) and fund the Base wallet with USDC.") }],
1247
+ content: [{ type: "text", text: formatError("blockrun_video currently settles on Base only. Switch BlockRun to Base (for example: run blockrun_wallet with action:chain chain:base) and fund the Base wallet with USDC.") }],
1170
1248
  isError: true
1171
1249
  };
1172
1250
  }
@@ -1507,7 +1585,7 @@ Enroll one: blockrun_realface action:"init" name:"\u2026".` }],
1507
1585
  }
1508
1586
  if (action === "enroll") {
1509
1587
  if (getChain() !== "base") {
1510
- return { content: [{ type: "text", text: formatError("blockrun_realface enroll settles on Base only. Switch BlockRun to Base (write 'base' to ~/.blockrun/.chain) and fund the Base wallet with USDC.") }], isError: true };
1588
+ return { content: [{ type: "text", text: formatError("blockrun_realface enroll settles on Base only. Switch BlockRun to Base (run blockrun_wallet with action:chain chain:base) and fund the Base wallet with USDC.") }], isError: true };
1511
1589
  }
1512
1590
  if (!name || !image_url || !group_id) {
1513
1591
  return { content: [{ type: "text", text: formatError("enroll requires name, image_url, and group_id (from init, after the group is active).") }], isError: true };
@@ -1901,7 +1979,7 @@ Examples:
1901
1979
  const paid = isPaidPriceCall(action, category);
1902
1980
  if (paid && getChain() !== "base") {
1903
1981
  return {
1904
- content: [{ type: "text", text: formatError("Paid stock price/history calls currently settle on Base only. Switch BlockRun to Base (for example: write 'base' to ~/.blockrun/.chain) and fund the Base wallet with USDC.") }],
1982
+ content: [{ type: "text", text: formatError("Paid stock price/history calls currently settle on Base only. Switch BlockRun to Base (for example: run blockrun_wallet with action:chain chain:base) and fund the Base wallet with USDC.") }],
1905
1983
  isError: true
1906
1984
  };
1907
1985
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/mcp",
3
- "version": "0.16.0",
3
+ "version": "0.16.1",
4
4
  "mcpName": "io.github.BlockRunAI/blockrun-mcp",
5
5
  "description": "BlockRun MCP Server - Give your AI agent web search, deep research, prediction markets, crypto data, X/Twitter intelligence. Paid via x402 micropayments.",
6
6
  "type": "module",