@blockrun/mcp 0.24.0 → 0.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +70 -9
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -20,8 +20,7 @@ import {
20
20
  loadSolanaWallet,
21
21
  getPaymentLinks,
22
22
  formatWalletCreatedMessage,
23
- formatNeedsFundingMessage,
24
- SOLANA_WALLET_FILE_PATH
23
+ formatNeedsFundingMessage
25
24
  } from "@blockrun/llm";
26
25
 
27
26
  // src/utils/constants.ts
@@ -80,7 +79,7 @@ function getChain() {
80
79
  if (preferred) return preferred;
81
80
  if (process.env.SOLANA_WALLET_KEY) return "solana";
82
81
  try {
83
- if (fs.existsSync(SOLANA_WALLET_FILE_PATH)) return "solana";
82
+ if (loadSolanaWallet()) return "solana";
84
83
  } catch {
85
84
  }
86
85
  return "base";
@@ -154,6 +153,10 @@ function buildClientWithTimeout(timeoutMs) {
154
153
  const privateKey = getOrCreateWalletKey();
155
154
  return new LLMClient({ privateKey, timeout: timeoutMs });
156
155
  }
156
+ function buildClient() {
157
+ if (getChain() === "solana") return buildSolanaClient();
158
+ return new LLMClient({ privateKey: getOrCreateWalletKey() });
159
+ }
157
160
  function getAnthropicClient() {
158
161
  if (!_anthropicClient) {
159
162
  const privateKey = getOrCreateWalletKey();
@@ -230,7 +233,8 @@ async function getBaseUsdcBalance(address) {
230
233
  const response = await fetch(rpcUrl, {
231
234
  method: "POST",
232
235
  headers: { "Content-Type": "application/json" },
233
- body: JSON.stringify(data)
236
+ body: JSON.stringify(data),
237
+ signal: AbortSignal.timeout(8e3)
234
238
  });
235
239
  const result = await response.json();
236
240
  const usd = parseBaseUsdcCallResult(result.result);
@@ -248,7 +252,7 @@ async function getChainBalance(chain, address) {
248
252
  // src/utils/model-cache.ts
249
253
  var CACHE_TTL_MS = 5 * 60 * 1e3;
250
254
  async function loadModels(llm, cache) {
251
- if (!cache.models) {
255
+ if (cache.models === null || cache.models.length === 0) {
252
256
  cache.models = llm.listAllModels ? await llm.listAllModels() : await llm.listModels();
253
257
  setTimeout(() => {
254
258
  cache.models = null;
@@ -998,7 +1002,7 @@ Run blockrun_models to see all available models with pricing.`,
998
1002
  }
999
1003
  },
1000
1004
  async ({ message, model, mode, routing, routing_profile, system, max_tokens, temperature, response_format, stop, thinking, agent_id, messages }) => {
1001
- const llm = getClient();
1005
+ const llm = buildClient();
1002
1006
  const responseFormat = response_format ? { type: response_format } : void 0;
1003
1007
  const estimatedCost = estimateChatCost(max_tokens, mode, model, routing, routing_profile);
1004
1008
  const gate = reserveBudget(budget, agent_id, estimatedCost);
@@ -1215,6 +1219,41 @@ ${lines.join("\n")}` }],
1215
1219
  // src/tools/image.ts
1216
1220
  import { z as z4 } from "zod";
1217
1221
  import { PaymentError } from "@blockrun/llm";
1222
+
1223
+ // src/utils/ssrf.ts
1224
+ function ipv4Blocked(host) {
1225
+ const m = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
1226
+ if (!m) return null;
1227
+ const o = m.slice(1).map(Number);
1228
+ if (o.some((n) => n > 255)) return true;
1229
+ const [a, b] = o;
1230
+ return a === 0 || // 0.0.0.0/8
1231
+ a === 127 || // loopback
1232
+ a === 10 || // private
1233
+ a === 172 && b >= 16 && b <= 31 || // private
1234
+ a === 192 && b === 168 || // private
1235
+ a === 169 && b === 254 || // link-local (incl. metadata)
1236
+ a === 100 && b >= 64 && b <= 127;
1237
+ }
1238
+ function isBlockedFetchHost(hostname) {
1239
+ let host = hostname.trim().toLowerCase();
1240
+ if (host.startsWith("[") && host.endsWith("]")) host = host.slice(1, -1);
1241
+ if (!host) return true;
1242
+ if (host === "localhost" || host.endsWith(".localhost")) return true;
1243
+ if (host.endsWith(".internal") || host.endsWith(".local")) return true;
1244
+ const v4 = ipv4Blocked(host);
1245
+ if (v4 !== null) return v4;
1246
+ if (host.includes(":")) {
1247
+ if (host === "::1" || host === "::") return true;
1248
+ if (host.startsWith("fc") || host.startsWith("fd") || host.startsWith("fe80")) return true;
1249
+ const mapped = /^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.exec(host);
1250
+ if (mapped) return ipv4Blocked(mapped[1]) === true;
1251
+ return false;
1252
+ }
1253
+ return false;
1254
+ }
1255
+
1256
+ // src/tools/image.ts
1218
1257
  import { readFile } from "fs/promises";
1219
1258
  var REFERENCE_IMAGE_MAX_BYTES = 4e6;
1220
1259
  var IMAGE_EXT_MIME = {
@@ -1230,7 +1269,22 @@ async function toImageDataUri(ref) {
1230
1269
  const ctrl = new AbortController();
1231
1270
  const timeout = setTimeout(() => ctrl.abort(), 3e4);
1232
1271
  try {
1233
- const res = await fetch(ref, { signal: ctrl.signal });
1272
+ let url = ref;
1273
+ let res;
1274
+ for (let hop = 0; ; hop++) {
1275
+ const host = new URL(url).hostname;
1276
+ if (isBlockedFetchHost(host)) {
1277
+ throw new Error(`refusing to fetch a private/loopback/link-local address: ${host}`);
1278
+ }
1279
+ res = await fetch(url, { signal: ctrl.signal, redirect: "manual" });
1280
+ const location = res.headers.get("location");
1281
+ if (res.status >= 300 && res.status < 400 && location) {
1282
+ if (hop >= 5) throw new Error("too many redirects");
1283
+ url = new URL(location, url).toString();
1284
+ continue;
1285
+ }
1286
+ break;
1287
+ }
1234
1288
  if (!res.ok) throw new Error(`fetch failed: ${res.status} ${res.statusText}`);
1235
1289
  const mime2 = (res.headers.get("content-type") || "").toLowerCase().split(";")[0].trim();
1236
1290
  if (!mime2.startsWith("image/")) throw new Error(`URL returned non-image content-type: ${mime2 || "(none)"}`);
@@ -2012,6 +2066,13 @@ Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GC
2012
2066
  const paymentRequired = parsePaymentRequired3(prHeader);
2013
2067
  const details = extractPaymentDetails3(paymentRequired);
2014
2068
  const settledUsd = amountToUsd(details.amount);
2069
+ if (settledUsd !== null && settledUsd > estimatedCost) {
2070
+ gate?.release();
2071
+ gate = reserveBudget(budget, agent_id, settledUsd);
2072
+ if (!gate.allowed) {
2073
+ return { content: [{ type: "text", text: `${gate.reason}. Use blockrun_wallet action:"report" to see usage or action:"delegate" to increase agent budget.` }], isError: true };
2074
+ }
2075
+ }
2015
2076
  const paymentPayload = await createPaymentPayload3(
2016
2077
  privateKey,
2017
2078
  account.address,
@@ -2088,7 +2149,7 @@ Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GC
2088
2149
  const lines = [
2089
2150
  `\u{1F3AC} Video ready!`,
2090
2151
  `URL: ${completed.url}`,
2091
- `Duration: ${completed.duration_seconds ? `${completed.duration_seconds}s` : "8s"}`,
2152
+ `Duration: ${completed.duration_seconds ?? billedSeconds}s`,
2092
2153
  `Model: ${completed.modelReturned || selectedModel}`,
2093
2154
  ...completed.backed_up ? [`Backed up to BlockRun storage (URL is permanent)`] : completed.source_url ? [`Source URL: ${completed.source_url}`] : [],
2094
2155
  ...completed.request_id ? [`Request ID: ${completed.request_id}`] : [],
@@ -2887,7 +2948,7 @@ Examples:
2887
2948
  isError: true
2888
2949
  };
2889
2950
  }
2890
- const response = await fetch(url);
2951
+ const response = await fetchWithTimeout(url, {}, 8e3);
2891
2952
  if (!response.ok) {
2892
2953
  throw new Error(`DexScreener API error: ${response.status}`);
2893
2954
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/mcp",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "mcpName": "io.github.BlockRunAI/blockrun-mcp",
5
5
  "description": "BlockRun MCP Server - Give your AI agent web search, deep research, prediction markets, crypto data, X/Twitter intelligence. Paid via x402 micropayments.",
6
6
  "type": "module",