@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.
- package/dist/index.js +162 -159
- 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:
|
|
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:
|
|
945
|
+
async ({ path: path3, params, body }) => {
|
|
942
946
|
try {
|
|
943
947
|
const llm = getClient();
|
|
944
|
-
const result = body ? await llm.pmQuery(
|
|
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/
|
|
1045
|
+
// src/tools/modal.ts
|
|
1042
1046
|
import { z as z10 } from "zod";
|
|
1043
|
-
var
|
|
1044
|
-
|
|
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
|
-
"
|
|
1050
|
+
"blockrun_modal",
|
|
1139
1051
|
{
|
|
1140
|
-
description: `
|
|
1052
|
+
description: `Run isolated code in a BlockRun-hosted Modal sandbox.
|
|
1141
1053
|
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
-
|
|
1146
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
1062
|
+
Pricing:
|
|
1063
|
+
- create: $0.01
|
|
1064
|
+
- exec/status/terminate: $0.001 each`,
|
|
1158
1065
|
inputSchema: {
|
|
1159
|
-
action: z10.enum(["
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
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,
|
|
1079
|
+
async ({ action, sandbox_id, command, image, timeout, cpu, memory, gpu, setup_commands }) => {
|
|
1170
1080
|
try {
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
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
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
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 {
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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",
|