@blockrun/mcp 0.20.0 → 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 +2 -1
- package/dist/index.js +176 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -135,7 +135,7 @@ $5 covers ~5,000 market queries, ~500 Exa searches, ~250 image generations, or ~
|
|
|
135
135
|
| `blockrun_chat` | 66+ LLMs (GPT, Claude, Gemini, DeepSeek, Kimi K2.6, GLM, NVIDIA free tier, ...) with `mode` tier routing | per token |
|
|
136
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
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
|
|
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 |
|
|
@@ -145,6 +145,7 @@ $5 covers ~5,000 market queries, ~500 Exa searches, ~250 image generations, or ~
|
|
|
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
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 |
|
|
148
149
|
| `blockrun_modal` | Isolated code execution in a BlockRun-hosted Modal sandbox — disposable container, optional GPU (T4 → H100) | $0.01 create; $0.001/op |
|
|
149
150
|
| `blockrun_phone` | Outbound AI voice calls (Bland) + wallet-owned US/CA numbers (Twilio), carrier + fraud lookups | $0.54/call; $5/number |
|
|
150
151
|
| `blockrun_models` | Live catalogue of every LLM/image/video/music model + pricing | free |
|
package/dist/index.js
CHANGED
|
@@ -1907,6 +1907,46 @@ async function fetchWithTimeout4(url, options, timeoutMs) {
|
|
|
1907
1907
|
clearTimeout(id);
|
|
1908
1908
|
}
|
|
1909
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
|
+
}
|
|
1910
1950
|
function registerRealfaceTool(server, budget) {
|
|
1911
1951
|
server.registerTool(
|
|
1912
1952
|
"blockrun_realface",
|
|
@@ -1919,7 +1959,8 @@ Actions:
|
|
|
1919
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.
|
|
1920
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.
|
|
1921
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.
|
|
1922
|
-
-
|
|
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.
|
|
1923
1964
|
|
|
1924
1965
|
Typical flow:
|
|
1925
1966
|
1. blockrun_realface action:"init" name:"Alice" \u2192 scan QR on phone, do liveness
|
|
@@ -1929,10 +1970,10 @@ Typical flow:
|
|
|
1929
1970
|
|
|
1930
1971
|
Privacy: BlockRun does not store face/liveness data \u2014 only the asset id, name, and the photo URL you supply.`,
|
|
1931
1972
|
inputSchema: {
|
|
1932
|
-
action: z8.enum(["init", "status", "enroll", "list"]).describe("What to do"),
|
|
1933
|
-
name: z8.string().min(1).max(64).optional().describe("Display name for the person (required for init and
|
|
1934
|
-
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)."),
|
|
1935
|
-
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."),
|
|
1936
1977
|
agent_id: z8.string().optional().describe("Agent identifier for budget tracking and enforcement (enroll only).")
|
|
1937
1978
|
}
|
|
1938
1979
|
},
|
|
@@ -2022,30 +2063,88 @@ QR opened for scanning (${qrPath}).`;
|
|
|
2022
2063
|
}
|
|
2023
2064
|
if (action === "list") {
|
|
2024
2065
|
const account = privateKeyToAccount4(getOrCreateWalletKey());
|
|
2025
|
-
const
|
|
2026
|
-
method: "GET"
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
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 };
|
|
2031
2073
|
}
|
|
2032
2074
|
const faces = Array.isArray(data.realfaces) ? data.realfaces : [];
|
|
2033
|
-
|
|
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) {
|
|
2034
2081
|
return {
|
|
2035
|
-
content: [{ type: "text", text: `No RealFace assets enrolled for ${account.address}.
|
|
2036
|
-
Enroll one: blockrun_realface action:"init" name:"\u2026".` }],
|
|
2037
|
-
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 }
|
|
2038
2085
|
};
|
|
2039
2086
|
}
|
|
2087
|
+
const first = faces[0] ?? portraits[0];
|
|
2040
2088
|
const lines = [
|
|
2041
|
-
`
|
|
2042
|
-
...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})` : ""}`),
|
|
2043
2092
|
``,
|
|
2044
|
-
`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".`
|
|
2045
2094
|
];
|
|
2046
2095
|
return {
|
|
2047
2096
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
2048
|
-
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
|
+
}
|
|
2049
2148
|
};
|
|
2050
2149
|
}
|
|
2051
2150
|
if (action === "enroll") {
|
|
@@ -2874,6 +2973,63 @@ Prefer blockrun_price (free quotes), blockrun_dex (free DEX data), or blockrun_s
|
|
|
2874
2973
|
);
|
|
2875
2974
|
}
|
|
2876
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
|
+
|
|
2877
3033
|
// src/mcp-handler.ts
|
|
2878
3034
|
function initializeMcpServer(server) {
|
|
2879
3035
|
const budget = { limit: null, spent: 0, calls: 0, agents: /* @__PURE__ */ new Map() };
|
|
@@ -2895,6 +3051,7 @@ function initializeMcpServer(server) {
|
|
|
2895
3051
|
registerPhoneTool(server, budget);
|
|
2896
3052
|
registerSurfTool(server, budget);
|
|
2897
3053
|
registerRpcTool(server, budget);
|
|
3054
|
+
registerDefiTool(server, budget);
|
|
2898
3055
|
server.registerResource(
|
|
2899
3056
|
"wallet",
|
|
2900
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",
|