@blockrun/mcp 0.22.1 → 0.22.3

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.
Files changed (3) hide show
  1. package/README.md +16 -2
  2. package/dist/index.js +85 -5
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -93,6 +93,14 @@ claude mcp add blockrun -s user -- npx -y @blockrun/mcp@latest
93
93
  The `-s user` flag installs globally (available in every project). The `--` separator
94
94
  ensures `-y` is passed to `npx`, not parsed by `claude mcp add`.
95
95
 
96
+ > 💡 **Homebrew / nvm users:** if the server doesn't connect after install, Claude Code
97
+ > likely can't find `node`/`npx` on its launcher PATH. Install with your shell PATH passed
98
+ > through — works on CLI and desktop:
99
+ > ```bash
100
+ > claude mcp add blockrun -s user -e PATH="$PATH" -- npx -y @blockrun/mcp@latest
101
+ > ```
102
+ > See [Troubleshooting](#troubleshooting) if it persists.
103
+
96
104
  **Tool profiles** — expose a trimmed tool set so the client loads fewer schemas into
97
105
  context. Pass `--profile <name>` (or set `BLOCKRUN_MCP_PROFILE`); omit it for the full set.
98
106
 
@@ -179,7 +187,7 @@ Then send USDC (SPL) on the **Solana** network — from Coinbase (pick "Solana")
179
187
  | Tool | Data source | Cost |
180
188
  |------|-------------|------|
181
189
  | `blockrun_chat` | 66+ LLMs (GPT, Claude, Gemini, DeepSeek, Kimi K2.6, GLM, NVIDIA free tier, ...) with `mode` tier routing | per token |
182
- | `blockrun_image` | GPT Image 1/2, Nano Banana / Pro (up to 4K), Grok Imagine, CogView-4 generation + img2img editing | $0.015–0.15 |
190
+ | `blockrun_image` | Generation: openai/gpt-image-2 ($0.06–0.12) — flagship, reasoning + text; openai/gpt-image-1 ($0.02–0.04); google/nano-banana ($0.05) / nano-banana-pro ($0.10–0.15, 4K); xai/grok-imagine-image ($0.02) / -pro ($0.07); zai/cogview-4 ($0.015). Edit: openai/gpt-image-*, google/nano-banana* (img2img, inpaint, fusion). | $0.015–0.15 |
183
191
  | `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 |
184
192
  | `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 |
185
193
  | `blockrun_music` | MiniMax music generation | per track |
@@ -281,7 +289,13 @@ Delegate a spending budget to a child agent with `agent_id`. The child is auto-b
281
289
 
282
290
  - **`Insufficient balance` / HTTP 402 after retry** → Run `blockrun_wallet action:"setup"`. Send USDC on Base (or Solana — see [Environment Variables](#environment-variables)).
283
291
  - **`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`.
284
- - **`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.
292
+ - **`blockrun` doesn't connect / "MCP server failed" / `spawn npx ENOENT`** → Almost always a **PATH issue**: Claude Code can't find `node`/`npx` on the PATH its MCP launcher uses. Common with **Homebrew** (`/opt/homebrew/bin`) or **nvm**, where node isn't on that PATH — and it affects the **CLI as well as the desktop app** (the launcher doesn't always inherit your interactive shell PATH). Fix by passing your shell PATH at install:
293
+ ```bash
294
+ claude mcp remove blockrun -s user
295
+ claude mcp add blockrun -s user -e PATH="$PATH" -- npx -y @blockrun/mcp@latest
296
+ ```
297
+ Then restart Claude Code (or `/mcp` to reconnect — MCP tools load at session start). Or pin absolute paths: `claude mcp add blockrun -s user -- /opt/homebrew/bin/npx -y @blockrun/mcp@latest` (use `which npx` for your path).
298
+ - **`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 (see the PATH fix above if node/npx aren't found).
285
299
  - **`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.
286
300
  - **`ENOENT: ~/.blockrun/.session`** → Expected on first run. The server auto-creates the wallet; check stderr for the `WALLET_CREATED` line confirming the address.
287
301
  - **`Video generation timed out` (5-min cap)** → Upstream Seedance / xAI queue congestion. **No charge** (payment-on-completion). Retry, or pick a faster model (`bytedance/seedance-1.5-pro`).
package/dist/index.js CHANGED
@@ -1104,6 +1104,43 @@ ${lines.join("\n")}` }],
1104
1104
  // src/tools/image.ts
1105
1105
  import { z as z4 } from "zod";
1106
1106
  import { PaymentError } from "@blockrun/llm";
1107
+ import { readFile } from "fs/promises";
1108
+ var REFERENCE_IMAGE_MAX_BYTES = 4e6;
1109
+ var IMAGE_EXT_MIME = {
1110
+ png: "image/png",
1111
+ jpg: "image/jpeg",
1112
+ jpeg: "image/jpeg",
1113
+ gif: "image/gif",
1114
+ webp: "image/webp"
1115
+ };
1116
+ async function toImageDataUri(ref) {
1117
+ if (ref.startsWith("data:image/")) return ref;
1118
+ if (/^https?:\/\//i.test(ref)) {
1119
+ const ctrl = new AbortController();
1120
+ const timeout = setTimeout(() => ctrl.abort(), 3e4);
1121
+ try {
1122
+ const res = await fetch(ref, { signal: ctrl.signal });
1123
+ if (!res.ok) throw new Error(`fetch failed: ${res.status} ${res.statusText}`);
1124
+ const mime2 = (res.headers.get("content-type") || "").toLowerCase().split(";")[0].trim();
1125
+ if (!mime2.startsWith("image/")) throw new Error(`URL returned non-image content-type: ${mime2 || "(none)"}`);
1126
+ const buffer2 = Buffer.from(await res.arrayBuffer());
1127
+ if (buffer2.byteLength > REFERENCE_IMAGE_MAX_BYTES) {
1128
+ throw new Error(`image too large: ${(buffer2.byteLength / 1e6).toFixed(1)}MB > ${REFERENCE_IMAGE_MAX_BYTES / 1e6}MB cap`);
1129
+ }
1130
+ return `data:${mime2};base64,${buffer2.toString("base64")}`;
1131
+ } finally {
1132
+ clearTimeout(timeout);
1133
+ }
1134
+ }
1135
+ const ext = ref.split(".").pop()?.toLowerCase() ?? "";
1136
+ const mime = IMAGE_EXT_MIME[ext];
1137
+ if (!mime) throw new Error(`unsupported image extension ".${ext}"; use png/jpg/jpeg/gif/webp`);
1138
+ const buffer = await readFile(ref);
1139
+ if (buffer.byteLength > REFERENCE_IMAGE_MAX_BYTES) {
1140
+ throw new Error(`image too large: ${(buffer.byteLength / 1e6).toFixed(1)}MB > ${REFERENCE_IMAGE_MAX_BYTES / 1e6}MB cap; resize or crop first`);
1141
+ }
1142
+ return `data:${mime};base64,${buffer.toString("base64")}`;
1143
+ }
1107
1144
  var GENERATE_MODEL_COST = {
1108
1145
  "zai/cogview-4": 0.015,
1109
1146
  "xai/grok-imagine-image": 0.02,
@@ -1125,6 +1162,11 @@ var EDIT_MODELS = /* @__PURE__ */ new Set([
1125
1162
  "google/nano-banana",
1126
1163
  "google/nano-banana-pro"
1127
1164
  ]);
1165
+ var MAX_EDIT_IMAGES_BY_PREFIX = {
1166
+ "openai/": 4,
1167
+ "google/": 3
1168
+ };
1169
+ var MASK_MODELS = /* @__PURE__ */ new Set(["openai/gpt-image-1", "openai/gpt-image-2"]);
1128
1170
  var IMAGE_MODELS = [
1129
1171
  "zai/cogview-4",
1130
1172
  "google/nano-banana",
@@ -1160,18 +1202,21 @@ Generation models (1024x1024 base price; larger sizes cost more on gpt-image-*):
1160
1202
  - xai/grok-imagine-image-pro ($0.07) \u2014 higher quality Grok Imagine
1161
1203
  - zai/cogview-4 ($0.015) \u2014 cheapest, photorealistic detailed scenes
1162
1204
 
1163
- Edit (img2img) models: openai/gpt-image-2 (default), openai/gpt-image-1, google/nano-banana, google/nano-banana-pro`,
1205
+ Edit (img2img) models: openai/gpt-image-2 (default), openai/gpt-image-1, google/nano-banana, google/nano-banana-pro
1206
+ Multi-image edit: pass an array of 2\u20134 source images to "image" to fuse them in one render (openai/* up to 4, google/* up to 3) \u2014 e.g. a subject plus a sprite layout guide, or a reference plus a brand logo.
1207
+ Source images and masks accept a base64 data URI, an http(s) URL, or a local file path (auto-encoded). Inpaint mask (openai/gpt-image-* only) via "mask"; not combinable with multiple source images.`,
1164
1208
  inputSchema: {
1165
1209
  prompt: z4.string().describe("Image description or edit instructions"),
1166
1210
  action: z4.enum(["generate", "edit"]).optional().default("generate").describe("generate: create from text; edit: transform existing image"),
1167
1211
  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."),
1168
- image: z4.string().optional().describe("Source image for edit action: base64-encoded image or URL"),
1212
+ image: z4.union([z4.string(), z4.array(z4.string()).min(1).max(4)]).optional().describe("Source image(s) for edit action: a base64 data URI, an http(s) URL, or a local file path (auto-encoded to a data URI) \u2014 or an array of 2\u20134 to fuse into one render (e.g. subject + layout guide, or reference + brand logo). openai/* accepts up to 4, google/* up to 3; a mask cannot be combined with multiple images."),
1213
+ mask: z4.string().optional().describe("Inpaint mask for edit action (openai/gpt-image-* only): a base64 data URI, http(s) URL, or local file path. Transparent areas of the mask are regenerated. Cannot be combined with multiple source images."),
1169
1214
  size: z4.string().optional().default("1024x1024").describe("Image size. Common values: 1024x1024 (all models), 1536x1024 / 1024x1536 (gpt-image-*), 2048x2048 / 4096x4096 (nano-banana-pro)"),
1170
1215
  quality: z4.enum(["standard", "hd"]).optional().default("standard"),
1171
1216
  agent_id: z4.string().optional().describe("Agent identifier for budget tracking and enforcement.")
1172
1217
  }
1173
1218
  },
1174
- async ({ prompt, action, model, image, size, quality, agent_id }) => {
1219
+ async ({ prompt, action, model, image, mask, size, quality, agent_id }) => {
1175
1220
  try {
1176
1221
  if (getChain() !== "base") {
1177
1222
  return {
@@ -1194,6 +1239,40 @@ Edit (img2img) models: openai/gpt-image-2 (default), openai/gpt-image-1, google/
1194
1239
  isError: true
1195
1240
  };
1196
1241
  }
1242
+ const sourceImages = Array.isArray(image) ? image : [image];
1243
+ const maxImages = MAX_EDIT_IMAGES_BY_PREFIX[`${selectedModel.split("/")[0]}/`] ?? 1;
1244
+ if (sourceImages.length > maxImages) {
1245
+ return {
1246
+ content: [{ type: "text", text: formatError(`${selectedModel} accepts at most ${maxImages} source image${maxImages > 1 ? "s" : ""} per edit (got ${sourceImages.length}).`) }],
1247
+ isError: true
1248
+ };
1249
+ }
1250
+ if (mask) {
1251
+ if (!MASK_MODELS.has(selectedModel)) {
1252
+ return {
1253
+ content: [{ type: "text", text: formatError("mask (inpaint) is supported only by openai/gpt-image-1 and openai/gpt-image-2") }],
1254
+ isError: true
1255
+ };
1256
+ }
1257
+ if (sourceImages.length > 1) {
1258
+ return {
1259
+ content: [{ type: "text", text: formatError("mask cannot be combined with multiple source images; send a single image with a mask, or multiple images without a mask") }],
1260
+ isError: true
1261
+ };
1262
+ }
1263
+ }
1264
+ let normalizedImage;
1265
+ let normalizedMask;
1266
+ try {
1267
+ const dataUris = await Promise.all(sourceImages.map(toImageDataUri));
1268
+ normalizedImage = dataUris.length === 1 ? dataUris[0] : dataUris;
1269
+ if (mask) normalizedMask = await toImageDataUri(mask);
1270
+ } catch (e) {
1271
+ return {
1272
+ content: [{ type: "text", text: formatError(`Could not load source image: ${e instanceof Error ? e.message : String(e)}`) }],
1273
+ isError: true
1274
+ };
1275
+ }
1197
1276
  const estimatedCost = estimateCost(selectedModel, size);
1198
1277
  const budgetCheck = checkBudget(budget, agent_id, estimatedCost);
1199
1278
  if (!budgetCheck.allowed) {
@@ -1202,9 +1281,10 @@ Edit (img2img) models: openai/gpt-image-2 (default), openai/gpt-image-1, google/
1202
1281
  isError: true
1203
1282
  };
1204
1283
  }
1205
- response = await getImageClient().edit(prompt, image, {
1284
+ response = await getImageClient().edit(prompt, normalizedImage, {
1206
1285
  model: selectedModel,
1207
- size
1286
+ size,
1287
+ ...normalizedMask ? { mask: normalizedMask } : {}
1208
1288
  });
1209
1289
  recordSpending(budget, estimatedCost, agent_id);
1210
1290
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/mcp",
3
- "version": "0.22.1",
3
+ "version": "0.22.3",
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",