@blockrun/mcp 0.7.2 → 0.8.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.
Files changed (2) hide show
  1. package/dist/index.js +177 -38
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -597,15 +597,17 @@ Actions:
597
597
  - edit: Transform an existing image using img2img
598
598
 
599
599
  Generation models:
600
- - zai/cogview-4 ($0.02) \u2014 Zhipu CogView-4, photorealistic, great for detailed scenes
600
+ - zai/cogview-4 ($0.015) \u2014 Zhipu CogView-4, photorealistic, great for detailed scenes
601
+ - xai/grok-imagine-image ($0.02) \u2014 xAI Grok Imagine, stylized, fast
602
+ - xai/grok-imagine-image-pro ($0.07) \u2014 xAI Grok Imagine Pro, higher quality
603
+ - openai/gpt-image-1 ($0.02-0.04) \u2014 GPT native image generation
601
604
  - 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
605
+ - google/nano-banana ($0.05) \u2014 Google image model
604
606
  Edit models: openai/gpt-image-1 (default for edits)`,
605
607
  inputSchema: {
606
608
  prompt: z4.string().describe("Image description or edit instructions"),
607
609
  action: z4.enum(["generate", "edit"]).optional().default("generate").describe("generate: create from text; edit: transform existing image"),
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."),
610
+ model: z4.enum(["zai/cogview-4", "openai/dall-e-3", "together/flux-schnell", "google/nano-banana", "openai/gpt-image-1", "xai/grok-imagine-image", "xai/grok-imagine-image-pro"]).optional().describe("Model to use (default: dall-e-3 for generate, gpt-image-1 for edit). xai/grok-imagine-image is stylized and fast; xai/grok-imagine-image-pro is higher quality."),
609
611
  image: z4.string().optional().describe("Source image for edit action: base64-encoded image or URL"),
610
612
  size: z4.enum(["1024x1024", "1792x1024", "1024x1792"]).optional().default("1024x1024"),
611
613
  quality: z4.enum(["standard", "hd"]).optional().default("standard")
@@ -804,8 +806,144 @@ async function fetchWithTimeout(url, options, timeoutMs) {
804
806
  }
805
807
  }
806
808
 
807
- // src/tools/search.ts
809
+ // src/tools/video.ts
808
810
  import { z as z6 } from "zod";
811
+ import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
812
+ import {
813
+ createPaymentPayload as createPaymentPayload2,
814
+ parsePaymentRequired as parsePaymentRequired2,
815
+ extractPaymentDetails as extractPaymentDetails2
816
+ } from "@blockrun/llm";
817
+ var BLOCKRUN_API2 = "https://blockrun.ai/api";
818
+ var VIDEO_TIMEOUT = 3e5;
819
+ function registerVideoTool(server) {
820
+ server.registerTool(
821
+ "blockrun_video",
822
+ {
823
+ description: `Generate short AI videos via BlockRun x402.
824
+
825
+ Uses xAI's Grok Imagine Video to turn a text prompt (and optional seed image) into an 8-second MP4 clip. The call blocks until the video is ready (30-120s typical).
826
+
827
+ Model: xai/grok-imagine-video ($0.05/sec, 8s default -> $0.42/clip)
828
+
829
+ Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GCS so URLs don't expire).`,
830
+ inputSchema: {
831
+ prompt: z6.string().describe("Text description of the video to generate. E.g. 'a red apple slowly spinning on a wooden table', 'a hummingbird hovering near a red flower, ultra slow motion'"),
832
+ image_url: z6.string().url().optional().describe("Optional seed image URL for image-to-video generation"),
833
+ duration_seconds: z6.number().int().min(1).max(60).optional().describe("Duration to bill for (defaults to the model's 8s default). Must not exceed max_duration_seconds."),
834
+ model: z6.enum(["xai/grok-imagine-video"]).optional().default("xai/grok-imagine-video").describe("Video model to use")
835
+ }
836
+ },
837
+ async ({ prompt, image_url, duration_seconds, model }) => {
838
+ try {
839
+ const privateKey = getOrCreateWalletKey();
840
+ const account = privateKeyToAccount2(privateKey);
841
+ const url = `${BLOCKRUN_API2}/v1/videos/generations`;
842
+ const body = { model, prompt };
843
+ if (image_url) body.image_url = image_url;
844
+ if (duration_seconds !== void 0) body.duration_seconds = duration_seconds;
845
+ const resp402 = await fetchWithTimeout2(url, {
846
+ method: "POST",
847
+ headers: { "Content-Type": "application/json" },
848
+ body: JSON.stringify(body)
849
+ }, 15e3);
850
+ if (resp402.status !== 402) {
851
+ const data2 = await resp402.json();
852
+ throw new Error(`Expected 402, got ${resp402.status}: ${JSON.stringify(data2)}`);
853
+ }
854
+ const prHeader = resp402.headers.get("payment-required") || resp402.headers.get("PAYMENT-REQUIRED");
855
+ if (!prHeader) throw new Error("No PAYMENT-REQUIRED header in 402 response");
856
+ const paymentRequired = parsePaymentRequired2(prHeader);
857
+ const details = extractPaymentDetails2(paymentRequired);
858
+ const paymentPayload = await createPaymentPayload2(
859
+ privateKey,
860
+ account.address,
861
+ details.recipient,
862
+ details.amount,
863
+ details.network || "eip155:8453",
864
+ {
865
+ resourceUrl: details.resource?.url || url,
866
+ resourceDescription: details.resource?.description || "BlockRun Video Generation",
867
+ maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
868
+ extra: details.extra
869
+ }
870
+ );
871
+ const resp = await fetchWithTimeout2(url, {
872
+ method: "POST",
873
+ headers: {
874
+ "Content-Type": "application/json",
875
+ "PAYMENT-SIGNATURE": paymentPayload
876
+ },
877
+ body: JSON.stringify(body)
878
+ }, VIDEO_TIMEOUT);
879
+ if (resp.status === 402) {
880
+ throw new Error("Payment rejected. Check your wallet balance.");
881
+ }
882
+ if (!resp.ok) {
883
+ const errBody = await resp.json().catch(() => ({ error: "Request failed" }));
884
+ throw new Error(`API error ${resp.status}: ${JSON.stringify(errBody)}`);
885
+ }
886
+ const data = await resp.json();
887
+ const clip = data.data?.[0];
888
+ if (!clip?.url) throw new Error("No video URL in response");
889
+ const txHash = resp.headers.get("X-Payment-Receipt") || resp.headers.get("x-payment-receipt");
890
+ const lines = [
891
+ `\u{1F3AC} Video ready!`,
892
+ `URL: ${clip.url}`,
893
+ `Duration: ${clip.duration_seconds ? `${clip.duration_seconds}s` : "8s"}`,
894
+ `Model: ${data.model || model}`,
895
+ ...clip.backed_up ? [`Backed up to BlockRun storage (URL is permanent)`] : clip.source_url ? [`Source URL: ${clip.source_url}`] : [],
896
+ ...clip.request_id ? [`Request ID: ${clip.request_id}`] : [],
897
+ ...txHash ? [`Tx: ${txHash}`] : []
898
+ ];
899
+ return {
900
+ content: [{ type: "text", text: lines.join("\n") }],
901
+ structuredContent: {
902
+ url: clip.url,
903
+ ...clip.source_url ? { source_url: clip.source_url } : {},
904
+ duration_seconds: clip.duration_seconds,
905
+ model: data.model || model,
906
+ ...clip.request_id ? { request_id: clip.request_id } : {},
907
+ ...clip.backed_up !== void 0 ? { backed_up: clip.backed_up } : {},
908
+ ...txHash ? { txHash } : {}
909
+ }
910
+ };
911
+ } catch (err) {
912
+ const errMsg = err instanceof Error ? err.message : String(err);
913
+ if (errMsg.includes("balance") || errMsg.includes("payment") || errMsg.includes("402") || errMsg.includes("rejected")) {
914
+ return {
915
+ content: [{ type: "text", text: `Video generation requires payment. Run blockrun_wallet with action: "setup" for funding instructions.
916
+ Error: ${errMsg}` }],
917
+ isError: true
918
+ };
919
+ }
920
+ if (errMsg.includes("abort") || errMsg.includes("timeout") || errMsg.includes("Timeout") || errMsg.includes("timed out")) {
921
+ return {
922
+ content: [{ type: "text", text: `Video generation timed out. xAI's async job didn't complete in time \u2014 please try again.
923
+ Error: ${errMsg}` }],
924
+ isError: true
925
+ };
926
+ }
927
+ return {
928
+ content: [{ type: "text", text: formatError(`Video generation failed: ${errMsg}`) }],
929
+ isError: true
930
+ };
931
+ }
932
+ }
933
+ );
934
+ }
935
+ async function fetchWithTimeout2(url, options, timeoutMs) {
936
+ const controller = new AbortController();
937
+ const id = setTimeout(() => controller.abort(), timeoutMs);
938
+ try {
939
+ return await fetch(url, { ...options, signal: controller.signal });
940
+ } finally {
941
+ clearTimeout(id);
942
+ }
943
+ }
944
+
945
+ // src/tools/search.ts
946
+ import { z as z7 } from "zod";
809
947
  function registerSearchTool(server) {
810
948
  server.registerTool(
811
949
  "blockrun_search",
@@ -817,11 +955,11 @@ Pricing: ~$0.01/search
817
955
 
818
956
  Returns a summary with cited sources.`,
819
957
  inputSchema: {
820
- query: z6.string().describe("Search query"),
821
- sources: z6.array(z6.enum(["web", "x", "news"])).optional().describe("Sources to search (default: web + x + news)"),
822
- max_results: z6.number().optional().default(10).describe("Max results per source (1-20)"),
823
- from_date: z6.string().optional().describe("Start date filter (YYYY-MM-DD)"),
824
- to_date: z6.string().optional().describe("End date filter (YYYY-MM-DD)")
958
+ query: z7.string().describe("Search query"),
959
+ sources: z7.array(z7.enum(["web", "x", "news"])).optional().describe("Sources to search (default: web + x + news)"),
960
+ max_results: z7.number().optional().default(10).describe("Max results per source (1-20)"),
961
+ from_date: z7.string().optional().describe("Start date filter (YYYY-MM-DD)"),
962
+ to_date: z7.string().optional().describe("End date filter (YYYY-MM-DD)")
825
963
  }
826
964
  },
827
965
  async ({ query, sources, max_results, from_date, to_date }) => {
@@ -849,7 +987,7 @@ Returns a summary with cited sources.`,
849
987
  }
850
988
 
851
989
  // src/tools/exa.ts
852
- import { z as z7 } from "zod";
990
+ import { z as z8 } from "zod";
853
991
  function registerExaTool(server) {
854
992
  server.registerTool(
855
993
  "blockrun_exa",
@@ -862,14 +1000,14 @@ Actions:
862
1000
  - contents: Fetch full Markdown text from URLs, ready for LLM context ($0.002/URL)
863
1001
  - similar: Find pages semantically similar to a given URL ($0.01/call)`,
864
1002
  inputSchema: {
865
- action: z7.enum(["search", "answer", "contents", "similar"]).describe("Action to perform"),
866
- query: z7.string().optional().describe("Natural language query (for search/answer)"),
867
- url: z7.string().optional().describe("Reference URL to find similar pages (for similar action)"),
868
- urls: z7.array(z7.string()).optional().describe("URLs to fetch content from (for contents action, up to 100)"),
869
- num_results: z7.number().optional().describe("Number of results to return (default: 10)"),
870
- category: z7.string().optional().describe("Category filter: 'news', 'research paper', 'company', 'tweet', 'github', 'pdf'"),
871
- include_domains: z7.array(z7.string()).optional().describe("Only search within these domains"),
872
- exclude_domains: z7.array(z7.string()).optional().describe("Exclude these domains from results")
1003
+ action: z8.enum(["search", "answer", "contents", "similar"]).describe("Action to perform"),
1004
+ query: z8.string().optional().describe("Natural language query (for search/answer)"),
1005
+ url: z8.string().optional().describe("Reference URL to find similar pages (for similar action)"),
1006
+ urls: z8.array(z8.string()).optional().describe("URLs to fetch content from (for contents action, up to 100)"),
1007
+ num_results: z8.number().optional().describe("Number of results to return (default: 10)"),
1008
+ category: z8.string().optional().describe("Category filter: 'news', 'research paper', 'company', 'tweet', 'github', 'pdf'"),
1009
+ include_domains: z8.array(z8.string()).optional().describe("Only search within these domains"),
1010
+ exclude_domains: z8.array(z8.string()).optional().describe("Exclude these domains from results")
873
1011
  }
874
1012
  },
875
1013
  async ({ action, query, url, urls, num_results, category, include_domains, exclude_domains }) => {
@@ -922,7 +1060,7 @@ Actions:
922
1060
  }
923
1061
 
924
1062
  // src/tools/markets.ts
925
- import { z as z8 } from "zod";
1063
+ import { z as z9 } from "zod";
926
1064
  function registerMarketsTool(server) {
927
1065
  server.registerTool(
928
1066
  "blockrun_markets",
@@ -937,9 +1075,9 @@ Example paths:
937
1075
 
938
1076
  $0.001/call.`,
939
1077
  inputSchema: {
940
- path: z8.string().describe("Endpoint path, e.g. 'polymarket/events', 'kalshi/markets/KXBTC-25MAR14'"),
941
- params: z8.record(z8.string(), z8.string()).optional().describe("Query parameters for GET requests"),
942
- body: z8.any().optional().describe("JSON body for POST queries (triggers pmQuery)")
1078
+ path: z9.string().describe("Endpoint path, e.g. 'polymarket/events', 'kalshi/markets/KXBTC-25MAR14'"),
1079
+ params: z9.record(z9.string(), z9.string()).optional().describe("Query parameters for GET requests"),
1080
+ body: z9.any().optional().describe("JSON body for POST queries (triggers pmQuery)")
943
1081
  }
944
1082
  },
945
1083
  async ({ path: path3, params, body }) => {
@@ -962,7 +1100,7 @@ $0.001/call.`,
962
1100
  }
963
1101
 
964
1102
  // src/tools/dex.ts
965
- import { z as z9 } from "zod";
1103
+ import { z as z10 } from "zod";
966
1104
  function registerDexTool(server) {
967
1105
  server.registerTool(
968
1106
  "blockrun_dex",
@@ -979,10 +1117,10 @@ Examples:
979
1117
  blockrun_dex({ token: "So11...xxx" }) -> Get specific token data
980
1118
  blockrun_dex({ symbol: "PEPE" }) -> Search by symbol`,
981
1119
  inputSchema: {
982
- query: z9.string().optional().describe("Search query (token name, symbol, or address)"),
983
- token: z9.string().optional().describe("Token address for direct lookup"),
984
- symbol: z9.string().optional().describe("Token symbol to search"),
985
- chain: z9.string().optional().describe("Filter by chain (ethereum, solana, base, etc.)")
1120
+ query: z10.string().optional().describe("Search query (token name, symbol, or address)"),
1121
+ token: z10.string().optional().describe("Token address for direct lookup"),
1122
+ symbol: z10.string().optional().describe("Token symbol to search"),
1123
+ chain: z10.string().optional().describe("Filter by chain (ethereum, solana, base, etc.)")
986
1124
  }
987
1125
  },
988
1126
  async ({ query, token, symbol, chain }) => {
@@ -1043,7 +1181,7 @@ ${lines.join("\n\n")}` }],
1043
1181
  }
1044
1182
 
1045
1183
  // src/tools/modal.ts
1046
- import { z as z10 } from "zod";
1184
+ import { z as z11 } from "zod";
1047
1185
  var MODAL_GPU_TYPES = ["T4", "L4", "A10G", "A100", "A100-80GB", "H100"];
1048
1186
  function registerModalTool(server) {
1049
1187
  server.registerTool(
@@ -1063,17 +1201,17 @@ Pricing:
1063
1201
  - create: $0.01
1064
1202
  - exec/status/terminate: $0.001 each`,
1065
1203
  inputSchema: {
1066
- action: z10.enum(["create", "exec", "status", "terminate"]).describe(
1204
+ action: z11.enum(["create", "exec", "status", "terminate"]).describe(
1067
1205
  "Sandbox action to perform"
1068
1206
  ),
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")
1207
+ sandbox_id: z11.string().optional().describe("Sandbox ID returned by a previous create"),
1208
+ command: z11.array(z11.string()).optional().describe('Command array for exec, for example ["python", "-c", "print(2+2)"]'),
1209
+ image: z11.string().optional().describe("Container image for create (default: python:3.11)"),
1210
+ timeout: z11.number().optional().describe("Timeout in seconds for create or exec"),
1211
+ cpu: z11.number().optional().describe("CPU cores for create"),
1212
+ memory: z11.number().optional().describe("Memory in MB for create"),
1213
+ gpu: z11.enum(MODAL_GPU_TYPES).optional().describe("Optional GPU type for create"),
1214
+ setup_commands: z11.array(z11.string()).optional().describe("Shell commands to run during sandbox setup")
1077
1215
  }
1078
1216
  },
1079
1217
  async ({ action, sandbox_id, command, image, timeout, cpu, memory, gpu, setup_commands }) => {
@@ -1138,6 +1276,7 @@ function initializeMcpServer(server) {
1138
1276
  registerModelsTool(server, modelCache);
1139
1277
  registerImageTool(server);
1140
1278
  registerMusicTool(server);
1279
+ registerVideoTool(server);
1141
1280
  registerSearchTool(server);
1142
1281
  registerExaTool(server);
1143
1282
  registerMarketsTool(server);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/mcp",
3
- "version": "0.7.2",
3
+ "version": "0.8.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",