@blockrun/mcp 0.7.2 → 0.9.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 +419 -42
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
8
8
  import {
9
9
  LLMClient,
10
10
  ImageClient,
11
+ PriceClient,
11
12
  getOrCreateWallet,
12
13
  getPaymentLinks,
13
14
  formatWalletCreatedMessage,
@@ -15,6 +16,7 @@ import {
15
16
  } from "@blockrun/llm";
16
17
  var _client = null;
17
18
  var _imageClient = null;
19
+ var _priceClient = null;
18
20
  var _walletInfo = null;
19
21
  function ensureWallet() {
20
22
  if (!_walletInfo) {
@@ -43,6 +45,13 @@ function getImageClient() {
43
45
  }
44
46
  return _imageClient;
45
47
  }
48
+ function getPriceClient() {
49
+ if (!_priceClient) {
50
+ const privateKey = getOrCreateWalletKey();
51
+ _priceClient = new PriceClient({ privateKey });
52
+ }
53
+ return _priceClient;
54
+ }
46
55
  function getWalletInfo() {
47
56
  const info = ensureWallet();
48
57
  const links = getPaymentLinks(info.address);
@@ -103,12 +112,12 @@ var USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
103
112
  var BASE_CHAIN_ID = "8453";
104
113
  var MODEL_TIERS = {
105
114
  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
- 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
- powerful: ["openai/gpt-5.4-pro", "anthropic/claude-opus-4.6", "anthropic/claude-opus-4.5", "openai/o3", "openai/gpt-5.4"],
115
+ balanced: ["openai/gpt-5.4", "anthropic/claude-sonnet-4.6", "google/gemini-2.5-pro", "moonshot/kimi-k2.6", "openai/gpt-5.3", "google/gemini-3.1-pro"],
116
+ powerful: ["openai/gpt-5.4-pro", "anthropic/claude-opus-4.7", "anthropic/claude-opus-4.6", "openai/o3", "openai/gpt-5.4"],
108
117
  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
- reasoning: ["openai/o3", "openai/o1", "openai/o3-mini", "deepseek/deepseek-reasoner", "openai/gpt-5.3-codex"],
118
+ reasoning: ["openai/o3", "openai/o1", "openai/o3-mini", "deepseek/deepseek-reasoner", "moonshot/kimi-k2.6", "openai/gpt-5.3-codex"],
110
119
  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"],
120
+ coding: ["zai/glm-5", "openai/gpt-5.3-codex", "moonshot/kimi-k2.6", "nvidia/qwen3-coder-480b", "nvidia/devstral-2-123b", "anthropic/claude-sonnet-4.6", "openai/gpt-5.4"],
112
121
  glm: ["zai/glm-5", "zai/glm-5-turbo", "nvidia/glm-4.7"]
113
122
  };
114
123
 
@@ -597,15 +606,17 @@ Actions:
597
606
  - edit: Transform an existing image using img2img
598
607
 
599
608
  Generation models:
600
- - zai/cogview-4 ($0.02) \u2014 Zhipu CogView-4, photorealistic, great for detailed scenes
609
+ - zai/cogview-4 ($0.015) \u2014 Zhipu CogView-4, photorealistic, great for detailed scenes
610
+ - xai/grok-imagine-image ($0.02) \u2014 xAI Grok Imagine, stylized, fast
611
+ - xai/grok-imagine-image-pro ($0.07) \u2014 xAI Grok Imagine Pro, higher quality
612
+ - openai/gpt-image-1 ($0.02-0.04) \u2014 GPT native image generation
601
613
  - 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
614
+ - google/nano-banana ($0.05) \u2014 Google image model
604
615
  Edit models: openai/gpt-image-1 (default for edits)`,
605
616
  inputSchema: {
606
617
  prompt: z4.string().describe("Image description or edit instructions"),
607
618
  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."),
619
+ 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
620
  image: z4.string().optional().describe("Source image for edit action: base64-encoded image or URL"),
610
621
  size: z4.enum(["1024x1024", "1792x1024", "1024x1792"]).optional().default("1024x1024"),
611
622
  quality: z4.enum(["standard", "hd"]).optional().default("standard")
@@ -804,8 +815,144 @@ async function fetchWithTimeout(url, options, timeoutMs) {
804
815
  }
805
816
  }
806
817
 
807
- // src/tools/search.ts
818
+ // src/tools/video.ts
808
819
  import { z as z6 } from "zod";
820
+ import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
821
+ import {
822
+ createPaymentPayload as createPaymentPayload2,
823
+ parsePaymentRequired as parsePaymentRequired2,
824
+ extractPaymentDetails as extractPaymentDetails2
825
+ } from "@blockrun/llm";
826
+ var BLOCKRUN_API2 = "https://blockrun.ai/api";
827
+ var VIDEO_TIMEOUT = 3e5;
828
+ function registerVideoTool(server) {
829
+ server.registerTool(
830
+ "blockrun_video",
831
+ {
832
+ description: `Generate short AI videos via BlockRun x402.
833
+
834
+ 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).
835
+
836
+ Model: xai/grok-imagine-video ($0.05/sec, 8s default -> $0.42/clip)
837
+
838
+ Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GCS so URLs don't expire).`,
839
+ inputSchema: {
840
+ 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'"),
841
+ image_url: z6.string().url().optional().describe("Optional seed image URL for image-to-video generation"),
842
+ 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."),
843
+ model: z6.enum(["xai/grok-imagine-video"]).optional().default("xai/grok-imagine-video").describe("Video model to use")
844
+ }
845
+ },
846
+ async ({ prompt, image_url, duration_seconds, model }) => {
847
+ try {
848
+ const privateKey = getOrCreateWalletKey();
849
+ const account = privateKeyToAccount2(privateKey);
850
+ const url = `${BLOCKRUN_API2}/v1/videos/generations`;
851
+ const body = { model, prompt };
852
+ if (image_url) body.image_url = image_url;
853
+ if (duration_seconds !== void 0) body.duration_seconds = duration_seconds;
854
+ const resp402 = await fetchWithTimeout2(url, {
855
+ method: "POST",
856
+ headers: { "Content-Type": "application/json" },
857
+ body: JSON.stringify(body)
858
+ }, 15e3);
859
+ if (resp402.status !== 402) {
860
+ const data2 = await resp402.json();
861
+ throw new Error(`Expected 402, got ${resp402.status}: ${JSON.stringify(data2)}`);
862
+ }
863
+ const prHeader = resp402.headers.get("payment-required") || resp402.headers.get("PAYMENT-REQUIRED");
864
+ if (!prHeader) throw new Error("No PAYMENT-REQUIRED header in 402 response");
865
+ const paymentRequired = parsePaymentRequired2(prHeader);
866
+ const details = extractPaymentDetails2(paymentRequired);
867
+ const paymentPayload = await createPaymentPayload2(
868
+ privateKey,
869
+ account.address,
870
+ details.recipient,
871
+ details.amount,
872
+ details.network || "eip155:8453",
873
+ {
874
+ resourceUrl: details.resource?.url || url,
875
+ resourceDescription: details.resource?.description || "BlockRun Video Generation",
876
+ maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
877
+ extra: details.extra
878
+ }
879
+ );
880
+ const resp = await fetchWithTimeout2(url, {
881
+ method: "POST",
882
+ headers: {
883
+ "Content-Type": "application/json",
884
+ "PAYMENT-SIGNATURE": paymentPayload
885
+ },
886
+ body: JSON.stringify(body)
887
+ }, VIDEO_TIMEOUT);
888
+ if (resp.status === 402) {
889
+ throw new Error("Payment rejected. Check your wallet balance.");
890
+ }
891
+ if (!resp.ok) {
892
+ const errBody = await resp.json().catch(() => ({ error: "Request failed" }));
893
+ throw new Error(`API error ${resp.status}: ${JSON.stringify(errBody)}`);
894
+ }
895
+ const data = await resp.json();
896
+ const clip = data.data?.[0];
897
+ if (!clip?.url) throw new Error("No video URL in response");
898
+ const txHash = resp.headers.get("X-Payment-Receipt") || resp.headers.get("x-payment-receipt");
899
+ const lines = [
900
+ `\u{1F3AC} Video ready!`,
901
+ `URL: ${clip.url}`,
902
+ `Duration: ${clip.duration_seconds ? `${clip.duration_seconds}s` : "8s"}`,
903
+ `Model: ${data.model || model}`,
904
+ ...clip.backed_up ? [`Backed up to BlockRun storage (URL is permanent)`] : clip.source_url ? [`Source URL: ${clip.source_url}`] : [],
905
+ ...clip.request_id ? [`Request ID: ${clip.request_id}`] : [],
906
+ ...txHash ? [`Tx: ${txHash}`] : []
907
+ ];
908
+ return {
909
+ content: [{ type: "text", text: lines.join("\n") }],
910
+ structuredContent: {
911
+ url: clip.url,
912
+ ...clip.source_url ? { source_url: clip.source_url } : {},
913
+ duration_seconds: clip.duration_seconds,
914
+ model: data.model || model,
915
+ ...clip.request_id ? { request_id: clip.request_id } : {},
916
+ ...clip.backed_up !== void 0 ? { backed_up: clip.backed_up } : {},
917
+ ...txHash ? { txHash } : {}
918
+ }
919
+ };
920
+ } catch (err) {
921
+ const errMsg = err instanceof Error ? err.message : String(err);
922
+ if (errMsg.includes("balance") || errMsg.includes("payment") || errMsg.includes("402") || errMsg.includes("rejected")) {
923
+ return {
924
+ content: [{ type: "text", text: `Video generation requires payment. Run blockrun_wallet with action: "setup" for funding instructions.
925
+ Error: ${errMsg}` }],
926
+ isError: true
927
+ };
928
+ }
929
+ if (errMsg.includes("abort") || errMsg.includes("timeout") || errMsg.includes("Timeout") || errMsg.includes("timed out")) {
930
+ return {
931
+ content: [{ type: "text", text: `Video generation timed out. xAI's async job didn't complete in time \u2014 please try again.
932
+ Error: ${errMsg}` }],
933
+ isError: true
934
+ };
935
+ }
936
+ return {
937
+ content: [{ type: "text", text: formatError(`Video generation failed: ${errMsg}`) }],
938
+ isError: true
939
+ };
940
+ }
941
+ }
942
+ );
943
+ }
944
+ async function fetchWithTimeout2(url, options, timeoutMs) {
945
+ const controller = new AbortController();
946
+ const id = setTimeout(() => controller.abort(), timeoutMs);
947
+ try {
948
+ return await fetch(url, { ...options, signal: controller.signal });
949
+ } finally {
950
+ clearTimeout(id);
951
+ }
952
+ }
953
+
954
+ // src/tools/search.ts
955
+ import { z as z7 } from "zod";
809
956
  function registerSearchTool(server) {
810
957
  server.registerTool(
811
958
  "blockrun_search",
@@ -817,11 +964,11 @@ Pricing: ~$0.01/search
817
964
 
818
965
  Returns a summary with cited sources.`,
819
966
  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)")
967
+ query: z7.string().describe("Search query"),
968
+ sources: z7.array(z7.enum(["web", "x", "news"])).optional().describe("Sources to search (default: web + x + news)"),
969
+ max_results: z7.number().optional().default(10).describe("Max results per source (1-20)"),
970
+ from_date: z7.string().optional().describe("Start date filter (YYYY-MM-DD)"),
971
+ to_date: z7.string().optional().describe("End date filter (YYYY-MM-DD)")
825
972
  }
826
973
  },
827
974
  async ({ query, sources, max_results, from_date, to_date }) => {
@@ -849,7 +996,7 @@ Returns a summary with cited sources.`,
849
996
  }
850
997
 
851
998
  // src/tools/exa.ts
852
- import { z as z7 } from "zod";
999
+ import { z as z8 } from "zod";
853
1000
  function registerExaTool(server) {
854
1001
  server.registerTool(
855
1002
  "blockrun_exa",
@@ -862,14 +1009,14 @@ Actions:
862
1009
  - contents: Fetch full Markdown text from URLs, ready for LLM context ($0.002/URL)
863
1010
  - similar: Find pages semantically similar to a given URL ($0.01/call)`,
864
1011
  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")
1012
+ action: z8.enum(["search", "answer", "contents", "similar"]).describe("Action to perform"),
1013
+ query: z8.string().optional().describe("Natural language query (for search/answer)"),
1014
+ url: z8.string().optional().describe("Reference URL to find similar pages (for similar action)"),
1015
+ urls: z8.array(z8.string()).optional().describe("URLs to fetch content from (for contents action, up to 100)"),
1016
+ num_results: z8.number().optional().describe("Number of results to return (default: 10)"),
1017
+ category: z8.string().optional().describe("Category filter: 'news', 'research paper', 'company', 'tweet', 'github', 'pdf'"),
1018
+ include_domains: z8.array(z8.string()).optional().describe("Only search within these domains"),
1019
+ exclude_domains: z8.array(z8.string()).optional().describe("Exclude these domains from results")
873
1020
  }
874
1021
  },
875
1022
  async ({ action, query, url, urls, num_results, category, include_domains, exclude_domains }) => {
@@ -922,7 +1069,7 @@ Actions:
922
1069
  }
923
1070
 
924
1071
  // src/tools/markets.ts
925
- import { z as z8 } from "zod";
1072
+ import { z as z9 } from "zod";
926
1073
  function registerMarketsTool(server) {
927
1074
  server.registerTool(
928
1075
  "blockrun_markets",
@@ -937,9 +1084,9 @@ Example paths:
937
1084
 
938
1085
  $0.001/call.`,
939
1086
  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)")
1087
+ path: z9.string().describe("Endpoint path, e.g. 'polymarket/events', 'kalshi/markets/KXBTC-25MAR14'"),
1088
+ params: z9.record(z9.string(), z9.string()).optional().describe("Query parameters for GET requests"),
1089
+ body: z9.any().optional().describe("JSON body for POST queries (triggers pmQuery)")
943
1090
  }
944
1091
  },
945
1092
  async ({ path: path3, params, body }) => {
@@ -961,8 +1108,235 @@ $0.001/call.`,
961
1108
  );
962
1109
  }
963
1110
 
1111
+ // src/tools/price.ts
1112
+ import { z as z10 } from "zod";
1113
+ var CATEGORY = z10.enum(["crypto", "fx", "commodity", "usstock", "stocks"]);
1114
+ var MARKET = z10.enum([
1115
+ "us",
1116
+ "hk",
1117
+ "jp",
1118
+ "kr",
1119
+ "gb",
1120
+ "de",
1121
+ "fr",
1122
+ "nl",
1123
+ "ie",
1124
+ "lu",
1125
+ "cn",
1126
+ "ca"
1127
+ ]);
1128
+ var RESOLUTION = z10.enum(["1", "5", "15", "60", "240", "D", "W", "M"]);
1129
+ var SESSION = z10.enum(["pre", "post", "on"]);
1130
+ var ACTION = z10.enum(["price", "history", "list"]);
1131
+ function registerPriceTool(server) {
1132
+ server.registerTool(
1133
+ "blockrun_price",
1134
+ {
1135
+ description: `Realtime quotes and OHLC history for crypto, FX, commodities and 12 global stock markets (Pyth-backed).
1136
+
1137
+ - action="price" \u2014 realtime quote for a symbol
1138
+ - action="history" \u2014 OHLC bars between from/to (unix seconds)
1139
+ - action="list" \u2014 discovery: list available symbols (free)
1140
+
1141
+ Pricing:
1142
+ - crypto / fx / commodity: FREE across price, history and list
1143
+ - stocks / usstock: $0.001 per price or history call (list free)
1144
+
1145
+ Stocks markets: us, hk, jp, kr, gb, de, fr, nl, ie, lu, cn, ca (required when category="stocks").
1146
+
1147
+ Examples:
1148
+ - { action: "price", category: "crypto", symbol: "BTC-USD" }
1149
+ - { action: "price", category: "stocks", symbol: "AAPL", market: "us" }
1150
+ - { action: "history", category: "crypto", symbol: "ETH-USD", resolution: "D", from: 1700000000, to: 1710000000 }
1151
+ - { action: "list", category: "crypto", query: "sol" }`,
1152
+ inputSchema: {
1153
+ action: ACTION.describe("Which endpoint to hit: price, history, or list."),
1154
+ category: CATEGORY.describe("Market category."),
1155
+ symbol: z10.string().optional().describe("Ticker (required for price+history). e.g. BTC-USD, AAPL, EUR-USD."),
1156
+ market: MARKET.optional().describe("Stock market code \u2014 required when category='stocks'."),
1157
+ session: SESSION.optional().describe("Equity session hint (pre/post/on); ignored for non-equity."),
1158
+ resolution: RESOLUTION.optional().describe("Bar resolution for history (default D)."),
1159
+ from: z10.number().optional().describe("History window start (unix seconds)."),
1160
+ to: z10.number().optional().describe("History window end (unix seconds)."),
1161
+ query: z10.string().optional().describe("Free-text filter for list."),
1162
+ limit: z10.number().optional().describe("Max items for list (default 100, max 2000).")
1163
+ }
1164
+ },
1165
+ async ({ action, category, symbol, market, session, resolution, from, to, query, limit }) => {
1166
+ try {
1167
+ const priceClient = getPriceClient();
1168
+ if (action === "price") {
1169
+ if (!symbol) throw new Error("symbol is required for action='price'");
1170
+ const result2 = await priceClient.price(category, symbol, {
1171
+ market,
1172
+ session
1173
+ });
1174
+ return {
1175
+ content: [{ type: "text", text: JSON.stringify(result2, null, 2) }],
1176
+ structuredContent: result2
1177
+ };
1178
+ }
1179
+ if (action === "history") {
1180
+ if (!symbol) throw new Error("symbol is required for action='history'");
1181
+ if (!from) throw new Error("from (unix seconds) is required for action='history'");
1182
+ const result2 = await priceClient.history(category, symbol, {
1183
+ market,
1184
+ session,
1185
+ resolution: resolution ?? "D",
1186
+ from,
1187
+ to
1188
+ });
1189
+ return {
1190
+ content: [{ type: "text", text: JSON.stringify(result2, null, 2) }],
1191
+ structuredContent: result2
1192
+ };
1193
+ }
1194
+ const result = await priceClient.listSymbols(category, {
1195
+ market,
1196
+ query,
1197
+ limit
1198
+ });
1199
+ return {
1200
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
1201
+ structuredContent: result
1202
+ };
1203
+ } catch (err) {
1204
+ const msg = err instanceof Error ? err.message : String(err);
1205
+ return {
1206
+ content: [{ type: "text", text: formatError(msg) }],
1207
+ isError: true
1208
+ };
1209
+ }
1210
+ }
1211
+ );
1212
+ }
1213
+
1214
+ // src/tools/twitter.ts
1215
+ import { z as z11 } from "zod";
1216
+ var ACTION2 = z11.enum([
1217
+ "user_lookup",
1218
+ "user_info",
1219
+ "followers",
1220
+ "followings",
1221
+ "verified_followers",
1222
+ "user_tweets",
1223
+ "user_mentions",
1224
+ "tweet_lookup",
1225
+ "tweet_replies",
1226
+ "tweet_thread",
1227
+ "search"
1228
+ ]);
1229
+ function registerTwitterTool(server) {
1230
+ server.registerTool(
1231
+ "blockrun_x",
1232
+ {
1233
+ description: `Structured X/Twitter data via AttentionVC partner API.
1234
+
1235
+ Actions:
1236
+ - user_lookup (usernames: string | string[])
1237
+ - user_info (username)
1238
+ - followers (username, cursor?)
1239
+ - followings (username, cursor?)
1240
+ - verified_followers (user_id, cursor?)
1241
+ - user_tweets (username, includeReplies?, cursor?)
1242
+ - user_mentions (username, sinceTime?, untilTime?, cursor?)
1243
+ - tweet_lookup (tweet_ids: string | string[])
1244
+ - tweet_replies (tweet_id, cursor?, queryType?)
1245
+ - tweet_thread (tweet_id, cursor?)
1246
+ - search (query, queryType?, cursor?)
1247
+
1248
+ Paid per request via x402; prices scale with the endpoint (e.g. user_lookup ~ $0.02, followers ~ $0.05/page).`,
1249
+ inputSchema: {
1250
+ action: ACTION2,
1251
+ usernames: z11.union([z11.string(), z11.array(z11.string())]).optional(),
1252
+ username: z11.string().optional(),
1253
+ user_id: z11.string().optional(),
1254
+ tweet_id: z11.string().optional(),
1255
+ tweet_ids: z11.union([z11.string(), z11.array(z11.string())]).optional(),
1256
+ query: z11.string().optional(),
1257
+ queryType: z11.enum(["Latest", "Top", "Default"]).optional(),
1258
+ cursor: z11.string().optional(),
1259
+ sinceTime: z11.string().optional(),
1260
+ untilTime: z11.string().optional(),
1261
+ includeReplies: z11.boolean().optional()
1262
+ }
1263
+ },
1264
+ async (args) => {
1265
+ try {
1266
+ const llm = getClient();
1267
+ const a = args.action;
1268
+ const require2 = (value, name) => {
1269
+ if (value === void 0 || value === null || value === "") {
1270
+ throw new Error(`${name} is required for action='${a}'`);
1271
+ }
1272
+ return value;
1273
+ };
1274
+ let result;
1275
+ switch (a) {
1276
+ case "user_lookup":
1277
+ result = await llm.xUserLookup(require2(args.usernames, "usernames"));
1278
+ break;
1279
+ case "user_info":
1280
+ result = await llm.xUserInfo(require2(args.username, "username"));
1281
+ break;
1282
+ case "followers":
1283
+ result = await llm.xFollowers(require2(args.username, "username"), args.cursor);
1284
+ break;
1285
+ case "followings":
1286
+ result = await llm.xFollowings(require2(args.username, "username"), args.cursor);
1287
+ break;
1288
+ case "verified_followers":
1289
+ result = await llm.xVerifiedFollowers(require2(args.user_id, "user_id"), args.cursor);
1290
+ break;
1291
+ case "user_tweets":
1292
+ result = await llm.xUserTweets(
1293
+ require2(args.username, "username"),
1294
+ args.includeReplies,
1295
+ args.cursor
1296
+ );
1297
+ break;
1298
+ case "user_mentions":
1299
+ result = await llm.xUserMentions(
1300
+ require2(args.username, "username"),
1301
+ args.sinceTime,
1302
+ args.untilTime,
1303
+ args.cursor
1304
+ );
1305
+ break;
1306
+ case "tweet_lookup":
1307
+ result = await llm.xTweetLookup(require2(args.tweet_ids, "tweet_ids"));
1308
+ break;
1309
+ case "tweet_replies":
1310
+ result = await llm.xTweetReplies(
1311
+ require2(args.tweet_id, "tweet_id"),
1312
+ args.queryType,
1313
+ args.cursor
1314
+ );
1315
+ break;
1316
+ case "tweet_thread":
1317
+ result = await llm.xTweetThread(require2(args.tweet_id, "tweet_id"), args.cursor);
1318
+ break;
1319
+ case "search":
1320
+ result = await llm.xSearch(require2(args.query, "query"), args.queryType, args.cursor);
1321
+ break;
1322
+ }
1323
+ return {
1324
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
1325
+ structuredContent: result
1326
+ };
1327
+ } catch (err) {
1328
+ const msg = err instanceof Error ? err.message : String(err);
1329
+ return {
1330
+ content: [{ type: "text", text: formatError(msg) }],
1331
+ isError: true
1332
+ };
1333
+ }
1334
+ }
1335
+ );
1336
+ }
1337
+
964
1338
  // src/tools/dex.ts
965
- import { z as z9 } from "zod";
1339
+ import { z as z12 } from "zod";
966
1340
  function registerDexTool(server) {
967
1341
  server.registerTool(
968
1342
  "blockrun_dex",
@@ -979,10 +1353,10 @@ Examples:
979
1353
  blockrun_dex({ token: "So11...xxx" }) -> Get specific token data
980
1354
  blockrun_dex({ symbol: "PEPE" }) -> Search by symbol`,
981
1355
  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.)")
1356
+ query: z12.string().optional().describe("Search query (token name, symbol, or address)"),
1357
+ token: z12.string().optional().describe("Token address for direct lookup"),
1358
+ symbol: z12.string().optional().describe("Token symbol to search"),
1359
+ chain: z12.string().optional().describe("Filter by chain (ethereum, solana, base, etc.)")
986
1360
  }
987
1361
  },
988
1362
  async ({ query, token, symbol, chain }) => {
@@ -1043,7 +1417,7 @@ ${lines.join("\n\n")}` }],
1043
1417
  }
1044
1418
 
1045
1419
  // src/tools/modal.ts
1046
- import { z as z10 } from "zod";
1420
+ import { z as z13 } from "zod";
1047
1421
  var MODAL_GPU_TYPES = ["T4", "L4", "A10G", "A100", "A100-80GB", "H100"];
1048
1422
  function registerModalTool(server) {
1049
1423
  server.registerTool(
@@ -1063,17 +1437,17 @@ Pricing:
1063
1437
  - create: $0.01
1064
1438
  - exec/status/terminate: $0.001 each`,
1065
1439
  inputSchema: {
1066
- action: z10.enum(["create", "exec", "status", "terminate"]).describe(
1440
+ action: z13.enum(["create", "exec", "status", "terminate"]).describe(
1067
1441
  "Sandbox action to perform"
1068
1442
  ),
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")
1443
+ sandbox_id: z13.string().optional().describe("Sandbox ID returned by a previous create"),
1444
+ command: z13.array(z13.string()).optional().describe('Command array for exec, for example ["python", "-c", "print(2+2)"]'),
1445
+ image: z13.string().optional().describe("Container image for create (default: python:3.11)"),
1446
+ timeout: z13.number().optional().describe("Timeout in seconds for create or exec"),
1447
+ cpu: z13.number().optional().describe("CPU cores for create"),
1448
+ memory: z13.number().optional().describe("Memory in MB for create"),
1449
+ gpu: z13.enum(MODAL_GPU_TYPES).optional().describe("Optional GPU type for create"),
1450
+ setup_commands: z13.array(z13.string()).optional().describe("Shell commands to run during sandbox setup")
1077
1451
  }
1078
1452
  },
1079
1453
  async ({ action, sandbox_id, command, image, timeout, cpu, memory, gpu, setup_commands }) => {
@@ -1138,9 +1512,12 @@ function initializeMcpServer(server) {
1138
1512
  registerModelsTool(server, modelCache);
1139
1513
  registerImageTool(server);
1140
1514
  registerMusicTool(server);
1515
+ registerVideoTool(server);
1141
1516
  registerSearchTool(server);
1142
1517
  registerExaTool(server);
1143
1518
  registerMarketsTool(server);
1519
+ registerPriceTool(server);
1520
+ registerTwitterTool(server);
1144
1521
  registerDexTool(server);
1145
1522
  registerModalTool(server);
1146
1523
  server.registerResource(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/mcp",
3
- "version": "0.7.2",
3
+ "version": "0.9.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",
@@ -44,7 +44,7 @@
44
44
  "url": "https://github.com/blockrunai/blockrun-mcp/issues"
45
45
  },
46
46
  "dependencies": {
47
- "@blockrun/llm": "^1.5.0",
47
+ "@blockrun/llm": "^1.8.0",
48
48
  "@modelcontextprotocol/sdk": "^1.0.0",
49
49
  "open": "^11.0.0",
50
50
  "qrcode": "^1.5.4",