@blockrun/mcp 0.6.8 → 0.7.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/dist/index.js +354 -34
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -105,10 +105,11 @@ var MODEL_TIERS = {
|
|
|
105
105
|
fast: ["google/gemini-2.5-flash", "google/gemini-3.1-flash-lite", "openai/gpt-5-mini", "deepseek/deepseek-chat", "google/gemini-3-flash-preview"],
|
|
106
106
|
balanced: ["openai/gpt-5.4", "anthropic/claude-sonnet-4.6", "google/gemini-2.5-pro", "openai/gpt-5.3", "google/gemini-3.1-pro"],
|
|
107
107
|
powerful: ["openai/gpt-5.4-pro", "anthropic/claude-opus-4.6", "anthropic/claude-opus-4.5", "openai/o3", "openai/gpt-5.4"],
|
|
108
|
-
cheap: ["nvidia/gpt-oss-120b", "nvidia/deepseek-v3.2", "google/gemini-2.5-flash", "deepseek/deepseek-chat", "openai/gpt-5.4-nano"],
|
|
108
|
+
cheap: ["zai/glm-5", "zai/glm-5-turbo", "nvidia/gpt-oss-120b", "nvidia/deepseek-v3.2", "google/gemini-2.5-flash", "deepseek/deepseek-chat", "openai/gpt-5.4-nano"],
|
|
109
109
|
reasoning: ["openai/o3", "openai/o1", "openai/o3-mini", "deepseek/deepseek-reasoner", "openai/gpt-5.3-codex"],
|
|
110
|
-
free: ["nvidia/gpt-oss-120b", "nvidia/deepseek-v3.2", "nvidia/nemotron-ultra-253b", "nvidia/nemotron-super-49b", "nvidia/qwen3-coder-480b", "nvidia/llama-4-maverick", "nvidia/gpt-oss-20b"],
|
|
111
|
-
coding: ["openai/gpt-5.3-codex", "nvidia/qwen3-coder-480b", "nvidia/devstral-2-123b", "anthropic/claude-sonnet-4.6", "openai/gpt-5.4"]
|
|
110
|
+
free: ["nvidia/gpt-oss-120b", "nvidia/deepseek-v3.2", "nvidia/nemotron-ultra-253b", "nvidia/nemotron-super-49b", "nvidia/qwen3-coder-480b", "nvidia/llama-4-maverick", "nvidia/gpt-oss-20b", "nvidia/glm-4.7"],
|
|
111
|
+
coding: ["zai/glm-5", "openai/gpt-5.3-codex", "nvidia/qwen3-coder-480b", "nvidia/devstral-2-123b", "anthropic/claude-sonnet-4.6", "openai/gpt-5.4"],
|
|
112
|
+
glm: ["zai/glm-5", "zai/glm-5-turbo", "nvidia/glm-4.7"]
|
|
112
113
|
};
|
|
113
114
|
|
|
114
115
|
// src/utils/qr.ts
|
|
@@ -398,17 +399,23 @@ function registerChatTool(server, budget) {
|
|
|
398
399
|
server.registerTool(
|
|
399
400
|
"blockrun_chat",
|
|
400
401
|
{
|
|
401
|
-
description: `Get a second opinion from another AI model, or use a specialized model for a specific task.
|
|
402
|
+
description: `Get a second opinion from another AI model, or use a specialized model for a specific task.
|
|
402
403
|
|
|
403
|
-
|
|
404
|
+
Notable modes:
|
|
405
|
+
- mode:"glm" \u2192 Zhipu GLM-5 / GLM-5-Turbo ($0.001/call, excellent for coding tasks, pays via USDC on BlockRun)
|
|
406
|
+
- mode:"coding" \u2192 GLM-5 first, then code-specialized models
|
|
407
|
+
- mode:"cheap" \u2192 GLM-5, NVIDIA free, DeepSeek
|
|
408
|
+
- mode:"reasoning" \u2192 o3, o1, DeepSeek-R1
|
|
409
|
+
- mode:"free" \u2192 NVIDIA models (no cost)
|
|
410
|
+
- routing:"smart" \u2192 auto-select via ClawRouter
|
|
404
411
|
|
|
405
|
-
|
|
412
|
+
Pick directly: model:"zai/glm-5", model:"openai/o3", model:"nvidia/deepseek-v3.2" (free).
|
|
406
413
|
|
|
407
|
-
Run blockrun_models to see all
|
|
414
|
+
Run blockrun_models to see all 41+ models with pricing.`,
|
|
408
415
|
inputSchema: {
|
|
409
416
|
message: z2.string().describe("Your message to the AI"),
|
|
410
|
-
model: z2.string().optional().describe("Specific model ID (e.g., '
|
|
411
|
-
mode: z2.enum(["fast", "balanced", "powerful", "cheap", "reasoning", "free", "coding"]).optional().describe("
|
|
417
|
+
model: z2.string().optional().describe("Specific model ID (e.g., 'zai/glm-5', 'openai/o3')"),
|
|
418
|
+
mode: z2.enum(["fast", "balanced", "powerful", "cheap", "reasoning", "free", "coding", "glm"]).optional().describe("Routing mode: glm = Zhipu GLM-5/GLM-5-Turbo ($0.001/call, great for coding), coding = GLM-5 + code models, cheap = GLM-5 + budget, free = NVIDIA only (ignored if model specified)"),
|
|
412
419
|
routing: z2.enum(["smart"]).optional().describe('Set to "smart" to auto-select the optimal model via ClawRouter (14-dimension AI routing)'),
|
|
413
420
|
routing_profile: z2.enum(["free", "eco", "auto", "premium"]).optional().default("auto").describe('Cost/quality profile for ClawRouter: "free" (zero cost NVIDIA), "eco" (budget), "auto" (balanced, default), "premium" (best quality) (only applies when routing: "smart")'),
|
|
414
421
|
system: z2.string().optional().describe("Optional system prompt"),
|
|
@@ -653,8 +660,148 @@ Error: ${errMsg}` }],
|
|
|
653
660
|
);
|
|
654
661
|
}
|
|
655
662
|
|
|
656
|
-
// src/tools/
|
|
663
|
+
// src/tools/music.ts
|
|
657
664
|
import { z as z5 } from "zod";
|
|
665
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
666
|
+
import {
|
|
667
|
+
createPaymentPayload,
|
|
668
|
+
parsePaymentRequired,
|
|
669
|
+
extractPaymentDetails
|
|
670
|
+
} from "@blockrun/llm";
|
|
671
|
+
var BLOCKRUN_API = "https://blockrun.ai/api";
|
|
672
|
+
var MUSIC_TIMEOUT = 2e5;
|
|
673
|
+
function registerMusicTool(server) {
|
|
674
|
+
server.registerTool(
|
|
675
|
+
"blockrun_music",
|
|
676
|
+
{
|
|
677
|
+
description: `Generate music tracks via BlockRun x402.
|
|
678
|
+
|
|
679
|
+
Generates a full-length ~3 minute MP3 track. Takes 1-3 minutes to complete.
|
|
680
|
+
|
|
681
|
+
Models: minimax/music-2.5+ ($0.1575), minimax/music-2.5 ($0.1575)
|
|
682
|
+
|
|
683
|
+
Returns a time-limited CDN URL \u2014 download immediately if you need to keep the file.`,
|
|
684
|
+
inputSchema: {
|
|
685
|
+
prompt: z5.string().describe("Music style, mood, or description. E.g. 'upbeat synthwave with neon pads', 'chill lo-fi beats', 'epic orchestral film score'"),
|
|
686
|
+
instrumental: z5.boolean().optional().default(true).describe("Generate without vocals (default: true)"),
|
|
687
|
+
lyrics: z5.string().optional().describe("Custom lyrics. Cannot be used with instrumental: true"),
|
|
688
|
+
model: z5.enum(["minimax/music-2.5+", "minimax/music-2.5"]).optional().default("minimax/music-2.5+").describe("Music model to use")
|
|
689
|
+
}
|
|
690
|
+
},
|
|
691
|
+
async ({ prompt, instrumental, lyrics, model }) => {
|
|
692
|
+
try {
|
|
693
|
+
if (instrumental && lyrics?.trim()) {
|
|
694
|
+
return {
|
|
695
|
+
content: [{ type: "text", text: formatError("Cannot specify lyrics when instrumental is true") }],
|
|
696
|
+
isError: true
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
const privateKey = getOrCreateWalletKey();
|
|
700
|
+
const account = privateKeyToAccount(privateKey);
|
|
701
|
+
const url = `${BLOCKRUN_API}/v1/audio/generations`;
|
|
702
|
+
const body = { model, prompt, instrumental };
|
|
703
|
+
if (lyrics?.trim()) body.lyrics = lyrics.trim();
|
|
704
|
+
const resp402 = await fetchWithTimeout(url, {
|
|
705
|
+
method: "POST",
|
|
706
|
+
headers: { "Content-Type": "application/json" },
|
|
707
|
+
body: JSON.stringify(body)
|
|
708
|
+
}, 15e3);
|
|
709
|
+
if (resp402.status !== 402) {
|
|
710
|
+
const data2 = await resp402.json();
|
|
711
|
+
throw new Error(`Expected 402, got ${resp402.status}: ${JSON.stringify(data2)}`);
|
|
712
|
+
}
|
|
713
|
+
const prHeader = resp402.headers.get("payment-required") || resp402.headers.get("PAYMENT-REQUIRED");
|
|
714
|
+
if (!prHeader) throw new Error("No PAYMENT-REQUIRED header in 402 response");
|
|
715
|
+
const paymentRequired = parsePaymentRequired(prHeader);
|
|
716
|
+
const details = extractPaymentDetails(paymentRequired);
|
|
717
|
+
const paymentPayload = await createPaymentPayload(
|
|
718
|
+
privateKey,
|
|
719
|
+
account.address,
|
|
720
|
+
details.recipient,
|
|
721
|
+
details.amount,
|
|
722
|
+
details.network || "eip155:8453",
|
|
723
|
+
{
|
|
724
|
+
resourceUrl: details.resource?.url || url,
|
|
725
|
+
resourceDescription: details.resource?.description || "BlockRun Music Generation",
|
|
726
|
+
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
727
|
+
extra: details.extra
|
|
728
|
+
}
|
|
729
|
+
);
|
|
730
|
+
const resp = await fetchWithTimeout(url, {
|
|
731
|
+
method: "POST",
|
|
732
|
+
headers: {
|
|
733
|
+
"Content-Type": "application/json",
|
|
734
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
735
|
+
},
|
|
736
|
+
body: JSON.stringify(body)
|
|
737
|
+
}, MUSIC_TIMEOUT);
|
|
738
|
+
if (resp.status === 402) {
|
|
739
|
+
throw new Error("Payment rejected. Check your wallet balance.");
|
|
740
|
+
}
|
|
741
|
+
if (!resp.ok) {
|
|
742
|
+
const errBody = await resp.json().catch(() => ({ error: "Request failed" }));
|
|
743
|
+
throw new Error(`API error ${resp.status}: ${JSON.stringify(errBody)}`);
|
|
744
|
+
}
|
|
745
|
+
const data = await resp.json();
|
|
746
|
+
const track = data.data?.[0];
|
|
747
|
+
if (!track?.url) throw new Error("No track URL in response");
|
|
748
|
+
const txHash = resp.headers.get("X-Payment-Receipt") || resp.headers.get("x-payment-receipt");
|
|
749
|
+
const lines = [
|
|
750
|
+
`\u{1F3B5} Track ready!`,
|
|
751
|
+
`URL: ${track.url}`,
|
|
752
|
+
`Duration: ${track.duration_seconds ? `${track.duration_seconds}s` : "~3 min"}`,
|
|
753
|
+
`Model: ${data.model || model}`,
|
|
754
|
+
...track.lyrics ? [`Lyrics: ${track.lyrics.slice(0, 200)}${track.lyrics.length > 200 ? "..." : ""}`] : [],
|
|
755
|
+
...txHash ? [`Tx: ${txHash}`] : [],
|
|
756
|
+
``,
|
|
757
|
+
`Note: This URL expires in ~24h. Download it now if you need to keep the file.`
|
|
758
|
+
];
|
|
759
|
+
return {
|
|
760
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
761
|
+
structuredContent: {
|
|
762
|
+
url: track.url,
|
|
763
|
+
duration_seconds: track.duration_seconds,
|
|
764
|
+
model: data.model || model,
|
|
765
|
+
...track.lyrics ? { lyrics: track.lyrics } : {},
|
|
766
|
+
...txHash ? { txHash } : {}
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
} catch (err) {
|
|
770
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
771
|
+
if (errMsg.includes("balance") || errMsg.includes("payment") || errMsg.includes("402") || errMsg.includes("rejected")) {
|
|
772
|
+
return {
|
|
773
|
+
content: [{ type: "text", text: `Music generation requires payment. Run blockrun_wallet with action: "setup" for funding instructions.
|
|
774
|
+
Error: ${errMsg}` }],
|
|
775
|
+
isError: true
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
if (errMsg.includes("abort") || errMsg.includes("timeout") || errMsg.includes("Timeout")) {
|
|
779
|
+
return {
|
|
780
|
+
content: [{ type: "text", text: `Music generation timed out. This can happen during peak load \u2014 please try again.
|
|
781
|
+
Error: ${errMsg}` }],
|
|
782
|
+
isError: true
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
return {
|
|
786
|
+
content: [{ type: "text", text: formatError(`Music generation failed: ${errMsg}`) }],
|
|
787
|
+
isError: true
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
async function fetchWithTimeout(url, options, timeoutMs) {
|
|
794
|
+
const controller = new AbortController();
|
|
795
|
+
const id = setTimeout(() => controller.abort(), timeoutMs);
|
|
796
|
+
try {
|
|
797
|
+
return await fetch(url, { ...options, signal: controller.signal });
|
|
798
|
+
} finally {
|
|
799
|
+
clearTimeout(id);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// src/tools/search.ts
|
|
804
|
+
import { z as z6 } from "zod";
|
|
658
805
|
function registerSearchTool(server) {
|
|
659
806
|
server.registerTool(
|
|
660
807
|
"blockrun_search",
|
|
@@ -666,11 +813,11 @@ Pricing: ~$0.01/search
|
|
|
666
813
|
|
|
667
814
|
Returns a summary with cited sources.`,
|
|
668
815
|
inputSchema: {
|
|
669
|
-
query:
|
|
670
|
-
sources:
|
|
671
|
-
max_results:
|
|
672
|
-
from_date:
|
|
673
|
-
to_date:
|
|
816
|
+
query: z6.string().describe("Search query"),
|
|
817
|
+
sources: z6.array(z6.enum(["web", "x", "news"])).optional().describe("Sources to search (default: web + x + news)"),
|
|
818
|
+
max_results: z6.number().optional().default(10).describe("Max results per source (1-20)"),
|
|
819
|
+
from_date: z6.string().optional().describe("Start date filter (YYYY-MM-DD)"),
|
|
820
|
+
to_date: z6.string().optional().describe("End date filter (YYYY-MM-DD)")
|
|
674
821
|
}
|
|
675
822
|
},
|
|
676
823
|
async ({ query, sources, max_results, from_date, to_date }) => {
|
|
@@ -698,7 +845,7 @@ Returns a summary with cited sources.`,
|
|
|
698
845
|
}
|
|
699
846
|
|
|
700
847
|
// src/tools/exa.ts
|
|
701
|
-
import { z as
|
|
848
|
+
import { z as z7 } from "zod";
|
|
702
849
|
function registerExaTool(server) {
|
|
703
850
|
server.registerTool(
|
|
704
851
|
"blockrun_exa",
|
|
@@ -711,14 +858,14 @@ Actions:
|
|
|
711
858
|
- contents: Fetch full Markdown text from URLs, ready for LLM context ($0.002/URL)
|
|
712
859
|
- similar: Find pages semantically similar to a given URL ($0.01/call)`,
|
|
713
860
|
inputSchema: {
|
|
714
|
-
action:
|
|
715
|
-
query:
|
|
716
|
-
url:
|
|
717
|
-
urls:
|
|
718
|
-
num_results:
|
|
719
|
-
category:
|
|
720
|
-
include_domains:
|
|
721
|
-
exclude_domains:
|
|
861
|
+
action: z7.enum(["search", "answer", "contents", "similar"]).describe("Action to perform"),
|
|
862
|
+
query: z7.string().optional().describe("Natural language query (for search/answer)"),
|
|
863
|
+
url: z7.string().optional().describe("Reference URL to find similar pages (for similar action)"),
|
|
864
|
+
urls: z7.array(z7.string()).optional().describe("URLs to fetch content from (for contents action, up to 100)"),
|
|
865
|
+
num_results: z7.number().optional().describe("Number of results to return (default: 10)"),
|
|
866
|
+
category: z7.string().optional().describe("Category filter: 'news', 'research paper', 'company', 'tweet', 'github', 'pdf'"),
|
|
867
|
+
include_domains: z7.array(z7.string()).optional().describe("Only search within these domains"),
|
|
868
|
+
exclude_domains: z7.array(z7.string()).optional().describe("Exclude these domains from results")
|
|
722
869
|
}
|
|
723
870
|
},
|
|
724
871
|
async ({ action, query, url, urls, num_results, category, include_domains, exclude_domains }) => {
|
|
@@ -771,7 +918,7 @@ Actions:
|
|
|
771
918
|
}
|
|
772
919
|
|
|
773
920
|
// src/tools/markets.ts
|
|
774
|
-
import { z as
|
|
921
|
+
import { z as z8 } from "zod";
|
|
775
922
|
function registerMarketsTool(server) {
|
|
776
923
|
server.registerTool(
|
|
777
924
|
"blockrun_markets",
|
|
@@ -786,9 +933,9 @@ Example paths:
|
|
|
786
933
|
|
|
787
934
|
$0.001/call.`,
|
|
788
935
|
inputSchema: {
|
|
789
|
-
path:
|
|
790
|
-
params:
|
|
791
|
-
body:
|
|
936
|
+
path: z8.string().describe("Endpoint path, e.g. 'polymarket/events', 'kalshi/markets/KXBTC-25MAR14'"),
|
|
937
|
+
params: z8.record(z8.string(), z8.string()).optional().describe("Query parameters for GET requests"),
|
|
938
|
+
body: z8.any().optional().describe("JSON body for POST queries (triggers pmQuery)")
|
|
792
939
|
}
|
|
793
940
|
},
|
|
794
941
|
async ({ path: path2, params, body }) => {
|
|
@@ -811,7 +958,7 @@ $0.001/call.`,
|
|
|
811
958
|
}
|
|
812
959
|
|
|
813
960
|
// src/tools/dex.ts
|
|
814
|
-
import { z as
|
|
961
|
+
import { z as z9 } from "zod";
|
|
815
962
|
function registerDexTool(server) {
|
|
816
963
|
server.registerTool(
|
|
817
964
|
"blockrun_dex",
|
|
@@ -828,10 +975,10 @@ Examples:
|
|
|
828
975
|
blockrun_dex({ token: "So11...xxx" }) -> Get specific token data
|
|
829
976
|
blockrun_dex({ symbol: "PEPE" }) -> Search by symbol`,
|
|
830
977
|
inputSchema: {
|
|
831
|
-
query:
|
|
832
|
-
token:
|
|
833
|
-
symbol:
|
|
834
|
-
chain:
|
|
978
|
+
query: z9.string().optional().describe("Search query (token name, symbol, or address)"),
|
|
979
|
+
token: z9.string().optional().describe("Token address for direct lookup"),
|
|
980
|
+
symbol: z9.string().optional().describe("Token symbol to search"),
|
|
981
|
+
chain: z9.string().optional().describe("Filter by chain (ethereum, solana, base, etc.)")
|
|
835
982
|
}
|
|
836
983
|
},
|
|
837
984
|
async ({ query, token, symbol, chain }) => {
|
|
@@ -891,6 +1038,175 @@ ${lines.join("\n\n")}` }],
|
|
|
891
1038
|
);
|
|
892
1039
|
}
|
|
893
1040
|
|
|
1041
|
+
// src/tools/glm-vision.ts
|
|
1042
|
+
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) {
|
|
1137
|
+
server.registerTool(
|
|
1138
|
+
"blockrun_glm_vision",
|
|
1139
|
+
{
|
|
1140
|
+
description: `Analyze images and documents using Zhipu AI's GLM vision models.
|
|
1141
|
+
|
|
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
|
|
1151
|
+
|
|
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
|
|
1156
|
+
|
|
1157
|
+
Cost: Zhipu AI pricing (separate from BlockRun x402 \u2014 uses ZHIPU_API_KEY)`,
|
|
1158
|
+
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)")
|
|
1167
|
+
}
|
|
1168
|
+
},
|
|
1169
|
+
async ({ action, image, prompt, model, size, quality, start_page, end_page }) => {
|
|
1170
|
+
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 };
|
|
1192
|
+
}
|
|
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."
|
|
1198
|
+
};
|
|
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
|
+
} catch (err) {
|
|
1203
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1204
|
+
return { content: [{ type: "text", text: formatError(errMsg) }], isError: true };
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1209
|
+
|
|
894
1210
|
// src/mcp-handler.ts
|
|
895
1211
|
function initializeMcpServer(server) {
|
|
896
1212
|
const budget = { limit: null, spent: 0, calls: 0, agents: /* @__PURE__ */ new Map() };
|
|
@@ -899,10 +1215,14 @@ function initializeMcpServer(server) {
|
|
|
899
1215
|
registerChatTool(server, budget);
|
|
900
1216
|
registerModelsTool(server, modelCache);
|
|
901
1217
|
registerImageTool(server);
|
|
1218
|
+
registerMusicTool(server);
|
|
902
1219
|
registerSearchTool(server);
|
|
903
1220
|
registerExaTool(server);
|
|
904
1221
|
registerMarketsTool(server);
|
|
905
1222
|
registerDexTool(server);
|
|
1223
|
+
if (process.env.ZHIPU_API_KEY) {
|
|
1224
|
+
registerGLMVisionTool(server);
|
|
1225
|
+
}
|
|
906
1226
|
server.registerResource(
|
|
907
1227
|
"wallet",
|
|
908
1228
|
"blockrun://wallet",
|
|
@@ -939,7 +1259,7 @@ function initializeMcpServer(server) {
|
|
|
939
1259
|
}
|
|
940
1260
|
|
|
941
1261
|
// src/index.ts
|
|
942
|
-
var VERSION = "0.6.
|
|
1262
|
+
var VERSION = "0.6.8";
|
|
943
1263
|
async function checkForUpdate() {
|
|
944
1264
|
try {
|
|
945
1265
|
const resp = await fetch("https://registry.npmjs.org/@blockrun/mcp/latest", {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockrun/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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",
|