BSC MCP Server

by ArcReactor9
Verified
import express from "express"; import * as cors from "cors"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import { BSCService } from "./bsc-service.js"; import { ethers } from "ethers"; // Augment McpServer interface to add our custom methods declare module '@modelcontextprotocol/sdk/server/mcp.js' { interface McpServer { tool: any; // Use any type to avoid strict typing issues } } // Define the BSC RPC URL - mainnet by default const BSC_RPC_URL = process.env.BSC_RPC_URL || "https://bsc-dataseed.binance.org/"; // Get the private key from environment variable (needed for token creation) const BSC_PRIVATE_KEY = process.env.BSC_PRIVATE_KEY || ""; // Set the port for the HTTP server const PORT = parseInt(process.env.PORT || "3000", 10); // Create a BSC service instance const bscService = new BSCService(BSC_RPC_URL, BSC_PRIVATE_KEY); // Create an MCP server const server = new McpServer({ name: "BSC Explorer", version: "1.0.0", description: "MCP server for interacting with Binance Smart Chain" }); // Get available tools information interface Tool { name: string; description: string; parameters: Record<string, unknown>; } // Maintain a record of registered tools const registeredTools: Record<string, Function> = {}; // Override the tool method to store tool functions const originalTool = server.tool.bind(server); server.tool = function(name: string, params: any, handler: Function) { registeredTools[name] = handler; return originalTool(name, params, handler); }; // Add method to list all available tools function listTools(): Tool[] { // This would need to be implemented more completely to match // the actual tools interface, but this is a placeholder return Object.keys(registeredTools).map(name => ({ name, description: "", // We don't store descriptions separately parameters: {} })); } // Add method to get a tool function by name function getToolFunction(name: string): Function | null { return registeredTools[name] || null; } // Add a tool to get current block number server.tool( "get-block-number", {}, async () => { try { const blockNumber = await bscService.getBlockNumber(); return { content: [{ type: "text", text: `Current BSC block number: ${blockNumber}` }] }; } catch (error) { return { content: [{ type: "text", text: `Error getting block number: ${error}` }], isError: true }; } } ); // Add a tool to get block details server.tool( "get-block", { blockHashOrNumber: z.union([z.string(), z.number()]) }, async ({ blockHashOrNumber }) => { try { const block = await bscService.getBlock(blockHashOrNumber); return { content: [{ type: "text", text: JSON.stringify(block, null, 2) }] }; } catch (error) { return { content: [{ type: "text", text: `Error getting block: ${error}` }], isError: true }; } } ); // Add a tool to get transaction details server.tool( "get-transaction", { txHash: z.string() }, async ({ txHash }) => { try { const tx = await bscService.getTransaction(txHash); return { content: [{ type: "text", text: JSON.stringify(tx, null, 2) }] }; } catch (error) { return { content: [{ type: "text", text: `Error getting transaction: ${error}` }], isError: true }; } } ); // Add a tool to get transaction receipt server.tool( "get-transaction-receipt", { txHash: z.string() }, async ({ txHash }) => { try { const receipt = await bscService.getTransactionReceipt(txHash); return { content: [{ type: "text", text: JSON.stringify(receipt, null, 2) }] }; } catch (error) { return { content: [{ type: "text", text: `Error getting transaction receipt: ${error}` }], isError: true }; } } ); // Add a tool to get account balance server.tool( "get-balance", { address: z.string() }, async ({ address }) => { try { const balance = await bscService.getBalance(address); const formattedBalance = ethers.formatEther(balance); return { content: [{ type: "text", text: `Balance: ${formattedBalance} BNB` }] }; } catch (error) { return { content: [{ type: "text", text: `Error getting balance: ${error}` }], isError: true }; } } ); // Add a tool to get BEP-20 token balance server.tool( "get-token-balance", { tokenAddress: z.string(), walletAddress: z.string() }, async ({ tokenAddress, walletAddress }) => { try { const balance = await bscService.getTokenBalance(tokenAddress, walletAddress); return { content: [{ type: "text", text: `Token Balance: ${balance}` }] }; } catch (error) { return { content: [{ type: "text", text: `Error getting token balance: ${error}` }], isError: true }; } } ); // Add a tool to create Four.meme token server.tool( "create-four-meme-token", { name: z.string().min(1, "Token name is required"), symbol: z.string().min(1, "Token symbol is required"), initialSupply: z.number().positive("Initial supply must be positive"), decimals: z.number().int().min(0).max(18).default(18), ownerAddress: z.string().regex(/^0x[a-fA-F0-9]{40}$/, "Invalid BSC address format") }, async ({ name, symbol, initialSupply, decimals, ownerAddress }) => { try { // Check if private key is available if (!BSC_PRIVATE_KEY) { return { content: [{ type: "text", text: "Cannot create token: BSC_PRIVATE_KEY environment variable is not set. Please provide a private key to deploy contracts." }], isError: true }; } // Create the token const result = await bscService.createFourMemeToken( name, symbol, initialSupply, decimals, ownerAddress ); return { content: [{ type: "text", text: `Successfully created Four.meme token!\n\nToken Name: ${name}\nToken Symbol: ${symbol}\nToken Address: ${result.tokenAddress}\nTransaction Hash: ${result.txHash}\n\nYou can view the token on BscScan: https://bscscan.com/token/${result.tokenAddress}` }] }; } catch (error) { return { content: [{ type: "text", text: `Error creating Four.meme token: ${error}` }], isError: true }; } } ); // Create Express app const app = express(); app.use(cors()); app.use(express.json()); // Parse JSON request body // Root endpoint app.get("/", (req, res) => { res.send("BSC MCP HTTP/SSE Server is running"); }); // Endpoint for MCP hello message app.post("/mcp/hello", async (req, res) => { try { // Return server info and available tools res.json({ version: "1.0.0", name: "BSC Explorer", tools: listTools() }); } catch (error) { console.error("Error processing hello:", error); res.status(500).json({ error: `Server error: ${error instanceof Error ? error.message : String(error)}` }); } }); // Endpoint for MCP tool calls app.post("/mcp/tools/:toolName", async (req, res) => { try { const { toolName } = req.params; const args = req.body; // Get tool function and call it const toolFn = getToolFunction(toolName); if (!toolFn) { res.status(404).json({ error: `Tool '${toolName}' not found` }); return; } const result = await toolFn(args); res.json(result); } catch (error) { console.error(`Error calling tool: ${error}`); res.status(500).json({ error: `Server error: ${error instanceof Error ? error.message : String(error)}` }); } }); // SSE endpoint for streaming connections app.get("/mcp/sse", (req, res) => { const headers = { "Content-Type": "text/event-stream", "Connection": "keep-alive", "Cache-Control": "no-cache" }; res.writeHead(200, headers); // Helper to send SSE messages const send = (data: any) => { res.write(`data: ${JSON.stringify(data)}\n\n`); }; // Send a connected event send({ type: "connected" }); // Handle client disconnection req.on("close", () => { res.end(); console.log("Client disconnected from SSE"); }); }); // Start the HTTP server app.listen(PORT, () => { console.log(`BSC MCP HTTP/SSE server is running on port ${PORT}`); console.log(`- Root endpoint: http://localhost:${PORT}/`); console.log(`- SSE endpoint: http://localhost:${PORT}/mcp/sse`); console.log(`- Hello endpoint: http://localhost:${PORT}/mcp/hello`); console.log(`- Tool calls: http://localhost:${PORT}/mcp/tools/:toolName`); });