@blockrun/mcp 0.6.7 → 0.6.9

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 (3) hide show
  1. package/README.md +2 -2
  2. package/dist/index.js +182 -26
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  **Real-time data for Claude — markets, research, X/Twitter, crypto. No API keys. Pay per call.**
8
8
 
9
9
  ```bash
10
- claude mcp add blockrun npx @blockrun/mcp
10
+ claude mcp add blockrun npx -y @blockrun/mcp@latest
11
11
  ```
12
12
 
13
13
  Wallet auto-created. Fund with $5 USDC. Ask Claude anything.
@@ -49,7 +49,7 @@ After BlockRun, it can. Each query costs fractions of a cent, billed from a loca
49
49
 
50
50
  **Claude Code (recommended)**
51
51
  ```bash
52
- claude mcp add blockrun npx @blockrun/mcp
52
+ claude mcp add blockrun npx -y @blockrun/mcp@latest
53
53
  ```
54
54
 
55
55
  **Claude Desktop** — add to `claude_desktop_config.json`:
package/dist/index.js CHANGED
@@ -653,8 +653,148 @@ Error: ${errMsg}` }],
653
653
  );
654
654
  }
655
655
 
656
- // src/tools/search.ts
656
+ // src/tools/music.ts
657
657
  import { z as z5 } from "zod";
658
+ import { privateKeyToAccount } from "viem/accounts";
659
+ import {
660
+ createPaymentPayload,
661
+ parsePaymentRequired,
662
+ extractPaymentDetails
663
+ } from "@blockrun/llm";
664
+ var BLOCKRUN_API = "https://blockrun.ai/api";
665
+ var MUSIC_TIMEOUT = 2e5;
666
+ function registerMusicTool(server) {
667
+ server.registerTool(
668
+ "blockrun_music",
669
+ {
670
+ description: `Generate music tracks via BlockRun x402.
671
+
672
+ Generates a full-length ~3 minute MP3 track. Takes 1-3 minutes to complete.
673
+
674
+ Models: minimax/music-2.5+ ($0.1575), minimax/music-2.5 ($0.1575)
675
+
676
+ Returns a time-limited CDN URL \u2014 download immediately if you need to keep the file.`,
677
+ inputSchema: {
678
+ prompt: z5.string().describe("Music style, mood, or description. E.g. 'upbeat synthwave with neon pads', 'chill lo-fi beats', 'epic orchestral film score'"),
679
+ instrumental: z5.boolean().optional().default(true).describe("Generate without vocals (default: true)"),
680
+ lyrics: z5.string().optional().describe("Custom lyrics. Cannot be used with instrumental: true"),
681
+ model: z5.enum(["minimax/music-2.5+", "minimax/music-2.5"]).optional().default("minimax/music-2.5+").describe("Music model to use")
682
+ }
683
+ },
684
+ async ({ prompt, instrumental, lyrics, model }) => {
685
+ try {
686
+ if (instrumental && lyrics?.trim()) {
687
+ return {
688
+ content: [{ type: "text", text: formatError("Cannot specify lyrics when instrumental is true") }],
689
+ isError: true
690
+ };
691
+ }
692
+ const privateKey = getOrCreateWalletKey();
693
+ const account = privateKeyToAccount(privateKey);
694
+ const url = `${BLOCKRUN_API}/v1/audio/generations`;
695
+ const body = { model, prompt, instrumental };
696
+ if (lyrics?.trim()) body.lyrics = lyrics.trim();
697
+ const resp402 = await fetchWithTimeout(url, {
698
+ method: "POST",
699
+ headers: { "Content-Type": "application/json" },
700
+ body: JSON.stringify(body)
701
+ }, 15e3);
702
+ if (resp402.status !== 402) {
703
+ const data2 = await resp402.json();
704
+ throw new Error(`Expected 402, got ${resp402.status}: ${JSON.stringify(data2)}`);
705
+ }
706
+ const prHeader = resp402.headers.get("payment-required") || resp402.headers.get("PAYMENT-REQUIRED");
707
+ if (!prHeader) throw new Error("No PAYMENT-REQUIRED header in 402 response");
708
+ const paymentRequired = parsePaymentRequired(prHeader);
709
+ const details = extractPaymentDetails(paymentRequired);
710
+ const paymentPayload = await createPaymentPayload(
711
+ privateKey,
712
+ account.address,
713
+ details.recipient,
714
+ details.amount,
715
+ details.network || "eip155:8453",
716
+ {
717
+ resourceUrl: details.resource?.url || url,
718
+ resourceDescription: details.resource?.description || "BlockRun Music Generation",
719
+ maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
720
+ extra: details.extra
721
+ }
722
+ );
723
+ const resp = await fetchWithTimeout(url, {
724
+ method: "POST",
725
+ headers: {
726
+ "Content-Type": "application/json",
727
+ "PAYMENT-SIGNATURE": paymentPayload
728
+ },
729
+ body: JSON.stringify(body)
730
+ }, MUSIC_TIMEOUT);
731
+ if (resp.status === 402) {
732
+ throw new Error("Payment rejected. Check your wallet balance.");
733
+ }
734
+ if (!resp.ok) {
735
+ const errBody = await resp.json().catch(() => ({ error: "Request failed" }));
736
+ throw new Error(`API error ${resp.status}: ${JSON.stringify(errBody)}`);
737
+ }
738
+ const data = await resp.json();
739
+ const track = data.data?.[0];
740
+ if (!track?.url) throw new Error("No track URL in response");
741
+ const txHash = resp.headers.get("X-Payment-Receipt") || resp.headers.get("x-payment-receipt");
742
+ const lines = [
743
+ `\u{1F3B5} Track ready!`,
744
+ `URL: ${track.url}`,
745
+ `Duration: ${track.duration_seconds ? `${track.duration_seconds}s` : "~3 min"}`,
746
+ `Model: ${data.model || model}`,
747
+ ...track.lyrics ? [`Lyrics: ${track.lyrics.slice(0, 200)}${track.lyrics.length > 200 ? "..." : ""}`] : [],
748
+ ...txHash ? [`Tx: ${txHash}`] : [],
749
+ ``,
750
+ `Note: This URL expires in ~24h. Download it now if you need to keep the file.`
751
+ ];
752
+ return {
753
+ content: [{ type: "text", text: lines.join("\n") }],
754
+ structuredContent: {
755
+ url: track.url,
756
+ duration_seconds: track.duration_seconds,
757
+ model: data.model || model,
758
+ ...track.lyrics ? { lyrics: track.lyrics } : {},
759
+ ...txHash ? { txHash } : {}
760
+ }
761
+ };
762
+ } catch (err) {
763
+ const errMsg = err instanceof Error ? err.message : String(err);
764
+ if (errMsg.includes("balance") || errMsg.includes("payment") || errMsg.includes("402") || errMsg.includes("rejected")) {
765
+ return {
766
+ content: [{ type: "text", text: `Music generation requires payment. Run blockrun_wallet with action: "setup" for funding instructions.
767
+ Error: ${errMsg}` }],
768
+ isError: true
769
+ };
770
+ }
771
+ if (errMsg.includes("abort") || errMsg.includes("timeout") || errMsg.includes("Timeout")) {
772
+ return {
773
+ content: [{ type: "text", text: `Music generation timed out. This can happen during peak load \u2014 please try again.
774
+ Error: ${errMsg}` }],
775
+ isError: true
776
+ };
777
+ }
778
+ return {
779
+ content: [{ type: "text", text: formatError(`Music generation failed: ${errMsg}`) }],
780
+ isError: true
781
+ };
782
+ }
783
+ }
784
+ );
785
+ }
786
+ async function fetchWithTimeout(url, options, timeoutMs) {
787
+ const controller = new AbortController();
788
+ const id = setTimeout(() => controller.abort(), timeoutMs);
789
+ try {
790
+ return await fetch(url, { ...options, signal: controller.signal });
791
+ } finally {
792
+ clearTimeout(id);
793
+ }
794
+ }
795
+
796
+ // src/tools/search.ts
797
+ import { z as z6 } from "zod";
658
798
  function registerSearchTool(server) {
659
799
  server.registerTool(
660
800
  "blockrun_search",
@@ -666,11 +806,11 @@ Pricing: ~$0.01/search
666
806
 
667
807
  Returns a summary with cited sources.`,
668
808
  inputSchema: {
669
- query: z5.string().describe("Search query"),
670
- sources: z5.array(z5.enum(["web", "x", "news"])).optional().describe("Sources to search (default: web + x + news)"),
671
- max_results: z5.number().optional().default(10).describe("Max results per source (1-20)"),
672
- from_date: z5.string().optional().describe("Start date filter (YYYY-MM-DD)"),
673
- to_date: z5.string().optional().describe("End date filter (YYYY-MM-DD)")
809
+ query: z6.string().describe("Search query"),
810
+ sources: z6.array(z6.enum(["web", "x", "news"])).optional().describe("Sources to search (default: web + x + news)"),
811
+ max_results: z6.number().optional().default(10).describe("Max results per source (1-20)"),
812
+ from_date: z6.string().optional().describe("Start date filter (YYYY-MM-DD)"),
813
+ to_date: z6.string().optional().describe("End date filter (YYYY-MM-DD)")
674
814
  }
675
815
  },
676
816
  async ({ query, sources, max_results, from_date, to_date }) => {
@@ -698,7 +838,7 @@ Returns a summary with cited sources.`,
698
838
  }
699
839
 
700
840
  // src/tools/exa.ts
701
- import { z as z6 } from "zod";
841
+ import { z as z7 } from "zod";
702
842
  function registerExaTool(server) {
703
843
  server.registerTool(
704
844
  "blockrun_exa",
@@ -711,14 +851,14 @@ Actions:
711
851
  - contents: Fetch full Markdown text from URLs, ready for LLM context ($0.002/URL)
712
852
  - similar: Find pages semantically similar to a given URL ($0.01/call)`,
713
853
  inputSchema: {
714
- action: z6.enum(["search", "answer", "contents", "similar"]).describe("Action to perform"),
715
- query: z6.string().optional().describe("Natural language query (for search/answer)"),
716
- url: z6.string().optional().describe("Reference URL to find similar pages (for similar action)"),
717
- urls: z6.array(z6.string()).optional().describe("URLs to fetch content from (for contents action, up to 100)"),
718
- num_results: z6.number().optional().describe("Number of results to return (default: 10)"),
719
- category: z6.string().optional().describe("Category filter: 'news', 'research paper', 'company', 'tweet', 'github', 'pdf'"),
720
- include_domains: z6.array(z6.string()).optional().describe("Only search within these domains"),
721
- exclude_domains: z6.array(z6.string()).optional().describe("Exclude these domains from results")
854
+ action: z7.enum(["search", "answer", "contents", "similar"]).describe("Action to perform"),
855
+ query: z7.string().optional().describe("Natural language query (for search/answer)"),
856
+ url: z7.string().optional().describe("Reference URL to find similar pages (for similar action)"),
857
+ urls: z7.array(z7.string()).optional().describe("URLs to fetch content from (for contents action, up to 100)"),
858
+ num_results: z7.number().optional().describe("Number of results to return (default: 10)"),
859
+ category: z7.string().optional().describe("Category filter: 'news', 'research paper', 'company', 'tweet', 'github', 'pdf'"),
860
+ include_domains: z7.array(z7.string()).optional().describe("Only search within these domains"),
861
+ exclude_domains: z7.array(z7.string()).optional().describe("Exclude these domains from results")
722
862
  }
723
863
  },
724
864
  async ({ action, query, url, urls, num_results, category, include_domains, exclude_domains }) => {
@@ -771,7 +911,7 @@ Actions:
771
911
  }
772
912
 
773
913
  // src/tools/markets.ts
774
- import { z as z7 } from "zod";
914
+ import { z as z8 } from "zod";
775
915
  function registerMarketsTool(server) {
776
916
  server.registerTool(
777
917
  "blockrun_markets",
@@ -786,9 +926,9 @@ Example paths:
786
926
 
787
927
  $0.001/call.`,
788
928
  inputSchema: {
789
- path: z7.string().describe("Endpoint path, e.g. 'polymarket/events', 'kalshi/markets/KXBTC-25MAR14'"),
790
- params: z7.record(z7.string(), z7.string()).optional().describe("Query parameters for GET requests"),
791
- body: z7.any().optional().describe("JSON body for POST queries (triggers pmQuery)")
929
+ path: z8.string().describe("Endpoint path, e.g. 'polymarket/events', 'kalshi/markets/KXBTC-25MAR14'"),
930
+ params: z8.record(z8.string(), z8.string()).optional().describe("Query parameters for GET requests"),
931
+ body: z8.any().optional().describe("JSON body for POST queries (triggers pmQuery)")
792
932
  }
793
933
  },
794
934
  async ({ path: path2, params, body }) => {
@@ -811,7 +951,7 @@ $0.001/call.`,
811
951
  }
812
952
 
813
953
  // src/tools/dex.ts
814
- import { z as z8 } from "zod";
954
+ import { z as z9 } from "zod";
815
955
  function registerDexTool(server) {
816
956
  server.registerTool(
817
957
  "blockrun_dex",
@@ -828,10 +968,10 @@ Examples:
828
968
  blockrun_dex({ token: "So11...xxx" }) -> Get specific token data
829
969
  blockrun_dex({ symbol: "PEPE" }) -> Search by symbol`,
830
970
  inputSchema: {
831
- query: z8.string().optional().describe("Search query (token name, symbol, or address)"),
832
- token: z8.string().optional().describe("Token address for direct lookup"),
833
- symbol: z8.string().optional().describe("Token symbol to search"),
834
- chain: z8.string().optional().describe("Filter by chain (ethereum, solana, base, etc.)")
971
+ query: z9.string().optional().describe("Search query (token name, symbol, or address)"),
972
+ token: z9.string().optional().describe("Token address for direct lookup"),
973
+ symbol: z9.string().optional().describe("Token symbol to search"),
974
+ chain: z9.string().optional().describe("Filter by chain (ethereum, solana, base, etc.)")
835
975
  }
836
976
  },
837
977
  async ({ query, token, symbol, chain }) => {
@@ -899,6 +1039,7 @@ function initializeMcpServer(server) {
899
1039
  registerChatTool(server, budget);
900
1040
  registerModelsTool(server, modelCache);
901
1041
  registerImageTool(server);
1042
+ registerMusicTool(server);
902
1043
  registerSearchTool(server);
903
1044
  registerExaTool(server);
904
1045
  registerMarketsTool(server);
@@ -939,15 +1080,30 @@ function initializeMcpServer(server) {
939
1080
  }
940
1081
 
941
1082
  // src/index.ts
1083
+ var VERSION = "0.6.8";
1084
+ async function checkForUpdate() {
1085
+ try {
1086
+ const resp = await fetch("https://registry.npmjs.org/@blockrun/mcp/latest", {
1087
+ signal: AbortSignal.timeout(3e3)
1088
+ });
1089
+ const data = await resp.json();
1090
+ if (data.version && data.version !== VERSION) {
1091
+ console.error(`[BlockRun] Update available: v${VERSION} \u2192 v${data.version}`);
1092
+ console.error(`[BlockRun] Run: claude mcp add blockrun npx -y @blockrun/mcp@latest`);
1093
+ }
1094
+ } catch {
1095
+ }
1096
+ }
942
1097
  async function main() {
943
1098
  const server = new McpServer({
944
1099
  name: "blockrun-mcp",
945
- version: "0.6.6"
1100
+ version: VERSION
946
1101
  });
947
1102
  initializeMcpServer(server);
948
1103
  const transport = new StdioServerTransport();
949
1104
  await server.connect(transport);
950
- console.error("BlockRun MCP Server started (v0.6.6) \u2014 stdio transport");
1105
+ console.error(`BlockRun MCP Server started (v${VERSION}) \u2014 stdio transport`);
1106
+ checkForUpdate();
951
1107
  }
952
1108
  main().catch((error) => {
953
1109
  console.error("Fatal error:", error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/mcp",
3
- "version": "0.6.7",
3
+ "version": "0.6.9",
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",