@blockrun/mcp 0.18.1 → 0.19.0
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 +337 -86
- 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"],
|
|
@@ -874,7 +874,7 @@ Run blockrun_models to see all 41+ models with pricing.`,
|
|
|
874
874
|
z2.object({ type: z2.literal("image_url"), image_url: z2.object({ url: z2.string().describe("https URL or data:<mime>;base64,<...> URI") }) })
|
|
875
875
|
]))
|
|
876
876
|
]).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.
|
|
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.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
878
|
}
|
|
879
879
|
},
|
|
880
880
|
async ({ message, model, mode, routing, routing_profile, system, max_tokens, temperature, response_format, stop, thinking, agent_id, messages }) => {
|
|
@@ -945,7 +945,7 @@ ${result.response}` }],
|
|
|
945
945
|
}
|
|
946
946
|
}
|
|
947
947
|
if (messages && messages.length > 0) {
|
|
948
|
-
const targetModel = model || MODEL_TIERS[mode ?? "balanced"]?.[0] || "openai/gpt-5.
|
|
948
|
+
const targetModel = model || MODEL_TIERS[mode ?? "balanced"]?.[0] || "openai/gpt-5.5";
|
|
949
949
|
const fullMessages = [
|
|
950
950
|
...system ? [{ role: "system", content: system }] : [],
|
|
951
951
|
...messages,
|
|
@@ -1367,7 +1367,7 @@ async function fetchWithTimeout(url, options, timeoutMs) {
|
|
|
1367
1367
|
}
|
|
1368
1368
|
}
|
|
1369
1369
|
|
|
1370
|
-
// src/tools/
|
|
1370
|
+
// src/tools/speech.ts
|
|
1371
1371
|
import { z as z6 } from "zod";
|
|
1372
1372
|
import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
|
|
1373
1373
|
import {
|
|
@@ -1376,6 +1376,256 @@ import {
|
|
|
1376
1376
|
extractPaymentDetails as extractPaymentDetails2
|
|
1377
1377
|
} from "@blockrun/llm";
|
|
1378
1378
|
var BLOCKRUN_API2 = "https://blockrun.ai/api";
|
|
1379
|
+
var SPEECH_TIMEOUT = 6e4;
|
|
1380
|
+
var VOICES_TIMEOUT = 15e3;
|
|
1381
|
+
var MARGIN = 1.05;
|
|
1382
|
+
var MIN_PAYMENT_USD = 1e-3;
|
|
1383
|
+
var SOUND_EFFECT_COST = 0.05 * MARGIN;
|
|
1384
|
+
var SPEECH_MODELS = {
|
|
1385
|
+
"elevenlabs/flash-v2.5": { pricePer1kChars: 0.05, maxInputChars: 4e4 },
|
|
1386
|
+
"elevenlabs/turbo-v2.5": { pricePer1kChars: 0.05, maxInputChars: 4e4 },
|
|
1387
|
+
"elevenlabs/multilingual-v2": { pricePer1kChars: 0.1, maxInputChars: 1e4 },
|
|
1388
|
+
"elevenlabs/v3": { pricePer1kChars: 0.1, maxInputChars: 5e3 }
|
|
1389
|
+
};
|
|
1390
|
+
var VOICE_ALIASES = [
|
|
1391
|
+
{ alias: "sarah", description: "Mature, reassuring, confident (default)" },
|
|
1392
|
+
{ alias: "george", description: "Warm, captivating storyteller" },
|
|
1393
|
+
{ alias: "laura", description: "Enthusiast, quirky" },
|
|
1394
|
+
{ alias: "charlie", description: "Deep, confident, energetic" },
|
|
1395
|
+
{ alias: "river", description: "Relaxed, neutral, informative" },
|
|
1396
|
+
{ alias: "roger", description: "Laid-back, casual, resonant" },
|
|
1397
|
+
{ alias: "callum", description: "Husky trickster" },
|
|
1398
|
+
{ alias: "harry", description: "Fierce warrior" }
|
|
1399
|
+
];
|
|
1400
|
+
function speechCost(model, charCount) {
|
|
1401
|
+
const m = SPEECH_MODELS[model];
|
|
1402
|
+
const raw = charCount / 1e3 * (m?.pricePer1kChars ?? 0.05) * MARGIN;
|
|
1403
|
+
return Math.max(raw, MIN_PAYMENT_USD);
|
|
1404
|
+
}
|
|
1405
|
+
function registerSpeechTool(server, budget) {
|
|
1406
|
+
server.registerTool(
|
|
1407
|
+
"blockrun_speech",
|
|
1408
|
+
{
|
|
1409
|
+
description: `ElevenLabs voice via BlockRun x402 \u2014 speak text aloud, generate sound effects, list voices.
|
|
1410
|
+
|
|
1411
|
+
Actions:
|
|
1412
|
+
- speak (default): text-to-speech. E.g. "speak this with the sarah voice". Price = chars/1000 \xD7 rate (min $0.001), quoted before payment.
|
|
1413
|
+
- sound_effect: cinematic sound effects from a text prompt, up to 22s ($0.0525/clip)
|
|
1414
|
+
- voices: list available voices (free)
|
|
1415
|
+
|
|
1416
|
+
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)
|
|
1417
|
+
|
|
1418
|
+
Voice aliases: sarah (default), george, laura, charlie, river, roger, callum, harry \u2014 or any raw ElevenLabs voice_id.
|
|
1419
|
+
|
|
1420
|
+
Returns a hosted audio URL \u2014 download immediately if you need to keep the file.`,
|
|
1421
|
+
inputSchema: {
|
|
1422
|
+
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)."),
|
|
1423
|
+
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)."),
|
|
1424
|
+
voice: z6.string().optional().describe("Voice alias (sarah, george, laura, charlie, river, roger, callum, harry) or raw ElevenLabs voice_id. Default: sarah."),
|
|
1425
|
+
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)"),
|
|
1426
|
+
response_format: z6.enum(["mp3", "opus", "pcm", "wav"]).optional().default("mp3").describe("Audio format"),
|
|
1427
|
+
speed: z6.number().min(0.7).max(1.2).optional().describe("Playback speed 0.7-1.2 (speak only)"),
|
|
1428
|
+
duration_seconds: z6.number().min(0.5).max(22).optional().describe("Sound effect length in seconds (sound_effect only; default: auto)"),
|
|
1429
|
+
prompt_influence: z6.number().min(0).max(1).optional().describe("How literally to follow the prompt, 0-1 (sound_effect only)"),
|
|
1430
|
+
agent_id: z6.string().optional().describe("Agent identifier for budget tracking and enforcement.")
|
|
1431
|
+
}
|
|
1432
|
+
},
|
|
1433
|
+
async ({ action, input, voice, model, response_format, speed, duration_seconds, prompt_influence, agent_id }) => {
|
|
1434
|
+
try {
|
|
1435
|
+
if (action === "voices") {
|
|
1436
|
+
return await listVoices();
|
|
1437
|
+
}
|
|
1438
|
+
if (getChain() !== "base") {
|
|
1439
|
+
return {
|
|
1440
|
+
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.") }],
|
|
1441
|
+
isError: true
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
if (!input?.trim()) {
|
|
1445
|
+
return {
|
|
1446
|
+
content: [{ type: "text", text: formatError(`'input' is required for action:"${action}" \u2014 the text to ${action === "speak" ? "synthesize" : "describe the sound effect"}.`) }],
|
|
1447
|
+
isError: true
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
let endpoint;
|
|
1451
|
+
let body;
|
|
1452
|
+
let cost;
|
|
1453
|
+
if (action === "sound_effect") {
|
|
1454
|
+
if (input.length > 1e3) {
|
|
1455
|
+
return {
|
|
1456
|
+
content: [{ type: "text", text: formatError(`Sound effect description too long: ${input.length} characters (max 1000).`) }],
|
|
1457
|
+
isError: true
|
|
1458
|
+
};
|
|
1459
|
+
}
|
|
1460
|
+
endpoint = `${BLOCKRUN_API2}/v1/audio/sound-effects`;
|
|
1461
|
+
body = { model: "elevenlabs/sound-effects", text: input, response_format };
|
|
1462
|
+
if (duration_seconds !== void 0) body.duration_seconds = duration_seconds;
|
|
1463
|
+
if (prompt_influence !== void 0) body.prompt_influence = prompt_influence;
|
|
1464
|
+
cost = SOUND_EFFECT_COST;
|
|
1465
|
+
} else {
|
|
1466
|
+
const modelInfo = SPEECH_MODELS[model] ?? SPEECH_MODELS["elevenlabs/flash-v2.5"];
|
|
1467
|
+
if (input.length > modelInfo.maxInputChars) {
|
|
1468
|
+
return {
|
|
1469
|
+
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).`) }],
|
|
1470
|
+
isError: true
|
|
1471
|
+
};
|
|
1472
|
+
}
|
|
1473
|
+
endpoint = `${BLOCKRUN_API2}/v1/audio/speech`;
|
|
1474
|
+
body = { model, input, voice: voice || "sarah", response_format };
|
|
1475
|
+
if (speed !== void 0) body.speed = speed;
|
|
1476
|
+
cost = speechCost(model, input.length);
|
|
1477
|
+
}
|
|
1478
|
+
const budgetCheck = checkBudget(budget, agent_id, cost);
|
|
1479
|
+
if (!budgetCheck.allowed) {
|
|
1480
|
+
return {
|
|
1481
|
+
content: [{ type: "text", text: `${budgetCheck.reason}. Use blockrun_wallet action:"report" to see usage or action:"delegate" to increase agent budget.` }],
|
|
1482
|
+
isError: true
|
|
1483
|
+
};
|
|
1484
|
+
}
|
|
1485
|
+
const privateKey = getOrCreateWalletKey();
|
|
1486
|
+
const account = privateKeyToAccount2(privateKey);
|
|
1487
|
+
const resp402 = await fetchWithTimeout2(endpoint, {
|
|
1488
|
+
method: "POST",
|
|
1489
|
+
headers: { "Content-Type": "application/json" },
|
|
1490
|
+
body: JSON.stringify(body)
|
|
1491
|
+
}, 15e3);
|
|
1492
|
+
if (resp402.status !== 402) {
|
|
1493
|
+
const data2 = await resp402.json().catch(() => ({}));
|
|
1494
|
+
throw new Error(`Expected 402, got ${resp402.status}: ${JSON.stringify(data2)}`);
|
|
1495
|
+
}
|
|
1496
|
+
const prHeader = resp402.headers.get("payment-required") || resp402.headers.get("PAYMENT-REQUIRED");
|
|
1497
|
+
if (!prHeader) throw new Error("No PAYMENT-REQUIRED header in 402 response");
|
|
1498
|
+
const paymentRequired = parsePaymentRequired2(prHeader);
|
|
1499
|
+
const details = extractPaymentDetails2(paymentRequired);
|
|
1500
|
+
const paymentPayload = await createPaymentPayload2(
|
|
1501
|
+
privateKey,
|
|
1502
|
+
account.address,
|
|
1503
|
+
details.recipient,
|
|
1504
|
+
details.amount,
|
|
1505
|
+
details.network || "eip155:8453",
|
|
1506
|
+
{
|
|
1507
|
+
resourceUrl: details.resource?.url || endpoint,
|
|
1508
|
+
resourceDescription: details.resource?.description || "BlockRun Speech",
|
|
1509
|
+
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
1510
|
+
extra: details.extra
|
|
1511
|
+
}
|
|
1512
|
+
);
|
|
1513
|
+
const resp = await fetchWithTimeout2(endpoint, {
|
|
1514
|
+
method: "POST",
|
|
1515
|
+
headers: {
|
|
1516
|
+
"Content-Type": "application/json",
|
|
1517
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
1518
|
+
},
|
|
1519
|
+
body: JSON.stringify(body)
|
|
1520
|
+
}, SPEECH_TIMEOUT);
|
|
1521
|
+
if (resp.status === 402) {
|
|
1522
|
+
throw new Error("Payment rejected. Check your wallet balance.");
|
|
1523
|
+
}
|
|
1524
|
+
if (!resp.ok) {
|
|
1525
|
+
const errBody = await resp.json().catch(() => ({ error: "Request failed" }));
|
|
1526
|
+
throw new Error(`API error ${resp.status}: ${JSON.stringify(errBody)}`);
|
|
1527
|
+
}
|
|
1528
|
+
const data = await resp.json();
|
|
1529
|
+
const clip = data.data?.[0];
|
|
1530
|
+
if (!clip?.url) throw new Error("No audio URL in response");
|
|
1531
|
+
const txHash = resp.headers.get("X-Payment-Receipt") || resp.headers.get("x-payment-receipt");
|
|
1532
|
+
recordSpending(budget, cost, agent_id);
|
|
1533
|
+
const lines = [
|
|
1534
|
+
action === "sound_effect" ? `\u{1F50A} Sound effect ready!` : `\u{1F5E3}\uFE0F Speech ready!`,
|
|
1535
|
+
`URL: ${clip.url}`,
|
|
1536
|
+
`Format: ${clip.format || response_format}`,
|
|
1537
|
+
...clip.characters !== void 0 ? [`Characters: ${clip.characters}`] : [],
|
|
1538
|
+
...clip.duration_seconds !== void 0 ? [`Duration: ${clip.duration_seconds}s`] : [],
|
|
1539
|
+
`Model: ${data.model || (action === "sound_effect" ? "elevenlabs/sound-effects" : model)}`,
|
|
1540
|
+
`Cost: $${cost.toFixed(4)}`,
|
|
1541
|
+
...txHash ? [`Tx: ${txHash}`] : [],
|
|
1542
|
+
``,
|
|
1543
|
+
`Note: This URL may expire \u2014 download it now if you need to keep the file.`
|
|
1544
|
+
];
|
|
1545
|
+
return {
|
|
1546
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
1547
|
+
structuredContent: {
|
|
1548
|
+
url: clip.url,
|
|
1549
|
+
format: clip.format || response_format,
|
|
1550
|
+
...clip.characters !== void 0 ? { characters: clip.characters } : {},
|
|
1551
|
+
...clip.duration_seconds !== void 0 ? { duration_seconds: clip.duration_seconds } : {},
|
|
1552
|
+
model: data.model || (action === "sound_effect" ? "elevenlabs/sound-effects" : model),
|
|
1553
|
+
cost_usd: cost,
|
|
1554
|
+
...txHash ? { txHash } : {}
|
|
1555
|
+
}
|
|
1556
|
+
};
|
|
1557
|
+
} catch (err) {
|
|
1558
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1559
|
+
if (errMsg.includes("balance") || errMsg.includes("payment") || errMsg.includes("402") || errMsg.includes("rejected")) {
|
|
1560
|
+
return {
|
|
1561
|
+
content: [{ type: "text", text: `Speech generation requires payment. Run blockrun_wallet with action: "setup" for funding instructions.
|
|
1562
|
+
Error: ${errMsg}` }],
|
|
1563
|
+
isError: true
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
if (errMsg.includes("abort") || errMsg.includes("timeout") || errMsg.includes("Timeout")) {
|
|
1567
|
+
return {
|
|
1568
|
+
content: [{ type: "text", text: `Speech generation timed out \u2014 please try again.
|
|
1569
|
+
Error: ${errMsg}` }],
|
|
1570
|
+
isError: true
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1573
|
+
return {
|
|
1574
|
+
content: [{ type: "text", text: formatError(`Speech generation failed: ${errMsg}`) }],
|
|
1575
|
+
isError: true
|
|
1576
|
+
};
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
);
|
|
1580
|
+
}
|
|
1581
|
+
async function listVoices() {
|
|
1582
|
+
try {
|
|
1583
|
+
const resp = await fetchWithTimeout2(`${BLOCKRUN_API2}/v1/audio/voices`, { method: "GET" }, VOICES_TIMEOUT);
|
|
1584
|
+
if (resp.ok) {
|
|
1585
|
+
const payload = await resp.json();
|
|
1586
|
+
const voices = payload.data || [];
|
|
1587
|
+
if (voices.length > 0) {
|
|
1588
|
+
const lines2 = voices.map((v) => {
|
|
1589
|
+
const tags = v.labels ? Object.values(v.labels).join(", ") : "";
|
|
1590
|
+
return `- ${v.alias ? `${v.alias} ` : ""}(${v.voice_id})${v.name ? ` \u2014 ${v.name}` : ""}${tags ? ` [${tags}]` : ""}`;
|
|
1591
|
+
});
|
|
1592
|
+
return {
|
|
1593
|
+
content: [{ type: "text", text: `Available voices (pass alias or voice_id as 'voice'):
|
|
1594
|
+
${lines2.join("\n")}` }],
|
|
1595
|
+
structuredContent: { voices }
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
} catch {
|
|
1600
|
+
}
|
|
1601
|
+
const lines = VOICE_ALIASES.map((v) => `- ${v.alias} \u2014 ${v.description}`);
|
|
1602
|
+
return {
|
|
1603
|
+
content: [{ type: "text", text: `Built-in voice aliases (pass as 'voice'; full catalog temporarily unavailable):
|
|
1604
|
+
${lines.join("\n")}
|
|
1605
|
+
|
|
1606
|
+
Any raw ElevenLabs voice_id also works.` }],
|
|
1607
|
+
structuredContent: { voices: VOICE_ALIASES }
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
async function fetchWithTimeout2(url, options, timeoutMs) {
|
|
1611
|
+
const controller = new AbortController();
|
|
1612
|
+
const id = setTimeout(() => controller.abort(), timeoutMs);
|
|
1613
|
+
try {
|
|
1614
|
+
return await fetch(url, { ...options, signal: controller.signal });
|
|
1615
|
+
} finally {
|
|
1616
|
+
clearTimeout(id);
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
// src/tools/video.ts
|
|
1621
|
+
import { z as z7 } from "zod";
|
|
1622
|
+
import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
|
|
1623
|
+
import {
|
|
1624
|
+
createPaymentPayload as createPaymentPayload3,
|
|
1625
|
+
parsePaymentRequired as parsePaymentRequired3,
|
|
1626
|
+
extractPaymentDetails as extractPaymentDetails3
|
|
1627
|
+
} from "@blockrun/llm";
|
|
1628
|
+
var BLOCKRUN_API3 = "https://blockrun.ai/api";
|
|
1379
1629
|
var TOTAL_BUDGET_MS = 3e5;
|
|
1380
1630
|
var POLL_INTERVAL_MS = 5e3;
|
|
1381
1631
|
var VIDEO_PRICE_PER_SECOND = {
|
|
@@ -1420,12 +1670,12 @@ RealFace: to generate video of a SPECIFIC real person, first enroll them with bl
|
|
|
1420
1670
|
|
|
1421
1671
|
Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GCS so URLs don't expire).`,
|
|
1422
1672
|
inputSchema: {
|
|
1423
|
-
prompt:
|
|
1424
|
-
image_url:
|
|
1425
|
-
real_face_asset_id:
|
|
1426
|
-
duration_seconds:
|
|
1427
|
-
model:
|
|
1428
|
-
agent_id:
|
|
1673
|
+
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'"),
|
|
1674
|
+
image_url: z7.string().url().optional().describe("Optional seed image URL for image-to-video generation"),
|
|
1675
|
+
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."),
|
|
1676
|
+
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)."),
|
|
1677
|
+
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"),
|
|
1678
|
+
agent_id: z7.string().optional().describe("Agent identifier for budget tracking and enforcement.")
|
|
1429
1679
|
}
|
|
1430
1680
|
},
|
|
1431
1681
|
async ({ prompt, image_url, real_face_asset_id, duration_seconds, model, agent_id }) => {
|
|
@@ -1463,13 +1713,13 @@ Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GC
|
|
|
1463
1713
|
};
|
|
1464
1714
|
}
|
|
1465
1715
|
const privateKey = getOrCreateWalletKey();
|
|
1466
|
-
const account =
|
|
1467
|
-
const submitUrl = `${
|
|
1716
|
+
const account = privateKeyToAccount3(privateKey);
|
|
1717
|
+
const submitUrl = `${BLOCKRUN_API3}/v1/videos/generations`;
|
|
1468
1718
|
const body = { model: selectedModel, prompt };
|
|
1469
1719
|
if (image_url) body.image_url = image_url;
|
|
1470
1720
|
if (real_face_asset_id) body.real_face_asset_id = real_face_asset_id;
|
|
1471
1721
|
if (duration_seconds !== void 0) body.duration_seconds = duration_seconds;
|
|
1472
|
-
const resp402 = await
|
|
1722
|
+
const resp402 = await fetchWithTimeout3(submitUrl, {
|
|
1473
1723
|
method: "POST",
|
|
1474
1724
|
headers: { "Content-Type": "application/json" },
|
|
1475
1725
|
body: JSON.stringify(body)
|
|
@@ -1480,9 +1730,9 @@ Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GC
|
|
|
1480
1730
|
}
|
|
1481
1731
|
const prHeader = resp402.headers.get("payment-required") || resp402.headers.get("PAYMENT-REQUIRED");
|
|
1482
1732
|
if (!prHeader) throw new Error("No PAYMENT-REQUIRED header in 402 response");
|
|
1483
|
-
const paymentRequired =
|
|
1484
|
-
const details =
|
|
1485
|
-
const paymentPayload = await
|
|
1733
|
+
const paymentRequired = parsePaymentRequired3(prHeader);
|
|
1734
|
+
const details = extractPaymentDetails3(paymentRequired);
|
|
1735
|
+
const paymentPayload = await createPaymentPayload3(
|
|
1486
1736
|
privateKey,
|
|
1487
1737
|
account.address,
|
|
1488
1738
|
details.recipient,
|
|
@@ -1497,7 +1747,7 @@ Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GC
|
|
|
1497
1747
|
extra: details.extra
|
|
1498
1748
|
}
|
|
1499
1749
|
);
|
|
1500
|
-
const submitResp = await
|
|
1750
|
+
const submitResp = await fetchWithTimeout3(submitUrl, {
|
|
1501
1751
|
method: "POST",
|
|
1502
1752
|
headers: {
|
|
1503
1753
|
"Content-Type": "application/json",
|
|
@@ -1516,13 +1766,13 @@ Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GC
|
|
|
1516
1766
|
if (!submitData.id || !submitData.poll_url) {
|
|
1517
1767
|
throw new Error(`Submit response missing id/poll_url: ${JSON.stringify(submitData)}`);
|
|
1518
1768
|
}
|
|
1519
|
-
const pollAbsoluteUrl = submitData.poll_url.startsWith("http") ? submitData.poll_url : `${
|
|
1769
|
+
const pollAbsoluteUrl = submitData.poll_url.startsWith("http") ? submitData.poll_url : `${BLOCKRUN_API3.replace(/\/api$/, "")}${submitData.poll_url}`;
|
|
1520
1770
|
const startedAt = Date.now();
|
|
1521
1771
|
let lastStatus = submitData.status || "queued";
|
|
1522
1772
|
let completed = null;
|
|
1523
1773
|
while (Date.now() - startedAt < TOTAL_BUDGET_MS) {
|
|
1524
1774
|
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
1525
|
-
const pollResp = await
|
|
1775
|
+
const pollResp = await fetchWithTimeout3(pollAbsoluteUrl, {
|
|
1526
1776
|
method: "GET",
|
|
1527
1777
|
headers: { "PAYMENT-SIGNATURE": paymentPayload }
|
|
1528
1778
|
}, 9e4);
|
|
@@ -1601,7 +1851,7 @@ Error: ${errMsg}` }],
|
|
|
1601
1851
|
}
|
|
1602
1852
|
);
|
|
1603
1853
|
}
|
|
1604
|
-
async function
|
|
1854
|
+
async function fetchWithTimeout3(url, options, timeoutMs) {
|
|
1605
1855
|
const controller = new AbortController();
|
|
1606
1856
|
const id = setTimeout(() => controller.abort(), timeoutMs);
|
|
1607
1857
|
try {
|
|
@@ -1612,16 +1862,16 @@ async function fetchWithTimeout2(url, options, timeoutMs) {
|
|
|
1612
1862
|
}
|
|
1613
1863
|
|
|
1614
1864
|
// src/tools/realface.ts
|
|
1615
|
-
import { z as
|
|
1616
|
-
import { privateKeyToAccount as
|
|
1865
|
+
import { z as z8 } from "zod";
|
|
1866
|
+
import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
|
|
1617
1867
|
import {
|
|
1618
|
-
createPaymentPayload as
|
|
1619
|
-
parsePaymentRequired as
|
|
1620
|
-
extractPaymentDetails as
|
|
1868
|
+
createPaymentPayload as createPaymentPayload4,
|
|
1869
|
+
parsePaymentRequired as parsePaymentRequired4,
|
|
1870
|
+
extractPaymentDetails as extractPaymentDetails4
|
|
1621
1871
|
} from "@blockrun/llm";
|
|
1622
|
-
var
|
|
1872
|
+
var BLOCKRUN_API4 = "https://blockrun.ai/api";
|
|
1623
1873
|
var ENROLLMENT_PRICE_USD = 0.01;
|
|
1624
|
-
async function
|
|
1874
|
+
async function fetchWithTimeout4(url, options, timeoutMs) {
|
|
1625
1875
|
const controller = new AbortController();
|
|
1626
1876
|
const id = setTimeout(() => controller.abort(), timeoutMs);
|
|
1627
1877
|
try {
|
|
@@ -1652,11 +1902,11 @@ Typical flow:
|
|
|
1652
1902
|
|
|
1653
1903
|
Privacy: BlockRun does not store face/liveness data \u2014 only the asset id, name, and the photo URL you supply.`,
|
|
1654
1904
|
inputSchema: {
|
|
1655
|
-
action:
|
|
1656
|
-
name:
|
|
1657
|
-
group_id:
|
|
1658
|
-
image_url:
|
|
1659
|
-
agent_id:
|
|
1905
|
+
action: z8.enum(["init", "status", "enroll", "list"]).describe("What to do"),
|
|
1906
|
+
name: z8.string().min(1).max(64).optional().describe("Display name for the person (required for init and enroll)."),
|
|
1907
|
+
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)."),
|
|
1908
|
+
image_url: z8.string().url().optional().describe("Public HTTPS URL to a clear front-facing face photo (JPG/PNG/WEBP, \u226410MB). Required for enroll."),
|
|
1909
|
+
agent_id: z8.string().optional().describe("Agent identifier for budget tracking and enforcement (enroll only).")
|
|
1660
1910
|
}
|
|
1661
1911
|
},
|
|
1662
1912
|
async ({ action, name, group_id, image_url, agent_id }) => {
|
|
@@ -1667,7 +1917,7 @@ Privacy: BlockRun does not store face/liveness data \u2014 only the asset id, na
|
|
|
1667
1917
|
}
|
|
1668
1918
|
const body = { name };
|
|
1669
1919
|
if (group_id) body.groupId = group_id;
|
|
1670
|
-
const resp = await
|
|
1920
|
+
const resp = await fetchWithTimeout4(`${BLOCKRUN_API4}/v1/realface/init`, {
|
|
1671
1921
|
method: "POST",
|
|
1672
1922
|
headers: { "Content-Type": "application/json" },
|
|
1673
1923
|
body: JSON.stringify(body)
|
|
@@ -1716,7 +1966,7 @@ QR opened for scanning (${qrPath}).`;
|
|
|
1716
1966
|
if (!group_id) {
|
|
1717
1967
|
return { content: [{ type: "text", text: formatError('group_id is required for action:"status".') }], isError: true };
|
|
1718
1968
|
}
|
|
1719
|
-
const resp = await
|
|
1969
|
+
const resp = await fetchWithTimeout4(`${BLOCKRUN_API4}/v1/realface/status?groupId=${encodeURIComponent(group_id)}`, {
|
|
1720
1970
|
method: "GET"
|
|
1721
1971
|
}, 3e4);
|
|
1722
1972
|
const data = await resp.json().catch(() => ({}));
|
|
@@ -1744,8 +1994,8 @@ QR opened for scanning (${qrPath}).`;
|
|
|
1744
1994
|
};
|
|
1745
1995
|
}
|
|
1746
1996
|
if (action === "list") {
|
|
1747
|
-
const account =
|
|
1748
|
-
const resp = await
|
|
1997
|
+
const account = privateKeyToAccount4(getOrCreateWalletKey());
|
|
1998
|
+
const resp = await fetchWithTimeout4(`${BLOCKRUN_API4}/v1/wallet/${account.address}/realfaces`, {
|
|
1749
1999
|
method: "GET"
|
|
1750
2000
|
}, 3e4);
|
|
1751
2001
|
const data = await resp.json().catch(() => ({}));
|
|
@@ -1783,10 +2033,10 @@ Enroll one: blockrun_realface action:"init" name:"\u2026".` }],
|
|
|
1783
2033
|
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
2034
|
}
|
|
1785
2035
|
const privateKey = getOrCreateWalletKey();
|
|
1786
|
-
const account =
|
|
1787
|
-
const enrollUrl = `${
|
|
2036
|
+
const account = privateKeyToAccount4(privateKey);
|
|
2037
|
+
const enrollUrl = `${BLOCKRUN_API4}/v1/realface/enroll`;
|
|
1788
2038
|
const reqBody = JSON.stringify({ name, image_url, group_id });
|
|
1789
|
-
const resp402 = await
|
|
2039
|
+
const resp402 = await fetchWithTimeout4(enrollUrl, {
|
|
1790
2040
|
method: "POST",
|
|
1791
2041
|
headers: { "Content-Type": "application/json" },
|
|
1792
2042
|
body: reqBody
|
|
@@ -1797,9 +2047,9 @@ Enroll one: blockrun_realface action:"init" name:"\u2026".` }],
|
|
|
1797
2047
|
}
|
|
1798
2048
|
const prHeader = resp402.headers.get("payment-required") || resp402.headers.get("PAYMENT-REQUIRED");
|
|
1799
2049
|
if (!prHeader) throw new Error("No PAYMENT-REQUIRED header in 402 response");
|
|
1800
|
-
const paymentRequired =
|
|
1801
|
-
const details =
|
|
1802
|
-
const paymentPayload = await
|
|
2050
|
+
const paymentRequired = parsePaymentRequired4(prHeader);
|
|
2051
|
+
const details = extractPaymentDetails4(paymentRequired);
|
|
2052
|
+
const paymentPayload = await createPaymentPayload4(
|
|
1803
2053
|
privateKey,
|
|
1804
2054
|
account.address,
|
|
1805
2055
|
details.recipient,
|
|
@@ -1812,7 +2062,7 @@ Enroll one: blockrun_realface action:"init" name:"\u2026".` }],
|
|
|
1812
2062
|
extra: details.extra
|
|
1813
2063
|
}
|
|
1814
2064
|
);
|
|
1815
|
-
const resp = await
|
|
2065
|
+
const resp = await fetchWithTimeout4(enrollUrl, {
|
|
1816
2066
|
method: "POST",
|
|
1817
2067
|
headers: {
|
|
1818
2068
|
"Content-Type": "application/json",
|
|
@@ -1874,7 +2124,7 @@ Error: ${errMsg}` }],
|
|
|
1874
2124
|
}
|
|
1875
2125
|
|
|
1876
2126
|
// src/tools/search.ts
|
|
1877
|
-
import { z as
|
|
2127
|
+
import { z as z9 } from "zod";
|
|
1878
2128
|
|
|
1879
2129
|
// src/utils/body.ts
|
|
1880
2130
|
function coerceBody(body) {
|
|
@@ -1910,9 +2160,9 @@ Common shape:
|
|
|
1910
2160
|
|
|
1911
2161
|
Full request shape + worked examples in the \`search\` skill (\`skills/search/SKILL.md\`).`,
|
|
1912
2162
|
inputSchema: {
|
|
1913
|
-
path:
|
|
1914
|
-
body:
|
|
1915
|
-
agent_id:
|
|
2163
|
+
path: z9.string().optional().default("").describe("Endpoint sub-path under /v1/search/ (default empty = root /v1/search). Reserved for future surfaces."),
|
|
2164
|
+
body: z9.any().optional().describe("Request body. At minimum { query: '...' }. Sent as POST."),
|
|
2165
|
+
agent_id: z9.string().optional().describe("Agent identifier for budget tracking and enforcement.")
|
|
1916
2166
|
}
|
|
1917
2167
|
},
|
|
1918
2168
|
async ({ path: path5, body, agent_id }) => {
|
|
@@ -1943,7 +2193,7 @@ Full request shape + worked examples in the \`search\` skill (\`skills/search/SK
|
|
|
1943
2193
|
}
|
|
1944
2194
|
|
|
1945
2195
|
// src/tools/exa.ts
|
|
1946
|
-
import { z as
|
|
2196
|
+
import { z as z10 } from "zod";
|
|
1947
2197
|
function estimateExaCost(path5, body) {
|
|
1948
2198
|
const cleanPath = path5.replace(/^\/+/, "").replace(/^v1\/exa\//, "");
|
|
1949
2199
|
if (cleanPath === "contents") {
|
|
@@ -1968,9 +2218,9 @@ Categories for search: "news", "research paper", "company", "tweet", "github", "
|
|
|
1968
2218
|
|
|
1969
2219
|
Full request/response shapes + worked research workflows in the \`exa-research\` skill.`,
|
|
1970
2220
|
inputSchema: {
|
|
1971
|
-
path:
|
|
1972
|
-
body:
|
|
1973
|
-
agent_id:
|
|
2221
|
+
path: z10.string().describe("Endpoint name under /v1/exa/, e.g. 'search', 'answer', 'contents', 'find-similar'"),
|
|
2222
|
+
body: z10.any().optional().describe("JSON body for the call. Sent as POST. Required for all four endpoints."),
|
|
2223
|
+
agent_id: z10.string().optional().describe("Agent identifier for budget tracking and enforcement.")
|
|
1974
2224
|
}
|
|
1975
2225
|
},
|
|
1976
2226
|
async ({ path: path5, body, agent_id }) => {
|
|
@@ -2001,7 +2251,7 @@ Full request/response shapes + worked research workflows in the \`exa-research\`
|
|
|
2001
2251
|
}
|
|
2002
2252
|
|
|
2003
2253
|
// src/tools/markets.ts
|
|
2004
|
-
import { z as
|
|
2254
|
+
import { z as z11 } from "zod";
|
|
2005
2255
|
function estimateMarketCost(path5, body) {
|
|
2006
2256
|
if (body !== void 0) return 5e-3;
|
|
2007
2257
|
const p = path5.toLowerCase();
|
|
@@ -2066,10 +2316,10 @@ CROSS-PLATFORM:
|
|
|
2066
2316
|
|
|
2067
2317
|
Pass query params via 'params' (GET). Use 'body' only for POST endpoints (e.g. polymarket/wallet/identities).`,
|
|
2068
2318
|
inputSchema: {
|
|
2069
|
-
path:
|
|
2070
|
-
params:
|
|
2071
|
-
body:
|
|
2072
|
-
agent_id:
|
|
2319
|
+
path: z11.string().describe("Endpoint path, e.g. 'polymarket/events', 'kalshi/markets/KXBTC-25MAR14', 'polymarket/wallet/0xabc...', 'markets/search'"),
|
|
2320
|
+
params: z11.record(z11.string(), z11.string()).optional().describe("Query parameters for GET requests (e.g. { limit: '20', active: 'true' })"),
|
|
2321
|
+
body: z11.any().optional().describe("JSON body for POST queries (triggers pmQuery \u2014 most endpoints are GET)"),
|
|
2322
|
+
agent_id: z11.string().optional().describe("Agent identifier for budget tracking and enforcement.")
|
|
2073
2323
|
}
|
|
2074
2324
|
},
|
|
2075
2325
|
async ({ path: path5, params, body, agent_id }) => {
|
|
@@ -2102,9 +2352,9 @@ Pass query params via 'params' (GET). Use 'body' only for POST endpoints (e.g. p
|
|
|
2102
2352
|
}
|
|
2103
2353
|
|
|
2104
2354
|
// src/tools/price.ts
|
|
2105
|
-
import { z as
|
|
2106
|
-
var CATEGORY =
|
|
2107
|
-
var MARKET =
|
|
2355
|
+
import { z as z12 } from "zod";
|
|
2356
|
+
var CATEGORY = z12.enum(["crypto", "fx", "commodity", "usstock", "stocks"]);
|
|
2357
|
+
var MARKET = z12.enum([
|
|
2108
2358
|
"us",
|
|
2109
2359
|
"hk",
|
|
2110
2360
|
"jp",
|
|
@@ -2118,9 +2368,9 @@ var MARKET = z11.enum([
|
|
|
2118
2368
|
"cn",
|
|
2119
2369
|
"ca"
|
|
2120
2370
|
]);
|
|
2121
|
-
var RESOLUTION =
|
|
2122
|
-
var SESSION =
|
|
2123
|
-
var ACTION =
|
|
2371
|
+
var RESOLUTION = z12.enum(["1", "5", "15", "60", "240", "D", "W", "M"]);
|
|
2372
|
+
var SESSION = z12.enum(["pre", "post", "on"]);
|
|
2373
|
+
var ACTION = z12.enum(["price", "history", "list"]);
|
|
2124
2374
|
function isPaidPriceCall(action, category) {
|
|
2125
2375
|
return action !== "list" && (category === "stocks" || category === "usstock");
|
|
2126
2376
|
}
|
|
@@ -2148,15 +2398,15 @@ Examples:
|
|
|
2148
2398
|
inputSchema: {
|
|
2149
2399
|
action: ACTION.describe("Which endpoint to hit: price, history, or list."),
|
|
2150
2400
|
category: CATEGORY.describe("Market category."),
|
|
2151
|
-
symbol:
|
|
2401
|
+
symbol: z12.string().optional().describe("Ticker (required for price+history). e.g. BTC-USD, AAPL, EUR-USD."),
|
|
2152
2402
|
market: MARKET.optional().describe("Stock market code \u2014 required when category='stocks'."),
|
|
2153
2403
|
session: SESSION.optional().describe("Equity session hint (pre/post/on); ignored for non-equity."),
|
|
2154
2404
|
resolution: RESOLUTION.optional().describe("Bar resolution for history (default D)."),
|
|
2155
|
-
from:
|
|
2156
|
-
to:
|
|
2157
|
-
query:
|
|
2158
|
-
limit:
|
|
2159
|
-
agent_id:
|
|
2405
|
+
from: z12.number().optional().describe("History window start (unix seconds)."),
|
|
2406
|
+
to: z12.number().optional().describe("History window end (unix seconds)."),
|
|
2407
|
+
query: z12.string().optional().describe("Free-text filter for list."),
|
|
2408
|
+
limit: z12.number().int().positive().max(2e3).optional().describe("Max items for list (default 100, max 2000)."),
|
|
2409
|
+
agent_id: z12.string().optional().describe("Agent identifier for budget tracking and enforcement.")
|
|
2160
2410
|
}
|
|
2161
2411
|
},
|
|
2162
2412
|
async ({ action, category, symbol, market, session, resolution, from, to, query, limit, agent_id }) => {
|
|
@@ -2229,7 +2479,7 @@ Examples:
|
|
|
2229
2479
|
}
|
|
2230
2480
|
|
|
2231
2481
|
// src/tools/dex.ts
|
|
2232
|
-
import { z as
|
|
2482
|
+
import { z as z13 } from "zod";
|
|
2233
2483
|
function registerDexTool(server) {
|
|
2234
2484
|
server.registerTool(
|
|
2235
2485
|
"blockrun_dex",
|
|
@@ -2246,10 +2496,10 @@ Examples:
|
|
|
2246
2496
|
blockrun_dex({ token: "So11...xxx" }) -> Get specific token data
|
|
2247
2497
|
blockrun_dex({ symbol: "PEPE" }) -> Search by symbol`,
|
|
2248
2498
|
inputSchema: {
|
|
2249
|
-
query:
|
|
2250
|
-
token:
|
|
2251
|
-
symbol:
|
|
2252
|
-
chain:
|
|
2499
|
+
query: z13.string().optional().describe("Search query (token name, symbol, or address)"),
|
|
2500
|
+
token: z13.string().optional().describe("Token address for direct lookup"),
|
|
2501
|
+
symbol: z13.string().optional().describe("Token symbol to search"),
|
|
2502
|
+
chain: z13.string().optional().describe("Filter by chain (ethereum, solana, base, etc.)")
|
|
2253
2503
|
}
|
|
2254
2504
|
},
|
|
2255
2505
|
async ({ query, token, symbol, chain }) => {
|
|
@@ -2310,7 +2560,7 @@ ${lines.join("\n\n")}` }],
|
|
|
2310
2560
|
}
|
|
2311
2561
|
|
|
2312
2562
|
// src/tools/modal.ts
|
|
2313
|
-
import { z as
|
|
2563
|
+
import { z as z14 } from "zod";
|
|
2314
2564
|
function estimateModalCost(path5) {
|
|
2315
2565
|
return path5.includes("sandbox/create") ? 0.01 : 1e-3;
|
|
2316
2566
|
}
|
|
@@ -2330,9 +2580,9 @@ Common paths (all POST):
|
|
|
2330
2580
|
|
|
2331
2581
|
Full action shapes + GPU type details in the \`modal\` skill.`,
|
|
2332
2582
|
inputSchema: {
|
|
2333
|
-
path:
|
|
2334
|
-
body:
|
|
2335
|
-
agent_id:
|
|
2583
|
+
path: z14.string().describe("Endpoint under /v1/modal/, e.g. 'sandbox/create', 'sandbox/exec'"),
|
|
2584
|
+
body: z14.any().optional().describe("JSON body. Sent as POST."),
|
|
2585
|
+
agent_id: z14.string().optional().describe("Agent identifier for budget tracking and enforcement.")
|
|
2336
2586
|
}
|
|
2337
2587
|
},
|
|
2338
2588
|
async ({ path: path5, body, agent_id }) => {
|
|
@@ -2363,7 +2613,7 @@ Full action shapes + GPU type details in the \`modal\` skill.`,
|
|
|
2363
2613
|
}
|
|
2364
2614
|
|
|
2365
2615
|
// src/tools/phone.ts
|
|
2366
|
-
import { z as
|
|
2616
|
+
import { z as z15 } from "zod";
|
|
2367
2617
|
function estimatePhoneCost(path5, hasBody) {
|
|
2368
2618
|
if (!hasBody && path5.startsWith("voice/call/")) return 0;
|
|
2369
2619
|
if (path5 === "phone/numbers/release") return 0;
|
|
@@ -2396,9 +2646,9 @@ Voice presets: nat, josh, maya, june, paige, derek, florian. Phone numbers use E
|
|
|
2396
2646
|
|
|
2397
2647
|
Voice call flow + voice preset details + full body shapes in the \`phone\` skill.`,
|
|
2398
2648
|
inputSchema: {
|
|
2399
|
-
path:
|
|
2400
|
-
body:
|
|
2401
|
-
agent_id:
|
|
2649
|
+
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."),
|
|
2650
|
+
body: z15.any().optional().describe("JSON body. Sent as POST. Omit for the free GET poll (voice/call/{call_id})."),
|
|
2651
|
+
agent_id: z15.string().optional().describe("Agent identifier for budget tracking and enforcement.")
|
|
2402
2652
|
}
|
|
2403
2653
|
},
|
|
2404
2654
|
async ({ path: path5, body, agent_id }) => {
|
|
@@ -2429,7 +2679,7 @@ Voice call flow + voice preset details + full body shapes in the \`phone\` skill
|
|
|
2429
2679
|
}
|
|
2430
2680
|
|
|
2431
2681
|
// src/tools/surf.ts
|
|
2432
|
-
import { z as
|
|
2682
|
+
import { z as z16 } from "zod";
|
|
2433
2683
|
var SURF_T3_PATHS = /* @__PURE__ */ new Set([
|
|
2434
2684
|
"onchain/sql",
|
|
2435
2685
|
"onchain/query",
|
|
@@ -2493,10 +2743,10 @@ Common paths (full 84-endpoint catalog in the surf skill):
|
|
|
2493
2743
|
Method is auto-routed: pass 'body' for POST endpoints; otherwise GET with 'params'.
|
|
2494
2744
|
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
2745
|
inputSchema: {
|
|
2496
|
-
path:
|
|
2497
|
-
params:
|
|
2498
|
-
body:
|
|
2499
|
-
agent_id:
|
|
2746
|
+
path: z16.string().describe("Endpoint path under /v1/surf/, e.g. 'market/price', 'prediction-market/polymarket/ranking', 'wallet/detail', 'onchain/sql', 'chat/completions'"),
|
|
2747
|
+
params: z16.record(z16.string(), z16.string()).optional().describe("Query parameters for GET endpoints, e.g. { symbol: 'BTC' } or { address: '0x...', chain: 'ethereum' }"),
|
|
2748
|
+
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."),
|
|
2749
|
+
agent_id: z16.string().optional().describe("Agent identifier for budget tracking and enforcement.")
|
|
2500
2750
|
}
|
|
2501
2751
|
},
|
|
2502
2752
|
async ({ path: path5, params, body, agent_id }) => {
|
|
@@ -2538,6 +2788,7 @@ function initializeMcpServer(server) {
|
|
|
2538
2788
|
registerModelsTool(server, modelCache);
|
|
2539
2789
|
registerImageTool(server, budget);
|
|
2540
2790
|
registerMusicTool(server, budget);
|
|
2791
|
+
registerSpeechTool(server, budget);
|
|
2541
2792
|
registerVideoTool(server, budget);
|
|
2542
2793
|
registerRealfaceTool(server, budget);
|
|
2543
2794
|
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.0",
|
|
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",
|