@blockrun/mcp 0.16.1 → 0.18.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 +197 -9
  2. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  ImageClient,
15
15
  PriceClient,
16
16
  SolanaLLMClient,
17
+ AnthropicClient,
17
18
  getOrCreateWallet,
18
19
  getOrCreateSolanaWallet,
19
20
  loadSolanaWallet,
@@ -33,6 +34,7 @@ var _priceClient = null;
33
34
  var _freePriceClient = null;
34
35
  var _evmWalletInfo = null;
35
36
  var _solanaClient = null;
37
+ var _anthropicClient = null;
36
38
  function readChainPreference() {
37
39
  for (const file of CHAIN_PREFERENCE_FILES) {
38
40
  try {
@@ -58,6 +60,7 @@ var CHAIN_FILE = path.join(BLOCKRUN_DIR, ".chain");
58
60
  function resetChainCaches() {
59
61
  _evmClient = null;
60
62
  _solanaClient = null;
63
+ _anthropicClient = null;
61
64
  _imageClient = null;
62
65
  _priceClient = null;
63
66
  _freePriceClient = null;
@@ -78,6 +81,12 @@ async function ensureBothWallets() {
78
81
  solana: { address: sol.address, isNew: sol.isNew }
79
82
  };
80
83
  }
84
+ function baseOnlyMessage(capability) {
85
+ if (getChain() === "solana") {
86
+ return `${capability} currently supports Base-chain payment only \u2014 your active chain is Solana. Switch with: blockrun_wallet action:"chain" chain:"base" (switch back later with chain:"solana"). Solana support depends on the @blockrun/llm SDK adding this capability.`;
87
+ }
88
+ return null;
89
+ }
81
90
  function ensureEvmWallet() {
82
91
  if (!_evmWalletInfo) {
83
92
  _evmWalletInfo = getOrCreateWallet();
@@ -108,6 +117,13 @@ function getClient() {
108
117
  }
109
118
  return _evmClient;
110
119
  }
120
+ function getAnthropicClient() {
121
+ if (!_anthropicClient) {
122
+ const privateKey = getOrCreateWalletKey();
123
+ _anthropicClient = new AnthropicClient({ privateKey });
124
+ }
125
+ return _anthropicClient;
126
+ }
111
127
  function getImageClient() {
112
128
  if (!_imageClient) {
113
129
  const privateKey = getOrCreateWalletKey();
@@ -214,11 +230,11 @@ var BASE_CHAIN_ID = "8453";
214
230
  var MODEL_TIERS = {
215
231
  fast: ["google/gemini-3.5-flash", "google/gemini-2.5-flash", "google/gemini-3.1-flash-lite", "openai/gpt-5-mini", "deepseek/deepseek-chat", "google/gemini-3-flash-preview"],
216
232
  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"],
217
- powerful: ["openai/gpt-5.4-pro", "anthropic/claude-opus-4.7", "anthropic/claude-opus-4.6", "openai/o3", "openai/gpt-5.4"],
233
+ powerful: ["anthropic/claude-opus-4.8", "openai/gpt-5.4-pro", "anthropic/claude-opus-4.7", "anthropic/claude-opus-4.6", "openai/o3", "openai/gpt-5.4"],
218
234
  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"],
219
- reasoning: ["openai/o3", "openai/o1", "openai/o3-mini", "deepseek/deepseek-reasoner", "moonshot/kimi-k2.6", "openai/gpt-5.3-codex"],
235
+ reasoning: ["anthropic/claude-opus-4.8", "openai/o3", "openai/o1", "openai/o3-mini", "deepseek/deepseek-reasoner", "moonshot/kimi-k2.6", "openai/gpt-5.3-codex"],
220
236
  free: ["nvidia/qwen3-next-80b-a3b-thinking", "nvidia/mistral-small-4-119b", "nvidia/gpt-oss-120b", "nvidia/deepseek-v3.2", "nvidia/qwen3-coder-480b", "nvidia/llama-4-maverick", "nvidia/gpt-oss-20b", "nvidia/glm-4.7"],
221
- coding: ["zai/glm-5", "openai/gpt-5.3-codex", "moonshot/kimi-k2.6", "nvidia/qwen3-coder-480b", "anthropic/claude-sonnet-4.6", "openai/gpt-5.4"],
237
+ coding: ["anthropic/claude-opus-4.8", "zai/glm-5", "openai/gpt-5.3-codex", "moonshot/kimi-k2.6", "nvidia/qwen3-coder-480b", "anthropic/claude-sonnet-4.6", "openai/gpt-5.4"],
222
238
  glm: ["zai/glm-5", "zai/glm-5-turbo", "nvidia/glm-4.7"]
223
239
  };
224
240
 
@@ -669,6 +685,134 @@ function recordSpending(budget, cost, agentId) {
669
685
  }
670
686
  }
671
687
 
688
+ // src/tools/chat-anthropic.ts
689
+ function isAnthropicModel(model) {
690
+ const id = model.trim();
691
+ return /^anthropic\//i.test(id) || /^claude-/i.test(id);
692
+ }
693
+ function parseDataUri(url) {
694
+ const match = /^data:(image\/(?:jpeg|png|gif|webp));base64,(.+)$/i.exec(url.trim());
695
+ if (!match) return null;
696
+ return {
697
+ type: "base64",
698
+ media_type: match[1].toLowerCase(),
699
+ data: match[2]
700
+ };
701
+ }
702
+ function toAnthropicContent(content) {
703
+ if (typeof content === "string") return content;
704
+ const blocks = [];
705
+ for (const part of content) {
706
+ if (part.type === "text") {
707
+ if (part.text) blocks.push({ type: "text", text: part.text });
708
+ } else if (part.type === "image_url") {
709
+ const url = part.image_url?.url;
710
+ if (!url) continue;
711
+ const base64 = parseDataUri(url);
712
+ const source = base64 ? base64 : { type: "url", url };
713
+ blocks.push({ type: "image", source });
714
+ }
715
+ }
716
+ return blocks;
717
+ }
718
+ function isTextBlock(b) {
719
+ return b.type === "text";
720
+ }
721
+ function isThinkingBlock(b) {
722
+ return b.type === "thinking";
723
+ }
724
+ async function handleAnthropicNative(args) {
725
+ const {
726
+ client,
727
+ model,
728
+ message,
729
+ system,
730
+ messages,
731
+ maxTokens,
732
+ temperature,
733
+ stop,
734
+ thinking,
735
+ budget,
736
+ agentId,
737
+ estimatedCost
738
+ } = args;
739
+ const systemParts = [];
740
+ if (system) systemParts.push(system);
741
+ const apiMessages = [];
742
+ for (const m of messages ?? []) {
743
+ if (m.role === "system") {
744
+ const text = typeof m.content === "string" ? m.content : m.content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
745
+ if (text) systemParts.push(text);
746
+ continue;
747
+ }
748
+ apiMessages.push({ role: m.role, content: toAnthropicContent(m.content) });
749
+ }
750
+ if (message.trim()) apiMessages.push({ role: "user", content: message });
751
+ if (apiMessages.length === 0) {
752
+ return { content: [{ type: "text", text: "No message content to send." }], isError: true };
753
+ }
754
+ let effectiveMax = maxTokens ?? 1024;
755
+ let raisedMaxTokens = false;
756
+ if (thinking && effectiveMax <= thinking.budget_tokens) {
757
+ effectiveMax = thinking.budget_tokens + 1024;
758
+ raisedMaxTokens = true;
759
+ }
760
+ const params = {
761
+ model,
762
+ max_tokens: effectiveMax,
763
+ messages: apiMessages
764
+ };
765
+ if (systemParts.length) params.system = systemParts.join("\n\n");
766
+ if (stop && stop.length) params.stop_sequences = stop;
767
+ if (thinking) {
768
+ params.thinking = { type: "enabled", budget_tokens: thinking.budget_tokens };
769
+ } else if (temperature !== void 0) {
770
+ params.temperature = Math.max(0, Math.min(1, temperature));
771
+ }
772
+ let native;
773
+ try {
774
+ native = await client.messages.create(params);
775
+ } catch (error) {
776
+ const errorMessage = error instanceof Error ? error.message : String(error);
777
+ return { content: [{ type: "text", text: formatError(errorMessage) }], isError: true };
778
+ }
779
+ recordSpending(budget, estimatedCost, agentId);
780
+ const thinkingBlocks = native.content.filter(isThinkingBlock);
781
+ const textBlocks = native.content.filter(isTextBlock);
782
+ const answerText = textBlocks.map((b) => b.text).join("\n");
783
+ const thinkingText = thinkingBlocks.map((b) => b.thinking).join("\n");
784
+ const signaturePresent = thinkingBlocks.some(
785
+ (b) => typeof b.signature === "string" && b.signature.length > 0
786
+ );
787
+ const headerBits = [native.model, "native /v1/messages", `thinking ${thinking ? "on" : "off"}`];
788
+ if (raisedMaxTokens) headerBits.push(`max_tokens\u2192${effectiveMax}`);
789
+ const header = `[${headerBits.join(" | ")}]`;
790
+ const content = [{ type: "text", text: `${header}
791
+
792
+ ${answerText}` }];
793
+ if (thinkingText) {
794
+ content.push({ type: "text", text: `\u{1F9E0} Thinking (signature ${signaturePresent ? "present" : "absent"}):
795
+ ${thinkingText}` });
796
+ }
797
+ return {
798
+ content,
799
+ structuredContent: {
800
+ requested_model: model,
801
+ // Verbatim upstream model id — proof the call hit real Claude with no
802
+ // substitution. Intentionally NOT rewritten back to the requested id.
803
+ model: native.model,
804
+ response: answerText,
805
+ thinking: thinkingText || void 0,
806
+ // Native thinking blocks verbatim, including their original signature.
807
+ thinking_blocks: thinkingBlocks,
808
+ signature_present: signaturePresent,
809
+ stop_reason: native.stop_reason,
810
+ usage: native.usage,
811
+ native
812
+ }
813
+ };
814
+ }
815
+
672
816
  // src/tools/chat.ts
673
817
  function estimateChatCost(mode, model, routing, routingProfile) {
674
818
  if (mode === "free") return 0;
@@ -714,15 +858,28 @@ Run blockrun_models to see all 41+ models with pricing.`,
714
858
  system: z2.string().optional().describe("Optional system prompt"),
715
859
  max_tokens: z2.number().optional().default(1024).describe("Max tokens in response"),
716
860
  temperature: z2.number().optional().default(1).describe("Creativity 0-2"),
861
+ response_format: z2.enum(["text", "json_object"]).optional().describe("Set to 'json_object' to force valid JSON output (no markdown fences). Works across all providers."),
862
+ stop: z2.array(z2.string()).max(4).optional().describe("Up to 4 stop sequences; generation halts when any is produced"),
863
+ thinking: z2.object({
864
+ type: z2.literal("enabled"),
865
+ budget_tokens: z2.number().min(1).describe("Tokens Claude may spend reasoning before answering. max_tokens is auto-raised above this if needed.")
866
+ }).optional().describe("Anthropic extended thinking. Only honored for anthropic/claude-* models \u2014 these go direct to the native /v1/messages endpoint and the response includes verbatim type:'thinking' blocks with their original signature. Ignored for non-Claude models (no native thinking channel)."),
717
867
  agent_id: z2.string().optional().describe("Agent identifier. If a budget was delegated for this agent_id via blockrun_wallet action:'delegate', spending is tracked and enforced. The agent is hard-stopped when its budget is exhausted."),
718
868
  messages: z2.array(z2.object({
719
869
  role: z2.enum(["user", "assistant", "system"]),
720
- content: z2.string()
870
+ content: z2.union([
871
+ z2.string(),
872
+ z2.array(z2.union([
873
+ z2.object({ type: z2.literal("text"), text: z2.string() }),
874
+ z2.object({ type: z2.literal("image_url"), image_url: z2.object({ url: z2.string().describe("https URL or data:<mime>;base64,<...> URI") }) })
875
+ ]))
876
+ ]).describe("Plain text, or an array of parts for multimodal input (text + image_url). Images are honored on the native anthropic/claude-* path.")
721
877
  })).optional().describe("Conversation history for multi-turn context. When provided, 'message' is appended as the final user turn. Use with explicit 'model' param (defaults to 'openai/gpt-5.4' if not specified). Note: if you include a role:'system' entry in messages[], do not also pass the system param to avoid duplicate system messages.")
722
878
  }
723
879
  },
724
- async ({ message, model, mode, routing, routing_profile, system, max_tokens, temperature, agent_id, messages }) => {
880
+ async ({ message, model, mode, routing, routing_profile, system, max_tokens, temperature, response_format, stop, thinking, agent_id, messages }) => {
725
881
  const llm = getClient();
882
+ const responseFormat = response_format ? { type: response_format } : void 0;
726
883
  const estimatedCost = estimateChatCost(mode, model, routing, routing_profile);
727
884
  const budgetCheck = checkBudget(budget, agent_id, estimatedCost);
728
885
  if (!budgetCheck.allowed) {
@@ -731,6 +888,26 @@ Run blockrun_models to see all 41+ models with pricing.`,
731
888
  isError: true
732
889
  };
733
890
  }
891
+ if (model && isAnthropicModel(model)) {
892
+ const solanaBlock = baseOnlyMessage("Native Anthropic (claude-*) calls");
893
+ if (solanaBlock) {
894
+ return { content: [{ type: "text", text: solanaBlock }], isError: true };
895
+ }
896
+ return handleAnthropicNative({
897
+ client: getAnthropicClient(),
898
+ model,
899
+ message,
900
+ system,
901
+ messages,
902
+ maxTokens: max_tokens,
903
+ temperature,
904
+ stop,
905
+ thinking,
906
+ budget,
907
+ agentId: agent_id,
908
+ estimatedCost
909
+ });
910
+ }
734
911
  if (routing === "smart") {
735
912
  if (!(llm instanceof LLMClient2)) {
736
913
  return {
@@ -744,7 +921,12 @@ Run blockrun_models to see all 41+ models with pricing.`,
744
921
  maxTokens: max_tokens,
745
922
  maxOutputTokens: max_tokens,
746
923
  temperature,
747
- routingProfile: routing_profile
924
+ // @blockrun/llm 2.x dropped the "free" routing profile; the gateway
925
+ // already routes to the most cost-effective model by default, so we
926
+ // omit it and let ClawRouter pick (matches the SDK upgrade path).
927
+ routingProfile: routing_profile === "free" ? void 0 : routing_profile,
928
+ responseFormat,
929
+ stop
748
930
  });
749
931
  recordSpending(budget, result.routing.costEstimate || 1e-3, agent_id);
750
932
  return {
@@ -772,7 +954,9 @@ ${result.response}` }],
772
954
  try {
773
955
  const result = await llm.chatCompletion(targetModel, fullMessages, {
774
956
  maxTokens: max_tokens,
775
- temperature
957
+ temperature,
958
+ responseFormat,
959
+ stop
776
960
  });
777
961
  const reply = result.choices?.[0]?.message?.content || "";
778
962
  recordSpending(budget, estimatedCost, agent_id);
@@ -792,7 +976,9 @@ ${reply}` }],
792
976
  const response = await llm.chat(model, message, {
793
977
  system,
794
978
  maxTokens: max_tokens,
795
- temperature
979
+ temperature,
980
+ responseFormat,
981
+ stop
796
982
  });
797
983
  recordSpending(budget, estimatedCost, agent_id);
798
984
  return { content: [{ type: "text", text: response }] };
@@ -812,7 +998,9 @@ ${reply}` }],
812
998
  const response = await llm.chat(m, message, {
813
999
  system,
814
1000
  maxTokens: max_tokens,
815
- temperature
1001
+ temperature,
1002
+ responseFormat,
1003
+ stop
816
1004
  });
817
1005
  recordSpending(budget, estimatedCost, agent_id);
818
1006
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/mcp",
3
- "version": "0.16.1",
3
+ "version": "0.18.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,8 @@
44
44
  "url": "https://github.com/blockrunai/blockrun-mcp/issues"
45
45
  },
46
46
  "dependencies": {
47
- "@blockrun/llm": "^1.12.0",
47
+ "@anthropic-ai/sdk": "^0.39.0",
48
+ "@blockrun/llm": "^2.11.0",
48
49
  "@modelcontextprotocol/sdk": "^1.0.0",
49
50
  "open": "^11.0.0",
50
51
  "qrcode": "^1.5.4",