@blockrun/mcp 0.19.2 → 0.21.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 +293 -41
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -132,10 +132,10 @@ $5 covers ~5,000 market queries, ~500 Exa searches, ~250 image generations, or ~
|
|
|
132
132
|
|
|
133
133
|
| Tool | Data source | Cost |
|
|
134
134
|
|------|-------------|------|
|
|
135
|
-
| `blockrun_chat` |
|
|
136
|
-
| `blockrun_image` |
|
|
137
|
-
| `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.
|
|
138
|
-
| `blockrun_realface` | Enroll a real person (phone liveness
|
|
135
|
+
| `blockrun_chat` | 66+ LLMs (GPT, Claude, Gemini, DeepSeek, Kimi K2.6, GLM, NVIDIA free tier, ...) with `mode` tier routing | per token |
|
|
136
|
+
| `blockrun_image` | GPT Image 1/2, Nano Banana / Pro (up to 4K), Grok Imagine, CogView-4 — generation + img2img editing | $0.015–0.15 |
|
|
137
|
+
| `blockrun_video` | Sora 2 + xAI Grok Imagine Video + ByteDance Seedance 1.5/2.0/2.0-fast (720p + audio defaults); RealFace asset → real-person video | $0.05–0.30/sec |
|
|
138
|
+
| `blockrun_realface` | Enroll a real person (phone liveness) or an AI character (Virtual Portrait, no liveness) as a `ta_xxxx` asset for Seedance 2.0 video | free; $0.01 to enroll |
|
|
139
139
|
| `blockrun_music` | MiniMax music generation | per track |
|
|
140
140
|
| `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 |
|
|
141
141
|
| `blockrun_price` | Pyth-backed realtime + OHLC — crypto / FX / commodity (free), 12 stock markets (paid) | free or $0.001/call |
|
|
@@ -144,6 +144,10 @@ $5 covers ~5,000 market queries, ~500 Exa searches, ~250 image generations, or ~
|
|
|
144
144
|
| `blockrun_exa` | Neural web search (Exa) — research, competitors, papers, URL content | $0.01/query |
|
|
145
145
|
| `blockrun_search` | Grok Live Search — web + news with citations | $0.025 × max_results (default 10) |
|
|
146
146
|
| `blockrun_dex` | Live DEX prices via DexScreener | free |
|
|
147
|
+
| `blockrun_rpc` | Raw JSON-RPC on 40+ chains (Ethereum, Base, Solana, Bitcoin, Sui, NEAR, ...) via Tatum gateway — eth_call, balances, blocks, logs | $0.002/call |
|
|
148
|
+
| `blockrun_defi` | DefiLlama — protocol TVL, chain TVL, yield pools (APY), token prices | $0.001–0.005/call |
|
|
149
|
+
| `blockrun_modal` | Isolated code execution in a BlockRun-hosted Modal sandbox — disposable container, optional GPU (T4 → H100) | $0.01 create; $0.001/op |
|
|
150
|
+
| `blockrun_phone` | Outbound AI voice calls (Bland) + wallet-owned US/CA numbers (Twilio), carrier + fraud lookups | $0.54/call; $5/number |
|
|
147
151
|
| `blockrun_models` | Live catalogue of every LLM/image/video/music model + pricing | free |
|
|
148
152
|
| `blockrun_wallet` | Balance, spending, agent budgets, setup QR | free |
|
|
149
153
|
|
package/dist/index.js
CHANGED
|
@@ -1092,13 +1092,39 @@ var GENERATE_MODEL_COST = {
|
|
|
1092
1092
|
"zai/cogview-4": 0.015,
|
|
1093
1093
|
"xai/grok-imagine-image": 0.02,
|
|
1094
1094
|
"xai/grok-imagine-image-pro": 0.07,
|
|
1095
|
+
"openai/gpt-image-1": 0.02,
|
|
1096
|
+
"openai/gpt-image-2": 0.06,
|
|
1097
|
+
"google/nano-banana": 0.05,
|
|
1098
|
+
"google/nano-banana-pro": 0.1
|
|
1099
|
+
};
|
|
1100
|
+
var LARGE_SIZE_COST = {
|
|
1095
1101
|
"openai/gpt-image-1": 0.04,
|
|
1096
1102
|
"openai/gpt-image-2": 0.12,
|
|
1097
|
-
"
|
|
1098
|
-
|
|
1099
|
-
"together/flux-schnell": 3e-3
|
|
1103
|
+
"google/nano-banana-pro": 0.15
|
|
1104
|
+
// 4096x4096 tier
|
|
1100
1105
|
};
|
|
1101
|
-
var EDIT_MODELS = /* @__PURE__ */ new Set([
|
|
1106
|
+
var EDIT_MODELS = /* @__PURE__ */ new Set([
|
|
1107
|
+
"openai/gpt-image-1",
|
|
1108
|
+
"openai/gpt-image-2",
|
|
1109
|
+
"google/nano-banana",
|
|
1110
|
+
"google/nano-banana-pro"
|
|
1111
|
+
]);
|
|
1112
|
+
var IMAGE_MODELS = [
|
|
1113
|
+
"zai/cogview-4",
|
|
1114
|
+
"google/nano-banana",
|
|
1115
|
+
"google/nano-banana-pro",
|
|
1116
|
+
"openai/gpt-image-1",
|
|
1117
|
+
"openai/gpt-image-2",
|
|
1118
|
+
"xai/grok-imagine-image",
|
|
1119
|
+
"xai/grok-imagine-image-pro"
|
|
1120
|
+
];
|
|
1121
|
+
function estimateCost(model, size) {
|
|
1122
|
+
const base = GENERATE_MODEL_COST[model] ?? 0.06;
|
|
1123
|
+
if (size !== "1024x1024" && LARGE_SIZE_COST[model]) {
|
|
1124
|
+
return LARGE_SIZE_COST[model];
|
|
1125
|
+
}
|
|
1126
|
+
return base;
|
|
1127
|
+
}
|
|
1102
1128
|
function registerImageTool(server, budget) {
|
|
1103
1129
|
server.registerTool(
|
|
1104
1130
|
"blockrun_image",
|
|
@@ -1109,21 +1135,22 @@ Actions:
|
|
|
1109
1135
|
- generate (default): Create image from text prompt
|
|
1110
1136
|
- edit: Transform an existing image using img2img
|
|
1111
1137
|
|
|
1112
|
-
Generation models:
|
|
1113
|
-
-
|
|
1114
|
-
-
|
|
1115
|
-
-
|
|
1116
|
-
-
|
|
1117
|
-
-
|
|
1118
|
-
-
|
|
1119
|
-
-
|
|
1120
|
-
|
|
1138
|
+
Generation models (1024x1024 base price; larger sizes cost more on gpt-image-*):
|
|
1139
|
+
- openai/gpt-image-2 ($0.06\u20130.12) \u2014 flagship, reasoning-driven, multilingual on-image text + character consistency (default)
|
|
1140
|
+
- openai/gpt-image-1 ($0.02\u20130.04) \u2014 GPT native image generation
|
|
1141
|
+
- google/nano-banana ($0.05) \u2014 Gemini-family image model
|
|
1142
|
+
- google/nano-banana-pro ($0.10; $0.15 at 4096px) \u2014 up to 4K, strongest photorealism
|
|
1143
|
+
- xai/grok-imagine-image ($0.02) \u2014 stylized, fast
|
|
1144
|
+
- xai/grok-imagine-image-pro ($0.07) \u2014 higher quality Grok Imagine
|
|
1145
|
+
- zai/cogview-4 ($0.015) \u2014 cheapest, photorealistic detailed scenes
|
|
1146
|
+
|
|
1147
|
+
Edit (img2img) models: openai/gpt-image-2 (default), openai/gpt-image-1, google/nano-banana, google/nano-banana-pro`,
|
|
1121
1148
|
inputSchema: {
|
|
1122
1149
|
prompt: z4.string().describe("Image description or edit instructions"),
|
|
1123
1150
|
action: z4.enum(["generate", "edit"]).optional().default("generate").describe("generate: create from text; edit: transform existing image"),
|
|
1124
|
-
model: z4.enum(
|
|
1151
|
+
model: z4.enum(IMAGE_MODELS).optional().describe("Model to use (default: openai/gpt-image-2 for both generate and edit). gpt-image-2 renders on-image text best; nano-banana-pro for 4K photorealism; cogview-4 / grok-imagine-image for cheap drafts."),
|
|
1125
1152
|
image: z4.string().optional().describe("Source image for edit action: base64-encoded image or URL"),
|
|
1126
|
-
size: z4.
|
|
1153
|
+
size: z4.string().optional().default("1024x1024").describe("Image size. Common values: 1024x1024 (all models), 1536x1024 / 1024x1536 (gpt-image-*), 2048x2048 / 4096x4096 (nano-banana-pro)"),
|
|
1127
1154
|
quality: z4.enum(["standard", "hd"]).optional().default("standard"),
|
|
1128
1155
|
agent_id: z4.string().optional().describe("Agent identifier for budget tracking and enforcement.")
|
|
1129
1156
|
}
|
|
@@ -1136,6 +1163,7 @@ Edit models: openai/gpt-image-1, openai/gpt-image-2 (default for edits)`,
|
|
|
1136
1163
|
isError: true
|
|
1137
1164
|
};
|
|
1138
1165
|
}
|
|
1166
|
+
const selectedModel = model || "openai/gpt-image-2";
|
|
1139
1167
|
let response;
|
|
1140
1168
|
if (action === "edit") {
|
|
1141
1169
|
if (!image) {
|
|
@@ -1144,14 +1172,13 @@ Edit models: openai/gpt-image-1, openai/gpt-image-2 (default for edits)`,
|
|
|
1144
1172
|
isError: true
|
|
1145
1173
|
};
|
|
1146
1174
|
}
|
|
1147
|
-
const selectedModel = model || "openai/gpt-image-2";
|
|
1148
1175
|
if (!EDIT_MODELS.has(selectedModel)) {
|
|
1149
1176
|
return {
|
|
1150
|
-
content: [{ type: "text", text: formatError("Image edits support
|
|
1177
|
+
content: [{ type: "text", text: formatError("Image edits support openai/gpt-image-1, openai/gpt-image-2, google/nano-banana, or google/nano-banana-pro") }],
|
|
1151
1178
|
isError: true
|
|
1152
1179
|
};
|
|
1153
1180
|
}
|
|
1154
|
-
const estimatedCost = selectedModel
|
|
1181
|
+
const estimatedCost = estimateCost(selectedModel, size);
|
|
1155
1182
|
const budgetCheck = checkBudget(budget, agent_id, estimatedCost);
|
|
1156
1183
|
if (!budgetCheck.allowed) {
|
|
1157
1184
|
return {
|
|
@@ -1165,8 +1192,7 @@ Edit models: openai/gpt-image-1, openai/gpt-image-2 (default for edits)`,
|
|
|
1165
1192
|
});
|
|
1166
1193
|
recordSpending(budget, estimatedCost, agent_id);
|
|
1167
1194
|
} else {
|
|
1168
|
-
const
|
|
1169
|
-
const estimatedCost = GENERATE_MODEL_COST[selectedModel] ?? 0.05;
|
|
1195
|
+
const estimatedCost = estimateCost(selectedModel, size);
|
|
1170
1196
|
const budgetCheck = checkBudget(budget, agent_id, estimatedCost);
|
|
1171
1197
|
if (!budgetCheck.allowed) {
|
|
1172
1198
|
return {
|
|
@@ -1191,8 +1217,8 @@ Edit models: openai/gpt-image-1, openai/gpt-image-2 (default for edits)`,
|
|
|
1191
1217
|
return {
|
|
1192
1218
|
content: [{ type: "text", text: `Image: ${imageUrl}
|
|
1193
1219
|
Prompt: ${prompt}
|
|
1194
|
-
Model: ${
|
|
1195
|
-
structuredContent: { url: imageUrl, prompt, model:
|
|
1220
|
+
Model: ${selectedModel}` }],
|
|
1221
|
+
structuredContent: { url: imageUrl, prompt, model: selectedModel }
|
|
1196
1222
|
};
|
|
1197
1223
|
} catch (err) {
|
|
1198
1224
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -1881,6 +1907,46 @@ async function fetchWithTimeout4(url, options, timeoutMs) {
|
|
|
1881
1907
|
clearTimeout(id);
|
|
1882
1908
|
}
|
|
1883
1909
|
}
|
|
1910
|
+
async function payAndPostJson(url, reqBody, fallbackDescription) {
|
|
1911
|
+
const privateKey = getOrCreateWalletKey();
|
|
1912
|
+
const account = privateKeyToAccount4(privateKey);
|
|
1913
|
+
const resp402 = await fetchWithTimeout4(url, {
|
|
1914
|
+
method: "POST",
|
|
1915
|
+
headers: { "Content-Type": "application/json" },
|
|
1916
|
+
body: reqBody
|
|
1917
|
+
}, 15e3);
|
|
1918
|
+
if (resp402.status !== 402) {
|
|
1919
|
+
const data2 = await resp402.json().catch(() => ({}));
|
|
1920
|
+
throw new Error(`Expected 402, got ${resp402.status}: ${data2.message || data2.error || JSON.stringify(data2)}`);
|
|
1921
|
+
}
|
|
1922
|
+
const prHeader = resp402.headers.get("payment-required") || resp402.headers.get("PAYMENT-REQUIRED");
|
|
1923
|
+
if (!prHeader) throw new Error("No PAYMENT-REQUIRED header in 402 response");
|
|
1924
|
+
const paymentRequired = parsePaymentRequired4(prHeader);
|
|
1925
|
+
const details = extractPaymentDetails4(paymentRequired);
|
|
1926
|
+
const paymentPayload = await createPaymentPayload4(
|
|
1927
|
+
privateKey,
|
|
1928
|
+
account.address,
|
|
1929
|
+
details.recipient,
|
|
1930
|
+
details.amount,
|
|
1931
|
+
details.network || "eip155:8453",
|
|
1932
|
+
{
|
|
1933
|
+
resourceUrl: details.resource?.url || url,
|
|
1934
|
+
resourceDescription: details.resource?.description || fallbackDescription,
|
|
1935
|
+
maxTimeoutSeconds: Math.max(details.maxTimeoutSeconds || 0, 120),
|
|
1936
|
+
extra: details.extra
|
|
1937
|
+
}
|
|
1938
|
+
);
|
|
1939
|
+
const resp = await fetchWithTimeout4(url, {
|
|
1940
|
+
method: "POST",
|
|
1941
|
+
headers: {
|
|
1942
|
+
"Content-Type": "application/json",
|
|
1943
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
1944
|
+
},
|
|
1945
|
+
body: reqBody
|
|
1946
|
+
}, 9e4);
|
|
1947
|
+
const data = await resp.json().catch(() => ({}));
|
|
1948
|
+
return { status: resp.status, data };
|
|
1949
|
+
}
|
|
1884
1950
|
function registerRealfaceTool(server, budget) {
|
|
1885
1951
|
server.registerTool(
|
|
1886
1952
|
"blockrun_realface",
|
|
@@ -1893,7 +1959,8 @@ Actions:
|
|
|
1893
1959
|
- init: FREE. Create an asset group + a phone H5 link. The tool renders the link as a QR code and opens it; the real person scans it on their phone and completes the ~1 min liveness check. Pass group_id to refresh an expired link.
|
|
1894
1960
|
- status: FREE. Poll a group until status:"active" (ready_to_finalize:true). The H5 link is valid ~120s \u2014 re-init if it expires.
|
|
1895
1961
|
- enroll: PAID ($0.01 USDC, Base only). After the group is active, upload a clear front-facing photo (image_url) of the SAME person. Returns the ta_xxxx asset id.
|
|
1896
|
-
-
|
|
1962
|
+
- portrait: PAID ($0.01 USDC, Base only). Virtual Portrait \u2014 enroll an AI-GENERATED character from an image URL directly, NO liveness needed (one step: name + image_url \u2192 ta_xxxx). For fictional/AI characters only; for a real person use the init\u2192status\u2192enroll liveness flow.
|
|
1963
|
+
- list: FREE. List the RealFace + Virtual Portrait assets enrolled by this wallet (their ta_xxxx ids + names) so you can pick one for blockrun_video.
|
|
1897
1964
|
|
|
1898
1965
|
Typical flow:
|
|
1899
1966
|
1. blockrun_realface action:"init" name:"Alice" \u2192 scan QR on phone, do liveness
|
|
@@ -1903,10 +1970,10 @@ Typical flow:
|
|
|
1903
1970
|
|
|
1904
1971
|
Privacy: BlockRun does not store face/liveness data \u2014 only the asset id, name, and the photo URL you supply.`,
|
|
1905
1972
|
inputSchema: {
|
|
1906
|
-
action: z8.enum(["init", "status", "enroll", "list"]).describe("What to do"),
|
|
1907
|
-
name: z8.string().min(1).max(64).optional().describe("Display name for the person (required for init and
|
|
1908
|
-
group_id: z8.string().regex(/^legacy_rf_\d+$/).optional().describe("Asset-group id from init (required for status and enroll; pass to init to refresh an expired H5 link)."),
|
|
1909
|
-
image_url: z8.string().url().optional().describe("Public HTTPS URL to a clear front-facing face
|
|
1973
|
+
action: z8.enum(["init", "status", "enroll", "portrait", "list"]).describe("What to do"),
|
|
1974
|
+
name: z8.string().min(1).max(64).optional().describe("Display name for the person/character (required for init, enroll, and portrait)."),
|
|
1975
|
+
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). Not used by portrait."),
|
|
1976
|
+
image_url: z8.string().url().optional().describe("Public HTTPS URL to a clear front-facing face image (JPG/PNG/WEBP, \u226410MB). Required for enroll and portrait."),
|
|
1910
1977
|
agent_id: z8.string().optional().describe("Agent identifier for budget tracking and enforcement (enroll only).")
|
|
1911
1978
|
}
|
|
1912
1979
|
},
|
|
@@ -1996,30 +2063,88 @@ QR opened for scanning (${qrPath}).`;
|
|
|
1996
2063
|
}
|
|
1997
2064
|
if (action === "list") {
|
|
1998
2065
|
const account = privateKeyToAccount4(getOrCreateWalletKey());
|
|
1999
|
-
const
|
|
2000
|
-
method: "GET"
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2066
|
+
const [rfResp, vpResp] = await Promise.all([
|
|
2067
|
+
fetchWithTimeout4(`${BLOCKRUN_API4}/v1/wallet/${account.address}/realfaces`, { method: "GET" }, 3e4),
|
|
2068
|
+
fetchWithTimeout4(`${BLOCKRUN_API4}/v1/wallet/${account.address}/portraits`, { method: "GET" }, 3e4).catch(() => null)
|
|
2069
|
+
]);
|
|
2070
|
+
const data = await rfResp.json().catch(() => ({}));
|
|
2071
|
+
if (!rfResp.ok) {
|
|
2072
|
+
return { content: [{ type: "text", text: formatError(`list failed (${rfResp.status}): ${data.error || JSON.stringify(data)}`) }], isError: true };
|
|
2005
2073
|
}
|
|
2006
2074
|
const faces = Array.isArray(data.realfaces) ? data.realfaces : [];
|
|
2007
|
-
|
|
2075
|
+
let portraits = [];
|
|
2076
|
+
if (vpResp?.ok) {
|
|
2077
|
+
const vpData = await vpResp.json().catch(() => ({}));
|
|
2078
|
+
portraits = Array.isArray(vpData.portraits) ? vpData.portraits : [];
|
|
2079
|
+
}
|
|
2080
|
+
if (faces.length === 0 && portraits.length === 0) {
|
|
2008
2081
|
return {
|
|
2009
|
-
content: [{ type: "text", text: `No RealFace assets enrolled for ${account.address}.
|
|
2010
|
-
Enroll one: blockrun_realface action:"init" name:"\u2026".` }],
|
|
2011
|
-
structuredContent: { wallet: account.address, realfaces: [], count: 0 }
|
|
2082
|
+
content: [{ type: "text", text: `No RealFace or Virtual Portrait assets enrolled for ${account.address}.
|
|
2083
|
+
Enroll one: blockrun_realface action:"init" name:"\u2026" (real person) or action:"portrait" name:"\u2026" image_url:"https://\u2026" (AI character).` }],
|
|
2084
|
+
structuredContent: { wallet: account.address, realfaces: [], portraits: [], count: 0 }
|
|
2012
2085
|
};
|
|
2013
2086
|
}
|
|
2087
|
+
const first = faces[0] ?? portraits[0];
|
|
2014
2088
|
const lines = [
|
|
2015
|
-
`
|
|
2016
|
-
...faces.map((f) => ` \u2022 ${f.assetId} \u2014 "${f.name}"${f.createdAt ? ` (${f.createdAt})` : ""}`),
|
|
2089
|
+
`Assets for ${account.address} (${faces.length} RealFace, ${portraits.length} Virtual Portrait):`,
|
|
2090
|
+
...faces.map((f) => ` \u2022 ${f.assetId} \u2014 "${f.name}" [realface]${f.createdAt ? ` (${f.createdAt})` : ""}`),
|
|
2091
|
+
...portraits.map((p) => ` \u2022 ${p.assetId} \u2014 "${p.name}" [portrait]${p.createdAt ? ` (${p.createdAt})` : ""}`),
|
|
2017
2092
|
``,
|
|
2018
|
-
`Use one: blockrun_video model:"bytedance/seedance-2.0" real_face_asset_id:"${
|
|
2093
|
+
`Use one: blockrun_video model:"bytedance/seedance-2.0" real_face_asset_id:"${first.assetId}" prompt:"\u2026".`
|
|
2019
2094
|
];
|
|
2020
2095
|
return {
|
|
2021
2096
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
2022
|
-
structuredContent: { wallet: account.address, realfaces: faces, count: faces.length }
|
|
2097
|
+
structuredContent: { wallet: account.address, realfaces: faces, portraits, count: faces.length + portraits.length }
|
|
2098
|
+
};
|
|
2099
|
+
}
|
|
2100
|
+
if (action === "portrait") {
|
|
2101
|
+
if (getChain() !== "base") {
|
|
2102
|
+
return { content: [{ type: "text", text: formatError("blockrun_realface portrait settles on Base only. Switch BlockRun to Base (run blockrun_wallet with action:chain chain:base) and fund the Base wallet with USDC.") }], isError: true };
|
|
2103
|
+
}
|
|
2104
|
+
if (!name || !image_url) {
|
|
2105
|
+
return { content: [{ type: "text", text: formatError("portrait requires name and image_url (public HTTPS URL of an AI-generated character image).") }], isError: true };
|
|
2106
|
+
}
|
|
2107
|
+
const budgetCheck = checkBudget(budget, agent_id, ENROLLMENT_PRICE_USD);
|
|
2108
|
+
if (!budgetCheck.allowed) {
|
|
2109
|
+
return { content: [{ type: "text", text: `${budgetCheck.reason}. Use blockrun_wallet action:"report" to see usage or action:"delegate" to increase agent budget.` }], isError: true };
|
|
2110
|
+
}
|
|
2111
|
+
const { status, data } = await payAndPostJson(
|
|
2112
|
+
`${BLOCKRUN_API4}/v1/portrait/enroll`,
|
|
2113
|
+
JSON.stringify({ name, image_url }),
|
|
2114
|
+
"BlockRun Virtual Portrait enrollment"
|
|
2115
|
+
);
|
|
2116
|
+
if (status === 402) {
|
|
2117
|
+
throw new Error("Payment rejected. Check your wallet balance.");
|
|
2118
|
+
}
|
|
2119
|
+
if (status === 422) {
|
|
2120
|
+
return { content: [{ type: "text", text: formatError(`Portrait rejected \u2014 ${data.hint || data.message || "use a clear front-facing character image"}. No payment taken.`) }], isError: true };
|
|
2121
|
+
}
|
|
2122
|
+
if (status < 200 || status >= 300) {
|
|
2123
|
+
throw new Error(`Portrait enroll error ${status}: ${data.error || JSON.stringify(data)}`);
|
|
2124
|
+
}
|
|
2125
|
+
const assetId = data.asset_id;
|
|
2126
|
+
if (!assetId) throw new Error(`Portrait response missing asset_id: ${JSON.stringify(data)}`);
|
|
2127
|
+
recordSpending(budget, ENROLLMENT_PRICE_USD, agent_id);
|
|
2128
|
+
const txHash = data.settlement?.tx_hash || void 0;
|
|
2129
|
+
const lines = [
|
|
2130
|
+
`\u2705 Virtual Portrait enrolled!`,
|
|
2131
|
+
`Asset ID: ${assetId}`,
|
|
2132
|
+
`Name: ${data.name || name}`,
|
|
2133
|
+
`Cost: $${ENROLLMENT_PRICE_USD.toFixed(2)} USDC`,
|
|
2134
|
+
...txHash ? [`Tx: ${txHash}`] : [],
|
|
2135
|
+
``,
|
|
2136
|
+
`Use it: blockrun_video model:"bytedance/seedance-2.0" real_face_asset_id:"${assetId}" prompt:"\u2026".`
|
|
2137
|
+
];
|
|
2138
|
+
return {
|
|
2139
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
2140
|
+
structuredContent: {
|
|
2141
|
+
asset_id: assetId,
|
|
2142
|
+
group_id: data.group_id,
|
|
2143
|
+
name: data.name || name,
|
|
2144
|
+
image_url: data.image_url,
|
|
2145
|
+
price_usd: ENROLLMENT_PRICE_USD,
|
|
2146
|
+
...txHash ? { txHash } : {}
|
|
2147
|
+
}
|
|
2023
2148
|
};
|
|
2024
2149
|
}
|
|
2025
2150
|
if (action === "enroll") {
|
|
@@ -2780,6 +2905,131 @@ Each Surf endpoint pre-validates required params before settling \u2014 you get
|
|
|
2780
2905
|
);
|
|
2781
2906
|
}
|
|
2782
2907
|
|
|
2908
|
+
// src/tools/rpc.ts
|
|
2909
|
+
import { z as z17 } from "zod";
|
|
2910
|
+
var RPC_PRICE_USD = 2e-3;
|
|
2911
|
+
function registerRpcTool(server, budget) {
|
|
2912
|
+
server.registerTool(
|
|
2913
|
+
"blockrun_rpc",
|
|
2914
|
+
{
|
|
2915
|
+
description: `Raw JSON-RPC against 40+ blockchains \u2014 one endpoint, no node, no API key. $0.002 per call (batch charges per element).
|
|
2916
|
+
|
|
2917
|
+
Use when you need data the higher-level tools don't cover: contract reads (eth_call), balances, blocks, txs, logs, gas, or any chain-native RPC method.
|
|
2918
|
+
|
|
2919
|
+
Networks (full catalog in the rpc skill): ethereum, base, arbitrum, optimism, polygon, bsc, avalanche, solana, bitcoin, sui, near, ripple, polkadot, dogecoin, litecoin, monad, berachain, unichain, hyperevm, sonic, and 20+ more.
|
|
2920
|
+
|
|
2921
|
+
Examples:
|
|
2922
|
+
blockrun_rpc({ network: "ethereum", method: "eth_blockNumber" })
|
|
2923
|
+
blockrun_rpc({ network: "base", method: "eth_getBalance", params: ["0xabc...", "latest"] })
|
|
2924
|
+
blockrun_rpc({ network: "solana", method: "getSlot" })
|
|
2925
|
+
blockrun_rpc({ network: "bitcoin", method: "getblockchaininfo" })
|
|
2926
|
+
blockrun_rpc({ network: "ethereum", body: [{jsonrpc:"2.0",id:1,method:"eth_blockNumber"},{...}] }) // batch
|
|
2927
|
+
|
|
2928
|
+
Prefer blockrun_price (free quotes), blockrun_dex (free DEX data), or blockrun_surf (labeled/aggregated data) when they cover the question \u2014 this tool is for raw chain access.`,
|
|
2929
|
+
inputSchema: {
|
|
2930
|
+
network: z17.string().describe("Chain key, e.g. 'ethereum', 'base', 'solana', 'bitcoin', 'arbitrum', 'polygon'. Unknown slugs pass through to the Tatum gateway."),
|
|
2931
|
+
method: z17.string().optional().describe("JSON-RPC method, e.g. 'eth_blockNumber', 'eth_call', 'getSlot' (Solana), 'getblockchaininfo' (Bitcoin). Required unless 'body' is set."),
|
|
2932
|
+
params: z17.any().optional().describe("JSON-RPC params array for the method, e.g. ['0xabc...', 'latest']."),
|
|
2933
|
+
body: z17.any().optional().describe("Full JSON-RPC 2.0 body or an array of them (batch). Overrides method/params when set."),
|
|
2934
|
+
agent_id: z17.string().optional().describe("Agent identifier for budget tracking and enforcement.")
|
|
2935
|
+
}
|
|
2936
|
+
},
|
|
2937
|
+
async ({ network, method, params, body, agent_id }) => {
|
|
2938
|
+
try {
|
|
2939
|
+
body = coerceBody(body);
|
|
2940
|
+
if (body === void 0) {
|
|
2941
|
+
if (!method) {
|
|
2942
|
+
return {
|
|
2943
|
+
content: [{ type: "text", text: formatError("Provide either 'method' (with optional 'params') or a full JSON-RPC 'body'.") }],
|
|
2944
|
+
isError: true
|
|
2945
|
+
};
|
|
2946
|
+
}
|
|
2947
|
+
body = { jsonrpc: "2.0", id: 1, method, params: params ?? [] };
|
|
2948
|
+
}
|
|
2949
|
+
const batchCount = Array.isArray(body) ? Math.max(body.length, 1) : 1;
|
|
2950
|
+
const estimatedCost = RPC_PRICE_USD * batchCount;
|
|
2951
|
+
const budgetCheck = checkBudget(budget, agent_id, estimatedCost);
|
|
2952
|
+
if (!budgetCheck.allowed) {
|
|
2953
|
+
return {
|
|
2954
|
+
content: [{ type: "text", text: `${budgetCheck.reason}. Use blockrun_wallet action:"report" to see usage or action:"delegate" to increase agent budget.` }],
|
|
2955
|
+
isError: true
|
|
2956
|
+
};
|
|
2957
|
+
}
|
|
2958
|
+
const cleanNetwork = network.trim().toLowerCase().replace(/^\/+|\/+$/g, "");
|
|
2959
|
+
const client = getClient();
|
|
2960
|
+
const result = await client.requestWithPaymentRaw(`/v1/rpc/${cleanNetwork}`, body);
|
|
2961
|
+
recordSpending(budget, estimatedCost, agent_id);
|
|
2962
|
+
return {
|
|
2963
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2964
|
+
structuredContent: typeof result === "object" && result !== null && !Array.isArray(result) ? result : { result }
|
|
2965
|
+
};
|
|
2966
|
+
} catch (err) {
|
|
2967
|
+
return {
|
|
2968
|
+
content: [{ type: "text", text: formatError(extractErrorMessage(err)) }],
|
|
2969
|
+
isError: true
|
|
2970
|
+
};
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
);
|
|
2974
|
+
}
|
|
2975
|
+
|
|
2976
|
+
// src/tools/defi.ts
|
|
2977
|
+
import { z as z18 } from "zod";
|
|
2978
|
+
function estimateDefiCost(path5) {
|
|
2979
|
+
return path5.startsWith("prices") ? 1e-3 : 5e-3;
|
|
2980
|
+
}
|
|
2981
|
+
function registerDefiTool(server, budget) {
|
|
2982
|
+
server.registerTool(
|
|
2983
|
+
"blockrun_defi",
|
|
2984
|
+
{
|
|
2985
|
+
description: `DeFi fundamentals via DefiLlama \u2014 protocol TVL, chain TVL, yield pools (APY), token prices. Pays per call in USDC, no API key.
|
|
2986
|
+
|
|
2987
|
+
Paths (GET only):
|
|
2988
|
+
- protocols ($0.005) \u2014 all DeFi protocols ranked by TVL
|
|
2989
|
+
- protocol/{slug} ($0.005) \u2014 one protocol's TVL history + chain breakdown, e.g. protocol/aave-v3
|
|
2990
|
+
- chains ($0.005) \u2014 TVL by chain
|
|
2991
|
+
- yields ($0.005) \u2014 yield pools with APY + TVL (large; filter client-side)
|
|
2992
|
+
- prices/{coins} ($0.001) \u2014 token prices, coins like 'base:0x833589...,coingecko:ethereum'
|
|
2993
|
+
|
|
2994
|
+
Examples:
|
|
2995
|
+
blockrun_defi({ path: "protocol/uniswap-v3" })
|
|
2996
|
+
blockrun_defi({ path: "prices/coingecko:bitcoin,coingecko:ethereum" })
|
|
2997
|
+
blockrun_defi({ path: "chains" })
|
|
2998
|
+
|
|
2999
|
+
Use blockrun_price (free) for plain spot quotes, blockrun_dex (free) for DEX pairs, blockrun_surf for labeled on-chain data \u2014 this tool is for protocol/TVL/yield fundamentals.`,
|
|
3000
|
+
inputSchema: {
|
|
3001
|
+
path: z18.string().describe("Endpoint under /v1/defillama/, e.g. 'protocols', 'protocol/aave-v3', 'chains', 'yields', 'prices/coingecko:ethereum'"),
|
|
3002
|
+
agent_id: z18.string().optional().describe("Agent identifier for budget tracking and enforcement.")
|
|
3003
|
+
}
|
|
3004
|
+
},
|
|
3005
|
+
async ({ path: path5, agent_id }) => {
|
|
3006
|
+
try {
|
|
3007
|
+
const cleanPath = path5.replace(/^\/+/, "").replace(/^v1\/defillama\//, "").replace(/^api\/v1\/defillama\//, "");
|
|
3008
|
+
const estimatedCost = estimateDefiCost(cleanPath);
|
|
3009
|
+
const budgetCheck = checkBudget(budget, agent_id, estimatedCost);
|
|
3010
|
+
if (!budgetCheck.allowed) {
|
|
3011
|
+
return {
|
|
3012
|
+
content: [{ type: "text", text: `${budgetCheck.reason}. Use blockrun_wallet action:"report" to see usage or action:"delegate" to increase agent budget.` }],
|
|
3013
|
+
isError: true
|
|
3014
|
+
};
|
|
3015
|
+
}
|
|
3016
|
+
const client = getClient();
|
|
3017
|
+
const result = await client.getWithPaymentRaw(`/v1/defillama/${cleanPath}`);
|
|
3018
|
+
recordSpending(budget, estimatedCost, agent_id);
|
|
3019
|
+
return {
|
|
3020
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
3021
|
+
structuredContent: typeof result === "object" && result !== null && !Array.isArray(result) ? result : { result }
|
|
3022
|
+
};
|
|
3023
|
+
} catch (err) {
|
|
3024
|
+
return {
|
|
3025
|
+
content: [{ type: "text", text: formatError(extractErrorMessage(err)) }],
|
|
3026
|
+
isError: true
|
|
3027
|
+
};
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
);
|
|
3031
|
+
}
|
|
3032
|
+
|
|
2783
3033
|
// src/mcp-handler.ts
|
|
2784
3034
|
function initializeMcpServer(server) {
|
|
2785
3035
|
const budget = { limit: null, spent: 0, calls: 0, agents: /* @__PURE__ */ new Map() };
|
|
@@ -2800,6 +3050,8 @@ function initializeMcpServer(server) {
|
|
|
2800
3050
|
registerModalTool(server, budget);
|
|
2801
3051
|
registerPhoneTool(server, budget);
|
|
2802
3052
|
registerSurfTool(server, budget);
|
|
3053
|
+
registerRpcTool(server, budget);
|
|
3054
|
+
registerDefiTool(server, budget);
|
|
2803
3055
|
server.registerResource(
|
|
2804
3056
|
"wallet",
|
|
2805
3057
|
"blockrun://wallet",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockrun/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.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",
|