@blockrun/mcp 0.2.2 → 0.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 +28 -2
- package/dist/index.js +352 -346
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -122,11 +122,34 @@ blockrun list models
|
|
|
122
122
|
blockrun show OpenAI models with pricing
|
|
123
123
|
```
|
|
124
124
|
|
|
125
|
-
###
|
|
125
|
+
### Real-Time X/Twitter Search
|
|
126
|
+
|
|
127
|
+
Get live data from X/Twitter using Grok's real-time search:
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
blockrun twitter: what is @elonmusk posting about today
|
|
131
|
+
|
|
132
|
+
blockrun twitter: trending AI news
|
|
133
|
+
|
|
134
|
+
blockrun twitter: reactions to [recent event]
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Wallet & Balance
|
|
126
138
|
|
|
127
139
|
```
|
|
128
140
|
blockrun setup # First-time setup instructions
|
|
129
141
|
blockrun wallet # Check your wallet address
|
|
142
|
+
blockrun balance # Check on-chain USDC balance
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Budget Management
|
|
146
|
+
|
|
147
|
+
Control your session spending:
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
blockrun budget check # View current spending
|
|
151
|
+
blockrun budget set $1.00 # Set $1.00 limit
|
|
152
|
+
blockrun budget clear # Remove limit
|
|
130
153
|
```
|
|
131
154
|
|
|
132
155
|
## Supported Models & Pricing
|
|
@@ -259,7 +282,10 @@ The MCP couldn't find or create a wallet. Check that `~/.blockrun/` directory is
|
|
|
259
282
|
Some models have rate limits. Try `blockrun smart cheap` or `blockrun smart fast` to use alternative models.
|
|
260
283
|
|
|
261
284
|
### Check wallet balance
|
|
262
|
-
Say `blockrun
|
|
285
|
+
Say `blockrun balance` to check your on-chain USDC balance, or visit: `https://basescan.org/address/YOUR_ADDRESS`
|
|
286
|
+
|
|
287
|
+
### Budget limit reached
|
|
288
|
+
If you've set a session budget and hit the limit, use `blockrun budget clear` to remove it or `blockrun budget set $X` to increase it.
|
|
263
289
|
|
|
264
290
|
## Configuration
|
|
265
291
|
|
package/dist/index.js
CHANGED
|
@@ -4,13 +4,19 @@
|
|
|
4
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
6
|
import { z } from "zod";
|
|
7
|
-
import { LLMClient } from "@blockrun/llm";
|
|
7
|
+
import { LLMClient, ImageClient } from "@blockrun/llm";
|
|
8
8
|
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
|
|
9
9
|
import * as fs from "fs";
|
|
10
10
|
import * as path from "path";
|
|
11
11
|
import * as os from "os";
|
|
12
|
+
import QRCode from "qrcode";
|
|
13
|
+
import { Jimp } from "jimp";
|
|
14
|
+
import open from "open";
|
|
12
15
|
var WALLET_DIR = path.join(os.homedir(), ".blockrun");
|
|
13
16
|
var WALLET_FILE = path.join(WALLET_DIR, ".session");
|
|
17
|
+
var QR_FILE = path.join(WALLET_DIR, "qr.png");
|
|
18
|
+
var USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
19
|
+
var BASE_CHAIN_ID = "8453";
|
|
14
20
|
var MODEL_TIERS = {
|
|
15
21
|
fast: ["google/gemini-2.5-flash", "openai/gpt-4o-mini", "deepseek/deepseek-chat"],
|
|
16
22
|
balanced: ["openai/gpt-4o", "anthropic/claude-sonnet-4", "google/gemini-2.5-pro"],
|
|
@@ -21,7 +27,14 @@ var MODEL_TIERS = {
|
|
|
21
27
|
var walletWasCreated = false;
|
|
22
28
|
var walletAddress = null;
|
|
23
29
|
var client = null;
|
|
30
|
+
var imageClient = null;
|
|
24
31
|
var cachedModels = null;
|
|
32
|
+
var sessionBudget = { limit: null, spent: 0, calls: 0 };
|
|
33
|
+
var BASE_RPC_URLS = [
|
|
34
|
+
"https://mainnet.base.org",
|
|
35
|
+
"https://base.llamarpc.com",
|
|
36
|
+
"https://1rpc.io/base"
|
|
37
|
+
];
|
|
25
38
|
function getOrCreateWalletKey() {
|
|
26
39
|
const envKey = process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY;
|
|
27
40
|
if (envKey) {
|
|
@@ -62,6 +75,13 @@ function getClient() {
|
|
|
62
75
|
}
|
|
63
76
|
return client;
|
|
64
77
|
}
|
|
78
|
+
function getImageClient() {
|
|
79
|
+
if (!imageClient) {
|
|
80
|
+
const privateKey = getOrCreateWalletKey();
|
|
81
|
+
imageClient = new ImageClient({ privateKey });
|
|
82
|
+
}
|
|
83
|
+
return imageClient;
|
|
84
|
+
}
|
|
65
85
|
function getWalletInfo() {
|
|
66
86
|
const llm = getClient();
|
|
67
87
|
const address = llm.getWalletAddress();
|
|
@@ -71,149 +91,296 @@ function getWalletInfo() {
|
|
|
71
91
|
chainId: 8453,
|
|
72
92
|
currency: "USDC",
|
|
73
93
|
isNew: walletWasCreated,
|
|
74
|
-
basescanUrl: `https://basescan.org/address/${address}
|
|
75
|
-
fundingOptions: {
|
|
76
|
-
coinbase: "Send USDC, select 'Base' network",
|
|
77
|
-
bridge: "https://bridge.base.org",
|
|
78
|
-
buy: "https://www.coinbase.com/onramp"
|
|
79
|
-
}
|
|
94
|
+
basescanUrl: `https://basescan.org/address/${address}`
|
|
80
95
|
};
|
|
81
96
|
}
|
|
82
|
-
function
|
|
83
|
-
|
|
84
|
-
|
|
97
|
+
async function getUsdcBalance(address) {
|
|
98
|
+
const data = {
|
|
99
|
+
jsonrpc: "2.0",
|
|
100
|
+
method: "eth_call",
|
|
101
|
+
params: [{
|
|
102
|
+
to: USDC_ADDRESS,
|
|
103
|
+
data: `0x70a08231000000000000000000000000${address.slice(2)}`
|
|
104
|
+
}, "latest"],
|
|
105
|
+
id: 1
|
|
106
|
+
};
|
|
107
|
+
for (const rpcUrl of BASE_RPC_URLS) {
|
|
108
|
+
try {
|
|
109
|
+
const response = await fetch(rpcUrl, {
|
|
110
|
+
method: "POST",
|
|
111
|
+
headers: { "Content-Type": "application/json" },
|
|
112
|
+
body: JSON.stringify(data)
|
|
113
|
+
});
|
|
114
|
+
const result = await response.json();
|
|
115
|
+
if (result.result) {
|
|
116
|
+
return parseInt(result.result, 16) / 1e6;
|
|
117
|
+
}
|
|
118
|
+
} catch {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
function getEip681Uri(address, amountUsdc = 1) {
|
|
125
|
+
const amountWei = Math.floor(amountUsdc * 1e6);
|
|
126
|
+
return `ethereum:${USDC_ADDRESS}@${BASE_CHAIN_ID}/transfer?address=${address}&uint256=${amountWei}`;
|
|
127
|
+
}
|
|
128
|
+
async function generateQrPng(address) {
|
|
129
|
+
const eip681Uri = getEip681Uri(address);
|
|
130
|
+
const qrBuffer = await QRCode.toBuffer(eip681Uri, {
|
|
131
|
+
type: "png",
|
|
132
|
+
width: 400,
|
|
133
|
+
margin: 2,
|
|
134
|
+
errorCorrectionLevel: "H",
|
|
135
|
+
color: { dark: "#000000", light: "#FFFFFF" }
|
|
136
|
+
});
|
|
137
|
+
const qrImage = await Jimp.read(qrBuffer);
|
|
138
|
+
try {
|
|
139
|
+
const logoUrl = "https://avatars.githubusercontent.com/u/108554348?s=200&v=4";
|
|
140
|
+
const logo = await Jimp.read(logoUrl);
|
|
141
|
+
const logoSize = Math.floor(qrImage.width * 0.2);
|
|
142
|
+
logo.resize({ w: logoSize, h: logoSize });
|
|
143
|
+
const x = Math.floor((qrImage.width - logoSize) / 2);
|
|
144
|
+
const y = Math.floor((qrImage.height - logoSize) / 2);
|
|
145
|
+
qrImage.composite(logo, x, y);
|
|
146
|
+
} catch {
|
|
147
|
+
}
|
|
148
|
+
if (!fs.existsSync(WALLET_DIR)) {
|
|
149
|
+
fs.mkdirSync(WALLET_DIR, { recursive: true, mode: 448 });
|
|
85
150
|
}
|
|
86
|
-
|
|
151
|
+
await qrImage.write(QR_FILE);
|
|
152
|
+
return QR_FILE;
|
|
153
|
+
}
|
|
154
|
+
async function openQrInViewer(qrPath) {
|
|
155
|
+
try {
|
|
156
|
+
await open(qrPath);
|
|
157
|
+
} catch {
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function recordSpending(cost) {
|
|
161
|
+
sessionBudget.spent += cost;
|
|
162
|
+
sessionBudget.calls += 1;
|
|
163
|
+
}
|
|
164
|
+
function checkBudget() {
|
|
165
|
+
if (sessionBudget.limit === null) {
|
|
166
|
+
return { allowed: true, remaining: null };
|
|
167
|
+
}
|
|
168
|
+
const remaining = sessionBudget.limit - sessionBudget.spent;
|
|
169
|
+
return { allowed: remaining > 0, remaining };
|
|
170
|
+
}
|
|
171
|
+
function formatError(message) {
|
|
172
|
+
const isPaymentError = message.toLowerCase().includes("payment") || message.toLowerCase().includes("402") || message.toLowerCase().includes("balance") || message.toLowerCase().includes("insufficient");
|
|
173
|
+
let errorText = `Error: ${message}`;
|
|
174
|
+
if (isPaymentError) {
|
|
175
|
+
errorText += `
|
|
176
|
+
|
|
177
|
+
This error usually means your wallet needs funding.
|
|
178
|
+
Run blockrun_wallet with action: "setup" to get funding instructions.
|
|
179
|
+
|
|
180
|
+
Quick fix: Send USDC to your wallet on Base network.`;
|
|
181
|
+
}
|
|
182
|
+
return errorText;
|
|
183
|
+
}
|
|
184
|
+
var server = new McpServer({
|
|
185
|
+
name: "blockrun-mcp",
|
|
186
|
+
version: "0.4.0"
|
|
187
|
+
});
|
|
188
|
+
server.registerTool(
|
|
189
|
+
"blockrun_wallet",
|
|
190
|
+
{
|
|
191
|
+
description: `Manage your BlockRun wallet - check status, get funding instructions, open QR code, or manage session budget.
|
|
192
|
+
|
|
193
|
+
Actions:
|
|
194
|
+
- status: Show wallet address, balance, and basescan link (default)
|
|
195
|
+
- setup: Full funding instructions with QR code
|
|
196
|
+
- qr: Generate and open QR code for easy funding
|
|
197
|
+
- budget: Manage session spending limit
|
|
198
|
+
|
|
199
|
+
Examples:
|
|
200
|
+
blockrun_wallet() -> status + balance
|
|
201
|
+
blockrun_wallet({ action: "setup" }) -> funding instructions + QR
|
|
202
|
+
blockrun_wallet({ action: "qr" }) -> open QR code
|
|
203
|
+
blockrun_wallet({ action: "budget", budget_action: "set", budget_amount: 1.00 })`,
|
|
204
|
+
inputSchema: {
|
|
205
|
+
action: z.enum(["status", "setup", "qr", "budget"]).optional().default("status").describe("What to do"),
|
|
206
|
+
budget_action: z.enum(["set", "check", "clear"]).optional().describe("Budget action (for action='budget')"),
|
|
207
|
+
budget_amount: z.number().optional().describe("Budget limit in USD (for budget_action='set')")
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
async ({ action, budget_action, budget_amount }) => {
|
|
211
|
+
const info = getWalletInfo();
|
|
212
|
+
const address = info.address;
|
|
213
|
+
if (action === "budget") {
|
|
214
|
+
const budgetAct = budget_action || "check";
|
|
215
|
+
if (budgetAct === "set") {
|
|
216
|
+
if (budget_amount === void 0 || budget_amount <= 0) {
|
|
217
|
+
return {
|
|
218
|
+
content: [{ type: "text", text: "Error: Provide a positive budget_amount (e.g., 1.00 for $1.00)" }],
|
|
219
|
+
isError: true
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
sessionBudget.limit = budget_amount;
|
|
223
|
+
} else if (budgetAct === "clear") {
|
|
224
|
+
sessionBudget.limit = null;
|
|
225
|
+
}
|
|
226
|
+
const remaining = sessionBudget.limit !== null ? sessionBudget.limit - sessionBudget.spent : null;
|
|
227
|
+
const limitStr = sessionBudget.limit !== null ? `$${sessionBudget.limit.toFixed(2)}` : "Unlimited";
|
|
228
|
+
const remainingStr = remaining !== null ? `$${remaining.toFixed(4)}` : "N/A";
|
|
229
|
+
return {
|
|
230
|
+
content: [{ type: "text", text: `Session Budget: ${limitStr} | Spent: $${sessionBudget.spent.toFixed(4)} | Calls: ${sessionBudget.calls} | Remaining: ${remainingStr}${budgetAct === "set" ? ` | Set to $${budget_amount?.toFixed(2)}` : ""}${budgetAct === "clear" ? " | Limit removed" : ""}` }],
|
|
231
|
+
structuredContent: {
|
|
232
|
+
limit: sessionBudget.limit,
|
|
233
|
+
spent: sessionBudget.spent,
|
|
234
|
+
calls: sessionBudget.calls,
|
|
235
|
+
remaining
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
if (action === "qr") {
|
|
240
|
+
try {
|
|
241
|
+
const qrPath = await generateQrPng(address);
|
|
242
|
+
await openQrInViewer(qrPath);
|
|
243
|
+
return {
|
|
244
|
+
content: [{ type: "text", text: `QR code opened! Scan with MetaMask to send USDC on Base.
|
|
245
|
+
|
|
246
|
+
Address: ${address}
|
|
247
|
+
QR saved: ${qrPath}` }]
|
|
248
|
+
};
|
|
249
|
+
} catch (err) {
|
|
250
|
+
return {
|
|
251
|
+
content: [{ type: "text", text: `Failed to generate QR: ${err}` }],
|
|
252
|
+
isError: true
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (action === "setup") {
|
|
257
|
+
let qrMessage = "";
|
|
258
|
+
try {
|
|
259
|
+
const qrPath = await generateQrPng(address);
|
|
260
|
+
await openQrInViewer(qrPath);
|
|
261
|
+
qrMessage = `
|
|
262
|
+
QR code opened for scanning! (${qrPath})`;
|
|
263
|
+
} catch {
|
|
264
|
+
qrMessage = "\n(QR generation failed - use address above)";
|
|
265
|
+
}
|
|
266
|
+
const text2 = `
|
|
87
267
|
================================================================================
|
|
88
268
|
BLOCKRUN WALLET SETUP
|
|
89
269
|
================================================================================
|
|
90
270
|
|
|
91
|
-
Your wallet address: ${
|
|
92
|
-
|
|
93
|
-
To use BlockRun AI models, you need USDC on Base network.
|
|
271
|
+
Your wallet address: ${address}
|
|
272
|
+
${qrMessage}
|
|
94
273
|
|
|
95
274
|
HOW TO FUND YOUR WALLET:
|
|
96
275
|
------------------------
|
|
97
276
|
|
|
98
277
|
Option 1: Transfer from Coinbase
|
|
99
278
|
1. Open Coinbase app or website
|
|
100
|
-
2. Go to Send/Receive
|
|
101
|
-
3.
|
|
102
|
-
4.
|
|
103
|
-
5.
|
|
104
|
-
6. Send any amount ($5 is enough to start)
|
|
279
|
+
2. Go to Send/Receive -> Select USDC
|
|
280
|
+
3. Choose "Base" network (important!)
|
|
281
|
+
4. Paste: ${address}
|
|
282
|
+
5. Send $1-5 to start
|
|
105
283
|
|
|
106
284
|
Option 2: Bridge from other chains
|
|
107
|
-
|
|
108
|
-
2. Connect your existing wallet
|
|
109
|
-
3. Bridge USDC to Base
|
|
110
|
-
4. Send to: ${walletAddress}
|
|
285
|
+
https://bridge.base.org -> Bridge USDC to Base -> Send to address above
|
|
111
286
|
|
|
112
287
|
Option 3: Buy directly
|
|
113
|
-
|
|
114
|
-
2. Buy USDC on Base network
|
|
115
|
-
3. Send to: ${walletAddress}
|
|
116
|
-
|
|
117
|
-
VERIFY YOUR BALANCE:
|
|
118
|
-
https://basescan.org/address/${walletAddress}
|
|
288
|
+
https://www.coinbase.com/onramp -> Buy USDC on Base -> Send to address above
|
|
119
289
|
|
|
120
|
-
|
|
121
|
-
- GPT-4o: ~$0.005 per request
|
|
122
|
-
- Claude Sonnet: ~$0.003 per request
|
|
123
|
-
- Gemini Flash: ~$0.0001 per request
|
|
124
|
-
- Full pricing: https://blockrun.ai/pricing
|
|
125
|
-
|
|
126
|
-
SECURITY NOTE:
|
|
127
|
-
Your private key is stored at: ~/.blockrun/.session
|
|
128
|
-
This key NEVER leaves your machine - only used for signing payments locally.
|
|
129
|
-
|
|
130
|
-
================================================================================
|
|
131
|
-
`;
|
|
132
|
-
}
|
|
133
|
-
var server = new McpServer({
|
|
134
|
-
name: "blockrun-mcp",
|
|
135
|
-
version: "0.2.0"
|
|
136
|
-
});
|
|
137
|
-
server.registerTool(
|
|
138
|
-
"blockrun_chat",
|
|
139
|
-
{
|
|
140
|
-
description: `Chat with any AI model via BlockRun. Supports 30+ models including GPT-5, Claude Opus 4, Gemini 3, and more.
|
|
141
|
-
Pay-per-request with x402 micropayments - no API keys needed.
|
|
290
|
+
VERIFY BALANCE: https://basescan.org/address/${address}
|
|
142
291
|
|
|
143
|
-
|
|
144
|
-
-
|
|
145
|
-
-
|
|
146
|
-
- anthropic/claude-sonnet-4: Fast & capable (recommended)
|
|
147
|
-
- google/gemini-2.5-pro: Great for long context
|
|
148
|
-
- deepseek/deepseek-chat: Very affordable
|
|
292
|
+
PRICING (pay per use):
|
|
293
|
+
- GPT-4o: ~$0.005/request | Claude Sonnet: ~$0.003/request
|
|
294
|
+
- Gemini Flash: ~$0.0001/request | Full pricing: https://blockrun.ai/pricing
|
|
149
295
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
message: z.string().describe("Your message to the AI"),
|
|
154
|
-
system: z.string().optional().describe("Optional system prompt to set context/behavior"),
|
|
155
|
-
max_tokens: z.number().optional().default(1024).describe("Maximum tokens in response"),
|
|
156
|
-
temperature: z.number().optional().default(1).describe("Creativity level 0-2")
|
|
157
|
-
}
|
|
158
|
-
},
|
|
159
|
-
async ({ model, message, system, max_tokens, temperature }) => {
|
|
160
|
-
try {
|
|
161
|
-
const llm = getClient();
|
|
162
|
-
const response = await llm.chat(model, message, {
|
|
163
|
-
system,
|
|
164
|
-
maxTokens: max_tokens,
|
|
165
|
-
temperature
|
|
166
|
-
});
|
|
167
|
-
return { content: [{ type: "text", text: response }] };
|
|
168
|
-
} catch (error) {
|
|
169
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
170
|
-
return {
|
|
171
|
-
content: [{ type: "text", text: formatError(errorMessage) }],
|
|
172
|
-
isError: true
|
|
173
|
-
};
|
|
296
|
+
SECURITY: Private key stored at ~/.blockrun/.session (never leaves your machine)
|
|
297
|
+
================================================================================`;
|
|
298
|
+
return { content: [{ type: "text", text: text2 }] };
|
|
174
299
|
}
|
|
300
|
+
const balance = await getUsdcBalance(address);
|
|
301
|
+
const balanceStr = balance !== null ? `$${balance.toFixed(6)} USDC` : "Unable to fetch";
|
|
302
|
+
const lowBalance = balance !== null && balance < 1;
|
|
303
|
+
const text = `Wallet: ${address}
|
|
304
|
+
Balance: ${balanceStr}${lowBalance ? " (low - add funds)" : ""}
|
|
305
|
+
Network: Base | View: ${info.basescanUrl}
|
|
306
|
+
${info.isNew ? "\nNEW WALLET - Run with action: 'setup' for funding instructions" : ""}`;
|
|
307
|
+
return {
|
|
308
|
+
content: [{ type: "text", text }],
|
|
309
|
+
structuredContent: {
|
|
310
|
+
address: info.address,
|
|
311
|
+
balance,
|
|
312
|
+
network: info.network,
|
|
313
|
+
chainId: info.chainId,
|
|
314
|
+
isNew: info.isNew,
|
|
315
|
+
basescanUrl: info.basescanUrl
|
|
316
|
+
}
|
|
317
|
+
};
|
|
175
318
|
}
|
|
176
319
|
);
|
|
177
320
|
server.registerTool(
|
|
178
|
-
"
|
|
321
|
+
"blockrun_chat",
|
|
179
322
|
{
|
|
180
|
-
description: `
|
|
323
|
+
description: `Chat with AI models via BlockRun. Supports 30+ models with pay-per-request micropayments.
|
|
324
|
+
|
|
325
|
+
Two ways to use:
|
|
326
|
+
1. Specify a model directly: model: "openai/gpt-4o"
|
|
327
|
+
2. Use smart routing: mode: "fast" | "balanced" | "powerful" | "cheap" | "reasoning"
|
|
328
|
+
|
|
329
|
+
Popular models:
|
|
330
|
+
- openai/gpt-5.2, openai/gpt-4o, openai/gpt-4o-mini
|
|
331
|
+
- anthropic/claude-opus-4, anthropic/claude-sonnet-4
|
|
332
|
+
- google/gemini-2.5-pro, google/gemini-2.5-flash
|
|
333
|
+
- deepseek/deepseek-chat (very affordable)
|
|
181
334
|
|
|
182
|
-
|
|
183
|
-
- fast:
|
|
184
|
-
- balanced:
|
|
185
|
-
- powerful:
|
|
186
|
-
- cheap:
|
|
187
|
-
- reasoning:
|
|
335
|
+
Smart routing modes:
|
|
336
|
+
- fast: Gemini Flash, GPT-4o-mini (quickest)
|
|
337
|
+
- balanced: GPT-4o, Claude Sonnet (good default)
|
|
338
|
+
- powerful: GPT-5.2, Claude Opus 4 (best quality)
|
|
339
|
+
- cheap: DeepSeek, Gemini Flash (lowest cost)
|
|
340
|
+
- reasoning: o3, o1 (complex logic)
|
|
188
341
|
|
|
189
|
-
|
|
342
|
+
Use blockrun_models to see all available models with pricing.`,
|
|
190
343
|
inputSchema: {
|
|
191
|
-
mode: z.enum(["fast", "balanced", "powerful", "cheap", "reasoning"]).describe("Routing mode"),
|
|
192
344
|
message: z.string().describe("Your message to the AI"),
|
|
345
|
+
model: z.string().optional().describe("Specific model ID (e.g., 'openai/gpt-4o')"),
|
|
346
|
+
mode: z.enum(["fast", "balanced", "powerful", "cheap", "reasoning"]).optional().describe("Smart routing mode (ignored if model specified)"),
|
|
193
347
|
system: z.string().optional().describe("Optional system prompt"),
|
|
194
|
-
max_tokens: z.number().optional().default(1024).describe("
|
|
195
|
-
|
|
196
|
-
outputSchema: {
|
|
197
|
-
model_used: z.string().describe("The model that was used"),
|
|
198
|
-
response: z.string().describe("The AI response")
|
|
348
|
+
max_tokens: z.number().optional().default(1024).describe("Max tokens in response"),
|
|
349
|
+
temperature: z.number().optional().default(1).describe("Creativity 0-2")
|
|
199
350
|
}
|
|
200
351
|
},
|
|
201
|
-
async ({
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
for (const model of models) {
|
|
352
|
+
async ({ message, model, mode, system, max_tokens, temperature }) => {
|
|
353
|
+
const llm = getClient();
|
|
354
|
+
if (model) {
|
|
205
355
|
try {
|
|
206
|
-
const llm = getClient();
|
|
207
356
|
const response = await llm.chat(model, message, {
|
|
357
|
+
system,
|
|
358
|
+
maxTokens: max_tokens,
|
|
359
|
+
temperature
|
|
360
|
+
});
|
|
361
|
+
return { content: [{ type: "text", text: response }] };
|
|
362
|
+
} catch (error) {
|
|
363
|
+
const errorMessage2 = error instanceof Error ? error.message : String(error);
|
|
364
|
+
return {
|
|
365
|
+
content: [{ type: "text", text: formatError(errorMessage2) }],
|
|
366
|
+
isError: true
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
const routingMode = mode || "balanced";
|
|
371
|
+
const models = MODEL_TIERS[routingMode];
|
|
372
|
+
let lastError = null;
|
|
373
|
+
for (const m of models) {
|
|
374
|
+
try {
|
|
375
|
+
const response = await llm.chat(m, message, {
|
|
208
376
|
system,
|
|
209
377
|
maxTokens: max_tokens
|
|
210
378
|
});
|
|
211
|
-
const result = { model_used: model, response };
|
|
212
379
|
return {
|
|
213
|
-
content: [{ type: "text", text: `[
|
|
380
|
+
content: [{ type: "text", text: `[${m}]
|
|
214
381
|
|
|
215
382
|
${response}` }],
|
|
216
|
-
structuredContent:
|
|
383
|
+
structuredContent: { model_used: m, response }
|
|
217
384
|
};
|
|
218
385
|
} catch (error) {
|
|
219
386
|
lastError = error;
|
|
@@ -230,19 +397,10 @@ ${response}` }],
|
|
|
230
397
|
server.registerTool(
|
|
231
398
|
"blockrun_models",
|
|
232
399
|
{
|
|
233
|
-
description: "List
|
|
400
|
+
description: "List available AI models with pricing. Use to discover models and compare costs.",
|
|
234
401
|
inputSchema: {
|
|
235
402
|
category: z.enum(["all", "chat", "reasoning", "image", "embedding"]).optional().default("all").describe("Filter by category"),
|
|
236
|
-
provider: z.string().optional().describe("Filter by provider (e.g., 'openai', 'anthropic'
|
|
237
|
-
},
|
|
238
|
-
outputSchema: {
|
|
239
|
-
count: z.number().describe("Number of models returned"),
|
|
240
|
-
models: z.array(z.object({
|
|
241
|
-
id: z.string(),
|
|
242
|
-
name: z.string().optional(),
|
|
243
|
-
inputPrice: z.number().optional(),
|
|
244
|
-
outputPrice: z.number().optional()
|
|
245
|
-
})).describe("List of available models")
|
|
403
|
+
provider: z.string().optional().describe("Filter by provider (e.g., 'openai', 'anthropic')")
|
|
246
404
|
}
|
|
247
405
|
},
|
|
248
406
|
async ({ category, provider }) => {
|
|
@@ -260,13 +418,9 @@ server.registerTool(
|
|
|
260
418
|
}
|
|
261
419
|
if (category && category !== "all") {
|
|
262
420
|
if (category === "image") {
|
|
263
|
-
models = models.filter(
|
|
264
|
-
(m) => m.id.includes("dall-e") || m.id.includes("flux") || m.id.includes("banana")
|
|
265
|
-
);
|
|
421
|
+
models = models.filter((m) => m.id.includes("dall-e") || m.id.includes("flux") || m.id.includes("banana"));
|
|
266
422
|
} else if (category === "reasoning") {
|
|
267
|
-
models = models.filter(
|
|
268
|
-
(m) => m.id.includes("/o1") || m.id.includes("/o3") || m.id.includes("reasoner")
|
|
269
|
-
);
|
|
423
|
+
models = models.filter((m) => m.id.includes("/o1") || m.id.includes("/o3") || m.id.includes("reasoner"));
|
|
270
424
|
} else if (category === "embedding") {
|
|
271
425
|
models = models.filter((m) => m.id.includes("embed"));
|
|
272
426
|
}
|
|
@@ -275,189 +429,118 @@ server.registerTool(
|
|
|
275
429
|
const input = m.inputPrice ? `$${m.inputPrice}/M in` : "";
|
|
276
430
|
const output = m.outputPrice ? `$${m.outputPrice}/M out` : "";
|
|
277
431
|
const pricing = [input, output].filter(Boolean).join(", ");
|
|
278
|
-
return `- ${m.id}
|
|
432
|
+
return `- ${m.id}${pricing ? ` (${pricing})` : ""}`;
|
|
279
433
|
});
|
|
280
|
-
const structuredModels = models.map((m) => ({
|
|
281
|
-
id: m.id,
|
|
282
|
-
name: m.name,
|
|
283
|
-
inputPrice: m.inputPrice,
|
|
284
|
-
outputPrice: m.outputPrice
|
|
285
|
-
}));
|
|
286
434
|
return {
|
|
287
|
-
content: [{ type: "text", text: `
|
|
288
|
-
|
|
435
|
+
content: [{ type: "text", text: `Models (${models.length}):
|
|
289
436
|
${lines.join("\n")}` }],
|
|
290
|
-
structuredContent: { count: models.length, models
|
|
437
|
+
structuredContent: { count: models.length, models }
|
|
291
438
|
};
|
|
292
439
|
}
|
|
293
440
|
);
|
|
294
441
|
server.registerTool(
|
|
295
442
|
"blockrun_image",
|
|
296
443
|
{
|
|
297
|
-
description: `Generate images
|
|
298
|
-
|
|
299
|
-
Models:
|
|
300
|
-
- openai/dall-e-3: High quality, creative ($0.04-0.08/image)
|
|
301
|
-
- together/flux-schnell: Fast generation ($0.02/image)
|
|
302
|
-
- google/nano-banana: Experimental Google model`,
|
|
444
|
+
description: `Generate images. Models: openai/dall-e-3 ($0.04-0.08), together/flux-schnell ($0.02), google/nano-banana`,
|
|
303
445
|
inputSchema: {
|
|
304
|
-
prompt: z.string().describe("
|
|
305
|
-
model: z.enum(["openai/dall-e-3", "together/flux-schnell", "google/nano-banana"]).optional().default("openai/dall-e-3")
|
|
306
|
-
size: z.enum(["1024x1024", "1792x1024", "1024x1792"]).optional().default("1024x1024")
|
|
307
|
-
quality: z.enum(["standard", "hd"]).optional().default("standard")
|
|
308
|
-
},
|
|
309
|
-
outputSchema: {
|
|
310
|
-
url: z.string().describe("URL of the generated image"),
|
|
311
|
-
prompt: z.string().describe("The prompt used"),
|
|
312
|
-
model: z.string().describe("The model used")
|
|
446
|
+
prompt: z.string().describe("Image description"),
|
|
447
|
+
model: z.enum(["openai/dall-e-3", "together/flux-schnell", "google/nano-banana"]).optional().default("openai/dall-e-3"),
|
|
448
|
+
size: z.enum(["1024x1024", "1792x1024", "1024x1792"]).optional().default("1024x1024"),
|
|
449
|
+
quality: z.enum(["standard", "hd"]).optional().default("standard")
|
|
313
450
|
}
|
|
314
451
|
},
|
|
315
452
|
async ({ prompt, model, size, quality }) => {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
return {
|
|
331
|
-
content: [{ type: "text", text: `Image generation requires payment. Please ensure your wallet has USDC on Base.
|
|
332
|
-
|
|
333
|
-
To generate "${prompt}" with ${model}, the approximate cost is $0.04-0.08 per image.` }],
|
|
334
|
-
isError: true
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
if (!response.ok) {
|
|
453
|
+
try {
|
|
454
|
+
const imgClient = getImageClient();
|
|
455
|
+
const response = await imgClient.generate(prompt, {
|
|
456
|
+
model,
|
|
457
|
+
size,
|
|
458
|
+
quality
|
|
459
|
+
});
|
|
460
|
+
const imageUrl = response.data?.[0]?.url;
|
|
461
|
+
if (!imageUrl) {
|
|
462
|
+
return {
|
|
463
|
+
content: [{ type: "text", text: formatError("No image URL in response") }],
|
|
464
|
+
isError: true
|
|
465
|
+
};
|
|
466
|
+
}
|
|
338
467
|
return {
|
|
339
|
-
content: [{ type: "text", text:
|
|
340
|
-
|
|
468
|
+
content: [{ type: "text", text: `Image: ${imageUrl}
|
|
469
|
+
Prompt: ${prompt}
|
|
470
|
+
Model: ${model}` }],
|
|
471
|
+
structuredContent: { url: imageUrl, prompt, model }
|
|
341
472
|
};
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
473
|
+
} catch (err) {
|
|
474
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
475
|
+
if (errMsg.includes("balance") || errMsg.includes("payment") || errMsg.includes("402")) {
|
|
476
|
+
return {
|
|
477
|
+
content: [{ type: "text", text: `Image generation requires payment. Run blockrun_wallet with action: "setup" for funding instructions.
|
|
478
|
+
Error: ${errMsg}` }],
|
|
479
|
+
isError: true
|
|
480
|
+
};
|
|
481
|
+
}
|
|
346
482
|
return {
|
|
347
|
-
content: [{ type: "text", text: formatError(
|
|
483
|
+
content: [{ type: "text", text: formatError(`Image generation failed: ${errMsg}`) }],
|
|
348
484
|
isError: true
|
|
349
485
|
};
|
|
350
486
|
}
|
|
351
|
-
return {
|
|
352
|
-
content: [{ type: "text", text: `Image generated successfully!
|
|
353
|
-
|
|
354
|
-
URL: ${imageUrl}
|
|
355
|
-
|
|
356
|
-
Prompt: ${prompt}
|
|
357
|
-
Model: ${model}` }],
|
|
358
|
-
structuredContent: { url: imageUrl, prompt, model }
|
|
359
|
-
};
|
|
360
487
|
}
|
|
361
488
|
);
|
|
362
489
|
server.registerTool(
|
|
363
|
-
"
|
|
490
|
+
"blockrun_twitter",
|
|
364
491
|
{
|
|
365
|
-
description:
|
|
366
|
-
inputSchema: {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
network: z.string().describe("Network name"),
|
|
370
|
-
chainId: z.number().describe("Chain ID"),
|
|
371
|
-
currency: z.string().describe("Currency"),
|
|
372
|
-
isNew: z.boolean().describe("Whether this is a newly created wallet"),
|
|
373
|
-
basescanUrl: z.string().describe("Link to view on Basescan")
|
|
492
|
+
description: `Search real-time X/Twitter via Grok. Use for trending topics, @handles, breaking news.`,
|
|
493
|
+
inputSchema: {
|
|
494
|
+
query: z.string().describe("Search query (can include @handles, topics)"),
|
|
495
|
+
max_results: z.number().optional().default(10).describe("Max results (1-25)")
|
|
374
496
|
}
|
|
375
497
|
},
|
|
376
|
-
async () => {
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
Network: ${info.network} (Chain ID: ${info.chainId})
|
|
384
|
-
Currency: ${info.currency}
|
|
385
|
-
|
|
386
|
-
View on Basescan: ${info.basescanUrl}
|
|
387
|
-
`;
|
|
388
|
-
if (isNewWallet) {
|
|
389
|
-
text += `
|
|
390
|
-
STATUS: NEW WALLET - NEEDS FUNDING
|
|
391
|
-
${getWalletSetupInstructions()}`;
|
|
392
|
-
} else {
|
|
393
|
-
text += `
|
|
394
|
-
HOW TO ADD FUNDS:
|
|
395
|
-
-----------------
|
|
396
|
-
Send USDC to the address above on Base network.
|
|
397
|
-
|
|
398
|
-
Quick options:
|
|
399
|
-
1. From Coinbase: ${info.fundingOptions.coinbase}
|
|
400
|
-
2. Bridge: ${info.fundingOptions.bridge}
|
|
401
|
-
3. Buy: ${info.fundingOptions.buy}
|
|
402
|
-
|
|
403
|
-
Full instructions: Run blockrun_setup tool
|
|
404
|
-
`;
|
|
498
|
+
async ({ query, max_results }) => {
|
|
499
|
+
const budget = checkBudget();
|
|
500
|
+
if (!budget.allowed) {
|
|
501
|
+
return {
|
|
502
|
+
content: [{ type: "text", text: `Budget limit reached ($${sessionBudget.spent.toFixed(4)} of $${sessionBudget.limit?.toFixed(2)}). Use blockrun_wallet with action: "budget" to adjust.` }],
|
|
503
|
+
isError: true
|
|
504
|
+
};
|
|
405
505
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
);
|
|
419
|
-
server.registerTool(
|
|
420
|
-
"blockrun_setup",
|
|
421
|
-
{
|
|
422
|
-
description: `Get detailed wallet setup and funding instructions. Use this for first-time setup or if you need help adding funds to your wallet.
|
|
506
|
+
try {
|
|
507
|
+
const llm = getClient();
|
|
508
|
+
const response = await llm.chat("xai/grok-3", query, {
|
|
509
|
+
system: `Real-time X/Twitter search. Focus on recent posts, key accounts, engagement. Max results: ${max_results}`,
|
|
510
|
+
search: true
|
|
511
|
+
});
|
|
512
|
+
recordSpending(2e-3);
|
|
513
|
+
return {
|
|
514
|
+
content: [{ type: "text", text: `[X/Twitter via Grok]
|
|
423
515
|
|
|
424
|
-
|
|
425
|
-
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
516
|
+
${response}` }],
|
|
517
|
+
structuredContent: { query, model: "xai/grok-3", response }
|
|
518
|
+
};
|
|
519
|
+
} catch (error) {
|
|
520
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
521
|
+
return {
|
|
522
|
+
content: [{ type: "text", text: formatError(errorMessage) }],
|
|
523
|
+
isError: true
|
|
524
|
+
};
|
|
525
|
+
}
|
|
434
526
|
}
|
|
435
527
|
);
|
|
436
528
|
server.registerResource(
|
|
437
529
|
"wallet",
|
|
438
530
|
"blockrun://wallet",
|
|
439
|
-
{
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
uri: "blockrun://wallet",
|
|
448
|
-
mimeType: "application/json",
|
|
449
|
-
text: JSON.stringify(info, null, 2)
|
|
450
|
-
}]
|
|
451
|
-
};
|
|
452
|
-
}
|
|
531
|
+
{ description: "Wallet address and status", mimeType: "application/json" },
|
|
532
|
+
async () => ({
|
|
533
|
+
contents: [{
|
|
534
|
+
uri: "blockrun://wallet",
|
|
535
|
+
mimeType: "application/json",
|
|
536
|
+
text: JSON.stringify(getWalletInfo(), null, 2)
|
|
537
|
+
}]
|
|
538
|
+
})
|
|
453
539
|
);
|
|
454
540
|
server.registerResource(
|
|
455
541
|
"models",
|
|
456
542
|
"blockrun://models",
|
|
457
|
-
{
|
|
458
|
-
description: "List of all available AI models with pricing",
|
|
459
|
-
mimeType: "application/json"
|
|
460
|
-
},
|
|
543
|
+
{ description: "Available AI models with pricing", mimeType: "application/json" },
|
|
461
544
|
async () => {
|
|
462
545
|
const llm = getClient();
|
|
463
546
|
if (!cachedModels) {
|
|
@@ -475,87 +558,10 @@ server.registerResource(
|
|
|
475
558
|
};
|
|
476
559
|
}
|
|
477
560
|
);
|
|
478
|
-
server.registerPrompt(
|
|
479
|
-
"quick_chat",
|
|
480
|
-
{
|
|
481
|
-
description: "Start a quick chat with a recommended model",
|
|
482
|
-
argsSchema: {
|
|
483
|
-
message: z.string().describe("Your message"),
|
|
484
|
-
style: z.enum(["concise", "detailed", "creative"]).optional().default("concise").describe("Response style")
|
|
485
|
-
}
|
|
486
|
-
},
|
|
487
|
-
async ({ message, style }) => {
|
|
488
|
-
const systemPrompts = {
|
|
489
|
-
concise: "Be concise and direct. Give short, focused answers.",
|
|
490
|
-
detailed: "Provide thorough, comprehensive answers with examples.",
|
|
491
|
-
creative: "Be creative and imaginative in your responses."
|
|
492
|
-
};
|
|
493
|
-
return {
|
|
494
|
-
messages: [
|
|
495
|
-
{
|
|
496
|
-
role: "user",
|
|
497
|
-
content: {
|
|
498
|
-
type: "text",
|
|
499
|
-
text: `[System: ${systemPrompts[style || "concise"]}]
|
|
500
|
-
|
|
501
|
-
${message}`
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
]
|
|
505
|
-
};
|
|
506
|
-
}
|
|
507
|
-
);
|
|
508
|
-
server.registerPrompt(
|
|
509
|
-
"code_review",
|
|
510
|
-
{
|
|
511
|
-
description: "Get a code review from a powerful model",
|
|
512
|
-
argsSchema: {
|
|
513
|
-
code: z.string().describe("The code to review"),
|
|
514
|
-
language: z.string().optional().describe("Programming language"),
|
|
515
|
-
focus: z.enum(["bugs", "performance", "style", "all"]).optional().default("all").describe("What to focus on")
|
|
516
|
-
}
|
|
517
|
-
},
|
|
518
|
-
async ({ code, language, focus }) => {
|
|
519
|
-
const focusInstructions = {
|
|
520
|
-
bugs: "Focus on potential bugs, errors, and edge cases.",
|
|
521
|
-
performance: "Focus on performance issues and optimization opportunities.",
|
|
522
|
-
style: "Focus on code style, readability, and best practices.",
|
|
523
|
-
all: "Review for bugs, performance, and style."
|
|
524
|
-
};
|
|
525
|
-
return {
|
|
526
|
-
messages: [
|
|
527
|
-
{
|
|
528
|
-
role: "user",
|
|
529
|
-
content: {
|
|
530
|
-
type: "text",
|
|
531
|
-
text: `Please review this ${language || ""} code. ${focusInstructions[focus || "all"]}
|
|
532
|
-
|
|
533
|
-
\`\`\`${language || ""}
|
|
534
|
-
${code}
|
|
535
|
-
\`\`\``
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
]
|
|
539
|
-
};
|
|
540
|
-
}
|
|
541
|
-
);
|
|
542
|
-
function formatError(message) {
|
|
543
|
-
const isPaymentError = message.toLowerCase().includes("payment") || message.toLowerCase().includes("402") || message.toLowerCase().includes("balance") || message.toLowerCase().includes("insufficient");
|
|
544
|
-
let errorText = `Error: ${message}`;
|
|
545
|
-
if (isPaymentError) {
|
|
546
|
-
errorText += `
|
|
547
|
-
|
|
548
|
-
This error usually means your wallet needs funding.
|
|
549
|
-
Run the blockrun_setup tool to get your wallet address and funding instructions.
|
|
550
|
-
|
|
551
|
-
Quick fix: Send USDC to your wallet on Base network.`;
|
|
552
|
-
}
|
|
553
|
-
return errorText;
|
|
554
|
-
}
|
|
555
561
|
async function main() {
|
|
556
562
|
const transport = new StdioServerTransport();
|
|
557
563
|
await server.connect(transport);
|
|
558
|
-
console.error("BlockRun MCP Server started (v0.
|
|
564
|
+
console.error("BlockRun MCP Server started (v0.4.0)");
|
|
559
565
|
}
|
|
560
566
|
main().catch((error) => {
|
|
561
567
|
console.error("Fatal error:", error);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockrun/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"mcpName": "io.github.BlockRunAI/blockrun-mcp",
|
|
5
5
|
"description": "BlockRun MCP Server - Access 30+ AI models via x402 micropayments. No API keys needed.",
|
|
6
6
|
"type": "module",
|
|
@@ -44,13 +44,17 @@
|
|
|
44
44
|
"url": "https://github.com/blockrunai/blockrun-mcp/issues"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@blockrun/llm": "^0.
|
|
47
|
+
"@blockrun/llm": "^0.2.0",
|
|
48
48
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
49
|
+
"jimp": "^1.6.0",
|
|
50
|
+
"open": "^11.0.0",
|
|
51
|
+
"qrcode": "^1.5.4",
|
|
49
52
|
"viem": "^2.21.0",
|
|
50
53
|
"zod": "^4.3.5"
|
|
51
54
|
},
|
|
52
55
|
"devDependencies": {
|
|
53
56
|
"@types/node": "^20.0.0",
|
|
57
|
+
"@types/qrcode": "^1.5.6",
|
|
54
58
|
"tsup": "^8.0.0",
|
|
55
59
|
"tsx": "^4.0.0",
|
|
56
60
|
"typescript": "^5.0.0"
|