@blockrun/mcp 0.14.0 → 0.14.2
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 +2 -4
- package/dist/index.js +147 -413
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -144,7 +144,6 @@ $5 covers ~5,000 market queries, ~500 Exa searches, ~250 image generations, or ~
|
|
|
144
144
|
| `blockrun_price` | Pyth-backed realtime + OHLC — crypto / FX / commodity (free), 12 stock markets (paid) | free or $0.001/call |
|
|
145
145
|
| `blockrun_markets` | Polymarket (markets, candles, trades, orderbooks, leaderboards, smart-wallet PnL/clusters, UMA oracle), Kalshi, Limitless, Opinion, Predict.Fun, dFlow, Binance Futures, cross-platform match + search | $0.001–0.005/query |
|
|
146
146
|
| `blockrun_surf` | Surf (asksurf.ai) — 84 endpoints: CEX market data, on-chain SQL (13 chains, 80+ ClickHouse tables), 100M+ labeled wallets, Polymarket + Kalshi side-by-side, social mindshare, news, search, Surf-1.5 chat with citations | $0.001–0.02/call |
|
|
147
|
-
| `blockrun_x` | X/Twitter — profiles, tweets, followers, mentions, search (AttentionVC) | per call |
|
|
148
147
|
| `blockrun_exa` | Neural web search (Exa) — research, competitors, papers, URL content | $0.01/query |
|
|
149
148
|
| `blockrun_search` | Grok Live Search — web + news with citations | ~$0.025 per source |
|
|
150
149
|
| `blockrun_dex` | Live DEX prices via DexScreener | free |
|
|
@@ -186,7 +185,7 @@ What kinds of questions can Claude (or any LLM agent) answer once BlockRun MCP i
|
|
|
186
185
|
> *"Generate a poster announcing GPT-5.5 on BlockRun, retro-futuristic, with the headline 'NOW LIVE'."* → `blockrun_image` + the `image-prompting` skill 5-section framework
|
|
187
186
|
|
|
188
187
|
6. **Voice phone-out**
|
|
189
|
-
> *"Call +1-415-555-... and confirm the appointment on Friday at 3pm."* → `blockrun_phone`
|
|
188
|
+
> *"Call +1-415-555-... and confirm the appointment on Friday at 3pm."* → `blockrun_phone` path:`voice/call`, body: `{ to, task, from }` (provision `from` first via `phone/numbers/buy`), then poll `voice/call/{call_id}`
|
|
190
189
|
|
|
191
190
|
7. **Multi-agent research with budget cap**
|
|
192
191
|
> *"Spawn 3 research agents on competing L1 narratives. Cap each at $0.50."* → `blockrun_wallet delegate × 3` → children call `blockrun_chat` + `blockrun_exa` with their `agent_id`
|
|
@@ -203,8 +202,7 @@ What kinds of questions can Claude (or any LLM agent) answer once BlockRun MCP i
|
|
|
203
202
|
| Exa | Sign up, $20/mo minimum | $0.01/call, no subscription |
|
|
204
203
|
| Polymarket | Undocumented, rate-limited | $0.001/call, clean JSON |
|
|
205
204
|
| Surf (asksurf.ai) | Account + monthly plan | $0.001/call, no Surf account, 84 endpoints |
|
|
206
|
-
|
|
|
207
|
-
| Multiple sources | 4 accounts, 4 API keys, 4 billing pages | 1 wallet |
|
|
205
|
+
| Multiple sources | 3 accounts, 3 API keys, 3 billing pages | 1 wallet |
|
|
208
206
|
|
|
209
207
|
One wallet. All sources. No dashboards.
|
|
210
208
|
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,8 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
7
7
|
|
|
8
8
|
// src/utils/wallet.ts
|
|
9
9
|
import fs from "fs";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import os from "os";
|
|
10
12
|
import {
|
|
11
13
|
LLMClient,
|
|
12
14
|
ImageClient,
|
|
@@ -19,12 +21,30 @@ import {
|
|
|
19
21
|
formatNeedsFundingMessage,
|
|
20
22
|
SOLANA_WALLET_FILE_PATH
|
|
21
23
|
} from "@blockrun/llm";
|
|
24
|
+
var BLOCKRUN_DIR = path.join(os.homedir(), ".blockrun");
|
|
25
|
+
var CHAIN_PREFERENCE_FILES = [
|
|
26
|
+
path.join(BLOCKRUN_DIR, ".chain"),
|
|
27
|
+
path.join(BLOCKRUN_DIR, "payment-chain")
|
|
28
|
+
];
|
|
22
29
|
var _evmClient = null;
|
|
23
30
|
var _imageClient = null;
|
|
24
31
|
var _priceClient = null;
|
|
25
32
|
var _evmWalletInfo = null;
|
|
26
33
|
var _solanaClient = null;
|
|
34
|
+
function readChainPreference() {
|
|
35
|
+
for (const file of CHAIN_PREFERENCE_FILES) {
|
|
36
|
+
try {
|
|
37
|
+
if (!fs.existsSync(file)) continue;
|
|
38
|
+
const value = fs.readFileSync(file, "utf-8").trim().toLowerCase();
|
|
39
|
+
if (value === "base" || value === "solana") return value;
|
|
40
|
+
} catch {
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
27
45
|
function getChain() {
|
|
46
|
+
const preferred = readChainPreference();
|
|
47
|
+
if (preferred) return preferred;
|
|
28
48
|
if (process.env.SOLANA_WALLET_KEY) return "solana";
|
|
29
49
|
try {
|
|
30
50
|
if (fs.existsSync(SOLANA_WALLET_FILE_PATH)) return "solana";
|
|
@@ -146,11 +166,11 @@ import * as fs2 from "fs";
|
|
|
146
166
|
import sharp from "sharp";
|
|
147
167
|
|
|
148
168
|
// src/utils/constants.ts
|
|
149
|
-
import * as
|
|
150
|
-
import * as
|
|
151
|
-
var WALLET_DIR =
|
|
152
|
-
var WALLET_FILE =
|
|
153
|
-
var QR_FILE =
|
|
169
|
+
import * as path2 from "path";
|
|
170
|
+
import * as os2 from "os";
|
|
171
|
+
var WALLET_DIR = path2.join(os2.homedir(), ".blockrun");
|
|
172
|
+
var WALLET_FILE = path2.join(WALLET_DIR, ".session");
|
|
173
|
+
var QR_FILE = path2.join(WALLET_DIR, "qr.png");
|
|
154
174
|
var USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
155
175
|
var BASE_CHAIN_ID = "8453";
|
|
156
176
|
var MODEL_TIERS = {
|
|
@@ -222,6 +242,32 @@ async function openQrInViewer(qrPath) {
|
|
|
222
242
|
}
|
|
223
243
|
|
|
224
244
|
// src/utils/errors.ts
|
|
245
|
+
function extractErrorMessage(err) {
|
|
246
|
+
if (!err || typeof err !== "object") return String(err);
|
|
247
|
+
const e = err;
|
|
248
|
+
const base = typeof e.message === "string" ? e.message : String(err);
|
|
249
|
+
if (e.response === void 0 || e.response === null) return base;
|
|
250
|
+
try {
|
|
251
|
+
const body = e.response;
|
|
252
|
+
if (typeof body === "string") return body.trim() ? `${base} \u2014 ${body}` : base;
|
|
253
|
+
if (typeof body === "object") {
|
|
254
|
+
const b = body;
|
|
255
|
+
const parts = [];
|
|
256
|
+
if (typeof b.message === "string") parts.push(b.message);
|
|
257
|
+
if (typeof b.hint === "string") parts.push(`Hint: ${b.hint}`);
|
|
258
|
+
if (Array.isArray(b.missing_params) && b.missing_params.length) {
|
|
259
|
+
parts.push(`Missing: ${b.missing_params.join(", ")}`);
|
|
260
|
+
}
|
|
261
|
+
if (parts.length === 0) {
|
|
262
|
+
parts.push(JSON.stringify(b));
|
|
263
|
+
}
|
|
264
|
+
return `${base}
|
|
265
|
+
${parts.join("\n")}`;
|
|
266
|
+
}
|
|
267
|
+
} catch {
|
|
268
|
+
}
|
|
269
|
+
return base;
|
|
270
|
+
}
|
|
225
271
|
function formatError(message) {
|
|
226
272
|
const msgLower = message.toLowerCase();
|
|
227
273
|
const isPaymentError = msgLower.includes("402") || msgLower.includes("balance") || msgLower.includes("insufficient") || msgLower.includes("payment") && !msgLower.includes("500");
|
|
@@ -1124,39 +1170,31 @@ function registerSearchTool(server) {
|
|
|
1124
1170
|
server.registerTool(
|
|
1125
1171
|
"blockrun_search",
|
|
1126
1172
|
{
|
|
1127
|
-
description: `
|
|
1173
|
+
description: `Grok Live Search \u2014 real-time web + X/Twitter + news with AI-summarized results and citations. ~$0.025 per source.
|
|
1174
|
+
|
|
1175
|
+
Common shape:
|
|
1176
|
+
- body: { query: "...", sources: ["web","x","news"], maxResults: 10, fromDate: "YYYY-MM-DD", toDate: "YYYY-MM-DD" }
|
|
1128
1177
|
|
|
1129
|
-
|
|
1130
|
-
Pricing: ~$0.01/search
|
|
1178
|
+
\`sources\` accepts any subset of ["web","x","news"] (defaults to all three). For tweet-only searches, use ["x"].
|
|
1131
1179
|
|
|
1132
|
-
|
|
1180
|
+
Full request shape + worked examples in the \`search\` skill (\`skills/search/SKILL.md\`).`,
|
|
1133
1181
|
inputSchema: {
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
max_results: z7.number().optional().default(10).describe("Max results per source (1-20)"),
|
|
1137
|
-
from_date: z7.string().optional().describe("Start date filter (YYYY-MM-DD)"),
|
|
1138
|
-
to_date: z7.string().optional().describe("End date filter (YYYY-MM-DD)")
|
|
1182
|
+
path: z7.string().optional().default("").describe("Endpoint sub-path under /v1/search/ (default empty = root /v1/search). Reserved for future surfaces."),
|
|
1183
|
+
body: z7.any().optional().describe("Request body. At minimum { query: '...' }. Sent as POST.")
|
|
1139
1184
|
}
|
|
1140
1185
|
},
|
|
1141
|
-
async ({
|
|
1186
|
+
async ({ path: path4, body }) => {
|
|
1142
1187
|
try {
|
|
1143
|
-
const
|
|
1144
|
-
const
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
fromDate: from_date,
|
|
1148
|
-
toDate: to_date
|
|
1149
|
-
});
|
|
1188
|
+
const client = getClient();
|
|
1189
|
+
const cleanPath = (path4 ?? "").replace(/^\/+/, "").replace(/^v1\/search\/?/, "");
|
|
1190
|
+
const endpoint = cleanPath ? `/v1/search/${cleanPath}` : "/v1/search";
|
|
1191
|
+
const result = await client.requestWithPaymentRaw(endpoint, body ?? {});
|
|
1150
1192
|
return {
|
|
1151
1193
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1152
1194
|
structuredContent: result
|
|
1153
1195
|
};
|
|
1154
1196
|
} catch (err) {
|
|
1155
|
-
|
|
1156
|
-
return {
|
|
1157
|
-
content: [{ type: "text", text: formatError(errMsg) }],
|
|
1158
|
-
isError: true
|
|
1159
|
-
};
|
|
1197
|
+
return { content: [{ type: "text", text: formatError(extractErrorMessage(err)) }], isError: true };
|
|
1160
1198
|
}
|
|
1161
1199
|
}
|
|
1162
1200
|
);
|
|
@@ -1168,68 +1206,34 @@ function registerExaTool(server) {
|
|
|
1168
1206
|
server.registerTool(
|
|
1169
1207
|
"blockrun_exa",
|
|
1170
1208
|
{
|
|
1171
|
-
description: `Neural web search via Exa
|
|
1209
|
+
description: `Neural web search via Exa \u2014 understands meaning, not just keywords. Great for research.
|
|
1172
1210
|
|
|
1173
|
-
|
|
1174
|
-
- search:
|
|
1175
|
-
- answer:
|
|
1176
|
-
- contents:
|
|
1177
|
-
- similar
|
|
1211
|
+
Common paths (all POST, body shapes documented in the exa-research skill):
|
|
1212
|
+
- search \u2014 body: { query, numResults?, category?, includeDomains?, excludeDomains? } ($0.01/call)
|
|
1213
|
+
- answer \u2014 body: { query } ($0.01/call)
|
|
1214
|
+
- contents \u2014 body: { urls: [...] } ($0.002/URL, up to 100)
|
|
1215
|
+
- find-similar \u2014 body: { url, numResults? } ($0.01/call)
|
|
1216
|
+
|
|
1217
|
+
Categories for search: "news", "research paper", "company", "tweet", "github", "pdf".
|
|
1218
|
+
|
|
1219
|
+
Full request/response shapes + worked research workflows in the \`exa-research\` skill.`,
|
|
1178
1220
|
inputSchema: {
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
url: z8.string().optional().describe("Reference URL to find similar pages (for similar action)"),
|
|
1182
|
-
urls: z8.array(z8.string()).optional().describe("URLs to fetch content from (for contents action, up to 100)"),
|
|
1183
|
-
num_results: z8.number().optional().describe("Number of results to return (default: 10)"),
|
|
1184
|
-
category: z8.string().optional().describe("Category filter: 'news', 'research paper', 'company', 'tweet', 'github', 'pdf'"),
|
|
1185
|
-
include_domains: z8.array(z8.string()).optional().describe("Only search within these domains"),
|
|
1186
|
-
exclude_domains: z8.array(z8.string()).optional().describe("Exclude these domains from results")
|
|
1221
|
+
path: z8.string().describe("Endpoint name under /v1/exa/, e.g. 'search', 'answer', 'contents', 'find-similar'"),
|
|
1222
|
+
body: z8.any().optional().describe("JSON body for the call. Sent as POST. Required for all four endpoints.")
|
|
1187
1223
|
}
|
|
1188
1224
|
},
|
|
1189
|
-
async ({
|
|
1225
|
+
async ({ path: path4, body }) => {
|
|
1190
1226
|
try {
|
|
1191
|
-
const
|
|
1192
|
-
|
|
1193
|
-
const
|
|
1194
|
-
|
|
1195
|
-
case "search":
|
|
1196
|
-
if (!query) throw new Error("query required for search action");
|
|
1197
|
-
result = await req.requestWithPaymentRaw("/v1/exa/search", {
|
|
1198
|
-
query,
|
|
1199
|
-
numResults: num_results,
|
|
1200
|
-
category,
|
|
1201
|
-
includeDomains: include_domains,
|
|
1202
|
-
excludeDomains: exclude_domains
|
|
1203
|
-
});
|
|
1204
|
-
break;
|
|
1205
|
-
case "answer":
|
|
1206
|
-
if (!query) throw new Error("query required for answer action");
|
|
1207
|
-
result = await req.requestWithPaymentRaw("/v1/exa/answer", { query });
|
|
1208
|
-
break;
|
|
1209
|
-
case "contents":
|
|
1210
|
-
if (!urls?.length) throw new Error("urls array required for contents action");
|
|
1211
|
-
result = await req.requestWithPaymentRaw("/v1/exa/contents", { urls });
|
|
1212
|
-
break;
|
|
1213
|
-
case "similar":
|
|
1214
|
-
if (!url) throw new Error("url required for similar action");
|
|
1215
|
-
result = await req.requestWithPaymentRaw("/v1/exa/find-similar", {
|
|
1216
|
-
url,
|
|
1217
|
-
numResults: num_results
|
|
1218
|
-
});
|
|
1219
|
-
break;
|
|
1220
|
-
default:
|
|
1221
|
-
throw new Error(`Unknown action: ${action}`);
|
|
1222
|
-
}
|
|
1227
|
+
const client = getClient();
|
|
1228
|
+
const cleanPath = path4.replace(/^\/+/, "").replace(/^v1\/exa\//, "");
|
|
1229
|
+
const endpoint = `/v1/exa/${cleanPath}`;
|
|
1230
|
+
const result = await client.requestWithPaymentRaw(endpoint, body ?? {});
|
|
1223
1231
|
return {
|
|
1224
1232
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1225
1233
|
structuredContent: result
|
|
1226
1234
|
};
|
|
1227
1235
|
} catch (err) {
|
|
1228
|
-
|
|
1229
|
-
return {
|
|
1230
|
-
content: [{ type: "text", text: formatError(errMsg) }],
|
|
1231
|
-
isError: true
|
|
1232
|
-
};
|
|
1236
|
+
return { content: [{ type: "text", text: formatError(extractErrorMessage(err)) }], isError: true };
|
|
1233
1237
|
}
|
|
1234
1238
|
}
|
|
1235
1239
|
);
|
|
@@ -1298,10 +1302,10 @@ Pass query params via 'params' (GET). Use 'body' only for POST endpoints (e.g. p
|
|
|
1298
1302
|
body: z9.any().optional().describe("JSON body for POST queries (triggers pmQuery \u2014 most endpoints are GET)")
|
|
1299
1303
|
}
|
|
1300
1304
|
},
|
|
1301
|
-
async ({ path:
|
|
1305
|
+
async ({ path: path4, params, body }) => {
|
|
1302
1306
|
try {
|
|
1303
1307
|
const llm = getClient();
|
|
1304
|
-
const result = body ? await llm.pmQuery(
|
|
1308
|
+
const result = body ? await llm.pmQuery(path4, body) : await llm.pm(path4, params);
|
|
1305
1309
|
return {
|
|
1306
1310
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1307
1311
|
structuredContent: result
|
|
@@ -1420,132 +1424,8 @@ Examples:
|
|
|
1420
1424
|
);
|
|
1421
1425
|
}
|
|
1422
1426
|
|
|
1423
|
-
// src/tools/twitter.ts
|
|
1424
|
-
import { z as z11 } from "zod";
|
|
1425
|
-
var ACTION2 = z11.enum([
|
|
1426
|
-
"user_lookup",
|
|
1427
|
-
"user_info",
|
|
1428
|
-
"followers",
|
|
1429
|
-
"followings",
|
|
1430
|
-
"verified_followers",
|
|
1431
|
-
"user_tweets",
|
|
1432
|
-
"user_mentions",
|
|
1433
|
-
"tweet_lookup",
|
|
1434
|
-
"tweet_replies",
|
|
1435
|
-
"tweet_thread",
|
|
1436
|
-
"search"
|
|
1437
|
-
]);
|
|
1438
|
-
function registerTwitterTool(server) {
|
|
1439
|
-
server.registerTool(
|
|
1440
|
-
"blockrun_x",
|
|
1441
|
-
{
|
|
1442
|
-
description: `Structured X/Twitter data via AttentionVC partner API.
|
|
1443
|
-
|
|
1444
|
-
Actions:
|
|
1445
|
-
- user_lookup (usernames: string | string[])
|
|
1446
|
-
- user_info (username)
|
|
1447
|
-
- followers (username, cursor?)
|
|
1448
|
-
- followings (username, cursor?)
|
|
1449
|
-
- verified_followers (user_id, cursor?)
|
|
1450
|
-
- user_tweets (username, includeReplies?, cursor?)
|
|
1451
|
-
- user_mentions (username, sinceTime?, untilTime?, cursor?)
|
|
1452
|
-
- tweet_lookup (tweet_ids: string | string[])
|
|
1453
|
-
- tweet_replies (tweet_id, cursor?, queryType?)
|
|
1454
|
-
- tweet_thread (tweet_id, cursor?)
|
|
1455
|
-
- search (query, queryType?, cursor?)
|
|
1456
|
-
|
|
1457
|
-
Paid per request via x402; prices scale with the endpoint (e.g. user_lookup ~ $0.02, followers ~ $0.05/page).`,
|
|
1458
|
-
inputSchema: {
|
|
1459
|
-
action: ACTION2,
|
|
1460
|
-
usernames: z11.union([z11.string(), z11.array(z11.string())]).optional(),
|
|
1461
|
-
username: z11.string().optional(),
|
|
1462
|
-
user_id: z11.string().optional(),
|
|
1463
|
-
tweet_id: z11.string().optional(),
|
|
1464
|
-
tweet_ids: z11.union([z11.string(), z11.array(z11.string())]).optional(),
|
|
1465
|
-
query: z11.string().optional(),
|
|
1466
|
-
queryType: z11.enum(["Latest", "Top", "Default"]).optional(),
|
|
1467
|
-
cursor: z11.string().optional(),
|
|
1468
|
-
sinceTime: z11.string().optional(),
|
|
1469
|
-
untilTime: z11.string().optional(),
|
|
1470
|
-
includeReplies: z11.boolean().optional()
|
|
1471
|
-
}
|
|
1472
|
-
},
|
|
1473
|
-
async (args) => {
|
|
1474
|
-
try {
|
|
1475
|
-
const llm = getClient();
|
|
1476
|
-
const a = args.action;
|
|
1477
|
-
const require2 = (value, name) => {
|
|
1478
|
-
if (value === void 0 || value === null || value === "") {
|
|
1479
|
-
throw new Error(`${name} is required for action='${a}'`);
|
|
1480
|
-
}
|
|
1481
|
-
return value;
|
|
1482
|
-
};
|
|
1483
|
-
let result;
|
|
1484
|
-
switch (a) {
|
|
1485
|
-
case "user_lookup":
|
|
1486
|
-
result = await llm.xUserLookup(require2(args.usernames, "usernames"));
|
|
1487
|
-
break;
|
|
1488
|
-
case "user_info":
|
|
1489
|
-
result = await llm.xUserInfo(require2(args.username, "username"));
|
|
1490
|
-
break;
|
|
1491
|
-
case "followers":
|
|
1492
|
-
result = await llm.xFollowers(require2(args.username, "username"), args.cursor);
|
|
1493
|
-
break;
|
|
1494
|
-
case "followings":
|
|
1495
|
-
result = await llm.xFollowings(require2(args.username, "username"), args.cursor);
|
|
1496
|
-
break;
|
|
1497
|
-
case "verified_followers":
|
|
1498
|
-
result = await llm.xVerifiedFollowers(require2(args.user_id, "user_id"), args.cursor);
|
|
1499
|
-
break;
|
|
1500
|
-
case "user_tweets":
|
|
1501
|
-
result = await llm.xUserTweets(
|
|
1502
|
-
require2(args.username, "username"),
|
|
1503
|
-
args.includeReplies,
|
|
1504
|
-
args.cursor
|
|
1505
|
-
);
|
|
1506
|
-
break;
|
|
1507
|
-
case "user_mentions":
|
|
1508
|
-
result = await llm.xUserMentions(
|
|
1509
|
-
require2(args.username, "username"),
|
|
1510
|
-
args.sinceTime,
|
|
1511
|
-
args.untilTime,
|
|
1512
|
-
args.cursor
|
|
1513
|
-
);
|
|
1514
|
-
break;
|
|
1515
|
-
case "tweet_lookup":
|
|
1516
|
-
result = await llm.xTweetLookup(require2(args.tweet_ids, "tweet_ids"));
|
|
1517
|
-
break;
|
|
1518
|
-
case "tweet_replies":
|
|
1519
|
-
result = await llm.xTweetReplies(
|
|
1520
|
-
require2(args.tweet_id, "tweet_id"),
|
|
1521
|
-
args.queryType,
|
|
1522
|
-
args.cursor
|
|
1523
|
-
);
|
|
1524
|
-
break;
|
|
1525
|
-
case "tweet_thread":
|
|
1526
|
-
result = await llm.xTweetThread(require2(args.tweet_id, "tweet_id"), args.cursor);
|
|
1527
|
-
break;
|
|
1528
|
-
case "search":
|
|
1529
|
-
result = await llm.xSearch(require2(args.query, "query"), args.queryType, args.cursor);
|
|
1530
|
-
break;
|
|
1531
|
-
}
|
|
1532
|
-
return {
|
|
1533
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1534
|
-
structuredContent: result
|
|
1535
|
-
};
|
|
1536
|
-
} catch (err) {
|
|
1537
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1538
|
-
return {
|
|
1539
|
-
content: [{ type: "text", text: formatError(msg) }],
|
|
1540
|
-
isError: true
|
|
1541
|
-
};
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
);
|
|
1545
|
-
}
|
|
1546
|
-
|
|
1547
1427
|
// src/tools/dex.ts
|
|
1548
|
-
import { z as
|
|
1428
|
+
import { z as z11 } from "zod";
|
|
1549
1429
|
function registerDexTool(server) {
|
|
1550
1430
|
server.registerTool(
|
|
1551
1431
|
"blockrun_dex",
|
|
@@ -1562,10 +1442,10 @@ Examples:
|
|
|
1562
1442
|
blockrun_dex({ token: "So11...xxx" }) -> Get specific token data
|
|
1563
1443
|
blockrun_dex({ symbol: "PEPE" }) -> Search by symbol`,
|
|
1564
1444
|
inputSchema: {
|
|
1565
|
-
query:
|
|
1566
|
-
token:
|
|
1567
|
-
symbol:
|
|
1568
|
-
chain:
|
|
1445
|
+
query: z11.string().optional().describe("Search query (token name, symbol, or address)"),
|
|
1446
|
+
token: z11.string().optional().describe("Token address for direct lookup"),
|
|
1447
|
+
symbol: z11.string().optional().describe("Token symbol to search"),
|
|
1448
|
+
chain: z11.string().optional().describe("Filter by chain (ethereum, solana, base, etc.)")
|
|
1569
1449
|
}
|
|
1570
1450
|
},
|
|
1571
1451
|
async ({ query, token, symbol, chain }) => {
|
|
@@ -1626,235 +1506,91 @@ ${lines.join("\n\n")}` }],
|
|
|
1626
1506
|
}
|
|
1627
1507
|
|
|
1628
1508
|
// src/tools/modal.ts
|
|
1629
|
-
import { z as
|
|
1630
|
-
var MODAL_GPU_TYPES = ["T4", "L4", "A10G", "A100", "A100-80GB", "H100"];
|
|
1509
|
+
import { z as z12 } from "zod";
|
|
1631
1510
|
function registerModalTool(server) {
|
|
1632
1511
|
server.registerTool(
|
|
1633
1512
|
"blockrun_modal",
|
|
1634
1513
|
{
|
|
1635
|
-
description: `Run isolated code in a BlockRun-hosted Modal sandbox.
|
|
1514
|
+
description: `Run isolated code in a BlockRun-hosted Modal sandbox \u2014 disposable remote container, optional GPU.
|
|
1636
1515
|
|
|
1637
|
-
Use
|
|
1638
|
-
- a disposable remote container
|
|
1639
|
-
- GPU access
|
|
1640
|
-
- a clean environment that will not affect the local machine
|
|
1641
|
-
- a safer place to run untrusted or heavy code
|
|
1516
|
+
Use when you need: a clean ephemeral environment, GPU access (T4/L4/A10G/A100/A100-80GB/H100), or a safer place for untrusted code. Prefer local tools for normal repo work.
|
|
1642
1517
|
|
|
1643
|
-
|
|
1518
|
+
Common paths (all POST):
|
|
1519
|
+
- sandbox/create \u2014 body: { image?, timeout?, cpu?, memory?, gpu?, setup_commands? } ($0.01)
|
|
1520
|
+
- sandbox/exec \u2014 body: { sandbox_id, command: ["python","-c","..."], timeout? } ($0.001)
|
|
1521
|
+
- sandbox/status \u2014 body: { sandbox_id } ($0.001)
|
|
1522
|
+
- sandbox/terminate \u2014 body: { sandbox_id } ($0.001)
|
|
1644
1523
|
|
|
1645
|
-
|
|
1646
|
-
- create: $0.01
|
|
1647
|
-
- exec/status/terminate: $0.001 each`,
|
|
1524
|
+
Full action shapes + GPU type details in the \`modal\` skill.`,
|
|
1648
1525
|
inputSchema: {
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
),
|
|
1652
|
-
sandbox_id: z13.string().optional().describe("Sandbox ID returned by a previous create"),
|
|
1653
|
-
command: z13.array(z13.string()).optional().describe('Command array for exec, for example ["python", "-c", "print(2+2)"]'),
|
|
1654
|
-
image: z13.string().optional().describe("Container image for create (default: python:3.11)"),
|
|
1655
|
-
timeout: z13.number().optional().describe("Timeout in seconds for create or exec"),
|
|
1656
|
-
cpu: z13.number().optional().describe("CPU cores for create"),
|
|
1657
|
-
memory: z13.number().optional().describe("Memory in MB for create"),
|
|
1658
|
-
gpu: z13.enum(MODAL_GPU_TYPES).optional().describe("Optional GPU type for create"),
|
|
1659
|
-
setup_commands: z13.array(z13.string()).optional().describe("Shell commands to run during sandbox setup")
|
|
1526
|
+
path: z12.string().describe("Endpoint under /v1/modal/, e.g. 'sandbox/create', 'sandbox/exec'"),
|
|
1527
|
+
body: z12.any().optional().describe("JSON body. Sent as POST.")
|
|
1660
1528
|
}
|
|
1661
1529
|
},
|
|
1662
|
-
async ({
|
|
1530
|
+
async ({ path: path4, body }) => {
|
|
1663
1531
|
try {
|
|
1664
|
-
const
|
|
1665
|
-
const
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
case "create":
|
|
1669
|
-
result = await req.requestWithPaymentRaw("/v1/modal/sandbox/create", {
|
|
1670
|
-
image,
|
|
1671
|
-
timeout,
|
|
1672
|
-
cpu,
|
|
1673
|
-
memory,
|
|
1674
|
-
gpu,
|
|
1675
|
-
setup_commands
|
|
1676
|
-
});
|
|
1677
|
-
break;
|
|
1678
|
-
case "exec":
|
|
1679
|
-
if (!sandbox_id) throw new Error("sandbox_id required for exec action");
|
|
1680
|
-
if (!command?.length) throw new Error("command array required for exec action");
|
|
1681
|
-
result = await req.requestWithPaymentRaw("/v1/modal/sandbox/exec", {
|
|
1682
|
-
sandbox_id,
|
|
1683
|
-
command,
|
|
1684
|
-
timeout
|
|
1685
|
-
});
|
|
1686
|
-
break;
|
|
1687
|
-
case "status":
|
|
1688
|
-
if (!sandbox_id) throw new Error("sandbox_id required for status action");
|
|
1689
|
-
result = await req.requestWithPaymentRaw("/v1/modal/sandbox/status", { sandbox_id });
|
|
1690
|
-
break;
|
|
1691
|
-
case "terminate":
|
|
1692
|
-
if (!sandbox_id) throw new Error("sandbox_id required for terminate action");
|
|
1693
|
-
result = await req.requestWithPaymentRaw("/v1/modal/sandbox/terminate", {
|
|
1694
|
-
sandbox_id
|
|
1695
|
-
});
|
|
1696
|
-
break;
|
|
1697
|
-
default:
|
|
1698
|
-
throw new Error(`Unknown action: ${String(action)}`);
|
|
1699
|
-
}
|
|
1532
|
+
const client = getClient();
|
|
1533
|
+
const cleanPath = path4.replace(/^\/+/, "").replace(/^v1\/modal\//, "");
|
|
1534
|
+
const endpoint = `/v1/modal/${cleanPath}`;
|
|
1535
|
+
const result = await client.requestWithPaymentRaw(endpoint, body ?? {});
|
|
1700
1536
|
return {
|
|
1701
1537
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1702
1538
|
structuredContent: result
|
|
1703
1539
|
};
|
|
1704
1540
|
} catch (err) {
|
|
1705
|
-
|
|
1706
|
-
return {
|
|
1707
|
-
content: [{ type: "text", text: formatError(errMsg) }],
|
|
1708
|
-
isError: true
|
|
1709
|
-
};
|
|
1541
|
+
return { content: [{ type: "text", text: formatError(extractErrorMessage(err)) }], isError: true };
|
|
1710
1542
|
}
|
|
1711
1543
|
}
|
|
1712
1544
|
);
|
|
1713
1545
|
}
|
|
1714
1546
|
|
|
1715
1547
|
// src/tools/phone.ts
|
|
1716
|
-
import { z as
|
|
1548
|
+
import { z as z13 } from "zod";
|
|
1717
1549
|
function registerPhoneTool(server) {
|
|
1718
1550
|
server.registerTool(
|
|
1719
1551
|
"blockrun_phone",
|
|
1720
1552
|
{
|
|
1721
|
-
description: `Phone
|
|
1553
|
+
description: `Phone-number intelligence, US/CA number provisioning, and outbound AI voice calls.
|
|
1722
1554
|
|
|
1723
|
-
|
|
1724
|
-
- lookup: $0.01
|
|
1725
|
-
-
|
|
1726
|
-
-
|
|
1727
|
-
-
|
|
1728
|
-
-
|
|
1729
|
-
-
|
|
1730
|
-
-
|
|
1731
|
-
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
Voice presets: nat, josh, maya, june, paige, derek, florian
|
|
1739
|
-
Phone numbers use E.164 format: +14155552671`,
|
|
1555
|
+
Common paths (path = everything after /v1/):
|
|
1556
|
+
- phone/lookup POST body: { phoneNumber } ($0.01)
|
|
1557
|
+
- phone/lookup/fraud POST body: { phoneNumber } \u2014 SIM-swap + call-forwarding signals ($0.05)
|
|
1558
|
+
- phone/numbers/buy POST body: { country?: "US"|"CA", areaCode? } \u2014 30-day lease ($5.00)
|
|
1559
|
+
- phone/numbers/renew POST body: { phoneNumber } \u2014 extend 30 days ($5.00)
|
|
1560
|
+
- phone/numbers/list POST body: {} \u2014 your wallet-owned numbers ($0.001)
|
|
1561
|
+
- phone/numbers/release POST body: { phoneNumber } \u2014 release back to pool (free)
|
|
1562
|
+
- voice/call POST body: { to, task, from, voice?, max_duration?, ... } ($0.54 flat)
|
|
1563
|
+
- voice/call/{call_id} GET (no body) \u2014 poll status + transcript (free)
|
|
1564
|
+
|
|
1565
|
+
REQUIRED for voice/call: \`from\` must be a number your wallet owns. Provision one with \`phone/numbers/buy\` first ($5, 30-day lease).
|
|
1566
|
+
|
|
1567
|
+
Voice presets: nat, josh, maya, june, paige, derek, florian. Phone numbers use E.164 format (e.g. +1 followed by 10 US digits, or +<country-code><number>).
|
|
1568
|
+
|
|
1569
|
+
Voice call flow + voice preset details + full body shapes in the \`phone\` skill.`,
|
|
1740
1570
|
inputSchema: {
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
"lookup_fraud",
|
|
1744
|
-
"numbers_buy",
|
|
1745
|
-
"numbers_renew",
|
|
1746
|
-
"numbers_list",
|
|
1747
|
-
"numbers_release",
|
|
1748
|
-
"voice_call",
|
|
1749
|
-
"voice_status"
|
|
1750
|
-
]).describe("Action to perform"),
|
|
1751
|
-
phone_number: z14.string().optional().describe("E.164 phone number, e.g. +14155552671 (required for lookup, lookup_fraud, numbers_renew, numbers_release)"),
|
|
1752
|
-
country: z14.string().optional().describe("Country for numbers_buy: US or CA (default: US)"),
|
|
1753
|
-
area_code: z14.string().optional().describe("Preferred 3-digit area code for numbers_buy (best effort)"),
|
|
1754
|
-
to: z14.string().optional().describe("Destination E.164 number (required for voice_call)"),
|
|
1755
|
-
task: z14.string().optional().describe("What the AI should do on the call, 10\u20134000 chars (required for voice_call)"),
|
|
1756
|
-
from: z14.string().optional().describe("Your provisioned BlockRun caller ID number (optional for voice_call)"),
|
|
1757
|
-
voice: z14.enum(["nat", "josh", "maya", "june", "paige", "derek", "florian"]).optional().describe("AI voice preset"),
|
|
1758
|
-
max_duration: z14.number().min(1).max(30).optional().describe("Max call duration in minutes (1\u201330, default: 5)"),
|
|
1759
|
-
language: z14.string().optional().describe("Language code, e.g. en-US (default: en-US)"),
|
|
1760
|
-
first_sentence: z14.string().optional().describe("Custom opening line for the AI agent"),
|
|
1761
|
-
wait_for_greeting: z14.boolean().optional().describe("Wait for recipient to speak before AI starts"),
|
|
1762
|
-
call_id: z14.string().optional().describe("Call ID from voice_call response (required for voice_status)")
|
|
1571
|
+
path: z13.string().describe("Endpoint after /v1/. Use 'phone/...' for lookup + number ops, 'voice/call' for outbound AI calls, 'voice/call/{id}' (no body) to poll status."),
|
|
1572
|
+
body: z13.any().optional().describe("JSON body. Sent as POST. Omit for the free GET poll (voice/call/{call_id}).")
|
|
1763
1573
|
}
|
|
1764
1574
|
},
|
|
1765
|
-
async ({
|
|
1766
|
-
action,
|
|
1767
|
-
phone_number,
|
|
1768
|
-
country,
|
|
1769
|
-
area_code,
|
|
1770
|
-
to,
|
|
1771
|
-
task,
|
|
1772
|
-
from,
|
|
1773
|
-
voice,
|
|
1774
|
-
max_duration,
|
|
1775
|
-
language,
|
|
1776
|
-
first_sentence,
|
|
1777
|
-
wait_for_greeting,
|
|
1778
|
-
call_id
|
|
1779
|
-
}) => {
|
|
1780
|
-
const client = getClient();
|
|
1781
|
-
const req = client;
|
|
1782
|
-
const chain = getChain();
|
|
1575
|
+
async ({ path: path4, body }) => {
|
|
1783
1576
|
try {
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
result = await req.requestWithPaymentRaw("/v1/phone/lookup", { phoneNumber: phone_number });
|
|
1789
|
-
break;
|
|
1790
|
-
}
|
|
1791
|
-
case "lookup_fraud": {
|
|
1792
|
-
if (!phone_number) return { content: [{ type: "text", text: "phone_number required (E.164)" }], isError: true };
|
|
1793
|
-
result = await req.requestWithPaymentRaw("/v1/phone/lookup/fraud", { phoneNumber: phone_number });
|
|
1794
|
-
break;
|
|
1795
|
-
}
|
|
1796
|
-
case "numbers_buy": {
|
|
1797
|
-
const body = {};
|
|
1798
|
-
if (country) body.country = country;
|
|
1799
|
-
if (area_code) body.areaCode = area_code;
|
|
1800
|
-
result = await req.requestWithPaymentRaw("/v1/phone/numbers/buy", body);
|
|
1801
|
-
break;
|
|
1802
|
-
}
|
|
1803
|
-
case "numbers_renew": {
|
|
1804
|
-
if (!phone_number) return { content: [{ type: "text", text: "phone_number required (E.164)" }], isError: true };
|
|
1805
|
-
result = await req.requestWithPaymentRaw("/v1/phone/numbers/renew", { phoneNumber: phone_number });
|
|
1806
|
-
break;
|
|
1807
|
-
}
|
|
1808
|
-
case "numbers_list": {
|
|
1809
|
-
result = await req.requestWithPaymentRaw("/v1/phone/numbers/list", {});
|
|
1810
|
-
break;
|
|
1811
|
-
}
|
|
1812
|
-
case "numbers_release": {
|
|
1813
|
-
if (!phone_number) return { content: [{ type: "text", text: "phone_number required (E.164)" }], isError: true };
|
|
1814
|
-
result = await req.requestWithPaymentRaw("/v1/phone/numbers/release", { phoneNumber: phone_number });
|
|
1815
|
-
break;
|
|
1816
|
-
}
|
|
1817
|
-
case "voice_call": {
|
|
1818
|
-
if (!to) return { content: [{ type: "text", text: "to (destination phone number) required" }], isError: true };
|
|
1819
|
-
if (!task) return { content: [{ type: "text", text: "task required (what the AI should do on the call)" }], isError: true };
|
|
1820
|
-
const body = { to, task };
|
|
1821
|
-
if (from) body.from = from;
|
|
1822
|
-
if (voice) body.voice = voice;
|
|
1823
|
-
if (max_duration !== void 0) body.max_duration = max_duration;
|
|
1824
|
-
if (language) body.language = language;
|
|
1825
|
-
if (first_sentence) body.first_sentence = first_sentence;
|
|
1826
|
-
if (wait_for_greeting !== void 0) body.wait_for_greeting = wait_for_greeting;
|
|
1827
|
-
result = await req.requestWithPaymentRaw("/v1/voice/call", body);
|
|
1828
|
-
break;
|
|
1829
|
-
}
|
|
1830
|
-
case "voice_status": {
|
|
1831
|
-
if (!call_id) return { content: [{ type: "text", text: "call_id required" }], isError: true };
|
|
1832
|
-
const apiBase = chain === "solana" ? "https://sol.blockrun.ai/api" : "https://blockrun.ai/api";
|
|
1833
|
-
const resp = await fetch(`${apiBase}/v1/voice/call/${encodeURIComponent(call_id)}`, {
|
|
1834
|
-
signal: AbortSignal.timeout(15e3)
|
|
1835
|
-
});
|
|
1836
|
-
if (!resp.ok) {
|
|
1837
|
-
const err = await resp.text().catch(() => resp.statusText);
|
|
1838
|
-
return { content: [{ type: "text", text: formatError(`voice_status ${resp.status}: ${err}`) }], isError: true };
|
|
1839
|
-
}
|
|
1840
|
-
result = await resp.json();
|
|
1841
|
-
break;
|
|
1842
|
-
}
|
|
1843
|
-
}
|
|
1844
|
-
const text = typeof result === "object" ? JSON.stringify(result, null, 2) : String(result);
|
|
1577
|
+
const client = getClient();
|
|
1578
|
+
const cleanPath = path4.replace(/^\/+/, "").replace(/^v1\//, "");
|
|
1579
|
+
const endpoint = `/v1/${cleanPath}`;
|
|
1580
|
+
const result = body !== void 0 ? await client.requestWithPaymentRaw(endpoint, body) : await client.getWithPaymentRaw(endpoint);
|
|
1845
1581
|
return {
|
|
1846
|
-
content: [{ type: "text", text }],
|
|
1582
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1847
1583
|
structuredContent: result
|
|
1848
1584
|
};
|
|
1849
1585
|
} catch (err) {
|
|
1850
|
-
return { content: [{ type: "text", text: formatError(
|
|
1586
|
+
return { content: [{ type: "text", text: formatError(extractErrorMessage(err)) }], isError: true };
|
|
1851
1587
|
}
|
|
1852
1588
|
}
|
|
1853
1589
|
);
|
|
1854
1590
|
}
|
|
1855
1591
|
|
|
1856
1592
|
// src/tools/surf.ts
|
|
1857
|
-
import { z as
|
|
1593
|
+
import { z as z14 } from "zod";
|
|
1858
1594
|
function registerSurfTool(server) {
|
|
1859
1595
|
server.registerTool(
|
|
1860
1596
|
"blockrun_surf",
|
|
@@ -1881,15 +1617,15 @@ Common paths (full 84-endpoint catalog in the surf skill):
|
|
|
1881
1617
|
Method is auto-routed: pass 'body' for POST endpoints; otherwise GET with 'params'.
|
|
1882
1618
|
Each Surf endpoint pre-validates required params before settling \u2014 you get a 400 (not a charge) if a required field is missing. Browse the full catalog: https://blockrun.ai/marketplace/surf`,
|
|
1883
1619
|
inputSchema: {
|
|
1884
|
-
path:
|
|
1885
|
-
params:
|
|
1886
|
-
body:
|
|
1620
|
+
path: z14.string().describe("Endpoint path under /v1/surf/, e.g. 'market/price', 'prediction-market/polymarket/ranking', 'wallet/detail', 'onchain/sql', 'chat/completions'"),
|
|
1621
|
+
params: z14.record(z14.string(), z14.string()).optional().describe("Query parameters for GET endpoints, e.g. { symbol: 'BTC' } or { address: '0x...', chain: 'ethereum' }"),
|
|
1622
|
+
body: z14.any().optional().describe("JSON body for POST endpoints. Provide for: onchain/query, onchain/sql, chat/completions. When set, the call is sent as POST; otherwise GET with params.")
|
|
1887
1623
|
}
|
|
1888
1624
|
},
|
|
1889
|
-
async ({ path:
|
|
1625
|
+
async ({ path: path4, params, body }) => {
|
|
1890
1626
|
try {
|
|
1891
1627
|
const client = getClient();
|
|
1892
|
-
const cleanPath =
|
|
1628
|
+
const cleanPath = path4.replace(/^\/+/, "").replace(/^v1\/surf\//, "").replace(/^api\/v1\/surf\//, "");
|
|
1893
1629
|
const endpoint = `/v1/surf/${cleanPath}`;
|
|
1894
1630
|
const result = body !== void 0 ? await client.requestWithPaymentRaw(endpoint, body) : await client.getWithPaymentRaw(endpoint, params);
|
|
1895
1631
|
return {
|
|
@@ -1897,9 +1633,8 @@ Each Surf endpoint pre-validates required params before settling \u2014 you get
|
|
|
1897
1633
|
structuredContent: result
|
|
1898
1634
|
};
|
|
1899
1635
|
} catch (err) {
|
|
1900
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1901
1636
|
return {
|
|
1902
|
-
content: [{ type: "text", text: formatError(
|
|
1637
|
+
content: [{ type: "text", text: formatError(extractErrorMessage(err)) }],
|
|
1903
1638
|
isError: true
|
|
1904
1639
|
};
|
|
1905
1640
|
}
|
|
@@ -1921,7 +1656,6 @@ function initializeMcpServer(server) {
|
|
|
1921
1656
|
registerExaTool(server);
|
|
1922
1657
|
registerMarketsTool(server);
|
|
1923
1658
|
registerPriceTool(server);
|
|
1924
|
-
registerTwitterTool(server);
|
|
1925
1659
|
registerDexTool(server);
|
|
1926
1660
|
registerModalTool(server);
|
|
1927
1661
|
registerPhoneTool(server);
|
|
@@ -1966,8 +1700,8 @@ function initializeMcpServer(server) {
|
|
|
1966
1700
|
|
|
1967
1701
|
// src/utils/key-leak-scanner.ts
|
|
1968
1702
|
import fs3 from "fs";
|
|
1969
|
-
import
|
|
1970
|
-
import
|
|
1703
|
+
import path3 from "path";
|
|
1704
|
+
import os3 from "os";
|
|
1971
1705
|
function looksLikeRawPrivateKey(value) {
|
|
1972
1706
|
if (typeof value !== "string") return false;
|
|
1973
1707
|
if (/^0x[0-9a-fA-F]{64}$/.test(value)) return true;
|
|
@@ -2003,12 +1737,12 @@ function scanFile(file) {
|
|
|
2003
1737
|
}
|
|
2004
1738
|
}
|
|
2005
1739
|
function warnOnLeakedKeys() {
|
|
2006
|
-
const home =
|
|
1740
|
+
const home = os3.homedir();
|
|
2007
1741
|
const candidates = [
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
1742
|
+
path3.join(home, ".claude.json"),
|
|
1743
|
+
path3.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json"),
|
|
1744
|
+
path3.join(home, ".config", "claude", "claude_desktop_config.json"),
|
|
1745
|
+
path3.join(home, "AppData", "Roaming", "Claude", "claude_desktop_config.json")
|
|
2012
1746
|
];
|
|
2013
1747
|
const findings = [];
|
|
2014
1748
|
for (const f of candidates) findings.push(...scanFile(f));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockrun/mcp",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.2",
|
|
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",
|