@blockrun/mcp 0.7.0 → 0.7.2

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 (2) hide show
  1. package/dist/index.js +162 -159
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -590,18 +590,22 @@ function registerImageTool(server) {
590
590
  server.registerTool(
591
591
  "blockrun_image",
592
592
  {
593
- description: `Generate or edit images via BlockRun.
593
+ description: `Generate or edit images via BlockRun. Pays with USDC \u2014 no separate API keys needed.
594
594
 
595
595
  Actions:
596
596
  - generate (default): Create image from text prompt
597
597
  - edit: Transform an existing image using img2img
598
598
 
599
- Generation models: openai/dall-e-3 ($0.04-0.08), together/flux-schnell ($0.02), google/nano-banana
599
+ Generation models:
600
+ - zai/cogview-4 ($0.02) \u2014 Zhipu CogView-4, photorealistic, great for detailed scenes
601
+ - openai/dall-e-3 ($0.04-0.08) \u2014 High quality, prompt adherence
602
+ - together/flux-schnell ($0.02) \u2014 Fast, stylized
603
+ - google/nano-banana \u2014 Google image model
600
604
  Edit models: openai/gpt-image-1 (default for edits)`,
601
605
  inputSchema: {
602
606
  prompt: z4.string().describe("Image description or edit instructions"),
603
607
  action: z4.enum(["generate", "edit"]).optional().default("generate").describe("generate: create from text; edit: transform existing image"),
604
- model: z4.enum(["openai/dall-e-3", "together/flux-schnell", "google/nano-banana", "openai/gpt-image-1"]).optional().describe("Model to use (default: dall-e-3 for generate, gpt-image-1 for edit)"),
608
+ model: z4.enum(["zai/cogview-4", "openai/dall-e-3", "together/flux-schnell", "google/nano-banana", "openai/gpt-image-1"]).optional().describe("Model to use (default: dall-e-3 for generate, gpt-image-1 for edit). zai/cogview-4 is Zhipu's photorealistic model."),
605
609
  image: z4.string().optional().describe("Source image for edit action: base64-encoded image or URL"),
606
610
  size: z4.enum(["1024x1024", "1792x1024", "1024x1792"]).optional().default("1024x1024"),
607
611
  quality: z4.enum(["standard", "hd"]).optional().default("standard")
@@ -938,10 +942,10 @@ $0.001/call.`,
938
942
  body: z8.any().optional().describe("JSON body for POST queries (triggers pmQuery)")
939
943
  }
940
944
  },
941
- async ({ path: path2, params, body }) => {
945
+ async ({ path: path3, params, body }) => {
942
946
  try {
943
947
  const llm = getClient();
944
- const result = body ? await llm.pmQuery(path2, body) : await llm.pm(path2, params);
948
+ const result = body ? await llm.pmQuery(path3, body) : await llm.pm(path3, params);
945
949
  return {
946
950
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
947
951
  structuredContent: result
@@ -1038,170 +1042,88 @@ ${lines.join("\n\n")}` }],
1038
1042
  );
1039
1043
  }
1040
1044
 
1041
- // src/tools/glm-vision.ts
1045
+ // src/tools/modal.ts
1042
1046
  import { z as z10 } from "zod";
1043
- var ZHIPU_BASE_URL = "https://open.bigmodel.cn/api/paas/v4";
1044
- async function callGLMVision(model, prompt, imageUrl, thinking = false) {
1045
- const apiKey = process.env.ZHIPU_API_KEY;
1046
- if (!apiKey) throw new Error("ZHIPU_API_KEY environment variable is required for GLM Vision");
1047
- const payload = {
1048
- model,
1049
- messages: [
1050
- {
1051
- role: "user",
1052
- content: [
1053
- { type: "image_url", image_url: { url: imageUrl } },
1054
- { type: "text", text: prompt }
1055
- ]
1056
- }
1057
- ],
1058
- temperature: 0.8,
1059
- top_p: 0.6,
1060
- max_tokens: 16384,
1061
- stream: false
1062
- };
1063
- if (thinking || model.includes("thinking")) {
1064
- payload["thinking"] = { type: "enabled" };
1065
- }
1066
- const res = await fetch(`${ZHIPU_BASE_URL}/chat/completions`, {
1067
- method: "POST",
1068
- headers: {
1069
- "Content-Type": "application/json",
1070
- Authorization: `Bearer ${apiKey}`
1071
- },
1072
- body: JSON.stringify(payload)
1073
- });
1074
- if (!res.ok) {
1075
- const err = await res.text().catch(() => res.statusText);
1076
- throw new Error(`GLM Vision API error ${res.status}: ${err}`);
1077
- }
1078
- const data = await res.json();
1079
- const choice = data.choices?.[0];
1080
- if (!choice) throw new Error("No response from GLM Vision");
1081
- if (choice.finish_reason === "sensitive") throw new Error("Content blocked by safety filter");
1082
- return choice.message.content;
1083
- }
1084
- async function callGLMOCR(fileUrl, startPage = 1, endPage) {
1085
- const apiKey = process.env.ZHIPU_API_KEY;
1086
- if (!apiKey) throw new Error("ZHIPU_API_KEY environment variable is required for GLM OCR");
1087
- const payload = {
1088
- model: "glm-ocr",
1089
- file: fileUrl,
1090
- return_crop_images: false,
1091
- need_layout_visualization: false,
1092
- start_page_id: startPage
1093
- };
1094
- if (endPage) payload["end_page_id"] = endPage;
1095
- const res = await fetch(`${ZHIPU_BASE_URL}/layout_parsing`, {
1096
- method: "POST",
1097
- headers: {
1098
- "Content-Type": "application/json",
1099
- Authorization: `Bearer ${apiKey}`
1100
- },
1101
- body: JSON.stringify(payload)
1102
- });
1103
- if (!res.ok) {
1104
- const err = await res.text().catch(() => res.statusText);
1105
- throw new Error(`GLM OCR API error ${res.status}: ${err}`);
1106
- }
1107
- const data = await res.json();
1108
- return data.markdown_result || data.choices?.[0]?.message?.content || JSON.stringify(data);
1109
- }
1110
- async function callGLMImageGen(prompt, size, quality) {
1111
- const apiKey = process.env.ZHIPU_API_KEY;
1112
- if (!apiKey) throw new Error("ZHIPU_API_KEY environment variable is required for GLM Image Gen");
1113
- const res = await fetch(`${ZHIPU_BASE_URL}/images/generations`, {
1114
- method: "POST",
1115
- headers: {
1116
- "Content-Type": "application/json",
1117
- Authorization: `Bearer ${apiKey}`
1118
- },
1119
- body: JSON.stringify({
1120
- model: "cogview-4-250304",
1121
- prompt,
1122
- size,
1123
- quality,
1124
- watermark_enabled: false
1125
- })
1126
- });
1127
- if (!res.ok) {
1128
- const err = await res.text().catch(() => res.statusText);
1129
- throw new Error(`GLM Image API error ${res.status}: ${err}`);
1130
- }
1131
- const data = await res.json();
1132
- const url = data.data?.[0]?.url;
1133
- if (!url) throw new Error("No image URL in GLM response");
1134
- return url;
1135
- }
1136
- function registerGLMVisionTool(server) {
1047
+ var MODAL_GPU_TYPES = ["T4", "L4", "A10G", "A100", "A100-80GB", "H100"];
1048
+ function registerModalTool(server) {
1137
1049
  server.registerTool(
1138
- "blockrun_glm_vision",
1050
+ "blockrun_modal",
1139
1051
  {
1140
- description: `Analyze images and documents using Zhipu AI's GLM vision models.
1052
+ description: `Run isolated code in a BlockRun-hosted Modal sandbox.
1141
1053
 
1142
- Requires ZHIPU_API_KEY environment variable.
1143
-
1144
- Actions:
1145
- - caption: Describe what's in an image
1146
- - analyze: Deep analysis of an image (objects, layout, text, colors)
1147
- - grounding: Locate specific elements in an image (returns bounding boxes)
1148
- - code: Generate code from a UI screenshot or mockup
1149
- - ocr: Extract text from a document/PDF (use file URL)
1150
- - imagegen: Generate an image using CogView-4
1054
+ Use this when you need:
1055
+ - a disposable remote container
1056
+ - GPU access
1057
+ - a clean environment that will not affect the local machine
1058
+ - a safer place to run untrusted or heavy code
1151
1059
 
1152
- Models:
1153
- - glm-4.6v (default): Best quality vision model
1154
- - glm-4.6v-flash: Faster, cheaper
1155
- - glm-4.1v-thinking-flash: With reasoning/thinking
1060
+ Prefer local tools for normal repo work. Modal is best for isolation or remote execution.
1156
1061
 
1157
- Cost: Zhipu AI pricing (separate from BlockRun x402 \u2014 uses ZHIPU_API_KEY)`,
1062
+ Pricing:
1063
+ - create: $0.01
1064
+ - exec/status/terminate: $0.001 each`,
1158
1065
  inputSchema: {
1159
- action: z10.enum(["caption", "analyze", "grounding", "code", "ocr", "imagegen"]).describe("Task to perform"),
1160
- image: z10.string().optional().describe("Image URL or base64 data URI (for vision actions)"),
1161
- prompt: z10.string().optional().describe("Custom prompt or question about the image. For imagegen: the image description"),
1162
- model: z10.enum(["glm-4.6v", "glm-4.6v-flash", "glm-4.1v-thinking-flash"]).optional().default("glm-4.6v").describe("Vision model to use"),
1163
- size: z10.enum(["1280x1280", "1280x720", "720x1280", "1024x1024"]).optional().default("1280x1280").describe("Image size (for imagegen)"),
1164
- quality: z10.enum(["hd", "standard"]).optional().default("hd").describe("Image quality (for imagegen)"),
1165
- start_page: z10.number().optional().default(1).describe("Start page for OCR (PDF)"),
1166
- end_page: z10.number().optional().describe("End page for OCR (PDF)")
1066
+ action: z10.enum(["create", "exec", "status", "terminate"]).describe(
1067
+ "Sandbox action to perform"
1068
+ ),
1069
+ sandbox_id: z10.string().optional().describe("Sandbox ID returned by a previous create"),
1070
+ command: z10.array(z10.string()).optional().describe('Command array for exec, for example ["python", "-c", "print(2+2)"]'),
1071
+ image: z10.string().optional().describe("Container image for create (default: python:3.11)"),
1072
+ timeout: z10.number().optional().describe("Timeout in seconds for create or exec"),
1073
+ cpu: z10.number().optional().describe("CPU cores for create"),
1074
+ memory: z10.number().optional().describe("Memory in MB for create"),
1075
+ gpu: z10.enum(MODAL_GPU_TYPES).optional().describe("Optional GPU type for create"),
1076
+ setup_commands: z10.array(z10.string()).optional().describe("Shell commands to run during sandbox setup")
1167
1077
  }
1168
1078
  },
1169
- async ({ action, image, prompt, model, size, quality, start_page, end_page }) => {
1079
+ async ({ action, sandbox_id, command, image, timeout, cpu, memory, gpu, setup_commands }) => {
1170
1080
  try {
1171
- if (action === "imagegen") {
1172
- const imagePrompt = prompt || image || "";
1173
- if (!imagePrompt) {
1174
- return { content: [{ type: "text", text: formatError("prompt is required for imagegen action") }], isError: true };
1175
- }
1176
- const url = await callGLMImageGen(imagePrompt, size ?? "1280x1280", quality ?? "hd");
1177
- return {
1178
- content: [{ type: "text", text: `Generated image: ${url}` }],
1179
- structuredContent: { url }
1180
- };
1181
- }
1182
- if (action === "ocr") {
1183
- const fileUrl = image || prompt;
1184
- if (!fileUrl) {
1185
- return { content: [{ type: "text", text: formatError("image (PDF URL) is required for OCR") }], isError: true };
1186
- }
1187
- const result2 = await callGLMOCR(fileUrl, start_page ?? 1, end_page);
1188
- return { content: [{ type: "text", text: result2 }] };
1189
- }
1190
- if (!image) {
1191
- return { content: [{ type: "text", text: formatError("image is required for vision actions") }], isError: true };
1081
+ const llm = getClient();
1082
+ const req = llm;
1083
+ let result;
1084
+ switch (action) {
1085
+ case "create":
1086
+ result = await req.requestWithPaymentRaw("/v1/modal/sandbox/create", {
1087
+ image,
1088
+ timeout,
1089
+ cpu,
1090
+ memory,
1091
+ gpu,
1092
+ setup_commands
1093
+ });
1094
+ break;
1095
+ case "exec":
1096
+ if (!sandbox_id) throw new Error("sandbox_id required for exec action");
1097
+ if (!command?.length) throw new Error("command array required for exec action");
1098
+ result = await req.requestWithPaymentRaw("/v1/modal/sandbox/exec", {
1099
+ sandbox_id,
1100
+ command,
1101
+ timeout
1102
+ });
1103
+ break;
1104
+ case "status":
1105
+ if (!sandbox_id) throw new Error("sandbox_id required for status action");
1106
+ result = await req.requestWithPaymentRaw("/v1/modal/sandbox/status", { sandbox_id });
1107
+ break;
1108
+ case "terminate":
1109
+ if (!sandbox_id) throw new Error("sandbox_id required for terminate action");
1110
+ result = await req.requestWithPaymentRaw("/v1/modal/sandbox/terminate", {
1111
+ sandbox_id
1112
+ });
1113
+ break;
1114
+ default:
1115
+ throw new Error(`Unknown action: ${String(action)}`);
1192
1116
  }
1193
- const actionPrompts = {
1194
- caption: "Describe this image concisely and accurately.",
1195
- analyze: "Analyze this image in detail: describe all visible objects, layout, colors, text, and any notable features.",
1196
- grounding: `Locate the following elements in the image and return their bounding boxes in [x1,y1,x2,y2] format (0-1000 normalized): ${prompt || "all interactive UI elements"}`,
1197
- code: "Generate complete, working code to replicate this UI. Use React + TypeScript + Tailwind CSS. Include all components, styling, and mock data visible in the screenshot."
1117
+ return {
1118
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
1119
+ structuredContent: result
1198
1120
  };
1199
- const visionPrompt = actionPrompts[action] || prompt || actionPrompts.caption;
1200
- const result = await callGLMVision(model ?? "glm-4.6v", visionPrompt, image);
1201
- return { content: [{ type: "text", text: result }] };
1202
1121
  } catch (err) {
1203
1122
  const errMsg = err instanceof Error ? err.message : String(err);
1204
- return { content: [{ type: "text", text: formatError(errMsg) }], isError: true };
1123
+ return {
1124
+ content: [{ type: "text", text: formatError(errMsg) }],
1125
+ isError: true
1126
+ };
1205
1127
  }
1206
1128
  }
1207
1129
  );
@@ -1220,9 +1142,7 @@ function initializeMcpServer(server) {
1220
1142
  registerExaTool(server);
1221
1143
  registerMarketsTool(server);
1222
1144
  registerDexTool(server);
1223
- if (process.env.ZHIPU_API_KEY) {
1224
- registerGLMVisionTool(server);
1225
- }
1145
+ registerModalTool(server);
1226
1146
  server.registerResource(
1227
1147
  "wallet",
1228
1148
  "blockrun://wallet",
@@ -1258,8 +1178,90 @@ function initializeMcpServer(server) {
1258
1178
  );
1259
1179
  }
1260
1180
 
1181
+ // src/utils/key-leak-scanner.ts
1182
+ import fs2 from "fs";
1183
+ import path2 from "path";
1184
+ import os2 from "os";
1185
+ function looksLikeRawPrivateKey(value) {
1186
+ if (typeof value !== "string") return false;
1187
+ if (/^0x[0-9a-fA-F]{64}$/.test(value)) return true;
1188
+ if (value.length >= 80 && value.length <= 100 && /^[1-9A-HJ-NP-Za-km-z]+$/.test(value)) return true;
1189
+ return false;
1190
+ }
1191
+ function walk(obj, file, jsonPath, out) {
1192
+ if (obj === null || typeof obj !== "object") return;
1193
+ if (Array.isArray(obj)) {
1194
+ obj.forEach((v, i) => walk(v, file, `${jsonPath}[${i}]`, out));
1195
+ return;
1196
+ }
1197
+ for (const [k, v] of Object.entries(obj)) {
1198
+ const next = jsonPath ? `${jsonPath}.${k}` : k;
1199
+ if (/wallet[-_ ]?key|private[-_ ]?key|secret/i.test(k) && looksLikeRawPrivateKey(v)) {
1200
+ out.push({ file, path: next });
1201
+ } else if (looksLikeRawPrivateKey(v)) {
1202
+ out.push({ file, path: next });
1203
+ }
1204
+ walk(v, file, next, out);
1205
+ }
1206
+ }
1207
+ function scanFile(file) {
1208
+ try {
1209
+ if (!fs2.existsSync(file)) return [];
1210
+ const raw = fs2.readFileSync(file, "utf-8");
1211
+ const data = JSON.parse(raw);
1212
+ const out = [];
1213
+ walk(data, file, "", out);
1214
+ return out;
1215
+ } catch {
1216
+ return [];
1217
+ }
1218
+ }
1219
+ function warnOnLeakedKeys() {
1220
+ const home = os2.homedir();
1221
+ const candidates = [
1222
+ path2.join(home, ".claude.json"),
1223
+ path2.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json"),
1224
+ path2.join(home, ".config", "claude", "claude_desktop_config.json"),
1225
+ path2.join(home, "AppData", "Roaming", "Claude", "claude_desktop_config.json")
1226
+ ];
1227
+ const findings = [];
1228
+ for (const f of candidates) findings.push(...scanFile(f));
1229
+ if (findings.length === 0) return false;
1230
+ const bar = "\u2550".repeat(72);
1231
+ console.error("");
1232
+ console.error(`\x1B[31m${bar}`);
1233
+ console.error(" \u{1F6A8} WALLET PRIVATE KEY DETECTED IN CONFIG FILE");
1234
+ console.error(bar + "\x1B[0m");
1235
+ console.error("");
1236
+ console.error(" Your config contains what looks like a raw wallet private key.");
1237
+ console.error(" Private keys should NEVER be stored in these files \u2014 they get");
1238
+ console.error(" backed up to iCloud / Dropbox / Time Machine, synced across");
1239
+ console.error(" machines, and readable by anything that can read your config.");
1240
+ console.error("");
1241
+ console.error(" Found in:");
1242
+ for (const f of findings) {
1243
+ console.error(` \xB7 ${f.file}`);
1244
+ console.error(` at: ${f.path}`);
1245
+ }
1246
+ console.error("");
1247
+ console.error(" RECOMMENDED ACTIONS:");
1248
+ console.error(" 1. Treat this key as compromised. Rotate your wallet:");
1249
+ console.error(" - Create a new wallet");
1250
+ console.error(" - Transfer remaining USDC to the new address");
1251
+ console.error(" - Retire the old key");
1252
+ console.error(" 2. Remove the X-Wallet-Key entries from your config.");
1253
+ console.error(" 3. Reconnect using the local package (signs locally, key");
1254
+ console.error(" never leaves your machine):");
1255
+ console.error(" claude mcp remove blockrun");
1256
+ console.error(" claude mcp add blockrun npx -y @blockrun/mcp@latest");
1257
+ console.error("");
1258
+ console.error(" Details: https://github.com/BlockRunAI/blockrun-mcp-server/issues/1");
1259
+ console.error("");
1260
+ return true;
1261
+ }
1262
+
1261
1263
  // src/index.ts
1262
- var VERSION = "0.6.8";
1264
+ var VERSION = "0.7.2";
1263
1265
  async function checkForUpdate() {
1264
1266
  try {
1265
1267
  const resp = await fetch("https://registry.npmjs.org/@blockrun/mcp/latest", {
@@ -1274,6 +1276,7 @@ async function checkForUpdate() {
1274
1276
  }
1275
1277
  }
1276
1278
  async function main() {
1279
+ warnOnLeakedKeys();
1277
1280
  const server = new McpServer({
1278
1281
  name: "blockrun-mcp",
1279
1282
  version: VERSION
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/mcp",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
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",