MCP Blockchain Server

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import { getChains, getChainById } from "../services/chainService.js"; import { getBalance } from "../services/accountService.js"; import { readContract } from "../services/contractService.js"; import { prepareTransaction, getTransaction, submitTransaction } from "../services/transactionService.js"; import { logger } from "../utils/logger.js"; // Create MCP server instance export const server = new McpServer({ name: "mcp-blockchain-server", version: "0.1.0", }); // Initialize tools export function initializeTools() { // Get supported chains server.tool( "get-chains", "Get list of supported blockchain networks", {}, async () => { try { const chains = await getChains(); return { content: [ { type: "text", text: JSON.stringify(chains, null, 2), }, ], }; } catch (error) { logger.error("Error in get-chains tool:", error); return { content: [ { type: "text", text: `Error fetching chains: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } } ); // Get account balance server.tool( "get-balance", "Get account balance for an address on a specific chain", { chainId: z.string().describe("Chain ID (e.g., '1' for Ethereum Mainnet)"), address: z.string().describe("Wallet address to check balance for"), }, async ({ chainId, address }) => { try { const balance = await getBalance(chainId, address); return { content: [ { type: "text", text: JSON.stringify(balance, null, 2), }, ], }; } catch (error) { logger.error("Error in get-balance tool:", error); return { content: [ { type: "text", text: `Error fetching balance: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } } ); // Read contract server.tool( "read-contract", "Read data from a smart contract", { chainId: z.string().describe("Chain ID (e.g., '1' for Ethereum Mainnet)"), address: z.string().describe("Contract address"), method: z.string().describe("Contract method to call"), args: z.array(z.any()).optional().describe("Arguments for the contract method (optional)"), }, async ({ chainId, address, method, args }) => { try { const result = await readContract(chainId, address, method, args || []); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { logger.error("Error in read-contract tool:", error); return { content: [ { type: "text", text: `Error reading contract: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } } ); // Prepare transaction server.tool( "prepare-transaction", "Prepare an unsigned transaction for user approval", { chainId: z.string().describe("Chain ID (e.g., '1' for Ethereum Mainnet)"), to: z.string().describe("Recipient address"), value: z.string().optional().describe("Amount to send in ETH/native token (optional)"), data: z.string().optional().describe("Transaction data for contract interactions (optional)"), gasLimit: z.string().optional().describe("Gas limit for the transaction (optional)"), }, async ({ chainId, to, value, data, gasLimit }) => { try { const chain = await getChainById(chainId); if (!chain) { return { content: [ { type: "text", text: `Chain with ID ${chainId} not found.`, }, ], isError: true, }; } const transaction = await prepareTransaction({ chainId, to, value: value || "0", data: data || "0x", gasLimit: gasLimit || undefined, userId: "system", // In a real implementation, this would come from authentication }); const transactionUrl = `${process.env.WEB_DAPP_URL}/tx/${transaction.id}`; return { content: [ { type: "text", text: `Transaction prepared successfully.\n\nTransaction URL: ${transactionUrl}\n\nPlease share this URL with the user to review and approve the transaction.`, }, ], }; } catch (error) { logger.error("Error in prepare-transaction tool:", error); return { content: [ { type: "text", text: `Error preparing transaction: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } } ); // Get transaction status server.tool( "get-transaction-status", "Get the current status of a transaction", { uuid: z.string().uuid().describe("Transaction UUID"), }, async ({ uuid }) => { try { const transaction = await getTransaction(uuid); if (!transaction) { return { content: [ { type: "text", text: `Transaction with UUID ${uuid} not found.`, }, ], isError: true, }; } return { content: [ { type: "text", text: JSON.stringify(transaction, null, 2), }, ], }; } catch (error) { logger.error("Error in get-transaction-status tool:", error); return { content: [ { type: "text", text: `Error fetching transaction: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } } ); logger.info("MCP tools initialized"); } // Start the MCP server export async function startMcpServer() { // Initialize tools initializeTools(); // Create transport const transport = new StdioServerTransport(); // Connect server to transport await server.connect(transport); logger.info("MCP Server started on stdio transport"); return server; }