@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.
- package/README.md +8 -4
- package/dist/index.js +340 -88
- 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. **
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
342
|
-
const
|
|
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.
|
|
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.
|
|
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/
|
|
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:
|
|
1424
|
-
image_url:
|
|
1425
|
-
real_face_asset_id:
|
|
1426
|
-
duration_seconds:
|
|
1427
|
-
model:
|
|
1428
|
-
agent_id:
|
|
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 =
|
|
1467
|
-
const submitUrl = `${
|
|
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
|
|
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 =
|
|
1484
|
-
const details =
|
|
1485
|
-
const paymentPayload = await
|
|
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
|
|
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 : `${
|
|
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
|
|
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
|
|
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
|
|
1616
|
-
import { privateKeyToAccount as
|
|
1866
|
+
import { z as z8 } from "zod";
|
|
1867
|
+
import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
|
|
1617
1868
|
import {
|
|
1618
|
-
createPaymentPayload as
|
|
1619
|
-
parsePaymentRequired as
|
|
1620
|
-
extractPaymentDetails as
|
|
1869
|
+
createPaymentPayload as createPaymentPayload4,
|
|
1870
|
+
parsePaymentRequired as parsePaymentRequired4,
|
|
1871
|
+
extractPaymentDetails as extractPaymentDetails4
|
|
1621
1872
|
} from "@blockrun/llm";
|
|
1622
|
-
var
|
|
1873
|
+
var BLOCKRUN_API4 = "https://blockrun.ai/api";
|
|
1623
1874
|
var ENROLLMENT_PRICE_USD = 0.01;
|
|
1624
|
-
async function
|
|
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:
|
|
1656
|
-
name:
|
|
1657
|
-
group_id:
|
|
1658
|
-
image_url:
|
|
1659
|
-
agent_id:
|
|
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
|
|
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
|
|
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 =
|
|
1748
|
-
const resp = await
|
|
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 =
|
|
1787
|
-
const enrollUrl = `${
|
|
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
|
|
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 =
|
|
1801
|
-
const details =
|
|
1802
|
-
const paymentPayload = await
|
|
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
|
|
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
|
|
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:
|
|
1914
|
-
body:
|
|
1915
|
-
agent_id:
|
|
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
|
|
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:
|
|
1972
|
-
body:
|
|
1973
|
-
agent_id:
|
|
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
|
|
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:
|
|
2070
|
-
params:
|
|
2071
|
-
body:
|
|
2072
|
-
agent_id:
|
|
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
|
|
2106
|
-
var CATEGORY =
|
|
2107
|
-
var MARKET =
|
|
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 =
|
|
2122
|
-
var SESSION =
|
|
2123
|
-
var ACTION =
|
|
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:
|
|
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:
|
|
2156
|
-
to:
|
|
2157
|
-
query:
|
|
2158
|
-
limit:
|
|
2159
|
-
agent_id:
|
|
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
|
|
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:
|
|
2250
|
-
token:
|
|
2251
|
-
symbol:
|
|
2252
|
-
chain:
|
|
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
|
|
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:
|
|
2334
|
-
body:
|
|
2335
|
-
agent_id:
|
|
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
|
|
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:
|
|
2400
|
-
body:
|
|
2401
|
-
agent_id:
|
|
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
|
|
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:
|
|
2497
|
-
params:
|
|
2498
|
-
body:
|
|
2499
|
-
agent_id:
|
|
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.
|
|
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",
|