@blockrun/mcp 0.14.2 → 0.14.4
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 +15 -11
- package/dist/index.js +377 -85
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -129,7 +129,7 @@ Run `blockrun_wallet` to see your address. Send USDC on Base.
|
|
|
129
129
|
| Coinbase | Send → USDC → Base network → paste address |
|
|
130
130
|
| Bridge from Ethereum | [bridge.base.org](https://bridge.base.org) |
|
|
131
131
|
|
|
132
|
-
$5 covers ~5,000 market queries, ~500 Exa searches, ~250 image generations, or ~
|
|
132
|
+
$5 covers ~5,000 market queries, ~500 Exa searches, ~250 image generations, or ~20 Seedance 1.5-pro clips (5s, ~$0.23 each).
|
|
133
133
|
|
|
134
134
|
---
|
|
135
135
|
|
|
@@ -139,13 +139,13 @@ $5 covers ~5,000 market queries, ~500 Exa searches, ~250 image generations, or ~
|
|
|
139
139
|
|------|-------------|------|
|
|
140
140
|
| `blockrun_chat` | 55+ LLMs (GPT, Claude, Gemini, DeepSeek, Kimi K2.6, GLM, NVIDIA free tier, ...) with `mode` tier routing | per token |
|
|
141
141
|
| `blockrun_image` | DALL-E 3, GPT Image 1/2, Grok Imagine, Flux, CogView-4, Nano Banana — generation + editing | $0.015–0.12 |
|
|
142
|
-
| `blockrun_video` | xAI Grok Imagine Video + ByteDance Seedance 1.5/2.0/2.0-fast | $0.
|
|
142
|
+
| `blockrun_video` | xAI Grok Imagine Video + ByteDance Seedance 1.5/2.0/2.0-fast | $0.046–0.149/sec |
|
|
143
143
|
| `blockrun_music` | MiniMax music generation | per track |
|
|
144
144
|
| `blockrun_price` | Pyth-backed realtime + OHLC — crypto / FX / commodity (free), 12 stock markets (paid) | free or $0.001/call |
|
|
145
145
|
| `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 |
|
|
146
146
|
| `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 |
|
|
147
147
|
| `blockrun_exa` | Neural web search (Exa) — research, competitors, papers, URL content | $0.01/query |
|
|
148
|
-
| `blockrun_search` | Grok Live Search — web + news with citations |
|
|
148
|
+
| `blockrun_search` | Grok Live Search — web + news with citations | $0.025 × max_results (default 10) |
|
|
149
149
|
| `blockrun_dex` | Live DEX prices via DexScreener | free |
|
|
150
150
|
| `blockrun_models` | Live catalogue of every LLM/image/video/music model + pricing | free |
|
|
151
151
|
| `blockrun_wallet` | Balance, spending, agent budgets, setup QR | free |
|
|
@@ -230,7 +230,7 @@ Delegate a spending budget to a child agent with `agent_id`. The child is auto-b
|
|
|
230
230
|
## Troubleshooting
|
|
231
231
|
|
|
232
232
|
- **`Insufficient balance` / HTTP 402 after retry** → Run `blockrun_wallet action:"setup"`. Send USDC on Base (or Solana — see [Environment Variables](#environment-variables)).
|
|
233
|
-
- **`Smart routing (ClawRouter) is not available on Solana`** → Pass `model:` or `mode:` explicitly to `blockrun_chat`, or switch back to Base
|
|
233
|
+
- **`Smart routing (ClawRouter) is not available on Solana`** → Pass `model:` or `mode:` explicitly to `blockrun_chat`, or switch back to Base with `echo base > ~/.blockrun/.chain`.
|
|
234
234
|
- **`claude mcp list` doesn't show `blockrun`** → Check `node -v` (must be ≥18). Clear the npx cache: `rm -rf ~/.npm/_npx`. Re-run the install command from above.
|
|
235
235
|
- **`fetch failed` / timeout when checking wallet balance** → Base RPC transient outage. The tool already falls through 3 public RPCs; retry after 30s. Persistent failures usually = local proxy / firewall blocking outbound RPC.
|
|
236
236
|
- **`ENOENT: ~/.blockrun/.session`** → Expected on first run. The server auto-creates the wallet; check stderr for the `WALLET_CREATED` line confirming the address.
|
|
@@ -244,19 +244,23 @@ Delegate a spending budget to a child agent with `agent_id`. The child is auto-b
|
|
|
244
244
|
| Variable / File | Default | Effect |
|
|
245
245
|
|---|---|---|
|
|
246
246
|
| `~/.blockrun/.session` | auto-created on first run | EVM private key (0x...). File exists → use Base. |
|
|
247
|
-
| `~/.blockrun/.
|
|
247
|
+
| `~/.blockrun/.chain` | unset | Optional explicit chain preference: `base` or `solana`. |
|
|
248
|
+
| `~/.blockrun/.solana-session` | not created | Solana private key. File exists → switch to Solana unless `.chain` says `base`. |
|
|
248
249
|
| `SOLANA_WALLET_KEY` | unset | Env-var override of `.solana-session`. Set → use Solana. |
|
|
249
250
|
|
|
250
|
-
Chain selection priority (see `src/utils/wallet.ts
|
|
251
|
+
Chain selection priority (see `src/utils/wallet.ts`):
|
|
251
252
|
|
|
252
|
-
1. `
|
|
253
|
-
2.
|
|
254
|
-
3.
|
|
253
|
+
1. `~/.blockrun/.chain` or `~/.blockrun/payment-chain` set to `base` or `solana` → explicit preference wins
|
|
254
|
+
2. `SOLANA_WALLET_KEY` env var present → Solana
|
|
255
|
+
3. `~/.blockrun/.solana-session` exists → Solana
|
|
256
|
+
4. Otherwise → Base (`~/.blockrun/.session` auto-created)
|
|
255
257
|
|
|
256
258
|
**Switching chains:**
|
|
257
259
|
|
|
258
|
-
- Base → Solana: `
|
|
259
|
-
- Solana → Base: `
|
|
260
|
+
- Base → Solana: `echo solana > ~/.blockrun/.chain`, then set `SOLANA_WALLET_KEY` or create `~/.blockrun/.solana-session`
|
|
261
|
+
- Solana → Base: `echo base > ~/.blockrun/.chain` (the existing `.session` is reused, so it's the same Base wallet)
|
|
262
|
+
|
|
263
|
+
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.
|
|
260
264
|
|
|
261
265
|
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.
|
|
262
266
|
|
package/dist/index.js
CHANGED
|
@@ -29,6 +29,7 @@ var CHAIN_PREFERENCE_FILES = [
|
|
|
29
29
|
var _evmClient = null;
|
|
30
30
|
var _imageClient = null;
|
|
31
31
|
var _priceClient = null;
|
|
32
|
+
var _freePriceClient = null;
|
|
32
33
|
var _evmWalletInfo = null;
|
|
33
34
|
var _solanaClient = null;
|
|
34
35
|
function readChainPreference() {
|
|
@@ -86,7 +87,13 @@ function getImageClient() {
|
|
|
86
87
|
}
|
|
87
88
|
return _imageClient;
|
|
88
89
|
}
|
|
89
|
-
function getPriceClient() {
|
|
90
|
+
function getPriceClient(requireWallet = true) {
|
|
91
|
+
if (!requireWallet) {
|
|
92
|
+
if (!_freePriceClient) {
|
|
93
|
+
_freePriceClient = new PriceClient({ requireWallet: false });
|
|
94
|
+
}
|
|
95
|
+
return _freePriceClient;
|
|
96
|
+
}
|
|
90
97
|
if (!_priceClient) {
|
|
91
98
|
const privateKey = getOrCreateWalletKey();
|
|
92
99
|
_priceClient = new PriceClient({ privateKey });
|
|
@@ -331,9 +338,6 @@ Do NOT call this for actual AI queries \u2014 use blockrun_chat for that.`,
|
|
|
331
338
|
}
|
|
332
339
|
},
|
|
333
340
|
async ({ action, budget_action, budget_amount, agent_id, agent_limit }) => {
|
|
334
|
-
const info = await getWalletInfo();
|
|
335
|
-
const address = info.address;
|
|
336
|
-
const chain = getChain();
|
|
337
341
|
if (action === "budget") {
|
|
338
342
|
const budgetAct = budget_action || "check";
|
|
339
343
|
if (budgetAct === "set") {
|
|
@@ -409,6 +413,9 @@ Pass agent_id: "${agent_id}" in any blockrun_* tool call to track and enforce th
|
|
|
409
413
|
structuredContent: { global: { limit: budget.limit, spent: budget.spent, calls: budget.calls }, agents: agentRows }
|
|
410
414
|
};
|
|
411
415
|
}
|
|
416
|
+
const info = await getWalletInfo();
|
|
417
|
+
const address = info.address;
|
|
418
|
+
const chain = getChain();
|
|
412
419
|
if (action === "qr") {
|
|
413
420
|
try {
|
|
414
421
|
const qrPath = await generateQrPng(address, chain);
|
|
@@ -532,19 +539,26 @@ import { z as z2 } from "zod";
|
|
|
532
539
|
import { LLMClient as LLMClient2 } from "@blockrun/llm";
|
|
533
540
|
|
|
534
541
|
// src/utils/budget.ts
|
|
535
|
-
|
|
536
|
-
|
|
542
|
+
var EPSILON = 1e-9;
|
|
543
|
+
function formatUsd(amount) {
|
|
544
|
+
return `$${amount.toFixed(amount >= 1 ? 2 : 4)}`;
|
|
545
|
+
}
|
|
546
|
+
function checkBudget(budget, agentId, estimatedCost = 1e-3) {
|
|
547
|
+
const cost = Math.max(0, estimatedCost);
|
|
548
|
+
if (cost > 0 && budget.limit !== null && budget.spent + cost > budget.limit + EPSILON) {
|
|
549
|
+
const remaining = Math.max(0, budget.limit - budget.spent);
|
|
537
550
|
return {
|
|
538
551
|
allowed: false,
|
|
539
|
-
reason: `Global budget limit
|
|
552
|
+
reason: `Global budget limit ${formatUsd(budget.limit)} would be exceeded (${formatUsd(budget.spent)} spent, ${formatUsd(remaining)} remaining, next call estimated ${formatUsd(cost)})`
|
|
540
553
|
};
|
|
541
554
|
}
|
|
542
555
|
if (agentId) {
|
|
543
556
|
const agentBudget = budget.agents.get(agentId);
|
|
544
|
-
if (agentBudget && agentBudget.spent
|
|
557
|
+
if (cost > 0 && agentBudget && agentBudget.spent + cost > agentBudget.limit + EPSILON) {
|
|
558
|
+
const remaining = Math.max(0, agentBudget.limit - agentBudget.spent);
|
|
545
559
|
return {
|
|
546
560
|
allowed: false,
|
|
547
|
-
reason: `Agent "${agentId}" budget
|
|
561
|
+
reason: `Agent "${agentId}" budget ${formatUsd(agentBudget.limit)} would be exceeded (${formatUsd(agentBudget.spent)} spent, ${formatUsd(remaining)} remaining, next call estimated ${formatUsd(cost)})`
|
|
548
562
|
};
|
|
549
563
|
}
|
|
550
564
|
}
|
|
@@ -563,6 +577,24 @@ function recordSpending(budget, cost, agentId) {
|
|
|
563
577
|
}
|
|
564
578
|
|
|
565
579
|
// src/tools/chat.ts
|
|
580
|
+
function estimateChatCost(mode, model, routing, routingProfile) {
|
|
581
|
+
if (mode === "free") return 0;
|
|
582
|
+
if (model?.startsWith("nvidia/")) return 0;
|
|
583
|
+
if (routing === "smart" && routingProfile === "free") return 0;
|
|
584
|
+
if (routing === "smart") {
|
|
585
|
+
switch (routingProfile) {
|
|
586
|
+
case "eco":
|
|
587
|
+
return 2e-3;
|
|
588
|
+
case "premium":
|
|
589
|
+
return 0.05;
|
|
590
|
+
case "auto":
|
|
591
|
+
default:
|
|
592
|
+
return 0.01;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
if (mode === "reasoning" || mode === "powerful") return 0.01;
|
|
596
|
+
return 1e-3;
|
|
597
|
+
}
|
|
566
598
|
function registerChatTool(server, budget) {
|
|
567
599
|
server.registerTool(
|
|
568
600
|
"blockrun_chat",
|
|
@@ -598,7 +630,8 @@ Run blockrun_models to see all 41+ models with pricing.`,
|
|
|
598
630
|
},
|
|
599
631
|
async ({ message, model, mode, routing, routing_profile, system, max_tokens, temperature, agent_id, messages }) => {
|
|
600
632
|
const llm = getClient();
|
|
601
|
-
const
|
|
633
|
+
const estimatedCost = estimateChatCost(mode, model, routing, routing_profile);
|
|
634
|
+
const budgetCheck = checkBudget(budget, agent_id, estimatedCost);
|
|
602
635
|
if (!budgetCheck.allowed) {
|
|
603
636
|
return {
|
|
604
637
|
content: [{ type: "text", text: `${budgetCheck.reason}. Use blockrun_wallet with action: "report" to see usage, or action: "delegate" to increase agent budget.` }],
|
|
@@ -616,6 +649,7 @@ Run blockrun_models to see all 41+ models with pricing.`,
|
|
|
616
649
|
const result = await llm.smartChat(message, {
|
|
617
650
|
system,
|
|
618
651
|
maxTokens: max_tokens,
|
|
652
|
+
maxOutputTokens: max_tokens,
|
|
619
653
|
temperature,
|
|
620
654
|
routingProfile: routing_profile
|
|
621
655
|
});
|
|
@@ -648,7 +682,7 @@ ${result.response}` }],
|
|
|
648
682
|
temperature
|
|
649
683
|
});
|
|
650
684
|
const reply = result.choices?.[0]?.message?.content || "";
|
|
651
|
-
recordSpending(budget,
|
|
685
|
+
recordSpending(budget, estimatedCost, agent_id);
|
|
652
686
|
return {
|
|
653
687
|
content: [{ type: "text", text: `[${targetModel} | ${fullMessages.length} msgs]
|
|
654
688
|
|
|
@@ -667,7 +701,7 @@ ${reply}` }],
|
|
|
667
701
|
maxTokens: max_tokens,
|
|
668
702
|
temperature
|
|
669
703
|
});
|
|
670
|
-
recordSpending(budget,
|
|
704
|
+
recordSpending(budget, estimatedCost, agent_id);
|
|
671
705
|
return { content: [{ type: "text", text: response }] };
|
|
672
706
|
} catch (error) {
|
|
673
707
|
const errorMessage2 = error instanceof Error ? error.message : String(error);
|
|
@@ -684,9 +718,10 @@ ${reply}` }],
|
|
|
684
718
|
try {
|
|
685
719
|
const response = await llm.chat(m, message, {
|
|
686
720
|
system,
|
|
687
|
-
maxTokens: max_tokens
|
|
721
|
+
maxTokens: max_tokens,
|
|
722
|
+
temperature
|
|
688
723
|
});
|
|
689
|
-
recordSpending(budget,
|
|
724
|
+
recordSpending(budget, estimatedCost, agent_id);
|
|
690
725
|
return {
|
|
691
726
|
content: [{ type: "text", text: `[${m}]
|
|
692
727
|
|
|
@@ -709,6 +744,9 @@ ${response}` }],
|
|
|
709
744
|
|
|
710
745
|
// src/tools/models.ts
|
|
711
746
|
import { z as z3 } from "zod";
|
|
747
|
+
function getModelType(model) {
|
|
748
|
+
return model.type === "image" || "pricePerImage" in model ? "image" : "llm";
|
|
749
|
+
}
|
|
712
750
|
function registerModelsTool(server, modelCache) {
|
|
713
751
|
server.registerTool(
|
|
714
752
|
"blockrun_models",
|
|
@@ -722,7 +760,7 @@ function registerModelsTool(server, modelCache) {
|
|
|
722
760
|
async ({ category, provider }) => {
|
|
723
761
|
const llm = getClient();
|
|
724
762
|
if (!modelCache.models) {
|
|
725
|
-
modelCache.models = await llm.listModels();
|
|
763
|
+
modelCache.models = "listAllModels" in llm ? await llm.listAllModels() : await llm.listModels();
|
|
726
764
|
setTimeout(() => {
|
|
727
765
|
modelCache.models = null;
|
|
728
766
|
}, 5 * 60 * 1e3);
|
|
@@ -734,20 +772,27 @@ function registerModelsTool(server, modelCache) {
|
|
|
734
772
|
}
|
|
735
773
|
if (category && category !== "all") {
|
|
736
774
|
if (category === "image") {
|
|
737
|
-
models = models.filter((m) =>
|
|
775
|
+
models = models.filter((m) => getModelType(m) === "image");
|
|
738
776
|
} else if (category === "embedding") {
|
|
739
777
|
models = models.filter((m) => m.id.includes("embed"));
|
|
740
778
|
} else {
|
|
741
|
-
models = models.filter((m) => m.categories?.includes(category));
|
|
779
|
+
models = models.filter((m) => "categories" in m && m.categories?.includes(category));
|
|
742
780
|
}
|
|
743
781
|
}
|
|
744
782
|
const lines = models.map((m) => {
|
|
745
|
-
|
|
746
|
-
|
|
783
|
+
if (getModelType(m) === "image") {
|
|
784
|
+
const image = m;
|
|
785
|
+
const pricing2 = image.pricePerImage ? `$${image.pricePerImage}/image` : "";
|
|
786
|
+
const sizes = image.supportedSizes?.length ? ` | sizes: ${image.supportedSizes.join(", ")}` : "";
|
|
787
|
+
return `- ${image.id}${pricing2 ? ` (${pricing2})` : ""}${sizes} [image]`;
|
|
788
|
+
}
|
|
789
|
+
const llmModel = m;
|
|
790
|
+
const input = llmModel.inputPrice ? `$${llmModel.inputPrice}/M in` : "";
|
|
791
|
+
const output = llmModel.outputPrice ? `$${llmModel.outputPrice}/M out` : "";
|
|
747
792
|
const pricing = [input, output].filter(Boolean).join(", ");
|
|
748
|
-
const ctx =
|
|
749
|
-
const cats =
|
|
750
|
-
return `- ${
|
|
793
|
+
const ctx = llmModel.contextWindow ? ` | ${Math.round(llmModel.contextWindow / 1e3)}K ctx` : "";
|
|
794
|
+
const cats = llmModel.categories?.length ? ` [${llmModel.categories.join(", ")}]` : "";
|
|
795
|
+
return `- ${llmModel.id}${pricing ? ` (${pricing})` : ""}${ctx}${cats}`;
|
|
751
796
|
});
|
|
752
797
|
return {
|
|
753
798
|
content: [{ type: "text", text: `Models (${models.length}):
|
|
@@ -761,7 +806,18 @@ ${lines.join("\n")}` }],
|
|
|
761
806
|
// src/tools/image.ts
|
|
762
807
|
import { z as z4 } from "zod";
|
|
763
808
|
import { PaymentError } from "@blockrun/llm";
|
|
764
|
-
|
|
809
|
+
var GENERATE_MODEL_COST = {
|
|
810
|
+
"zai/cogview-4": 0.015,
|
|
811
|
+
"xai/grok-imagine-image": 0.02,
|
|
812
|
+
"xai/grok-imagine-image-pro": 0.07,
|
|
813
|
+
"openai/gpt-image-1": 0.04,
|
|
814
|
+
"openai/gpt-image-2": 0.12,
|
|
815
|
+
"openai/dall-e-3": 0.08,
|
|
816
|
+
"google/nano-banana": 0.05,
|
|
817
|
+
"together/flux-schnell": 3e-3
|
|
818
|
+
};
|
|
819
|
+
var EDIT_MODELS = /* @__PURE__ */ new Set(["openai/gpt-image-1", "openai/gpt-image-2"]);
|
|
820
|
+
function registerImageTool(server, budget) {
|
|
765
821
|
server.registerTool(
|
|
766
822
|
"blockrun_image",
|
|
767
823
|
{
|
|
@@ -786,12 +842,18 @@ Edit models: openai/gpt-image-1, openai/gpt-image-2 (default for edits)`,
|
|
|
786
842
|
model: z4.enum(["zai/cogview-4", "openai/dall-e-3", "together/flux-schnell", "google/nano-banana", "openai/gpt-image-1", "openai/gpt-image-2", "xai/grok-imagine-image", "xai/grok-imagine-image-pro"]).optional().describe("Model to use (default: dall-e-3 for generate, gpt-image-2 for edit). xai/grok-imagine-image is stylized and fast; xai/grok-imagine-image-pro is higher quality; gpt-image-2 is the newest edit-capable model with stronger instruction following."),
|
|
787
843
|
image: z4.string().optional().describe("Source image for edit action: base64-encoded image or URL"),
|
|
788
844
|
size: z4.enum(["1024x1024", "1792x1024", "1024x1792"]).optional().default("1024x1024"),
|
|
789
|
-
quality: z4.enum(["standard", "hd"]).optional().default("standard")
|
|
845
|
+
quality: z4.enum(["standard", "hd"]).optional().default("standard"),
|
|
846
|
+
agent_id: z4.string().optional().describe("Agent identifier for budget tracking and enforcement.")
|
|
790
847
|
}
|
|
791
848
|
},
|
|
792
|
-
async ({ prompt, action, model, image, size, quality }) => {
|
|
849
|
+
async ({ prompt, action, model, image, size, quality, agent_id }) => {
|
|
793
850
|
try {
|
|
794
|
-
|
|
851
|
+
if (getChain() !== "base") {
|
|
852
|
+
return {
|
|
853
|
+
content: [{ type: "text", text: formatError("blockrun_image currently settles on Base only. Switch BlockRun to Base (for example: write 'base' to ~/.blockrun/.chain) and fund the Base wallet with USDC.") }],
|
|
854
|
+
isError: true
|
|
855
|
+
};
|
|
856
|
+
}
|
|
795
857
|
let response;
|
|
796
858
|
if (action === "edit") {
|
|
797
859
|
if (!image) {
|
|
@@ -800,16 +862,42 @@ Edit models: openai/gpt-image-1, openai/gpt-image-2 (default for edits)`,
|
|
|
800
862
|
isError: true
|
|
801
863
|
};
|
|
802
864
|
}
|
|
803
|
-
|
|
804
|
-
|
|
865
|
+
const selectedModel = model || "openai/gpt-image-2";
|
|
866
|
+
if (!EDIT_MODELS.has(selectedModel)) {
|
|
867
|
+
return {
|
|
868
|
+
content: [{ type: "text", text: formatError("Image edits support only openai/gpt-image-1 or openai/gpt-image-2") }],
|
|
869
|
+
isError: true
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
const estimatedCost = selectedModel === "openai/gpt-image-2" ? 0.12 : 0.04;
|
|
873
|
+
const budgetCheck = checkBudget(budget, agent_id, estimatedCost);
|
|
874
|
+
if (!budgetCheck.allowed) {
|
|
875
|
+
return {
|
|
876
|
+
content: [{ type: "text", text: `${budgetCheck.reason}. Use blockrun_wallet action:"report" to see usage or action:"delegate" to increase agent budget.` }],
|
|
877
|
+
isError: true
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
response = await getImageClient().edit(prompt, image, {
|
|
881
|
+
model: selectedModel,
|
|
805
882
|
size
|
|
806
883
|
});
|
|
884
|
+
recordSpending(budget, estimatedCost, agent_id);
|
|
807
885
|
} else {
|
|
808
|
-
|
|
809
|
-
|
|
886
|
+
const selectedModel = model || "openai/dall-e-3";
|
|
887
|
+
const estimatedCost = GENERATE_MODEL_COST[selectedModel] ?? 0.05;
|
|
888
|
+
const budgetCheck = checkBudget(budget, agent_id, estimatedCost);
|
|
889
|
+
if (!budgetCheck.allowed) {
|
|
890
|
+
return {
|
|
891
|
+
content: [{ type: "text", text: `${budgetCheck.reason}. Use blockrun_wallet action:"report" to see usage or action:"delegate" to increase agent budget.` }],
|
|
892
|
+
isError: true
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
response = await getImageClient().generate(prompt, {
|
|
896
|
+
model: selectedModel,
|
|
810
897
|
size,
|
|
811
898
|
quality
|
|
812
899
|
});
|
|
900
|
+
recordSpending(budget, estimatedCost, agent_id);
|
|
813
901
|
}
|
|
814
902
|
const imageUrl = response.data?.[0]?.url;
|
|
815
903
|
if (!imageUrl) {
|
|
@@ -821,8 +909,8 @@ Edit models: openai/gpt-image-1, openai/gpt-image-2 (default for edits)`,
|
|
|
821
909
|
return {
|
|
822
910
|
content: [{ type: "text", text: `Image: ${imageUrl}
|
|
823
911
|
Prompt: ${prompt}
|
|
824
|
-
Model: ${model}` }],
|
|
825
|
-
structuredContent: { url: imageUrl, prompt, model }
|
|
912
|
+
Model: ${action === "edit" ? model || "openai/gpt-image-2" : model || "openai/dall-e-3"}` }],
|
|
913
|
+
structuredContent: { url: imageUrl, prompt, model: action === "edit" ? model || "openai/gpt-image-2" : model || "openai/dall-e-3" }
|
|
826
914
|
};
|
|
827
915
|
} catch (err) {
|
|
828
916
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -852,7 +940,8 @@ import {
|
|
|
852
940
|
} from "@blockrun/llm";
|
|
853
941
|
var BLOCKRUN_API = "https://blockrun.ai/api";
|
|
854
942
|
var MUSIC_TIMEOUT = 2e5;
|
|
855
|
-
|
|
943
|
+
var MUSIC_COST = 0.1575;
|
|
944
|
+
function registerMusicTool(server, budget) {
|
|
856
945
|
server.registerTool(
|
|
857
946
|
"blockrun_music",
|
|
858
947
|
{
|
|
@@ -867,17 +956,31 @@ Returns a time-limited CDN URL \u2014 download immediately if you need to keep t
|
|
|
867
956
|
prompt: z5.string().describe("Music style, mood, or description. E.g. 'upbeat synthwave with neon pads', 'chill lo-fi beats', 'epic orchestral film score'"),
|
|
868
957
|
instrumental: z5.boolean().optional().default(true).describe("Generate without vocals (default: true)"),
|
|
869
958
|
lyrics: z5.string().optional().describe("Custom lyrics. Cannot be used with instrumental: true"),
|
|
870
|
-
model: z5.enum(["minimax/music-2.5+", "minimax/music-2.5"]).optional().default("minimax/music-2.5+").describe("Music model to use")
|
|
959
|
+
model: z5.enum(["minimax/music-2.5+", "minimax/music-2.5"]).optional().default("minimax/music-2.5+").describe("Music model to use"),
|
|
960
|
+
agent_id: z5.string().optional().describe("Agent identifier for budget tracking and enforcement.")
|
|
871
961
|
}
|
|
872
962
|
},
|
|
873
|
-
async ({ prompt, instrumental, lyrics, model }) => {
|
|
963
|
+
async ({ prompt, instrumental, lyrics, model, agent_id }) => {
|
|
874
964
|
try {
|
|
965
|
+
if (getChain() !== "base") {
|
|
966
|
+
return {
|
|
967
|
+
content: [{ type: "text", text: formatError("blockrun_music currently settles on Base only. Switch BlockRun to Base (for example: write 'base' to ~/.blockrun/.chain) and fund the Base wallet with USDC.") }],
|
|
968
|
+
isError: true
|
|
969
|
+
};
|
|
970
|
+
}
|
|
875
971
|
if (instrumental && lyrics?.trim()) {
|
|
876
972
|
return {
|
|
877
973
|
content: [{ type: "text", text: formatError("Cannot specify lyrics when instrumental is true") }],
|
|
878
974
|
isError: true
|
|
879
975
|
};
|
|
880
976
|
}
|
|
977
|
+
const budgetCheck = checkBudget(budget, agent_id, MUSIC_COST);
|
|
978
|
+
if (!budgetCheck.allowed) {
|
|
979
|
+
return {
|
|
980
|
+
content: [{ type: "text", text: `${budgetCheck.reason}. Use blockrun_wallet action:"report" to see usage or action:"delegate" to increase agent budget.` }],
|
|
981
|
+
isError: true
|
|
982
|
+
};
|
|
983
|
+
}
|
|
881
984
|
const privateKey = getOrCreateWalletKey();
|
|
882
985
|
const account = privateKeyToAccount(privateKey);
|
|
883
986
|
const url = `${BLOCKRUN_API}/v1/audio/generations`;
|
|
@@ -928,6 +1031,7 @@ Returns a time-limited CDN URL \u2014 download immediately if you need to keep t
|
|
|
928
1031
|
const track = data.data?.[0];
|
|
929
1032
|
if (!track?.url) throw new Error("No track URL in response");
|
|
930
1033
|
const txHash = resp.headers.get("X-Payment-Receipt") || resp.headers.get("x-payment-receipt");
|
|
1034
|
+
recordSpending(budget, MUSIC_COST, agent_id);
|
|
931
1035
|
const lines = [
|
|
932
1036
|
`\u{1F3B5} Track ready!`,
|
|
933
1037
|
`URL: ${track.url}`,
|
|
@@ -993,7 +1097,19 @@ import {
|
|
|
993
1097
|
var BLOCKRUN_API2 = "https://blockrun.ai/api";
|
|
994
1098
|
var TOTAL_BUDGET_MS = 3e5;
|
|
995
1099
|
var POLL_INTERVAL_MS = 5e3;
|
|
996
|
-
|
|
1100
|
+
var VIDEO_PRICE_PER_SECOND = {
|
|
1101
|
+
"xai/grok-imagine-video": 0.05,
|
|
1102
|
+
"bytedance/seedance-1.5-pro": 0.046,
|
|
1103
|
+
"bytedance/seedance-2.0-fast": 0.119,
|
|
1104
|
+
"bytedance/seedance-2.0": 0.149
|
|
1105
|
+
};
|
|
1106
|
+
var VIDEO_DEFAULT_DURATION = {
|
|
1107
|
+
"xai/grok-imagine-video": 8,
|
|
1108
|
+
"bytedance/seedance-1.5-pro": 5,
|
|
1109
|
+
"bytedance/seedance-2.0-fast": 5,
|
|
1110
|
+
"bytedance/seedance-2.0": 5
|
|
1111
|
+
};
|
|
1112
|
+
function registerVideoTool(server, budget) {
|
|
997
1113
|
server.registerTool(
|
|
998
1114
|
"blockrun_video",
|
|
999
1115
|
{
|
|
@@ -1003,24 +1119,41 @@ Turns a text prompt (and optional seed image) into a short MP4 clip. The tool su
|
|
|
1003
1119
|
|
|
1004
1120
|
Models:
|
|
1005
1121
|
- xai/grok-imagine-video ($0.05/sec, 8s default -> $0.42/clip) \u2014 stylized, fast
|
|
1006
|
-
- bytedance/seedance-1.5-pro (
|
|
1007
|
-
- bytedance/seedance-2.0-fast (
|
|
1008
|
-
- bytedance/seedance-2.0 (
|
|
1122
|
+
- bytedance/seedance-1.5-pro (~$0.046/sec, 480p, 5s default up to 10s) \u2014 cheapest, token-priced upstream
|
|
1123
|
+
- bytedance/seedance-2.0-fast (~$0.119/sec text \xB7 ~$0.07/sec image-to-video, ~60-80s gen) \u2014 sweet-spot price/quality
|
|
1124
|
+
- bytedance/seedance-2.0 (~$0.149/sec text \xB7 ~$0.092/sec image-to-video, 480p Pro) \u2014 highest quality
|
|
1009
1125
|
|
|
1010
1126
|
Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GCS so URLs don't expire).`,
|
|
1011
1127
|
inputSchema: {
|
|
1012
1128
|
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'"),
|
|
1013
1129
|
image_url: z6.string().url().optional().describe("Optional seed image URL for image-to-video generation"),
|
|
1014
1130
|
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)."),
|
|
1015
|
-
model: z6.enum(["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")
|
|
1131
|
+
model: z6.enum(["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"),
|
|
1132
|
+
agent_id: z6.string().optional().describe("Agent identifier for budget tracking and enforcement.")
|
|
1016
1133
|
}
|
|
1017
1134
|
},
|
|
1018
|
-
async ({ prompt, image_url, duration_seconds, model }) => {
|
|
1135
|
+
async ({ prompt, image_url, duration_seconds, model, agent_id }) => {
|
|
1019
1136
|
try {
|
|
1137
|
+
if (getChain() !== "base") {
|
|
1138
|
+
return {
|
|
1139
|
+
content: [{ type: "text", text: formatError("blockrun_video currently settles on Base only. Switch BlockRun to Base (for example: write 'base' to ~/.blockrun/.chain) and fund the Base wallet with USDC.") }],
|
|
1140
|
+
isError: true
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
const selectedModel = model || "xai/grok-imagine-video";
|
|
1144
|
+
const billedSeconds = duration_seconds ?? VIDEO_DEFAULT_DURATION[selectedModel] ?? 8;
|
|
1145
|
+
const estimatedCost = (VIDEO_PRICE_PER_SECOND[selectedModel] ?? 0.05) * billedSeconds;
|
|
1146
|
+
const budgetCheck = checkBudget(budget, agent_id, estimatedCost);
|
|
1147
|
+
if (!budgetCheck.allowed) {
|
|
1148
|
+
return {
|
|
1149
|
+
content: [{ type: "text", text: `${budgetCheck.reason}. Use blockrun_wallet action:"report" to see usage or action:"delegate" to increase agent budget.` }],
|
|
1150
|
+
isError: true
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1020
1153
|
const privateKey = getOrCreateWalletKey();
|
|
1021
1154
|
const account = privateKeyToAccount2(privateKey);
|
|
1022
1155
|
const submitUrl = `${BLOCKRUN_API2}/v1/videos/generations`;
|
|
1023
|
-
const body = { model, prompt };
|
|
1156
|
+
const body = { model: selectedModel, prompt };
|
|
1024
1157
|
if (image_url) body.image_url = image_url;
|
|
1025
1158
|
if (duration_seconds !== void 0) body.duration_seconds = duration_seconds;
|
|
1026
1159
|
const resp402 = await fetchWithTimeout2(submitUrl, {
|
|
@@ -1113,18 +1246,19 @@ Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GC
|
|
|
1113
1246
|
`\u{1F3AC} Video ready!`,
|
|
1114
1247
|
`URL: ${completed.url}`,
|
|
1115
1248
|
`Duration: ${completed.duration_seconds ? `${completed.duration_seconds}s` : "8s"}`,
|
|
1116
|
-
`Model: ${completed.modelReturned ||
|
|
1249
|
+
`Model: ${completed.modelReturned || selectedModel}`,
|
|
1117
1250
|
...completed.backed_up ? [`Backed up to BlockRun storage (URL is permanent)`] : completed.source_url ? [`Source URL: ${completed.source_url}`] : [],
|
|
1118
1251
|
...completed.request_id ? [`Request ID: ${completed.request_id}`] : [],
|
|
1119
1252
|
...completed.txHash ? [`Tx: ${completed.txHash}`] : []
|
|
1120
1253
|
];
|
|
1254
|
+
recordSpending(budget, estimatedCost, agent_id);
|
|
1121
1255
|
return {
|
|
1122
1256
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
1123
1257
|
structuredContent: {
|
|
1124
1258
|
url: completed.url,
|
|
1125
1259
|
...completed.source_url ? { source_url: completed.source_url } : {},
|
|
1126
1260
|
duration_seconds: completed.duration_seconds,
|
|
1127
|
-
model: completed.modelReturned ||
|
|
1261
|
+
model: completed.modelReturned || selectedModel,
|
|
1128
1262
|
...completed.request_id ? { request_id: completed.request_id } : {},
|
|
1129
1263
|
...completed.backed_up !== void 0 ? { backed_up: completed.backed_up } : {},
|
|
1130
1264
|
...completed.txHash ? { txHash: completed.txHash } : {}
|
|
@@ -1166,29 +1300,47 @@ async function fetchWithTimeout2(url, options, timeoutMs) {
|
|
|
1166
1300
|
|
|
1167
1301
|
// src/tools/search.ts
|
|
1168
1302
|
import { z as z7 } from "zod";
|
|
1169
|
-
|
|
1303
|
+
var SEARCH_PRICE_PER_SOURCE = 0.025;
|
|
1304
|
+
var SEARCH_DEFAULT_MAX_RESULTS = 10;
|
|
1305
|
+
function estimateSearchCost(body) {
|
|
1306
|
+
if (!body || typeof body !== "object") return SEARCH_PRICE_PER_SOURCE * SEARCH_DEFAULT_MAX_RESULTS;
|
|
1307
|
+
const raw = body.max_results;
|
|
1308
|
+
const max = typeof raw === "number" && raw > 0 ? Math.min(50, Math.floor(raw)) : SEARCH_DEFAULT_MAX_RESULTS;
|
|
1309
|
+
return SEARCH_PRICE_PER_SOURCE * max;
|
|
1310
|
+
}
|
|
1311
|
+
function registerSearchTool(server, budget) {
|
|
1170
1312
|
server.registerTool(
|
|
1171
1313
|
"blockrun_search",
|
|
1172
1314
|
{
|
|
1173
|
-
description: `Grok Live Search \u2014 real-time web + X/Twitter + news with AI-summarized results and citations.
|
|
1315
|
+
description: `Grok Live Search \u2014 real-time web + X/Twitter + news with AI-summarized results and citations. $0.025 per returned source (max_results \xD7 $0.025; default max_results=10 \u2192 $0.25).
|
|
1174
1316
|
|
|
1175
1317
|
Common shape:
|
|
1176
|
-
- body: { query: "...", sources: ["web","x","news"],
|
|
1318
|
+
- body: { query: "...", sources: ["web","x","news"], max_results: 10, from_date: "YYYY-MM-DD", to_date: "YYYY-MM-DD" }
|
|
1177
1319
|
|
|
1178
|
-
\`sources\` accepts any subset of ["web","x","news"] (defaults to all three). For tweet-only searches, use ["x"].
|
|
1320
|
+
\`sources\` accepts any subset of ["web","x","news"] (defaults to all three). For tweet-only searches, use ["x"]. \`max_results\` is 1\u201350 (default 10) and drives the price \u2014 pass a smaller value if you want to cap spend.
|
|
1179
1321
|
|
|
1180
1322
|
Full request shape + worked examples in the \`search\` skill (\`skills/search/SKILL.md\`).`,
|
|
1181
1323
|
inputSchema: {
|
|
1182
1324
|
path: z7.string().optional().default("").describe("Endpoint sub-path under /v1/search/ (default empty = root /v1/search). Reserved for future surfaces."),
|
|
1183
|
-
body: z7.any().optional().describe("Request body. At minimum { query: '...' }. Sent as POST.")
|
|
1325
|
+
body: z7.any().optional().describe("Request body. At minimum { query: '...' }. Sent as POST."),
|
|
1326
|
+
agent_id: z7.string().optional().describe("Agent identifier for budget tracking and enforcement.")
|
|
1184
1327
|
}
|
|
1185
1328
|
},
|
|
1186
|
-
async ({ path: path4, body }) => {
|
|
1329
|
+
async ({ path: path4, body, agent_id }) => {
|
|
1187
1330
|
try {
|
|
1331
|
+
const estimatedCost = estimateSearchCost(body);
|
|
1332
|
+
const budgetCheck = checkBudget(budget, agent_id, estimatedCost);
|
|
1333
|
+
if (!budgetCheck.allowed) {
|
|
1334
|
+
return {
|
|
1335
|
+
content: [{ type: "text", text: `${budgetCheck.reason}. Use blockrun_wallet action:"report" to see usage or action:"delegate" to increase agent budget.` }],
|
|
1336
|
+
isError: true
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1188
1339
|
const client = getClient();
|
|
1189
1340
|
const cleanPath = (path4 ?? "").replace(/^\/+/, "").replace(/^v1\/search\/?/, "");
|
|
1190
1341
|
const endpoint = cleanPath ? `/v1/search/${cleanPath}` : "/v1/search";
|
|
1191
1342
|
const result = await client.requestWithPaymentRaw(endpoint, body ?? {});
|
|
1343
|
+
recordSpending(budget, estimatedCost, agent_id);
|
|
1192
1344
|
return {
|
|
1193
1345
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1194
1346
|
structuredContent: result
|
|
@@ -1202,7 +1354,15 @@ Full request shape + worked examples in the \`search\` skill (\`skills/search/SK
|
|
|
1202
1354
|
|
|
1203
1355
|
// src/tools/exa.ts
|
|
1204
1356
|
import { z as z8 } from "zod";
|
|
1205
|
-
function
|
|
1357
|
+
function estimateExaCost(path4, body) {
|
|
1358
|
+
const cleanPath = path4.replace(/^\/+/, "").replace(/^v1\/exa\//, "");
|
|
1359
|
+
if (cleanPath === "contents") {
|
|
1360
|
+
const urls = body && typeof body === "object" ? body.urls : void 0;
|
|
1361
|
+
return 2e-3 * (Array.isArray(urls) && urls.length > 0 ? urls.length : 1);
|
|
1362
|
+
}
|
|
1363
|
+
return 0.01;
|
|
1364
|
+
}
|
|
1365
|
+
function registerExaTool(server, budget) {
|
|
1206
1366
|
server.registerTool(
|
|
1207
1367
|
"blockrun_exa",
|
|
1208
1368
|
{
|
|
@@ -1219,15 +1379,25 @@ Categories for search: "news", "research paper", "company", "tweet", "github", "
|
|
|
1219
1379
|
Full request/response shapes + worked research workflows in the \`exa-research\` skill.`,
|
|
1220
1380
|
inputSchema: {
|
|
1221
1381
|
path: z8.string().describe("Endpoint name under /v1/exa/, e.g. 'search', 'answer', 'contents', 'find-similar'"),
|
|
1222
|
-
body: z8.any().optional().describe("JSON body for the call. Sent as POST. Required for all four endpoints.")
|
|
1382
|
+
body: z8.any().optional().describe("JSON body for the call. Sent as POST. Required for all four endpoints."),
|
|
1383
|
+
agent_id: z8.string().optional().describe("Agent identifier for budget tracking and enforcement.")
|
|
1223
1384
|
}
|
|
1224
1385
|
},
|
|
1225
|
-
async ({ path: path4, body }) => {
|
|
1386
|
+
async ({ path: path4, body, agent_id }) => {
|
|
1226
1387
|
try {
|
|
1388
|
+
const estimatedCost = estimateExaCost(path4, body);
|
|
1389
|
+
const budgetCheck = checkBudget(budget, agent_id, estimatedCost);
|
|
1390
|
+
if (!budgetCheck.allowed) {
|
|
1391
|
+
return {
|
|
1392
|
+
content: [{ type: "text", text: `${budgetCheck.reason}. Use blockrun_wallet action:"report" to see usage or action:"delegate" to increase agent budget.` }],
|
|
1393
|
+
isError: true
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1227
1396
|
const client = getClient();
|
|
1228
1397
|
const cleanPath = path4.replace(/^\/+/, "").replace(/^v1\/exa\//, "");
|
|
1229
1398
|
const endpoint = `/v1/exa/${cleanPath}`;
|
|
1230
1399
|
const result = await client.requestWithPaymentRaw(endpoint, body ?? {});
|
|
1400
|
+
recordSpending(budget, estimatedCost, agent_id);
|
|
1231
1401
|
return {
|
|
1232
1402
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1233
1403
|
structuredContent: result
|
|
@@ -1241,7 +1411,15 @@ Full request/response shapes + worked research workflows in the \`exa-research\`
|
|
|
1241
1411
|
|
|
1242
1412
|
// src/tools/markets.ts
|
|
1243
1413
|
import { z as z9 } from "zod";
|
|
1244
|
-
function
|
|
1414
|
+
function estimateMarketCost(path4, body) {
|
|
1415
|
+
if (body !== void 0) return 5e-3;
|
|
1416
|
+
const p = path4.toLowerCase();
|
|
1417
|
+
if (p.includes("wallet") || p.includes("smart") || p.includes("matching-markets") || p.includes("markets/search") || p.includes("binance/")) {
|
|
1418
|
+
return 5e-3;
|
|
1419
|
+
}
|
|
1420
|
+
return 1e-3;
|
|
1421
|
+
}
|
|
1422
|
+
function registerMarketsTool(server, budget) {
|
|
1245
1423
|
server.registerTool(
|
|
1246
1424
|
"blockrun_markets",
|
|
1247
1425
|
{
|
|
@@ -1299,13 +1477,23 @@ Pass query params via 'params' (GET). Use 'body' only for POST endpoints (e.g. p
|
|
|
1299
1477
|
inputSchema: {
|
|
1300
1478
|
path: z9.string().describe("Endpoint path, e.g. 'polymarket/events', 'kalshi/markets/KXBTC-25MAR14', 'polymarket/wallet/0xabc...', 'markets/search'"),
|
|
1301
1479
|
params: z9.record(z9.string(), z9.string()).optional().describe("Query parameters for GET requests (e.g. { limit: '20', active: 'true' })"),
|
|
1302
|
-
body: z9.any().optional().describe("JSON body for POST queries (triggers pmQuery \u2014 most endpoints are GET)")
|
|
1480
|
+
body: z9.any().optional().describe("JSON body for POST queries (triggers pmQuery \u2014 most endpoints are GET)"),
|
|
1481
|
+
agent_id: z9.string().optional().describe("Agent identifier for budget tracking and enforcement.")
|
|
1303
1482
|
}
|
|
1304
1483
|
},
|
|
1305
|
-
async ({ path: path4, params, body }) => {
|
|
1484
|
+
async ({ path: path4, params, body, agent_id }) => {
|
|
1306
1485
|
try {
|
|
1486
|
+
const estimatedCost = estimateMarketCost(path4, body);
|
|
1487
|
+
const budgetCheck = checkBudget(budget, agent_id, estimatedCost);
|
|
1488
|
+
if (!budgetCheck.allowed) {
|
|
1489
|
+
return {
|
|
1490
|
+
content: [{ type: "text", text: `${budgetCheck.reason}. Use blockrun_wallet action:"report" to see usage or action:"delegate" to increase agent budget.` }],
|
|
1491
|
+
isError: true
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1307
1494
|
const llm = getClient();
|
|
1308
|
-
const result = body ? await llm.pmQuery(path4, body) : await llm.pm(path4, params);
|
|
1495
|
+
const result = body !== void 0 ? await llm.pmQuery(path4, body) : await llm.pm(path4, params);
|
|
1496
|
+
recordSpending(budget, estimatedCost, agent_id);
|
|
1309
1497
|
return {
|
|
1310
1498
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1311
1499
|
structuredContent: result
|
|
@@ -1341,7 +1529,10 @@ var MARKET = z10.enum([
|
|
|
1341
1529
|
var RESOLUTION = z10.enum(["1", "5", "15", "60", "240", "D", "W", "M"]);
|
|
1342
1530
|
var SESSION = z10.enum(["pre", "post", "on"]);
|
|
1343
1531
|
var ACTION = z10.enum(["price", "history", "list"]);
|
|
1344
|
-
function
|
|
1532
|
+
function isPaidPriceCall(action, category) {
|
|
1533
|
+
return action !== "list" && (category === "stocks" || category === "usstock");
|
|
1534
|
+
}
|
|
1535
|
+
function registerPriceTool(server, budget) {
|
|
1345
1536
|
server.registerTool(
|
|
1346
1537
|
"blockrun_price",
|
|
1347
1538
|
{
|
|
@@ -1372,18 +1563,38 @@ Examples:
|
|
|
1372
1563
|
from: z10.number().optional().describe("History window start (unix seconds)."),
|
|
1373
1564
|
to: z10.number().optional().describe("History window end (unix seconds)."),
|
|
1374
1565
|
query: z10.string().optional().describe("Free-text filter for list."),
|
|
1375
|
-
limit: z10.number().optional().describe("Max items for list (default 100, max 2000).")
|
|
1566
|
+
limit: z10.number().int().positive().max(2e3).optional().describe("Max items for list (default 100, max 2000)."),
|
|
1567
|
+
agent_id: z10.string().optional().describe("Agent identifier for budget tracking and enforcement.")
|
|
1376
1568
|
}
|
|
1377
1569
|
},
|
|
1378
|
-
async ({ action, category, symbol, market, session, resolution, from, to, query, limit }) => {
|
|
1570
|
+
async ({ action, category, symbol, market, session, resolution, from, to, query, limit, agent_id }) => {
|
|
1379
1571
|
try {
|
|
1380
|
-
|
|
1572
|
+
if (category === "stocks" && !market) {
|
|
1573
|
+
throw new Error("market is required when category='stocks'");
|
|
1574
|
+
}
|
|
1575
|
+
const paid = isPaidPriceCall(action, category);
|
|
1576
|
+
if (paid && getChain() !== "base") {
|
|
1577
|
+
return {
|
|
1578
|
+
content: [{ type: "text", text: formatError("Paid stock price/history calls currently settle on Base only. Switch BlockRun to Base (for example: write 'base' to ~/.blockrun/.chain) and fund the Base wallet with USDC.") }],
|
|
1579
|
+
isError: true
|
|
1580
|
+
};
|
|
1581
|
+
}
|
|
1582
|
+
const estimatedCost = paid ? 1e-3 : 0;
|
|
1583
|
+
const budgetCheck = checkBudget(budget, agent_id, estimatedCost);
|
|
1584
|
+
if (!budgetCheck.allowed) {
|
|
1585
|
+
return {
|
|
1586
|
+
content: [{ type: "text", text: `${budgetCheck.reason}. Use blockrun_wallet action:"report" to see usage or action:"delegate" to increase agent budget.` }],
|
|
1587
|
+
isError: true
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
const priceClient = getPriceClient(paid);
|
|
1381
1591
|
if (action === "price") {
|
|
1382
1592
|
if (!symbol) throw new Error("symbol is required for action='price'");
|
|
1383
1593
|
const result2 = await priceClient.price(category, symbol, {
|
|
1384
1594
|
market,
|
|
1385
1595
|
session
|
|
1386
1596
|
});
|
|
1597
|
+
if (estimatedCost > 0) recordSpending(budget, estimatedCost, agent_id);
|
|
1387
1598
|
return {
|
|
1388
1599
|
content: [{ type: "text", text: JSON.stringify(result2, null, 2) }],
|
|
1389
1600
|
structuredContent: result2
|
|
@@ -1391,7 +1602,7 @@ Examples:
|
|
|
1391
1602
|
}
|
|
1392
1603
|
if (action === "history") {
|
|
1393
1604
|
if (!symbol) throw new Error("symbol is required for action='history'");
|
|
1394
|
-
if (
|
|
1605
|
+
if (from === void 0) throw new Error("from (unix seconds) is required for action='history'");
|
|
1395
1606
|
const result2 = await priceClient.history(category, symbol, {
|
|
1396
1607
|
market,
|
|
1397
1608
|
session,
|
|
@@ -1399,6 +1610,7 @@ Examples:
|
|
|
1399
1610
|
from,
|
|
1400
1611
|
to
|
|
1401
1612
|
});
|
|
1613
|
+
if (estimatedCost > 0) recordSpending(budget, estimatedCost, agent_id);
|
|
1402
1614
|
return {
|
|
1403
1615
|
content: [{ type: "text", text: JSON.stringify(result2, null, 2) }],
|
|
1404
1616
|
structuredContent: result2
|
|
@@ -1507,7 +1719,10 @@ ${lines.join("\n\n")}` }],
|
|
|
1507
1719
|
|
|
1508
1720
|
// src/tools/modal.ts
|
|
1509
1721
|
import { z as z12 } from "zod";
|
|
1510
|
-
function
|
|
1722
|
+
function estimateModalCost(path4) {
|
|
1723
|
+
return path4.includes("sandbox/create") ? 0.01 : 1e-3;
|
|
1724
|
+
}
|
|
1725
|
+
function registerModalTool(server, budget) {
|
|
1511
1726
|
server.registerTool(
|
|
1512
1727
|
"blockrun_modal",
|
|
1513
1728
|
{
|
|
@@ -1524,15 +1739,25 @@ Common paths (all POST):
|
|
|
1524
1739
|
Full action shapes + GPU type details in the \`modal\` skill.`,
|
|
1525
1740
|
inputSchema: {
|
|
1526
1741
|
path: z12.string().describe("Endpoint under /v1/modal/, e.g. 'sandbox/create', 'sandbox/exec'"),
|
|
1527
|
-
body: z12.any().optional().describe("JSON body. Sent as POST.")
|
|
1742
|
+
body: z12.any().optional().describe("JSON body. Sent as POST."),
|
|
1743
|
+
agent_id: z12.string().optional().describe("Agent identifier for budget tracking and enforcement.")
|
|
1528
1744
|
}
|
|
1529
1745
|
},
|
|
1530
|
-
async ({ path: path4, body }) => {
|
|
1746
|
+
async ({ path: path4, body, agent_id }) => {
|
|
1531
1747
|
try {
|
|
1532
|
-
const client = getClient();
|
|
1533
1748
|
const cleanPath = path4.replace(/^\/+/, "").replace(/^v1\/modal\//, "");
|
|
1749
|
+
const estimatedCost = estimateModalCost(cleanPath);
|
|
1750
|
+
const budgetCheck = checkBudget(budget, agent_id, estimatedCost);
|
|
1751
|
+
if (!budgetCheck.allowed) {
|
|
1752
|
+
return {
|
|
1753
|
+
content: [{ type: "text", text: `${budgetCheck.reason}. Use blockrun_wallet action:"report" to see usage or action:"delegate" to increase agent budget.` }],
|
|
1754
|
+
isError: true
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1757
|
+
const client = getClient();
|
|
1534
1758
|
const endpoint = `/v1/modal/${cleanPath}`;
|
|
1535
1759
|
const result = await client.requestWithPaymentRaw(endpoint, body ?? {});
|
|
1760
|
+
recordSpending(budget, estimatedCost, agent_id);
|
|
1536
1761
|
return {
|
|
1537
1762
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1538
1763
|
structuredContent: result
|
|
@@ -1546,7 +1771,17 @@ Full action shapes + GPU type details in the \`modal\` skill.`,
|
|
|
1546
1771
|
|
|
1547
1772
|
// src/tools/phone.ts
|
|
1548
1773
|
import { z as z13 } from "zod";
|
|
1549
|
-
function
|
|
1774
|
+
function estimatePhoneCost(path4, hasBody) {
|
|
1775
|
+
if (!hasBody && path4.startsWith("voice/call/")) return 0;
|
|
1776
|
+
if (path4 === "phone/numbers/release") return 0;
|
|
1777
|
+
if (path4 === "phone/lookup") return 0.01;
|
|
1778
|
+
if (path4 === "phone/lookup/fraud") return 0.05;
|
|
1779
|
+
if (path4 === "phone/numbers/buy" || path4 === "phone/numbers/renew") return 5;
|
|
1780
|
+
if (path4 === "phone/numbers/list") return 1e-3;
|
|
1781
|
+
if (path4 === "voice/call") return 0.54;
|
|
1782
|
+
return hasBody ? 1e-3 : 0;
|
|
1783
|
+
}
|
|
1784
|
+
function registerPhoneTool(server, budget) {
|
|
1550
1785
|
server.registerTool(
|
|
1551
1786
|
"blockrun_phone",
|
|
1552
1787
|
{
|
|
@@ -1569,15 +1804,25 @@ Voice presets: nat, josh, maya, june, paige, derek, florian. Phone numbers use E
|
|
|
1569
1804
|
Voice call flow + voice preset details + full body shapes in the \`phone\` skill.`,
|
|
1570
1805
|
inputSchema: {
|
|
1571
1806
|
path: z13.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."),
|
|
1572
|
-
body: z13.any().optional().describe("JSON body. Sent as POST. Omit for the free GET poll (voice/call/{call_id}).")
|
|
1807
|
+
body: z13.any().optional().describe("JSON body. Sent as POST. Omit for the free GET poll (voice/call/{call_id})."),
|
|
1808
|
+
agent_id: z13.string().optional().describe("Agent identifier for budget tracking and enforcement.")
|
|
1573
1809
|
}
|
|
1574
1810
|
},
|
|
1575
|
-
async ({ path: path4, body }) => {
|
|
1811
|
+
async ({ path: path4, body, agent_id }) => {
|
|
1576
1812
|
try {
|
|
1577
|
-
const client = getClient();
|
|
1578
1813
|
const cleanPath = path4.replace(/^\/+/, "").replace(/^v1\//, "");
|
|
1814
|
+
const estimatedCost = estimatePhoneCost(cleanPath, body !== void 0);
|
|
1815
|
+
const budgetCheck = checkBudget(budget, agent_id, estimatedCost);
|
|
1816
|
+
if (!budgetCheck.allowed) {
|
|
1817
|
+
return {
|
|
1818
|
+
content: [{ type: "text", text: `${budgetCheck.reason}. Use blockrun_wallet action:"report" to see usage or action:"delegate" to increase agent budget.` }],
|
|
1819
|
+
isError: true
|
|
1820
|
+
};
|
|
1821
|
+
}
|
|
1822
|
+
const client = getClient();
|
|
1579
1823
|
const endpoint = `/v1/${cleanPath}`;
|
|
1580
1824
|
const result = body !== void 0 ? await client.requestWithPaymentRaw(endpoint, body) : await client.getWithPaymentRaw(endpoint);
|
|
1825
|
+
if (estimatedCost > 0) recordSpending(budget, estimatedCost, agent_id);
|
|
1581
1826
|
return {
|
|
1582
1827
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1583
1828
|
structuredContent: result
|
|
@@ -1591,7 +1836,44 @@ Voice call flow + voice preset details + full body shapes in the \`phone\` skill
|
|
|
1591
1836
|
|
|
1592
1837
|
// src/tools/surf.ts
|
|
1593
1838
|
import { z as z14 } from "zod";
|
|
1594
|
-
|
|
1839
|
+
var SURF_T3_PATHS = /* @__PURE__ */ new Set([
|
|
1840
|
+
"onchain/sql",
|
|
1841
|
+
"onchain/query",
|
|
1842
|
+
"onchain/schema",
|
|
1843
|
+
"chat/completions"
|
|
1844
|
+
]);
|
|
1845
|
+
var SURF_T2_PATHS = /* @__PURE__ */ new Set([
|
|
1846
|
+
"exchange/depth",
|
|
1847
|
+
"exchange/klines",
|
|
1848
|
+
"exchange/funding-history",
|
|
1849
|
+
"exchange/long-short-ratio",
|
|
1850
|
+
"market/liquidation/exchange-list",
|
|
1851
|
+
"market/liquidation/order",
|
|
1852
|
+
"market/liquidation/chart",
|
|
1853
|
+
"market/onchain-indicator",
|
|
1854
|
+
"market/price-indicator",
|
|
1855
|
+
"prediction-market/polymarket/positions",
|
|
1856
|
+
"prediction-market/polymarket/activity",
|
|
1857
|
+
"social/detail",
|
|
1858
|
+
"social/ranking",
|
|
1859
|
+
"social/smart-followers/history",
|
|
1860
|
+
"social/mindshare",
|
|
1861
|
+
"token/dex-trades",
|
|
1862
|
+
"token/holders",
|
|
1863
|
+
"token/transfers",
|
|
1864
|
+
"web/fetch"
|
|
1865
|
+
]);
|
|
1866
|
+
var SURF_T2_PREFIXES = ["search/", "wallet/"];
|
|
1867
|
+
function estimateSurfCost(path4) {
|
|
1868
|
+
const p = path4.toLowerCase().replace(/^\/+/, "");
|
|
1869
|
+
if (SURF_T3_PATHS.has(p)) return 0.02;
|
|
1870
|
+
if (SURF_T2_PATHS.has(p)) return 5e-3;
|
|
1871
|
+
for (const prefix of SURF_T2_PREFIXES) {
|
|
1872
|
+
if (p.startsWith(prefix)) return 5e-3;
|
|
1873
|
+
}
|
|
1874
|
+
return 1e-3;
|
|
1875
|
+
}
|
|
1876
|
+
function registerSurfTool(server, budget) {
|
|
1595
1877
|
server.registerTool(
|
|
1596
1878
|
"blockrun_surf",
|
|
1597
1879
|
{
|
|
@@ -1619,15 +1901,25 @@ Each Surf endpoint pre-validates required params before settling \u2014 you get
|
|
|
1619
1901
|
inputSchema: {
|
|
1620
1902
|
path: z14.string().describe("Endpoint path under /v1/surf/, e.g. 'market/price', 'prediction-market/polymarket/ranking', 'wallet/detail', 'onchain/sql', 'chat/completions'"),
|
|
1621
1903
|
params: z14.record(z14.string(), z14.string()).optional().describe("Query parameters for GET endpoints, e.g. { symbol: 'BTC' } or { address: '0x...', chain: 'ethereum' }"),
|
|
1622
|
-
body: z14.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.")
|
|
1904
|
+
body: z14.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."),
|
|
1905
|
+
agent_id: z14.string().optional().describe("Agent identifier for budget tracking and enforcement.")
|
|
1623
1906
|
}
|
|
1624
1907
|
},
|
|
1625
|
-
async ({ path: path4, params, body }) => {
|
|
1908
|
+
async ({ path: path4, params, body, agent_id }) => {
|
|
1626
1909
|
try {
|
|
1627
|
-
const client = getClient();
|
|
1628
1910
|
const cleanPath = path4.replace(/^\/+/, "").replace(/^v1\/surf\//, "").replace(/^api\/v1\/surf\//, "");
|
|
1911
|
+
const estimatedCost = estimateSurfCost(cleanPath);
|
|
1912
|
+
const budgetCheck = checkBudget(budget, agent_id, estimatedCost);
|
|
1913
|
+
if (!budgetCheck.allowed) {
|
|
1914
|
+
return {
|
|
1915
|
+
content: [{ type: "text", text: `${budgetCheck.reason}. Use blockrun_wallet action:"report" to see usage or action:"delegate" to increase agent budget.` }],
|
|
1916
|
+
isError: true
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1919
|
+
const client = getClient();
|
|
1629
1920
|
const endpoint = `/v1/surf/${cleanPath}`;
|
|
1630
1921
|
const result = body !== void 0 ? await client.requestWithPaymentRaw(endpoint, body) : await client.getWithPaymentRaw(endpoint, params);
|
|
1922
|
+
recordSpending(budget, estimatedCost, agent_id);
|
|
1631
1923
|
return {
|
|
1632
1924
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1633
1925
|
structuredContent: result
|
|
@@ -1649,17 +1941,17 @@ function initializeMcpServer(server) {
|
|
|
1649
1941
|
registerWalletTool(server, budget);
|
|
1650
1942
|
registerChatTool(server, budget);
|
|
1651
1943
|
registerModelsTool(server, modelCache);
|
|
1652
|
-
registerImageTool(server);
|
|
1653
|
-
registerMusicTool(server);
|
|
1654
|
-
registerVideoTool(server);
|
|
1655
|
-
registerSearchTool(server);
|
|
1656
|
-
registerExaTool(server);
|
|
1657
|
-
registerMarketsTool(server);
|
|
1658
|
-
registerPriceTool(server);
|
|
1944
|
+
registerImageTool(server, budget);
|
|
1945
|
+
registerMusicTool(server, budget);
|
|
1946
|
+
registerVideoTool(server, budget);
|
|
1947
|
+
registerSearchTool(server, budget);
|
|
1948
|
+
registerExaTool(server, budget);
|
|
1949
|
+
registerMarketsTool(server, budget);
|
|
1950
|
+
registerPriceTool(server, budget);
|
|
1659
1951
|
registerDexTool(server);
|
|
1660
|
-
registerModalTool(server);
|
|
1661
|
-
registerPhoneTool(server);
|
|
1662
|
-
registerSurfTool(server);
|
|
1952
|
+
registerModalTool(server, budget);
|
|
1953
|
+
registerPhoneTool(server, budget);
|
|
1954
|
+
registerSurfTool(server, budget);
|
|
1663
1955
|
server.registerResource(
|
|
1664
1956
|
"wallet",
|
|
1665
1957
|
"blockrun://wallet",
|
|
@@ -1682,7 +1974,7 @@ function initializeMcpServer(server) {
|
|
|
1682
1974
|
async () => {
|
|
1683
1975
|
const llm = getClient();
|
|
1684
1976
|
if (!modelCache.models) {
|
|
1685
|
-
modelCache.models = await llm.listModels();
|
|
1977
|
+
modelCache.models = "listAllModels" in llm ? await llm.listAllModels() : await llm.listModels();
|
|
1686
1978
|
setTimeout(() => {
|
|
1687
1979
|
modelCache.models = null;
|
|
1688
1980
|
}, 5 * 60 * 1e3);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockrun/mcp",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.4",
|
|
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",
|