@blockrun/mcp 0.4.1 → 0.5.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 +219 -273
- package/dist/chunk-NHJMKXNS.js +961 -0
- package/dist/http-server.d.ts +20 -0
- package/dist/http-server.js +59 -0
- package/dist/index.js +19 -557
- package/package.json +4 -4
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* BlockRun MCP Server — HTTP Transport
|
|
4
|
+
*
|
|
5
|
+
* Runs as an HTTP server for:
|
|
6
|
+
* - Claude.ai connectors (claudeai-proxy)
|
|
7
|
+
* - Claude Code Remote (CCR) cloud sessions
|
|
8
|
+
* - Web-based agents
|
|
9
|
+
* - Any MCP client supporting HTTP transport
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* npx @blockrun/mcp --http # Start HTTP server on port 3402
|
|
13
|
+
* npx @blockrun/mcp --http --port 8080 # Custom port
|
|
14
|
+
*
|
|
15
|
+
* The server implements the MCP Streamable HTTP transport specification,
|
|
16
|
+
* supporting both SSE streaming and direct HTTP responses.
|
|
17
|
+
*/
|
|
18
|
+
declare function startHttpServer(port?: number): Promise<void>;
|
|
19
|
+
|
|
20
|
+
export { startHttpServer };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
initializeMcpServer
|
|
4
|
+
} from "./chunk-NHJMKXNS.js";
|
|
5
|
+
|
|
6
|
+
// src/http-server.ts
|
|
7
|
+
import { createServer } from "http";
|
|
8
|
+
import { randomUUID } from "crypto";
|
|
9
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
11
|
+
var DEFAULT_PORT = 3402;
|
|
12
|
+
async function startHttpServer(port = DEFAULT_PORT) {
|
|
13
|
+
const server = new McpServer({
|
|
14
|
+
name: "blockrun-mcp",
|
|
15
|
+
version: "0.5.1"
|
|
16
|
+
});
|
|
17
|
+
initializeMcpServer(server);
|
|
18
|
+
const transport = new StreamableHTTPServerTransport({
|
|
19
|
+
sessionIdGenerator: () => randomUUID()
|
|
20
|
+
});
|
|
21
|
+
await server.connect(transport);
|
|
22
|
+
const httpServer = createServer((req, res) => {
|
|
23
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
24
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
25
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, mcp-session-id, Authorization");
|
|
26
|
+
res.setHeader("Access-Control-Expose-Headers", "mcp-session-id");
|
|
27
|
+
if (req.method === "OPTIONS") {
|
|
28
|
+
res.writeHead(204);
|
|
29
|
+
res.end();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (req.method === "GET" && req.url === "/health") {
|
|
33
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
34
|
+
res.end(JSON.stringify({ status: "ok", server: "blockrun-mcp", version: "0.5.1", transport: "http" }));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (req.url === "/mcp" || req.url === "/") {
|
|
38
|
+
transport.handleRequest(req, res);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
res.writeHead(404);
|
|
42
|
+
res.end("Not found");
|
|
43
|
+
});
|
|
44
|
+
httpServer.listen(port, () => {
|
|
45
|
+
console.error(`BlockRun MCP Server (HTTP) listening on http://localhost:${port}`);
|
|
46
|
+
console.error(`MCP endpoint: http://localhost:${port}/mcp`);
|
|
47
|
+
console.error(`Health check: http://localhost:${port}/health`);
|
|
48
|
+
});
|
|
49
|
+
const shutdown = () => {
|
|
50
|
+
console.error("Shutting down...");
|
|
51
|
+
httpServer.close();
|
|
52
|
+
process.exit(0);
|
|
53
|
+
};
|
|
54
|
+
process.on("SIGINT", shutdown);
|
|
55
|
+
process.on("SIGTERM", shutdown);
|
|
56
|
+
}
|
|
57
|
+
export {
|
|
58
|
+
startHttpServer
|
|
59
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -1,567 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
initializeMcpServer
|
|
4
|
+
} from "./chunk-NHJMKXNS.js";
|
|
2
5
|
|
|
3
6
|
// src/index.ts
|
|
4
7
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
8
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
var USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
19
|
-
var BASE_CHAIN_ID = "8453";
|
|
20
|
-
var MODEL_TIERS = {
|
|
21
|
-
fast: ["google/gemini-2.5-flash", "openai/gpt-4o-mini", "deepseek/deepseek-chat"],
|
|
22
|
-
balanced: ["openai/gpt-4o", "anthropic/claude-sonnet-4", "google/gemini-2.5-pro"],
|
|
23
|
-
powerful: ["openai/gpt-5.2", "anthropic/claude-opus-4", "openai/o3"],
|
|
24
|
-
cheap: ["google/gemini-2.5-flash", "deepseek/deepseek-chat", "openai/gpt-4o-mini"],
|
|
25
|
-
reasoning: ["openai/o3", "openai/o1", "deepseek/deepseek-reasoner"]
|
|
26
|
-
};
|
|
27
|
-
var walletWasCreated = false;
|
|
28
|
-
var walletAddress = null;
|
|
29
|
-
var client = null;
|
|
30
|
-
var imageClient = null;
|
|
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
|
-
];
|
|
38
|
-
function getOrCreateWalletKey() {
|
|
39
|
-
const envKey = process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY;
|
|
40
|
-
if (envKey) {
|
|
41
|
-
const account2 = privateKeyToAccount(envKey);
|
|
42
|
-
walletAddress = account2.address;
|
|
43
|
-
return envKey;
|
|
44
|
-
}
|
|
45
|
-
if (fs.existsSync(WALLET_FILE)) {
|
|
46
|
-
try {
|
|
47
|
-
const savedKey = fs.readFileSync(WALLET_FILE, "utf-8").trim();
|
|
48
|
-
if (savedKey.startsWith("0x") && savedKey.length === 66) {
|
|
49
|
-
const account2 = privateKeyToAccount(savedKey);
|
|
50
|
-
walletAddress = account2.address;
|
|
51
|
-
return savedKey;
|
|
52
|
-
}
|
|
53
|
-
} catch {
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
const newKey = generatePrivateKey();
|
|
57
|
-
const account = privateKeyToAccount(newKey);
|
|
58
|
-
walletAddress = account.address;
|
|
59
|
-
walletWasCreated = true;
|
|
60
|
-
try {
|
|
61
|
-
if (!fs.existsSync(WALLET_DIR)) {
|
|
62
|
-
fs.mkdirSync(WALLET_DIR, { recursive: true, mode: 448 });
|
|
63
|
-
}
|
|
64
|
-
fs.writeFileSync(WALLET_FILE, newKey, { mode: 384 });
|
|
65
|
-
console.error(`[BlockRun] New wallet created and saved to ${WALLET_FILE}`);
|
|
66
|
-
} catch (err) {
|
|
67
|
-
console.error(`[BlockRun] Warning: Could not save wallet to file: ${err}`);
|
|
68
|
-
}
|
|
69
|
-
return newKey;
|
|
70
|
-
}
|
|
71
|
-
function getClient() {
|
|
72
|
-
if (!client) {
|
|
73
|
-
const privateKey = getOrCreateWalletKey();
|
|
74
|
-
client = new LLMClient({ privateKey });
|
|
75
|
-
}
|
|
76
|
-
return client;
|
|
77
|
-
}
|
|
78
|
-
function getImageClient() {
|
|
79
|
-
if (!imageClient) {
|
|
80
|
-
const privateKey = getOrCreateWalletKey();
|
|
81
|
-
imageClient = new ImageClient({ privateKey });
|
|
82
|
-
}
|
|
83
|
-
return imageClient;
|
|
84
|
-
}
|
|
85
|
-
function getWalletInfo() {
|
|
86
|
-
const llm = getClient();
|
|
87
|
-
const address = llm.getWalletAddress();
|
|
88
|
-
return {
|
|
89
|
-
address,
|
|
90
|
-
network: "Base",
|
|
91
|
-
chainId: 8453,
|
|
92
|
-
currency: "USDC",
|
|
93
|
-
isNew: walletWasCreated,
|
|
94
|
-
basescanUrl: `https://basescan.org/address/${address}`
|
|
95
|
-
};
|
|
96
|
-
}
|
|
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 });
|
|
150
|
-
}
|
|
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 = `
|
|
267
|
-
================================================================================
|
|
268
|
-
BLOCKRUN WALLET SETUP
|
|
269
|
-
================================================================================
|
|
270
|
-
|
|
271
|
-
Your wallet address: ${address}
|
|
272
|
-
${qrMessage}
|
|
273
|
-
|
|
274
|
-
HOW TO FUND YOUR WALLET:
|
|
275
|
-
------------------------
|
|
276
|
-
|
|
277
|
-
Option 1: Transfer from Coinbase
|
|
278
|
-
1. Open Coinbase app or website
|
|
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
|
|
283
|
-
|
|
284
|
-
Option 2: Bridge from other chains
|
|
285
|
-
https://bridge.base.org -> Bridge USDC to Base -> Send to address above
|
|
286
|
-
|
|
287
|
-
Option 3: Buy directly
|
|
288
|
-
https://www.coinbase.com/onramp -> Buy USDC on Base -> Send to address above
|
|
289
|
-
|
|
290
|
-
VERIFY BALANCE: https://basescan.org/address/${address}
|
|
291
|
-
|
|
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
|
|
295
|
-
|
|
296
|
-
SECURITY: Private key stored at ~/.blockrun/.session (never leaves your machine)
|
|
297
|
-
================================================================================`;
|
|
298
|
-
return { content: [{ type: "text", text: text2 }] };
|
|
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
|
-
};
|
|
318
|
-
}
|
|
319
|
-
);
|
|
320
|
-
server.registerTool(
|
|
321
|
-
"blockrun_chat",
|
|
322
|
-
{
|
|
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)
|
|
334
|
-
|
|
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)
|
|
341
|
-
|
|
342
|
-
Use blockrun_models to see all available models with pricing.`,
|
|
343
|
-
inputSchema: {
|
|
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)"),
|
|
347
|
-
system: z.string().optional().describe("Optional system prompt"),
|
|
348
|
-
max_tokens: z.number().optional().default(1024).describe("Max tokens in response"),
|
|
349
|
-
temperature: z.number().optional().default(1).describe("Creativity 0-2")
|
|
350
|
-
}
|
|
351
|
-
},
|
|
352
|
-
async ({ message, model, mode, system, max_tokens, temperature }) => {
|
|
353
|
-
const llm = getClient();
|
|
354
|
-
if (model) {
|
|
355
|
-
try {
|
|
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, {
|
|
376
|
-
system,
|
|
377
|
-
maxTokens: max_tokens
|
|
378
|
-
});
|
|
379
|
-
return {
|
|
380
|
-
content: [{ type: "text", text: `[${m}]
|
|
381
|
-
|
|
382
|
-
${response}` }],
|
|
383
|
-
structuredContent: { model_used: m, response }
|
|
384
|
-
};
|
|
385
|
-
} catch (error) {
|
|
386
|
-
lastError = error;
|
|
387
|
-
continue;
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
const errorMessage = lastError?.message || "All models failed";
|
|
391
|
-
return {
|
|
392
|
-
content: [{ type: "text", text: formatError(errorMessage) }],
|
|
393
|
-
isError: true
|
|
394
|
-
};
|
|
395
|
-
}
|
|
396
|
-
);
|
|
397
|
-
server.registerTool(
|
|
398
|
-
"blockrun_models",
|
|
399
|
-
{
|
|
400
|
-
description: "List available AI models with pricing. Use to discover models and compare costs.",
|
|
401
|
-
inputSchema: {
|
|
402
|
-
category: z.enum(["all", "chat", "reasoning", "image", "embedding"]).optional().default("all").describe("Filter by category"),
|
|
403
|
-
provider: z.string().optional().describe("Filter by provider (e.g., 'openai', 'anthropic')")
|
|
404
|
-
}
|
|
405
|
-
},
|
|
406
|
-
async ({ category, provider }) => {
|
|
407
|
-
const llm = getClient();
|
|
408
|
-
if (!cachedModels) {
|
|
409
|
-
cachedModels = await llm.listModels();
|
|
410
|
-
setTimeout(() => {
|
|
411
|
-
cachedModels = null;
|
|
412
|
-
}, 5 * 60 * 1e3);
|
|
413
|
-
}
|
|
414
|
-
let models = cachedModels;
|
|
415
|
-
if (provider) {
|
|
416
|
-
const p = provider.toLowerCase();
|
|
417
|
-
models = models.filter((m) => m.id.toLowerCase().startsWith(p + "/"));
|
|
418
|
-
}
|
|
419
|
-
if (category && category !== "all") {
|
|
420
|
-
if (category === "image") {
|
|
421
|
-
models = models.filter((m) => m.id.includes("dall-e") || m.id.includes("flux") || m.id.includes("banana"));
|
|
422
|
-
} else if (category === "reasoning") {
|
|
423
|
-
models = models.filter((m) => m.id.includes("/o1") || m.id.includes("/o3") || m.id.includes("reasoner"));
|
|
424
|
-
} else if (category === "embedding") {
|
|
425
|
-
models = models.filter((m) => m.id.includes("embed"));
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
const lines = models.map((m) => {
|
|
429
|
-
const input = m.inputPrice ? `$${m.inputPrice}/M in` : "";
|
|
430
|
-
const output = m.outputPrice ? `$${m.outputPrice}/M out` : "";
|
|
431
|
-
const pricing = [input, output].filter(Boolean).join(", ");
|
|
432
|
-
return `- ${m.id}${pricing ? ` (${pricing})` : ""}`;
|
|
9
|
+
var args = process.argv.slice(2);
|
|
10
|
+
var isHttp = args.includes("--http");
|
|
11
|
+
var portIdx = args.indexOf("--port");
|
|
12
|
+
var port = portIdx !== -1 ? parseInt(args[portIdx + 1], 10) : 3402;
|
|
13
|
+
async function main() {
|
|
14
|
+
if (isHttp) {
|
|
15
|
+
const { startHttpServer } = await import("./http-server.js");
|
|
16
|
+
await startHttpServer(port);
|
|
17
|
+
} else {
|
|
18
|
+
const server = new McpServer({
|
|
19
|
+
name: "blockrun-mcp",
|
|
20
|
+
version: "0.5.1"
|
|
433
21
|
});
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
);
|
|
441
|
-
server.registerTool(
|
|
442
|
-
"blockrun_image",
|
|
443
|
-
{
|
|
444
|
-
description: `Generate images. Models: openai/dall-e-3 ($0.04-0.08), together/flux-schnell ($0.02), google/nano-banana`,
|
|
445
|
-
inputSchema: {
|
|
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")
|
|
450
|
-
}
|
|
451
|
-
},
|
|
452
|
-
async ({ prompt, model, size, quality }) => {
|
|
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
|
-
}
|
|
467
|
-
return {
|
|
468
|
-
content: [{ type: "text", text: `Image: ${imageUrl}
|
|
469
|
-
Prompt: ${prompt}
|
|
470
|
-
Model: ${model}` }],
|
|
471
|
-
structuredContent: { url: imageUrl, prompt, model }
|
|
472
|
-
};
|
|
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
|
-
}
|
|
482
|
-
return {
|
|
483
|
-
content: [{ type: "text", text: formatError(`Image generation failed: ${errMsg}`) }],
|
|
484
|
-
isError: true
|
|
485
|
-
};
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
);
|
|
489
|
-
server.registerTool(
|
|
490
|
-
"blockrun_twitter",
|
|
491
|
-
{
|
|
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)")
|
|
496
|
-
}
|
|
497
|
-
},
|
|
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
|
-
};
|
|
505
|
-
}
|
|
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]
|
|
515
|
-
|
|
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
|
-
}
|
|
526
|
-
}
|
|
527
|
-
);
|
|
528
|
-
server.registerResource(
|
|
529
|
-
"wallet",
|
|
530
|
-
"blockrun://wallet",
|
|
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
|
-
})
|
|
539
|
-
);
|
|
540
|
-
server.registerResource(
|
|
541
|
-
"models",
|
|
542
|
-
"blockrun://models",
|
|
543
|
-
{ description: "Available AI models with pricing", mimeType: "application/json" },
|
|
544
|
-
async () => {
|
|
545
|
-
const llm = getClient();
|
|
546
|
-
if (!cachedModels) {
|
|
547
|
-
cachedModels = await llm.listModels();
|
|
548
|
-
setTimeout(() => {
|
|
549
|
-
cachedModels = null;
|
|
550
|
-
}, 5 * 60 * 1e3);
|
|
551
|
-
}
|
|
552
|
-
return {
|
|
553
|
-
contents: [{
|
|
554
|
-
uri: "blockrun://models",
|
|
555
|
-
mimeType: "application/json",
|
|
556
|
-
text: JSON.stringify(cachedModels, null, 2)
|
|
557
|
-
}]
|
|
558
|
-
};
|
|
22
|
+
initializeMcpServer(server);
|
|
23
|
+
const transport = new StdioServerTransport();
|
|
24
|
+
await server.connect(transport);
|
|
25
|
+
console.error("BlockRun MCP Server started (v0.5.1) \u2014 stdio transport");
|
|
559
26
|
}
|
|
560
|
-
);
|
|
561
|
-
async function main() {
|
|
562
|
-
const transport = new StdioServerTransport();
|
|
563
|
-
await server.connect(transport);
|
|
564
|
-
console.error("BlockRun MCP Server started (v0.4.0)");
|
|
565
27
|
}
|
|
566
28
|
main().catch((error) => {
|
|
567
29
|
console.error("Fatal error:", error);
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockrun/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"mcpName": "io.github.BlockRunAI/blockrun-mcp",
|
|
5
|
-
"description": "BlockRun MCP Server - Access
|
|
5
|
+
"description": "BlockRun MCP Server - Access 41+ AI models + on-chain data via x402 micropayments. Supports stdio and HTTP transport.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"types": "dist/index.d.ts",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"README.md"
|
|
15
15
|
],
|
|
16
16
|
"scripts": {
|
|
17
|
-
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
17
|
+
"build": "tsup src/index.ts src/http-server.ts --format esm --dts --clean",
|
|
18
18
|
"dev": "tsx watch src/index.ts",
|
|
19
19
|
"start": "node dist/index.js",
|
|
20
20
|
"typecheck": "tsc --noEmit",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"url": "https://github.com/blockrunai/blockrun-mcp/issues"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@blockrun/llm": "^
|
|
47
|
+
"@blockrun/llm": "^1.5.0",
|
|
48
48
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
49
49
|
"jimp": "^1.6.0",
|
|
50
50
|
"open": "^11.0.0",
|