@blockrun/mcp 0.18.1 → 0.19.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 (3) hide show
  1. package/README.md +8 -4
  2. package/dist/index.js +340 -88
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -142,6 +142,7 @@ $5 covers ~5,000 market queries, ~500 Exa searches, ~250 image generations, or ~
142
142
  | `blockrun_video` | xAI Grok Imagine Video + ByteDance Seedance 1.5/2.0/2.0-fast (720p + audio defaults); RealFace asset → real-person video | $0.05–0.298/sec |
143
143
  | `blockrun_realface` | Enroll a real person (phone liveness → `ta_xxxx` asset) for Seedance 2.0 real-person video | free; $0.01 to enroll |
144
144
  | `blockrun_music` | MiniMax music generation | per track |
145
+ | `blockrun_speech` | ElevenLabs text-to-speech (Flash/Turbo/Multilingual/v3, 8 voice aliases) + cinematic sound effects; free voice listing | $0.05–0.10/1k chars; $0.0525/effect |
145
146
  | `blockrun_price` | Pyth-backed realtime + OHLC — crypto / FX / commodity (free), 12 stock markets (paid) | free or $0.001/call |
146
147
  | `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 |
147
148
  | `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 |
@@ -185,13 +186,16 @@ What kinds of questions can Claude (or any LLM agent) answer once BlockRun MCP i
185
186
  5. **Image generation with on-image text**
186
187
  > *"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
188
 
188
- 6. **Voice phone-out**
189
+ 6. **Give your agent a voice**
190
+ > *"Use blockrun to speak this with the sarah voice."* → `blockrun_speech` action:`speak` (default), `input` + `voice` → hosted MP3 URL. Sound effects via action:`sound_effect`
191
+
192
+ 7. **Voice phone-out**
189
193
  > *"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
194
 
191
- 7. **Multi-agent research with budget cap**
195
+ 8. **Multi-agent research with budget cap**
192
196
  > *"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`
193
197
 
194
- 8. **Cross-chain SQL forensics**
198
+ 9. **Cross-chain SQL forensics**
195
199
  > *"Top 10 tokens by DEX volume on Base in the last 24h."* → `blockrun_surf` path:`onchain/sql`, body: `{ sql: "SELECT..." }`
196
200
 
197
201
  ---
@@ -261,7 +265,7 @@ Chain selection priority (see `src/utils/wallet.ts`):
261
265
  - Base → Solana: `echo solana > ~/.blockrun/.chain`, then set `SOLANA_WALLET_KEY` or create `~/.blockrun/.solana-session`
262
266
  - Solana → Base: `echo base > ~/.blockrun/.chain` (the existing `.session` is reused, so it's the same Base wallet)
263
267
 
264
- Some media and paid market-data tools still settle on Base only: `blockrun_image`, `blockrun_music`, `blockrun_video`, and paid stock `blockrun_price` calls. In Solana mode they fail before creating or charging a Base wallet.
268
+ Some media and paid market-data tools still settle on Base only: `blockrun_image`, `blockrun_music`, `blockrun_speech`, `blockrun_video`, and paid stock `blockrun_price` calls. In Solana mode they fail before creating or charging a Base wallet.
265
269
 
266
270
  The server also runs a non-blocking npm registry check at startup and prints an `Update available` notice to stderr when a newer `@blockrun/mcp` version exists. Upgrade by re-running the install command — no manual `npm update` needed.
267
271
 
package/dist/index.js CHANGED
@@ -229,7 +229,7 @@ var USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
229
229
  var BASE_CHAIN_ID = "8453";
230
230
  var MODEL_TIERS = {
231
231
  fast: ["google/gemini-3.5-flash", "google/gemini-2.5-flash", "google/gemini-3.1-flash-lite", "openai/gpt-5-mini", "deepseek/deepseek-chat", "google/gemini-3-flash-preview"],
232
- balanced: ["openai/gpt-5.4", "anthropic/claude-sonnet-4.6", "google/gemini-2.5-pro", "moonshot/kimi-k2.6", "openai/gpt-5.3", "google/gemini-3.1-pro"],
232
+ balanced: ["openai/gpt-5.5", "anthropic/claude-sonnet-4.6", "google/gemini-3.1-pro", "moonshot/kimi-k2.6", "openai/gpt-5.3", "openai/gpt-5.4"],
233
233
  powerful: ["anthropic/claude-opus-4.8", "openai/gpt-5.4-pro", "anthropic/claude-opus-4.7", "anthropic/claude-opus-4.6", "openai/o3", "openai/gpt-5.4"],
234
234
  cheap: ["zai/glm-5", "zai/glm-5-turbo", "nvidia/gpt-oss-120b", "nvidia/deepseek-v3.2", "google/gemini-2.5-flash", "deepseek/deepseek-chat", "openai/gpt-5.4-nano"],
235
235
  reasoning: ["anthropic/claude-opus-4.8", "openai/o3", "openai/o1", "openai/o3-mini", "deepseek/deepseek-reasoner", "moonshot/kimi-k2.6", "openai/gpt-5.3-codex"],
@@ -338,8 +338,9 @@ ${parts.join("\n")}`;
338
338
  }
339
339
  function formatError(message) {
340
340
  const msgLower = message.toLowerCase();
341
- const isPaymentError = msgLower.includes("402") || msgLower.includes("balance") || msgLower.includes("insufficient") || msgLower.includes("payment") && !msgLower.includes("500");
342
- const isServerError = msgLower.includes("500") || msgLower.includes("api error after payment");
341
+ const hasStatus = (code) => new RegExp(`(^|[^0-9.])${code}([^0-9]|$)`).test(msgLower);
342
+ const isPaymentError = hasStatus("402") || msgLower.includes("balance") || msgLower.includes("insufficient") || msgLower.includes("payment") && !hasStatus("500");
343
+ const isServerError = hasStatus("500") || msgLower.includes("api error after payment");
343
344
  let errorText = `Error: ${message}`;
344
345
  if (isServerError) {
345
346
  errorText += `
@@ -874,7 +875,7 @@ Run blockrun_models to see all 41+ models with pricing.`,
874
875
  z2.object({ type: z2.literal("image_url"), image_url: z2.object({ url: z2.string().describe("https URL or data:<mime>;base64,<...> URI") }) })
875
876
  ]))
876
877
  ]).describe("Plain text, or an array of parts for multimodal input (text + image_url). Images are honored on the native anthropic/claude-* path.")
877
- })).optional().describe("Conversation history for multi-turn context. When provided, 'message' is appended as the final user turn. Use with explicit 'model' param (defaults to 'openai/gpt-5.4' if not specified). Note: if you include a role:'system' entry in messages[], do not also pass the system param to avoid duplicate system messages.")
878
+ })).optional().describe("Conversation history for multi-turn context. When provided, 'message' is appended as the final user turn. Use with explicit 'model' param (defaults to 'openai/gpt-5.5' if not specified). Note: if you include a role:'system' entry in messages[], do not also pass the system param to avoid duplicate system messages.")
878
879
  }
879
880
  },
880
881
  async ({ message, model, mode, routing, routing_profile, system, max_tokens, temperature, response_format, stop, thinking, agent_id, messages }) => {
@@ -945,7 +946,7 @@ ${result.response}` }],
945
946
  }
946
947
  }
947
948
  if (messages && messages.length > 0) {
948
- const targetModel = model || MODEL_TIERS[mode ?? "balanced"]?.[0] || "openai/gpt-5.4";
949
+ const targetModel = model || MODEL_TIERS[mode ?? "balanced"]?.[0] || "openai/gpt-5.5";
949
950
  const fullMessages = [
950
951
  ...system ? [{ role: "system", content: system }] : [],
951
952
  ...messages,
@@ -1367,7 +1368,7 @@ async function fetchWithTimeout(url, options, timeoutMs) {
1367
1368
  }
1368
1369
  }
1369
1370
 
1370
- // src/tools/video.ts
1371
+ // src/tools/speech.ts
1371
1372
  import { z as z6 } from "zod";
1372
1373
  import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
1373
1374
  import {
@@ -1376,6 +1377,256 @@ import {
1376
1377
  extractPaymentDetails as extractPaymentDetails2
1377
1378
  } from "@blockrun/llm";
1378
1379
  var BLOCKRUN_API2 = "https://blockrun.ai/api";
1380
+ var SPEECH_TIMEOUT = 6e4;
1381
+ var VOICES_TIMEOUT = 15e3;
1382
+ var MARGIN = 1.05;
1383
+ var MIN_PAYMENT_USD = 1e-3;
1384
+ var SOUND_EFFECT_COST = 0.05 * MARGIN;
1385
+ var SPEECH_MODELS = {
1386
+ "elevenlabs/flash-v2.5": { pricePer1kChars: 0.05, maxInputChars: 4e4 },
1387
+ "elevenlabs/turbo-v2.5": { pricePer1kChars: 0.05, maxInputChars: 4e4 },
1388
+ "elevenlabs/multilingual-v2": { pricePer1kChars: 0.1, maxInputChars: 1e4 },
1389
+ "elevenlabs/v3": { pricePer1kChars: 0.1, maxInputChars: 5e3 }
1390
+ };
1391
+ var VOICE_ALIASES = [
1392
+ { alias: "sarah", description: "Mature, reassuring, confident (default)" },
1393
+ { alias: "george", description: "Warm, captivating storyteller" },
1394
+ { alias: "laura", description: "Enthusiast, quirky" },
1395
+ { alias: "charlie", description: "Deep, confident, energetic" },
1396
+ { alias: "river", description: "Relaxed, neutral, informative" },
1397
+ { alias: "roger", description: "Laid-back, casual, resonant" },
1398
+ { alias: "callum", description: "Husky trickster" },
1399
+ { alias: "harry", description: "Fierce warrior" }
1400
+ ];
1401
+ function speechCost(model, charCount) {
1402
+ const m = SPEECH_MODELS[model];
1403
+ const raw = charCount / 1e3 * (m?.pricePer1kChars ?? 0.05) * MARGIN;
1404
+ return Math.max(raw, MIN_PAYMENT_USD);
1405
+ }
1406
+ function registerSpeechTool(server, budget) {
1407
+ server.registerTool(
1408
+ "blockrun_speech",
1409
+ {
1410
+ description: `ElevenLabs voice via BlockRun x402 \u2014 speak text aloud, generate sound effects, list voices.
1411
+
1412
+ Actions:
1413
+ - speak (default): text-to-speech. E.g. "speak this with the sarah voice". Price = chars/1000 \xD7 rate (min $0.001), quoted before payment.
1414
+ - sound_effect: cinematic sound effects from a text prompt, up to 22s ($0.0525/clip)
1415
+ - voices: list available voices (free)
1416
+
1417
+ Models (speak): elevenlabs/flash-v2.5 ($0.05/1k chars, ~75ms, default), elevenlabs/turbo-v2.5 ($0.05/1k), elevenlabs/multilingual-v2 ($0.10/1k, narration), elevenlabs/v3 ($0.10/1k, most expressive)
1418
+
1419
+ Voice aliases: sarah (default), george, laura, charlie, river, roger, callum, harry \u2014 or any raw ElevenLabs voice_id.
1420
+
1421
+ Returns a hosted audio URL \u2014 download immediately if you need to keep the file.`,
1422
+ inputSchema: {
1423
+ action: z6.enum(["speak", "sound_effect", "voices"]).optional().default("speak").describe("speak: text-to-speech (default). sound_effect: generate a sound effect. voices: list voices (free)."),
1424
+ input: z6.string().optional().describe("speak: text to synthesize. sound_effect: description of the sound, e.g. 'rain on a tin roof, distant thunder' (max 1000 chars)."),
1425
+ voice: z6.string().optional().describe("Voice alias (sarah, george, laura, charlie, river, roger, callum, harry) or raw ElevenLabs voice_id. Default: sarah."),
1426
+ model: z6.enum(["elevenlabs/flash-v2.5", "elevenlabs/turbo-v2.5", "elevenlabs/multilingual-v2", "elevenlabs/v3"]).optional().default("elevenlabs/flash-v2.5").describe("Speech model (speak only)"),
1427
+ response_format: z6.enum(["mp3", "opus", "pcm", "wav"]).optional().default("mp3").describe("Audio format"),
1428
+ speed: z6.number().min(0.7).max(1.2).optional().describe("Playback speed 0.7-1.2 (speak only)"),
1429
+ duration_seconds: z6.number().min(0.5).max(22).optional().describe("Sound effect length in seconds (sound_effect only; default: auto)"),
1430
+ prompt_influence: z6.number().min(0).max(1).optional().describe("How literally to follow the prompt, 0-1 (sound_effect only)"),
1431
+ agent_id: z6.string().optional().describe("Agent identifier for budget tracking and enforcement.")
1432
+ }
1433
+ },
1434
+ async ({ action, input, voice, model, response_format, speed, duration_seconds, prompt_influence, agent_id }) => {
1435
+ try {
1436
+ if (action === "voices") {
1437
+ return await listVoices();
1438
+ }
1439
+ if (getChain() !== "base") {
1440
+ return {
1441
+ content: [{ type: "text", text: formatError("blockrun_speech currently settles on Base only. Switch BlockRun to Base (for example: run blockrun_wallet with action:chain chain:base) and fund the Base wallet with USDC.") }],
1442
+ isError: true
1443
+ };
1444
+ }
1445
+ if (!input?.trim()) {
1446
+ return {
1447
+ content: [{ type: "text", text: formatError(`'input' is required for action:"${action}" \u2014 the text to ${action === "speak" ? "synthesize" : "describe the sound effect"}.`) }],
1448
+ isError: true
1449
+ };
1450
+ }
1451
+ let endpoint;
1452
+ let body;
1453
+ let cost;
1454
+ if (action === "sound_effect") {
1455
+ if (input.length > 1e3) {
1456
+ return {
1457
+ content: [{ type: "text", text: formatError(`Sound effect description too long: ${input.length} characters (max 1000).`) }],
1458
+ isError: true
1459
+ };
1460
+ }
1461
+ endpoint = `${BLOCKRUN_API2}/v1/audio/sound-effects`;
1462
+ body = { model: "elevenlabs/sound-effects", text: input, response_format };
1463
+ if (duration_seconds !== void 0) body.duration_seconds = duration_seconds;
1464
+ if (prompt_influence !== void 0) body.prompt_influence = prompt_influence;
1465
+ cost = SOUND_EFFECT_COST;
1466
+ } else {
1467
+ const modelInfo = SPEECH_MODELS[model] ?? SPEECH_MODELS["elevenlabs/flash-v2.5"];
1468
+ if (input.length > modelInfo.maxInputChars) {
1469
+ return {
1470
+ content: [{ type: "text", text: formatError(`Input too long: ${input.length.toLocaleString("en-US")} characters (max ${modelInfo.maxInputChars.toLocaleString("en-US")} for ${model}). Split the text or use elevenlabs/flash-v2.5 (40k max).`) }],
1471
+ isError: true
1472
+ };
1473
+ }
1474
+ endpoint = `${BLOCKRUN_API2}/v1/audio/speech`;
1475
+ body = { model, input, voice: voice || "sarah", response_format };
1476
+ if (speed !== void 0) body.speed = speed;
1477
+ cost = speechCost(model, input.length);
1478
+ }
1479
+ const budgetCheck = checkBudget(budget, agent_id, cost);
1480
+ if (!budgetCheck.allowed) {
1481
+ return {
1482
+ content: [{ type: "text", text: `${budgetCheck.reason}. Use blockrun_wallet action:"report" to see usage or action:"delegate" to increase agent budget.` }],
1483
+ isError: true
1484
+ };
1485
+ }
1486
+ const privateKey = getOrCreateWalletKey();
1487
+ const account = privateKeyToAccount2(privateKey);
1488
+ const resp402 = await fetchWithTimeout2(endpoint, {
1489
+ method: "POST",
1490
+ headers: { "Content-Type": "application/json" },
1491
+ body: JSON.stringify(body)
1492
+ }, 15e3);
1493
+ if (resp402.status !== 402) {
1494
+ const data2 = await resp402.json().catch(() => ({}));
1495
+ throw new Error(`Expected 402, got ${resp402.status}: ${JSON.stringify(data2)}`);
1496
+ }
1497
+ const prHeader = resp402.headers.get("payment-required") || resp402.headers.get("PAYMENT-REQUIRED");
1498
+ if (!prHeader) throw new Error("No PAYMENT-REQUIRED header in 402 response");
1499
+ const paymentRequired = parsePaymentRequired2(prHeader);
1500
+ const details = extractPaymentDetails2(paymentRequired);
1501
+ const paymentPayload = await createPaymentPayload2(
1502
+ privateKey,
1503
+ account.address,
1504
+ details.recipient,
1505
+ details.amount,
1506
+ details.network || "eip155:8453",
1507
+ {
1508
+ resourceUrl: details.resource?.url || endpoint,
1509
+ resourceDescription: details.resource?.description || "BlockRun Speech",
1510
+ maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
1511
+ extra: details.extra
1512
+ }
1513
+ );
1514
+ const resp = await fetchWithTimeout2(endpoint, {
1515
+ method: "POST",
1516
+ headers: {
1517
+ "Content-Type": "application/json",
1518
+ "PAYMENT-SIGNATURE": paymentPayload
1519
+ },
1520
+ body: JSON.stringify(body)
1521
+ }, SPEECH_TIMEOUT);
1522
+ if (resp.status === 402) {
1523
+ throw new Error("Payment rejected. Check your wallet balance.");
1524
+ }
1525
+ if (!resp.ok) {
1526
+ const errBody = await resp.json().catch(() => ({ error: "Request failed" }));
1527
+ throw new Error(`API error ${resp.status}: ${JSON.stringify(errBody)}`);
1528
+ }
1529
+ const data = await resp.json();
1530
+ const clip = data.data?.[0];
1531
+ if (!clip?.url) throw new Error("No audio URL in response");
1532
+ const txHash = resp.headers.get("X-Payment-Receipt") || resp.headers.get("x-payment-receipt");
1533
+ recordSpending(budget, cost, agent_id);
1534
+ const lines = [
1535
+ action === "sound_effect" ? `\u{1F50A} Sound effect ready!` : `\u{1F5E3}\uFE0F Speech ready!`,
1536
+ `URL: ${clip.url}`,
1537
+ `Format: ${clip.format || response_format}`,
1538
+ ...clip.characters !== void 0 ? [`Characters: ${clip.characters}`] : [],
1539
+ ...clip.duration_seconds !== void 0 ? [`Duration: ${clip.duration_seconds}s`] : [],
1540
+ `Model: ${data.model || (action === "sound_effect" ? "elevenlabs/sound-effects" : model)}`,
1541
+ `Cost: $${cost.toFixed(4)}`,
1542
+ ...txHash ? [`Tx: ${txHash}`] : [],
1543
+ ``,
1544
+ `Note: This URL may expire \u2014 download it now if you need to keep the file.`
1545
+ ];
1546
+ return {
1547
+ content: [{ type: "text", text: lines.join("\n") }],
1548
+ structuredContent: {
1549
+ url: clip.url,
1550
+ format: clip.format || response_format,
1551
+ ...clip.characters !== void 0 ? { characters: clip.characters } : {},
1552
+ ...clip.duration_seconds !== void 0 ? { duration_seconds: clip.duration_seconds } : {},
1553
+ model: data.model || (action === "sound_effect" ? "elevenlabs/sound-effects" : model),
1554
+ cost_usd: cost,
1555
+ ...txHash ? { txHash } : {}
1556
+ }
1557
+ };
1558
+ } catch (err) {
1559
+ const errMsg = err instanceof Error ? err.message : String(err);
1560
+ if (errMsg.includes("balance") || errMsg.includes("payment") || errMsg.includes("402") || errMsg.includes("rejected")) {
1561
+ return {
1562
+ content: [{ type: "text", text: `Speech generation requires payment. Run blockrun_wallet with action: "setup" for funding instructions.
1563
+ Error: ${errMsg}` }],
1564
+ isError: true
1565
+ };
1566
+ }
1567
+ if (errMsg.includes("abort") || errMsg.includes("timeout") || errMsg.includes("Timeout")) {
1568
+ return {
1569
+ content: [{ type: "text", text: `Speech generation timed out \u2014 please try again.
1570
+ Error: ${errMsg}` }],
1571
+ isError: true
1572
+ };
1573
+ }
1574
+ return {
1575
+ content: [{ type: "text", text: formatError(`Speech generation failed: ${errMsg}`) }],
1576
+ isError: true
1577
+ };
1578
+ }
1579
+ }
1580
+ );
1581
+ }
1582
+ async function listVoices() {
1583
+ try {
1584
+ const resp = await fetchWithTimeout2(`${BLOCKRUN_API2}/v1/audio/voices`, { method: "GET" }, VOICES_TIMEOUT);
1585
+ if (resp.ok) {
1586
+ const payload = await resp.json();
1587
+ const voices = payload.data || [];
1588
+ if (voices.length > 0) {
1589
+ const lines2 = voices.map((v) => {
1590
+ const tags = v.labels ? Object.values(v.labels).join(", ") : "";
1591
+ return `- ${v.alias ? `${v.alias} ` : ""}(${v.voice_id})${v.name ? ` \u2014 ${v.name}` : ""}${tags ? ` [${tags}]` : ""}`;
1592
+ });
1593
+ return {
1594
+ content: [{ type: "text", text: `Available voices (pass alias or voice_id as 'voice'):
1595
+ ${lines2.join("\n")}` }],
1596
+ structuredContent: { voices }
1597
+ };
1598
+ }
1599
+ }
1600
+ } catch {
1601
+ }
1602
+ const lines = VOICE_ALIASES.map((v) => `- ${v.alias} \u2014 ${v.description}`);
1603
+ return {
1604
+ content: [{ type: "text", text: `Built-in voice aliases (pass as 'voice'; full catalog temporarily unavailable):
1605
+ ${lines.join("\n")}
1606
+
1607
+ Any raw ElevenLabs voice_id also works.` }],
1608
+ structuredContent: { voices: VOICE_ALIASES }
1609
+ };
1610
+ }
1611
+ async function fetchWithTimeout2(url, options, timeoutMs) {
1612
+ const controller = new AbortController();
1613
+ const id = setTimeout(() => controller.abort(), timeoutMs);
1614
+ try {
1615
+ return await fetch(url, { ...options, signal: controller.signal });
1616
+ } finally {
1617
+ clearTimeout(id);
1618
+ }
1619
+ }
1620
+
1621
+ // src/tools/video.ts
1622
+ import { z as z7 } from "zod";
1623
+ import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
1624
+ import {
1625
+ createPaymentPayload as createPaymentPayload3,
1626
+ parsePaymentRequired as parsePaymentRequired3,
1627
+ extractPaymentDetails as extractPaymentDetails3
1628
+ } from "@blockrun/llm";
1629
+ var BLOCKRUN_API3 = "https://blockrun.ai/api";
1379
1630
  var TOTAL_BUDGET_MS = 3e5;
1380
1631
  var POLL_INTERVAL_MS = 5e3;
1381
1632
  var VIDEO_PRICE_PER_SECOND = {
@@ -1420,12 +1671,12 @@ RealFace: to generate video of a SPECIFIC real person, first enroll them with bl
1420
1671
 
1421
1672
  Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GCS so URLs don't expire).`,
1422
1673
  inputSchema: {
1423
- prompt: z6.string().describe("Text description of the video to generate. E.g. 'a red apple slowly spinning on a wooden table', 'a hummingbird hovering near a red flower, ultra slow motion'"),
1424
- image_url: z6.string().url().optional().describe("Optional seed image URL for image-to-video generation"),
1425
- real_face_asset_id: z6.string().regex(/^ta_[A-Za-z0-9]+$/, "token360 asset id like 'ta_xxxx'").optional().describe("BytePlus RealFace asset id (from blockrun_realface enroll/list) to generate video of a specific real person. Seedance 2.0 / 2.0-fast only. Mutually exclusive with image_url."),
1426
- duration_seconds: z6.number().int().min(1).max(60).optional().describe("Duration to bill for (defaults to the model's default \u2014 8s for xAI, 5s for Seedance; Seedance supports up to 10s)."),
1427
- model: z6.enum(["azure/sora-2", "xai/grok-imagine-video", "bytedance/seedance-1.5-pro", "bytedance/seedance-2.0-fast", "bytedance/seedance-2.0"]).optional().default("xai/grok-imagine-video").describe("Video model to use"),
1428
- agent_id: z6.string().optional().describe("Agent identifier for budget tracking and enforcement.")
1674
+ prompt: z7.string().describe("Text description of the video to generate. E.g. 'a red apple slowly spinning on a wooden table', 'a hummingbird hovering near a red flower, ultra slow motion'"),
1675
+ image_url: z7.string().url().optional().describe("Optional seed image URL for image-to-video generation"),
1676
+ real_face_asset_id: z7.string().regex(/^ta_[A-Za-z0-9]+$/, "token360 asset id like 'ta_xxxx'").optional().describe("BytePlus RealFace asset id (from blockrun_realface enroll/list) to generate video of a specific real person. Seedance 2.0 / 2.0-fast only. Mutually exclusive with image_url."),
1677
+ duration_seconds: z7.number().int().min(1).max(60).optional().describe("Duration to bill for (defaults to the model's default \u2014 8s for xAI, 5s for Seedance; Seedance supports up to 10s)."),
1678
+ model: z7.enum(["azure/sora-2", "xai/grok-imagine-video", "bytedance/seedance-1.5-pro", "bytedance/seedance-2.0-fast", "bytedance/seedance-2.0"]).optional().default("xai/grok-imagine-video").describe("Video model to use"),
1679
+ agent_id: z7.string().optional().describe("Agent identifier for budget tracking and enforcement.")
1429
1680
  }
1430
1681
  },
1431
1682
  async ({ prompt, image_url, real_face_asset_id, duration_seconds, model, agent_id }) => {
@@ -1463,13 +1714,13 @@ Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GC
1463
1714
  };
1464
1715
  }
1465
1716
  const privateKey = getOrCreateWalletKey();
1466
- const account = privateKeyToAccount2(privateKey);
1467
- const submitUrl = `${BLOCKRUN_API2}/v1/videos/generations`;
1717
+ const account = privateKeyToAccount3(privateKey);
1718
+ const submitUrl = `${BLOCKRUN_API3}/v1/videos/generations`;
1468
1719
  const body = { model: selectedModel, prompt };
1469
1720
  if (image_url) body.image_url = image_url;
1470
1721
  if (real_face_asset_id) body.real_face_asset_id = real_face_asset_id;
1471
1722
  if (duration_seconds !== void 0) body.duration_seconds = duration_seconds;
1472
- const resp402 = await fetchWithTimeout2(submitUrl, {
1723
+ const resp402 = await fetchWithTimeout3(submitUrl, {
1473
1724
  method: "POST",
1474
1725
  headers: { "Content-Type": "application/json" },
1475
1726
  body: JSON.stringify(body)
@@ -1480,9 +1731,9 @@ Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GC
1480
1731
  }
1481
1732
  const prHeader = resp402.headers.get("payment-required") || resp402.headers.get("PAYMENT-REQUIRED");
1482
1733
  if (!prHeader) throw new Error("No PAYMENT-REQUIRED header in 402 response");
1483
- const paymentRequired = parsePaymentRequired2(prHeader);
1484
- const details = extractPaymentDetails2(paymentRequired);
1485
- const paymentPayload = await createPaymentPayload2(
1734
+ const paymentRequired = parsePaymentRequired3(prHeader);
1735
+ const details = extractPaymentDetails3(paymentRequired);
1736
+ const paymentPayload = await createPaymentPayload3(
1486
1737
  privateKey,
1487
1738
  account.address,
1488
1739
  details.recipient,
@@ -1497,7 +1748,7 @@ Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GC
1497
1748
  extra: details.extra
1498
1749
  }
1499
1750
  );
1500
- const submitResp = await fetchWithTimeout2(submitUrl, {
1751
+ const submitResp = await fetchWithTimeout3(submitUrl, {
1501
1752
  method: "POST",
1502
1753
  headers: {
1503
1754
  "Content-Type": "application/json",
@@ -1516,13 +1767,13 @@ Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GC
1516
1767
  if (!submitData.id || !submitData.poll_url) {
1517
1768
  throw new Error(`Submit response missing id/poll_url: ${JSON.stringify(submitData)}`);
1518
1769
  }
1519
- const pollAbsoluteUrl = submitData.poll_url.startsWith("http") ? submitData.poll_url : `${BLOCKRUN_API2.replace(/\/api$/, "")}${submitData.poll_url}`;
1770
+ const pollAbsoluteUrl = submitData.poll_url.startsWith("http") ? submitData.poll_url : `${BLOCKRUN_API3.replace(/\/api$/, "")}${submitData.poll_url}`;
1520
1771
  const startedAt = Date.now();
1521
1772
  let lastStatus = submitData.status || "queued";
1522
1773
  let completed = null;
1523
1774
  while (Date.now() - startedAt < TOTAL_BUDGET_MS) {
1524
1775
  await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
1525
- const pollResp = await fetchWithTimeout2(pollAbsoluteUrl, {
1776
+ const pollResp = await fetchWithTimeout3(pollAbsoluteUrl, {
1526
1777
  method: "GET",
1527
1778
  headers: { "PAYMENT-SIGNATURE": paymentPayload }
1528
1779
  }, 9e4);
@@ -1601,7 +1852,7 @@ Error: ${errMsg}` }],
1601
1852
  }
1602
1853
  );
1603
1854
  }
1604
- async function fetchWithTimeout2(url, options, timeoutMs) {
1855
+ async function fetchWithTimeout3(url, options, timeoutMs) {
1605
1856
  const controller = new AbortController();
1606
1857
  const id = setTimeout(() => controller.abort(), timeoutMs);
1607
1858
  try {
@@ -1612,16 +1863,16 @@ async function fetchWithTimeout2(url, options, timeoutMs) {
1612
1863
  }
1613
1864
 
1614
1865
  // src/tools/realface.ts
1615
- import { z as z7 } from "zod";
1616
- import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
1866
+ import { z as z8 } from "zod";
1867
+ import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
1617
1868
  import {
1618
- createPaymentPayload as createPaymentPayload3,
1619
- parsePaymentRequired as parsePaymentRequired3,
1620
- extractPaymentDetails as extractPaymentDetails3
1869
+ createPaymentPayload as createPaymentPayload4,
1870
+ parsePaymentRequired as parsePaymentRequired4,
1871
+ extractPaymentDetails as extractPaymentDetails4
1621
1872
  } from "@blockrun/llm";
1622
- var BLOCKRUN_API3 = "https://blockrun.ai/api";
1873
+ var BLOCKRUN_API4 = "https://blockrun.ai/api";
1623
1874
  var ENROLLMENT_PRICE_USD = 0.01;
1624
- async function fetchWithTimeout3(url, options, timeoutMs) {
1875
+ async function fetchWithTimeout4(url, options, timeoutMs) {
1625
1876
  const controller = new AbortController();
1626
1877
  const id = setTimeout(() => controller.abort(), timeoutMs);
1627
1878
  try {
@@ -1652,11 +1903,11 @@ Typical flow:
1652
1903
 
1653
1904
  Privacy: BlockRun does not store face/liveness data \u2014 only the asset id, name, and the photo URL you supply.`,
1654
1905
  inputSchema: {
1655
- action: z7.enum(["init", "status", "enroll", "list"]).describe("What to do"),
1656
- name: z7.string().min(1).max(64).optional().describe("Display name for the person (required for init and enroll)."),
1657
- group_id: z7.string().regex(/^legacy_rf_\d+$/).optional().describe("Asset-group id from init (required for status and enroll; pass to init to refresh an expired H5 link)."),
1658
- image_url: z7.string().url().optional().describe("Public HTTPS URL to a clear front-facing face photo (JPG/PNG/WEBP, \u226410MB). Required for enroll."),
1659
- agent_id: z7.string().optional().describe("Agent identifier for budget tracking and enforcement (enroll only).")
1906
+ action: z8.enum(["init", "status", "enroll", "list"]).describe("What to do"),
1907
+ name: z8.string().min(1).max(64).optional().describe("Display name for the person (required for init and enroll)."),
1908
+ group_id: z8.string().regex(/^legacy_rf_\d+$/).optional().describe("Asset-group id from init (required for status and enroll; pass to init to refresh an expired H5 link)."),
1909
+ image_url: z8.string().url().optional().describe("Public HTTPS URL to a clear front-facing face photo (JPG/PNG/WEBP, \u226410MB). Required for enroll."),
1910
+ agent_id: z8.string().optional().describe("Agent identifier for budget tracking and enforcement (enroll only).")
1660
1911
  }
1661
1912
  },
1662
1913
  async ({ action, name, group_id, image_url, agent_id }) => {
@@ -1667,7 +1918,7 @@ Privacy: BlockRun does not store face/liveness data \u2014 only the asset id, na
1667
1918
  }
1668
1919
  const body = { name };
1669
1920
  if (group_id) body.groupId = group_id;
1670
- const resp = await fetchWithTimeout3(`${BLOCKRUN_API3}/v1/realface/init`, {
1921
+ const resp = await fetchWithTimeout4(`${BLOCKRUN_API4}/v1/realface/init`, {
1671
1922
  method: "POST",
1672
1923
  headers: { "Content-Type": "application/json" },
1673
1924
  body: JSON.stringify(body)
@@ -1716,7 +1967,7 @@ QR opened for scanning (${qrPath}).`;
1716
1967
  if (!group_id) {
1717
1968
  return { content: [{ type: "text", text: formatError('group_id is required for action:"status".') }], isError: true };
1718
1969
  }
1719
- const resp = await fetchWithTimeout3(`${BLOCKRUN_API3}/v1/realface/status?groupId=${encodeURIComponent(group_id)}`, {
1970
+ const resp = await fetchWithTimeout4(`${BLOCKRUN_API4}/v1/realface/status?groupId=${encodeURIComponent(group_id)}`, {
1720
1971
  method: "GET"
1721
1972
  }, 3e4);
1722
1973
  const data = await resp.json().catch(() => ({}));
@@ -1744,8 +1995,8 @@ QR opened for scanning (${qrPath}).`;
1744
1995
  };
1745
1996
  }
1746
1997
  if (action === "list") {
1747
- const account = privateKeyToAccount3(getOrCreateWalletKey());
1748
- const resp = await fetchWithTimeout3(`${BLOCKRUN_API3}/v1/wallet/${account.address}/realfaces`, {
1998
+ const account = privateKeyToAccount4(getOrCreateWalletKey());
1999
+ const resp = await fetchWithTimeout4(`${BLOCKRUN_API4}/v1/wallet/${account.address}/realfaces`, {
1749
2000
  method: "GET"
1750
2001
  }, 3e4);
1751
2002
  const data = await resp.json().catch(() => ({}));
@@ -1783,10 +2034,10 @@ Enroll one: blockrun_realface action:"init" name:"\u2026".` }],
1783
2034
  return { content: [{ type: "text", text: `${budgetCheck.reason}. Use blockrun_wallet action:"report" to see usage or action:"delegate" to increase agent budget.` }], isError: true };
1784
2035
  }
1785
2036
  const privateKey = getOrCreateWalletKey();
1786
- const account = privateKeyToAccount3(privateKey);
1787
- const enrollUrl = `${BLOCKRUN_API3}/v1/realface/enroll`;
2037
+ const account = privateKeyToAccount4(privateKey);
2038
+ const enrollUrl = `${BLOCKRUN_API4}/v1/realface/enroll`;
1788
2039
  const reqBody = JSON.stringify({ name, image_url, group_id });
1789
- const resp402 = await fetchWithTimeout3(enrollUrl, {
2040
+ const resp402 = await fetchWithTimeout4(enrollUrl, {
1790
2041
  method: "POST",
1791
2042
  headers: { "Content-Type": "application/json" },
1792
2043
  body: reqBody
@@ -1797,9 +2048,9 @@ Enroll one: blockrun_realface action:"init" name:"\u2026".` }],
1797
2048
  }
1798
2049
  const prHeader = resp402.headers.get("payment-required") || resp402.headers.get("PAYMENT-REQUIRED");
1799
2050
  if (!prHeader) throw new Error("No PAYMENT-REQUIRED header in 402 response");
1800
- const paymentRequired = parsePaymentRequired3(prHeader);
1801
- const details = extractPaymentDetails3(paymentRequired);
1802
- const paymentPayload = await createPaymentPayload3(
2051
+ const paymentRequired = parsePaymentRequired4(prHeader);
2052
+ const details = extractPaymentDetails4(paymentRequired);
2053
+ const paymentPayload = await createPaymentPayload4(
1803
2054
  privateKey,
1804
2055
  account.address,
1805
2056
  details.recipient,
@@ -1812,7 +2063,7 @@ Enroll one: blockrun_realface action:"init" name:"\u2026".` }],
1812
2063
  extra: details.extra
1813
2064
  }
1814
2065
  );
1815
- const resp = await fetchWithTimeout3(enrollUrl, {
2066
+ const resp = await fetchWithTimeout4(enrollUrl, {
1816
2067
  method: "POST",
1817
2068
  headers: {
1818
2069
  "Content-Type": "application/json",
@@ -1874,7 +2125,7 @@ Error: ${errMsg}` }],
1874
2125
  }
1875
2126
 
1876
2127
  // src/tools/search.ts
1877
- import { z as z8 } from "zod";
2128
+ import { z as z9 } from "zod";
1878
2129
 
1879
2130
  // src/utils/body.ts
1880
2131
  function coerceBody(body) {
@@ -1910,9 +2161,9 @@ Common shape:
1910
2161
 
1911
2162
  Full request shape + worked examples in the \`search\` skill (\`skills/search/SKILL.md\`).`,
1912
2163
  inputSchema: {
1913
- path: z8.string().optional().default("").describe("Endpoint sub-path under /v1/search/ (default empty = root /v1/search). Reserved for future surfaces."),
1914
- body: z8.any().optional().describe("Request body. At minimum { query: '...' }. Sent as POST."),
1915
- agent_id: z8.string().optional().describe("Agent identifier for budget tracking and enforcement.")
2164
+ path: z9.string().optional().default("").describe("Endpoint sub-path under /v1/search/ (default empty = root /v1/search). Reserved for future surfaces."),
2165
+ body: z9.any().optional().describe("Request body. At minimum { query: '...' }. Sent as POST."),
2166
+ agent_id: z9.string().optional().describe("Agent identifier for budget tracking and enforcement.")
1916
2167
  }
1917
2168
  },
1918
2169
  async ({ path: path5, body, agent_id }) => {
@@ -1943,7 +2194,7 @@ Full request shape + worked examples in the \`search\` skill (\`skills/search/SK
1943
2194
  }
1944
2195
 
1945
2196
  // src/tools/exa.ts
1946
- import { z as z9 } from "zod";
2197
+ import { z as z10 } from "zod";
1947
2198
  function estimateExaCost(path5, body) {
1948
2199
  const cleanPath = path5.replace(/^\/+/, "").replace(/^v1\/exa\//, "");
1949
2200
  if (cleanPath === "contents") {
@@ -1968,9 +2219,9 @@ Categories for search: "news", "research paper", "company", "tweet", "github", "
1968
2219
 
1969
2220
  Full request/response shapes + worked research workflows in the \`exa-research\` skill.`,
1970
2221
  inputSchema: {
1971
- path: z9.string().describe("Endpoint name under /v1/exa/, e.g. 'search', 'answer', 'contents', 'find-similar'"),
1972
- body: z9.any().optional().describe("JSON body for the call. Sent as POST. Required for all four endpoints."),
1973
- agent_id: z9.string().optional().describe("Agent identifier for budget tracking and enforcement.")
2222
+ path: z10.string().describe("Endpoint name under /v1/exa/, e.g. 'search', 'answer', 'contents', 'find-similar'"),
2223
+ body: z10.any().optional().describe("JSON body for the call. Sent as POST. Required for all four endpoints."),
2224
+ agent_id: z10.string().optional().describe("Agent identifier for budget tracking and enforcement.")
1974
2225
  }
1975
2226
  },
1976
2227
  async ({ path: path5, body, agent_id }) => {
@@ -2001,7 +2252,7 @@ Full request/response shapes + worked research workflows in the \`exa-research\`
2001
2252
  }
2002
2253
 
2003
2254
  // src/tools/markets.ts
2004
- import { z as z10 } from "zod";
2255
+ import { z as z11 } from "zod";
2005
2256
  function estimateMarketCost(path5, body) {
2006
2257
  if (body !== void 0) return 5e-3;
2007
2258
  const p = path5.toLowerCase();
@@ -2066,10 +2317,10 @@ CROSS-PLATFORM:
2066
2317
 
2067
2318
  Pass query params via 'params' (GET). Use 'body' only for POST endpoints (e.g. polymarket/wallet/identities).`,
2068
2319
  inputSchema: {
2069
- path: z10.string().describe("Endpoint path, e.g. 'polymarket/events', 'kalshi/markets/KXBTC-25MAR14', 'polymarket/wallet/0xabc...', 'markets/search'"),
2070
- params: z10.record(z10.string(), z10.string()).optional().describe("Query parameters for GET requests (e.g. { limit: '20', active: 'true' })"),
2071
- body: z10.any().optional().describe("JSON body for POST queries (triggers pmQuery \u2014 most endpoints are GET)"),
2072
- agent_id: z10.string().optional().describe("Agent identifier for budget tracking and enforcement.")
2320
+ path: z11.string().describe("Endpoint path, e.g. 'polymarket/events', 'kalshi/markets/KXBTC-25MAR14', 'polymarket/wallet/0xabc...', 'markets/search'"),
2321
+ params: z11.record(z11.string(), z11.string()).optional().describe("Query parameters for GET requests (e.g. { limit: '20', active: 'true' })"),
2322
+ body: z11.any().optional().describe("JSON body for POST queries (triggers pmQuery \u2014 most endpoints are GET)"),
2323
+ agent_id: z11.string().optional().describe("Agent identifier for budget tracking and enforcement.")
2073
2324
  }
2074
2325
  },
2075
2326
  async ({ path: path5, params, body, agent_id }) => {
@@ -2102,9 +2353,9 @@ Pass query params via 'params' (GET). Use 'body' only for POST endpoints (e.g. p
2102
2353
  }
2103
2354
 
2104
2355
  // src/tools/price.ts
2105
- import { z as z11 } from "zod";
2106
- var CATEGORY = z11.enum(["crypto", "fx", "commodity", "usstock", "stocks"]);
2107
- var MARKET = z11.enum([
2356
+ import { z as z12 } from "zod";
2357
+ var CATEGORY = z12.enum(["crypto", "fx", "commodity", "usstock", "stocks"]);
2358
+ var MARKET = z12.enum([
2108
2359
  "us",
2109
2360
  "hk",
2110
2361
  "jp",
@@ -2118,9 +2369,9 @@ var MARKET = z11.enum([
2118
2369
  "cn",
2119
2370
  "ca"
2120
2371
  ]);
2121
- var RESOLUTION = z11.enum(["1", "5", "15", "60", "240", "D", "W", "M"]);
2122
- var SESSION = z11.enum(["pre", "post", "on"]);
2123
- var ACTION = z11.enum(["price", "history", "list"]);
2372
+ var RESOLUTION = z12.enum(["1", "5", "15", "60", "240", "D", "W", "M"]);
2373
+ var SESSION = z12.enum(["pre", "post", "on"]);
2374
+ var ACTION = z12.enum(["price", "history", "list"]);
2124
2375
  function isPaidPriceCall(action, category) {
2125
2376
  return action !== "list" && (category === "stocks" || category === "usstock");
2126
2377
  }
@@ -2148,15 +2399,15 @@ Examples:
2148
2399
  inputSchema: {
2149
2400
  action: ACTION.describe("Which endpoint to hit: price, history, or list."),
2150
2401
  category: CATEGORY.describe("Market category."),
2151
- symbol: z11.string().optional().describe("Ticker (required for price+history). e.g. BTC-USD, AAPL, EUR-USD."),
2402
+ symbol: z12.string().optional().describe("Ticker (required for price+history). e.g. BTC-USD, AAPL, EUR-USD."),
2152
2403
  market: MARKET.optional().describe("Stock market code \u2014 required when category='stocks'."),
2153
2404
  session: SESSION.optional().describe("Equity session hint (pre/post/on); ignored for non-equity."),
2154
2405
  resolution: RESOLUTION.optional().describe("Bar resolution for history (default D)."),
2155
- from: z11.number().optional().describe("History window start (unix seconds)."),
2156
- to: z11.number().optional().describe("History window end (unix seconds)."),
2157
- query: z11.string().optional().describe("Free-text filter for list."),
2158
- limit: z11.number().int().positive().max(2e3).optional().describe("Max items for list (default 100, max 2000)."),
2159
- agent_id: z11.string().optional().describe("Agent identifier for budget tracking and enforcement.")
2406
+ from: z12.number().optional().describe("History window start (unix seconds)."),
2407
+ to: z12.number().optional().describe("History window end (unix seconds)."),
2408
+ query: z12.string().optional().describe("Free-text filter for list."),
2409
+ limit: z12.number().int().positive().max(2e3).optional().describe("Max items for list (default 100, max 2000)."),
2410
+ agent_id: z12.string().optional().describe("Agent identifier for budget tracking and enforcement.")
2160
2411
  }
2161
2412
  },
2162
2413
  async ({ action, category, symbol, market, session, resolution, from, to, query, limit, agent_id }) => {
@@ -2229,7 +2480,7 @@ Examples:
2229
2480
  }
2230
2481
 
2231
2482
  // src/tools/dex.ts
2232
- import { z as z12 } from "zod";
2483
+ import { z as z13 } from "zod";
2233
2484
  function registerDexTool(server) {
2234
2485
  server.registerTool(
2235
2486
  "blockrun_dex",
@@ -2246,10 +2497,10 @@ Examples:
2246
2497
  blockrun_dex({ token: "So11...xxx" }) -> Get specific token data
2247
2498
  blockrun_dex({ symbol: "PEPE" }) -> Search by symbol`,
2248
2499
  inputSchema: {
2249
- query: z12.string().optional().describe("Search query (token name, symbol, or address)"),
2250
- token: z12.string().optional().describe("Token address for direct lookup"),
2251
- symbol: z12.string().optional().describe("Token symbol to search"),
2252
- chain: z12.string().optional().describe("Filter by chain (ethereum, solana, base, etc.)")
2500
+ query: z13.string().optional().describe("Search query (token name, symbol, or address)"),
2501
+ token: z13.string().optional().describe("Token address for direct lookup"),
2502
+ symbol: z13.string().optional().describe("Token symbol to search"),
2503
+ chain: z13.string().optional().describe("Filter by chain (ethereum, solana, base, etc.)")
2253
2504
  }
2254
2505
  },
2255
2506
  async ({ query, token, symbol, chain }) => {
@@ -2310,7 +2561,7 @@ ${lines.join("\n\n")}` }],
2310
2561
  }
2311
2562
 
2312
2563
  // src/tools/modal.ts
2313
- import { z as z13 } from "zod";
2564
+ import { z as z14 } from "zod";
2314
2565
  function estimateModalCost(path5) {
2315
2566
  return path5.includes("sandbox/create") ? 0.01 : 1e-3;
2316
2567
  }
@@ -2330,9 +2581,9 @@ Common paths (all POST):
2330
2581
 
2331
2582
  Full action shapes + GPU type details in the \`modal\` skill.`,
2332
2583
  inputSchema: {
2333
- path: z13.string().describe("Endpoint under /v1/modal/, e.g. 'sandbox/create', 'sandbox/exec'"),
2334
- body: z13.any().optional().describe("JSON body. Sent as POST."),
2335
- agent_id: z13.string().optional().describe("Agent identifier for budget tracking and enforcement.")
2584
+ path: z14.string().describe("Endpoint under /v1/modal/, e.g. 'sandbox/create', 'sandbox/exec'"),
2585
+ body: z14.any().optional().describe("JSON body. Sent as POST."),
2586
+ agent_id: z14.string().optional().describe("Agent identifier for budget tracking and enforcement.")
2336
2587
  }
2337
2588
  },
2338
2589
  async ({ path: path5, body, agent_id }) => {
@@ -2363,7 +2614,7 @@ Full action shapes + GPU type details in the \`modal\` skill.`,
2363
2614
  }
2364
2615
 
2365
2616
  // src/tools/phone.ts
2366
- import { z as z14 } from "zod";
2617
+ import { z as z15 } from "zod";
2367
2618
  function estimatePhoneCost(path5, hasBody) {
2368
2619
  if (!hasBody && path5.startsWith("voice/call/")) return 0;
2369
2620
  if (path5 === "phone/numbers/release") return 0;
@@ -2396,9 +2647,9 @@ Voice presets: nat, josh, maya, june, paige, derek, florian. Phone numbers use E
2396
2647
 
2397
2648
  Voice call flow + voice preset details + full body shapes in the \`phone\` skill.`,
2398
2649
  inputSchema: {
2399
- path: z14.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."),
2400
- body: z14.any().optional().describe("JSON body. Sent as POST. Omit for the free GET poll (voice/call/{call_id})."),
2401
- agent_id: z14.string().optional().describe("Agent identifier for budget tracking and enforcement.")
2650
+ path: z15.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."),
2651
+ body: z15.any().optional().describe("JSON body. Sent as POST. Omit for the free GET poll (voice/call/{call_id})."),
2652
+ agent_id: z15.string().optional().describe("Agent identifier for budget tracking and enforcement.")
2402
2653
  }
2403
2654
  },
2404
2655
  async ({ path: path5, body, agent_id }) => {
@@ -2429,7 +2680,7 @@ Voice call flow + voice preset details + full body shapes in the \`phone\` skill
2429
2680
  }
2430
2681
 
2431
2682
  // src/tools/surf.ts
2432
- import { z as z15 } from "zod";
2683
+ import { z as z16 } from "zod";
2433
2684
  var SURF_T3_PATHS = /* @__PURE__ */ new Set([
2434
2685
  "onchain/sql",
2435
2686
  "onchain/query",
@@ -2493,10 +2744,10 @@ Common paths (full 84-endpoint catalog in the surf skill):
2493
2744
  Method is auto-routed: pass 'body' for POST endpoints; otherwise GET with 'params'.
2494
2745
  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`,
2495
2746
  inputSchema: {
2496
- path: z15.string().describe("Endpoint path under /v1/surf/, e.g. 'market/price', 'prediction-market/polymarket/ranking', 'wallet/detail', 'onchain/sql', 'chat/completions'"),
2497
- params: z15.record(z15.string(), z15.string()).optional().describe("Query parameters for GET endpoints, e.g. { symbol: 'BTC' } or { address: '0x...', chain: 'ethereum' }"),
2498
- body: z15.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."),
2499
- agent_id: z15.string().optional().describe("Agent identifier for budget tracking and enforcement.")
2747
+ path: z16.string().describe("Endpoint path under /v1/surf/, e.g. 'market/price', 'prediction-market/polymarket/ranking', 'wallet/detail', 'onchain/sql', 'chat/completions'"),
2748
+ params: z16.record(z16.string(), z16.string()).optional().describe("Query parameters for GET endpoints, e.g. { symbol: 'BTC' } or { address: '0x...', chain: 'ethereum' }"),
2749
+ body: z16.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."),
2750
+ agent_id: z16.string().optional().describe("Agent identifier for budget tracking and enforcement.")
2500
2751
  }
2501
2752
  },
2502
2753
  async ({ path: path5, params, body, agent_id }) => {
@@ -2538,6 +2789,7 @@ function initializeMcpServer(server) {
2538
2789
  registerModelsTool(server, modelCache);
2539
2790
  registerImageTool(server, budget);
2540
2791
  registerMusicTool(server, budget);
2792
+ registerSpeechTool(server, budget);
2541
2793
  registerVideoTool(server, budget);
2542
2794
  registerRealfaceTool(server, budget);
2543
2795
  registerSearchTool(server, budget);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/mcp",
3
- "version": "0.18.1",
3
+ "version": "0.19.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",