@blockrun/mcp 0.21.4 → 0.21.5

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 +52 -69
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -209,6 +209,18 @@ async function getChainBalance(chain, address) {
209
209
  return chain === "solana" ? getSolanaUsdcBalance() : getBaseUsdcBalance(address);
210
210
  }
211
211
 
212
+ // src/utils/model-cache.ts
213
+ var CACHE_TTL_MS = 5 * 60 * 1e3;
214
+ async function loadModels(llm, cache) {
215
+ if (!cache.models) {
216
+ cache.models = llm.listAllModels ? await llm.listAllModels() : await llm.listModels();
217
+ setTimeout(() => {
218
+ cache.models = null;
219
+ }, CACHE_TTL_MS).unref();
220
+ }
221
+ return cache.models;
222
+ }
223
+
212
224
  // src/tools/wallet.ts
213
225
  import { z } from "zod";
214
226
 
@@ -1043,14 +1055,7 @@ function registerModelsTool(server, modelCache) {
1043
1055
  }
1044
1056
  },
1045
1057
  async ({ category, provider }) => {
1046
- const llm = getClient();
1047
- if (!modelCache.models) {
1048
- modelCache.models = "listAllModels" in llm ? await llm.listAllModels() : await llm.listModels();
1049
- setTimeout(() => {
1050
- modelCache.models = null;
1051
- }, 5 * 60 * 1e3);
1052
- }
1053
- let models = modelCache.models;
1058
+ let models = await loadModels(getClient(), modelCache);
1054
1059
  if (provider) {
1055
1060
  const p = provider.toLowerCase();
1056
1061
  models = models.filter((m) => m.id.toLowerCase().startsWith(p + "/"));
@@ -1243,6 +1248,26 @@ Error: ${errMsg}` }],
1243
1248
 
1244
1249
  // src/tools/music.ts
1245
1250
  import { z as z5 } from "zod";
1251
+
1252
+ // src/utils/http.ts
1253
+ async function fetchWithTimeout(url, options, timeoutMs) {
1254
+ const controller = new AbortController();
1255
+ const id = setTimeout(() => controller.abort(), timeoutMs);
1256
+ id.unref?.();
1257
+ try {
1258
+ return await fetch(url, { ...options, signal: controller.signal });
1259
+ } finally {
1260
+ clearTimeout(id);
1261
+ }
1262
+ }
1263
+ function isTimeoutError(err) {
1264
+ const name = err instanceof Error ? err.name : "";
1265
+ if (name === "AbortError" || name === "TimeoutError") return true;
1266
+ const msg = (err instanceof Error ? err.message : String(err)).toLowerCase();
1267
+ return msg.includes("abort") || msg.includes("timeout") || msg.includes("timed out") || msg.includes("did not complete within");
1268
+ }
1269
+
1270
+ // src/tools/music.ts
1246
1271
  import { privateKeyToAccount } from "viem/accounts";
1247
1272
  import {
1248
1273
  createPaymentPayload,
@@ -1372,7 +1397,7 @@ Error: ${errMsg}` }],
1372
1397
  isError: true
1373
1398
  };
1374
1399
  }
1375
- if (errMsg.includes("abort") || errMsg.includes("timeout") || errMsg.includes("Timeout")) {
1400
+ if (isTimeoutError(err)) {
1376
1401
  return {
1377
1402
  content: [{ type: "text", text: `Music generation timed out. This can happen during peak load \u2014 please try again.
1378
1403
  Error: ${errMsg}` }],
@@ -1387,15 +1412,6 @@ Error: ${errMsg}` }],
1387
1412
  }
1388
1413
  );
1389
1414
  }
1390
- async function fetchWithTimeout(url, options, timeoutMs) {
1391
- const controller = new AbortController();
1392
- const id = setTimeout(() => controller.abort(), timeoutMs);
1393
- try {
1394
- return await fetch(url, { ...options, signal: controller.signal });
1395
- } finally {
1396
- clearTimeout(id);
1397
- }
1398
- }
1399
1415
 
1400
1416
  // src/tools/speech.ts
1401
1417
  import { z as z6 } from "zod";
@@ -1514,7 +1530,7 @@ Returns a hosted audio URL \u2014 download immediately if you need to keep the f
1514
1530
  }
1515
1531
  const privateKey = getOrCreateWalletKey();
1516
1532
  const account = privateKeyToAccount2(privateKey);
1517
- const resp402 = await fetchWithTimeout2(endpoint, {
1533
+ const resp402 = await fetchWithTimeout(endpoint, {
1518
1534
  method: "POST",
1519
1535
  headers: { "Content-Type": "application/json" },
1520
1536
  body: JSON.stringify(body)
@@ -1540,7 +1556,7 @@ Returns a hosted audio URL \u2014 download immediately if you need to keep the f
1540
1556
  extra: details.extra
1541
1557
  }
1542
1558
  );
1543
- const resp = await fetchWithTimeout2(endpoint, {
1559
+ const resp = await fetchWithTimeout(endpoint, {
1544
1560
  method: "POST",
1545
1561
  headers: {
1546
1562
  "Content-Type": "application/json",
@@ -1593,7 +1609,7 @@ Error: ${errMsg}` }],
1593
1609
  isError: true
1594
1610
  };
1595
1611
  }
1596
- if (errMsg.includes("abort") || errMsg.includes("timeout") || errMsg.includes("Timeout")) {
1612
+ if (isTimeoutError(err)) {
1597
1613
  return {
1598
1614
  content: [{ type: "text", text: `Speech generation timed out \u2014 please try again.
1599
1615
  Error: ${errMsg}` }],
@@ -1610,7 +1626,7 @@ Error: ${errMsg}` }],
1610
1626
  }
1611
1627
  async function listVoices() {
1612
1628
  try {
1613
- const resp = await fetchWithTimeout2(`${BLOCKRUN_API2}/v1/audio/voices`, { method: "GET" }, VOICES_TIMEOUT);
1629
+ const resp = await fetchWithTimeout(`${BLOCKRUN_API2}/v1/audio/voices`, { method: "GET" }, VOICES_TIMEOUT);
1614
1630
  if (resp.ok) {
1615
1631
  const payload = await resp.json();
1616
1632
  const voices = payload.data || [];
@@ -1637,15 +1653,6 @@ Any raw ElevenLabs voice_id also works.` }],
1637
1653
  structuredContent: { voices: VOICE_ALIASES }
1638
1654
  };
1639
1655
  }
1640
- async function fetchWithTimeout2(url, options, timeoutMs) {
1641
- const controller = new AbortController();
1642
- const id = setTimeout(() => controller.abort(), timeoutMs);
1643
- try {
1644
- return await fetch(url, { ...options, signal: controller.signal });
1645
- } finally {
1646
- clearTimeout(id);
1647
- }
1648
- }
1649
1656
 
1650
1657
  // src/tools/video.ts
1651
1658
  import { z as z7 } from "zod";
@@ -1749,7 +1756,7 @@ Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GC
1749
1756
  if (image_url) body.image_url = image_url;
1750
1757
  if (real_face_asset_id) body.real_face_asset_id = real_face_asset_id;
1751
1758
  if (duration_seconds !== void 0) body.duration_seconds = duration_seconds;
1752
- const resp402 = await fetchWithTimeout3(submitUrl, {
1759
+ const resp402 = await fetchWithTimeout(submitUrl, {
1753
1760
  method: "POST",
1754
1761
  headers: { "Content-Type": "application/json" },
1755
1762
  body: JSON.stringify(body)
@@ -1777,7 +1784,7 @@ Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GC
1777
1784
  extra: details.extra
1778
1785
  }
1779
1786
  );
1780
- const submitResp = await fetchWithTimeout3(submitUrl, {
1787
+ const submitResp = await fetchWithTimeout(submitUrl, {
1781
1788
  method: "POST",
1782
1789
  headers: {
1783
1790
  "Content-Type": "application/json",
@@ -1802,7 +1809,7 @@ Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GC
1802
1809
  let completed = null;
1803
1810
  while (Date.now() - startedAt < TOTAL_BUDGET_MS) {
1804
1811
  await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
1805
- const pollResp = await fetchWithTimeout3(pollAbsoluteUrl, {
1812
+ const pollResp = await fetchWithTimeout(pollAbsoluteUrl, {
1806
1813
  method: "GET",
1807
1814
  headers: { "PAYMENT-SIGNATURE": paymentPayload }
1808
1815
  }, 9e4);
@@ -1866,7 +1873,7 @@ Error: ${errMsg}` }],
1866
1873
  isError: true
1867
1874
  };
1868
1875
  }
1869
- if (errMsg.includes("abort") || errMsg.includes("timeout") || errMsg.includes("Timeout") || errMsg.includes("timed out") || errMsg.includes("did not complete within")) {
1876
+ if (isTimeoutError(err)) {
1870
1877
  return {
1871
1878
  content: [{ type: "text", text: `Video generation timed out. The upstream async job didn't complete in time \u2014 please try again.
1872
1879
  Error: ${errMsg}` }],
@@ -1881,15 +1888,6 @@ Error: ${errMsg}` }],
1881
1888
  }
1882
1889
  );
1883
1890
  }
1884
- async function fetchWithTimeout3(url, options, timeoutMs) {
1885
- const controller = new AbortController();
1886
- const id = setTimeout(() => controller.abort(), timeoutMs);
1887
- try {
1888
- return await fetch(url, { ...options, signal: controller.signal });
1889
- } finally {
1890
- clearTimeout(id);
1891
- }
1892
- }
1893
1891
 
1894
1892
  // src/tools/realface.ts
1895
1893
  import { z as z8 } from "zod";
@@ -1901,19 +1899,10 @@ import {
1901
1899
  } from "@blockrun/llm";
1902
1900
  var BLOCKRUN_API4 = "https://blockrun.ai/api";
1903
1901
  var ENROLLMENT_PRICE_USD = 0.01;
1904
- async function fetchWithTimeout4(url, options, timeoutMs) {
1905
- const controller = new AbortController();
1906
- const id = setTimeout(() => controller.abort(), timeoutMs);
1907
- try {
1908
- return await fetch(url, { ...options, signal: controller.signal });
1909
- } finally {
1910
- clearTimeout(id);
1911
- }
1912
- }
1913
1902
  async function payAndPostJson(url, reqBody, fallbackDescription) {
1914
1903
  const privateKey = getOrCreateWalletKey();
1915
1904
  const account = privateKeyToAccount4(privateKey);
1916
- const resp402 = await fetchWithTimeout4(url, {
1905
+ const resp402 = await fetchWithTimeout(url, {
1917
1906
  method: "POST",
1918
1907
  headers: { "Content-Type": "application/json" },
1919
1908
  body: reqBody
@@ -1939,7 +1928,7 @@ async function payAndPostJson(url, reqBody, fallbackDescription) {
1939
1928
  extra: details.extra
1940
1929
  }
1941
1930
  );
1942
- const resp = await fetchWithTimeout4(url, {
1931
+ const resp = await fetchWithTimeout(url, {
1943
1932
  method: "POST",
1944
1933
  headers: {
1945
1934
  "Content-Type": "application/json",
@@ -1988,7 +1977,7 @@ Privacy: BlockRun does not store face/liveness data \u2014 only the asset id, na
1988
1977
  }
1989
1978
  const body = { name };
1990
1979
  if (group_id) body.groupId = group_id;
1991
- const resp = await fetchWithTimeout4(`${BLOCKRUN_API4}/v1/realface/init`, {
1980
+ const resp = await fetchWithTimeout(`${BLOCKRUN_API4}/v1/realface/init`, {
1992
1981
  method: "POST",
1993
1982
  headers: { "Content-Type": "application/json" },
1994
1983
  body: JSON.stringify(body)
@@ -2037,7 +2026,7 @@ QR opened for scanning (${qrPath}).`;
2037
2026
  if (!group_id) {
2038
2027
  return { content: [{ type: "text", text: formatError('group_id is required for action:"status".') }], isError: true };
2039
2028
  }
2040
- const resp = await fetchWithTimeout4(`${BLOCKRUN_API4}/v1/realface/status?groupId=${encodeURIComponent(group_id)}`, {
2029
+ const resp = await fetchWithTimeout(`${BLOCKRUN_API4}/v1/realface/status?groupId=${encodeURIComponent(group_id)}`, {
2041
2030
  method: "GET"
2042
2031
  }, 3e4);
2043
2032
  const data = await resp.json().catch(() => ({}));
@@ -2067,8 +2056,8 @@ QR opened for scanning (${qrPath}).`;
2067
2056
  if (action === "list") {
2068
2057
  const account = privateKeyToAccount4(getOrCreateWalletKey());
2069
2058
  const [rfResp, vpResp] = await Promise.all([
2070
- fetchWithTimeout4(`${BLOCKRUN_API4}/v1/wallet/${account.address}/realfaces`, { method: "GET" }, 3e4),
2071
- fetchWithTimeout4(`${BLOCKRUN_API4}/v1/wallet/${account.address}/portraits`, { method: "GET" }, 3e4).catch(() => null)
2059
+ fetchWithTimeout(`${BLOCKRUN_API4}/v1/wallet/${account.address}/realfaces`, { method: "GET" }, 3e4),
2060
+ fetchWithTimeout(`${BLOCKRUN_API4}/v1/wallet/${account.address}/portraits`, { method: "GET" }, 3e4).catch(() => null)
2072
2061
  ]);
2073
2062
  const data = await rfResp.json().catch(() => ({}));
2074
2063
  if (!rfResp.ok) {
@@ -2165,7 +2154,7 @@ Enroll one: blockrun_realface action:"init" name:"\u2026" (real person) or actio
2165
2154
  const account = privateKeyToAccount4(privateKey);
2166
2155
  const enrollUrl = `${BLOCKRUN_API4}/v1/realface/enroll`;
2167
2156
  const reqBody = JSON.stringify({ name, image_url, group_id });
2168
- const resp402 = await fetchWithTimeout4(enrollUrl, {
2157
+ const resp402 = await fetchWithTimeout(enrollUrl, {
2169
2158
  method: "POST",
2170
2159
  headers: { "Content-Type": "application/json" },
2171
2160
  body: reqBody
@@ -2191,7 +2180,7 @@ Enroll one: blockrun_realface action:"init" name:"\u2026" (real person) or actio
2191
2180
  extra: details.extra
2192
2181
  }
2193
2182
  );
2194
- const resp = await fetchWithTimeout4(enrollUrl, {
2183
+ const resp = await fetchWithTimeout(enrollUrl, {
2195
2184
  method: "POST",
2196
2185
  headers: {
2197
2186
  "Content-Type": "application/json",
@@ -3075,18 +3064,12 @@ function initializeMcpServer(server) {
3075
3064
  "blockrun://models",
3076
3065
  { description: "Available AI models with pricing", mimeType: "application/json" },
3077
3066
  async () => {
3078
- const llm = getClient();
3079
- if (!modelCache.models) {
3080
- modelCache.models = "listAllModels" in llm ? await llm.listAllModels() : await llm.listModels();
3081
- setTimeout(() => {
3082
- modelCache.models = null;
3083
- }, 5 * 60 * 1e3);
3084
- }
3067
+ const models = await loadModels(getClient(), modelCache);
3085
3068
  return {
3086
3069
  contents: [{
3087
3070
  uri: "blockrun://models",
3088
3071
  mimeType: "application/json",
3089
- text: JSON.stringify(modelCache.models, null, 2)
3072
+ text: JSON.stringify(models, null, 2)
3090
3073
  }]
3091
3074
  };
3092
3075
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/mcp",
3
- "version": "0.21.4",
3
+ "version": "0.21.5",
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",