Skip to main content
Glama

EVM MCP Server

by JamesANZ
index.ts•22.8 kB
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import { ethers } from "ethers"; // Configuration const RPC_URL = process.env.RPC_URL || process.env.ETHEREUM_RPC_URL; const CHAIN_ID = process.env.CHAIN_ID ? parseInt(process.env.CHAIN_ID) : 1; // Default to Ethereum mainnet if (!RPC_URL) { console.error("Error: RPC_URL environment variable is required"); console.error("Example: RPC_URL=https://mainnet.infura.io/v3/YOUR_API_KEY"); console.error( "Or: RPC_URL=https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY", ); process.exit(1); } // Create provider const provider = new ethers.JsonRpcProvider(RPC_URL); const server = new McpServer({ name: "evm-mcp", version: "1.0.0", capabilities: { resources: {}, tools: {}, }, }); // Helper function to format responses function formatResponse(data: any, title: string): string { let result = `**${title}**\n\n`; if (typeof data === "object" && data !== null) { if (Array.isArray(data)) { // Handle arrays result += `**Count:** ${data.length}\n\n`; data.forEach((item, index) => { result += `**${index + 1}.**\n`; if (typeof item === "object" && item !== null) { result += formatObject(item, " "); } else { result += ` ${item}\n`; } result += "\n"; }); } else { // Handle objects result += formatObject(data, ""); } } else { result += `${data}\n`; } return result; } // Helper function to format objects recursively function formatObject(obj: any, indent: string): string { let result = ""; for (const [key, value] of Object.entries(obj)) { if (typeof value === "object" && value !== null) { if (Array.isArray(value)) { result += `${indent}**${key}:** [${value.length} items]\n`; if (value.length > 0 && value.length <= 10) { value.forEach((item, index) => { if (typeof item === "object" && item !== null) { result += `${indent} ${index}: ${JSON.stringify(item, null, 2).replace(/\n/g, "\n" + indent + " ")}\n`; } else { result += `${indent} ${index}: ${item}\n`; } }); } } else { result += `${indent}**${key}:**\n`; result += formatObject(value, indent + " "); } } else { result += `${indent}**${key}:** ${value}\n`; } } return result; } // Generic RPC call function async function makeRPCCall(method: string, params: any[] = []): Promise<any> { try { const result = await provider.send(method, params); return result; } catch (error: any) { throw new Error(`RPC call failed: ${error.message}`); } } // Core EVM RPC Methods server.tool( "web3_clientVersion", "Returns the current client version", {}, async () => { try { const result = await makeRPCCall("web3_clientVersion"); return { content: [ { type: "text", text: formatResponse(result, "Web3 Client Version"), }, ], }; } catch (error: any) { return { content: [ { type: "text", text: `Error: ${error.message}`, }, ], }; } }, ); server.tool( "web3_sha3", "Returns Keccak-256 hash of the given data", { data: z.string().describe("Data to hash (hex string starting with 0x)"), }, async ({ data }) => { try { const result = await makeRPCCall("web3_sha3", [data]); return { content: [ { type: "text", text: formatResponse(result, "Keccak-256 Hash"), }, ], }; } catch (error: any) { return { content: [ { type: "text", text: `Error: ${error.message}`, }, ], }; } }, ); server.tool( "eth_blockNumber", "Returns the number of the most recent block", {}, async () => { try { const result = await makeRPCCall("eth_blockNumber"); const blockNumber = parseInt(result, 16); return { content: [ { type: "text", text: formatResponse( { hex: result, decimal: blockNumber, timestamp: new Date().toISOString(), }, "Latest Block Number", ), }, ], }; } catch (error: any) { return { content: [ { type: "text", text: `Error: ${error.message}`, }, ], }; } }, ); server.tool( "eth_getBalance", "Returns the balance of the account of given address", { address: z.string().describe("Address to check balance for"), blockNumber: z .string() .optional() .default("latest") .describe("Block number or 'latest', 'earliest', 'pending'"), }, async ({ address, blockNumber }) => { try { const result = await makeRPCCall("eth_getBalance", [ address, blockNumber, ]); const balance = ethers.formatEther(result); return { content: [ { type: "text", text: formatResponse( { address, balance_wei: result, balance_eth: balance, block: blockNumber, }, "Account Balance", ), }, ], }; } catch (error: any) { return { content: [ { type: "text", text: `Error: ${error.message}`, }, ], }; } }, ); server.tool( "eth_getTransactionCount", "Returns the number of transactions sent from an address", { address: z.string().describe("Address to check transaction count for"), blockNumber: z .string() .optional() .default("latest") .describe("Block number or 'latest', 'earliest', 'pending'"), }, async ({ address, blockNumber }) => { try { const result = await makeRPCCall("eth_getTransactionCount", [ address, blockNumber, ]); const nonce = parseInt(result, 16); return { content: [ { type: "text", text: formatResponse( { address, nonce_hex: result, nonce_decimal: nonce, block: blockNumber, }, "Transaction Count (Nonce)", ), }, ], }; } catch (error: any) { return { content: [ { type: "text", text: `Error: ${error.message}`, }, ], }; } }, ); server.tool( "eth_getBlockByNumber", "Returns information about a block by block number", { blockNumber: z .string() .describe("Block number (hex) or 'latest', 'earliest', 'pending'"), includeTransactions: z .boolean() .optional() .default(false) .describe("Include full transaction objects"), }, async ({ blockNumber, includeTransactions }) => { try { const result = await makeRPCCall("eth_getBlockByNumber", [ blockNumber, includeTransactions, ]); if (!result) { return { content: [ { type: "text", text: `Block not found: ${blockNumber}`, }, ], }; } const blockInfo = { number: result.number, hash: result.hash, parentHash: result.parentHash, timestamp: result.timestamp, gasLimit: result.gasLimit, gasUsed: result.gasUsed, transactionCount: result.transactions.length, baseFeePerGas: result.baseFeePerGas, }; return { content: [ { type: "text", text: formatResponse(blockInfo, "Block Information"), }, ], }; } catch (error: any) { return { content: [ { type: "text", text: `Error: ${error.message}`, }, ], }; } }, ); server.tool( "eth_getTransactionByHash", "Returns the information about a transaction requested by transaction hash", { txHash: z.string().describe("Transaction hash"), }, async ({ txHash }) => { try { const result = await makeRPCCall("eth_getTransactionByHash", [txHash]); if (!result) { return { content: [ { type: "text", text: `Transaction not found: ${txHash}`, }, ], }; } return { content: [ { type: "text", text: formatResponse(result, "Transaction Information"), }, ], }; } catch (error: any) { return { content: [ { type: "text", text: `Error: ${error.message}`, }, ], }; } }, ); server.tool( "eth_getTransactionReceipt", "Returns the receipt of a transaction by transaction hash", { txHash: z.string().describe("Transaction hash"), }, async ({ txHash }) => { try { const result = await makeRPCCall("eth_getTransactionReceipt", [txHash]); if (!result) { return { content: [ { type: "text", text: `Transaction receipt not found: ${txHash}`, }, ], }; } return { content: [ { type: "text", text: formatResponse(result, "Transaction Receipt"), }, ], }; } catch (error: any) { return { content: [ { type: "text", text: `Error: ${error.message}`, }, ], }; } }, ); server.tool( "eth_call", "Executes a new message call immediately without creating a transaction", { to: z.string().describe("Contract address"), data: z.string().describe("Data to send (hex string)"), blockNumber: z .string() .optional() .default("latest") .describe("Block number or 'latest', 'earliest', 'pending'"), from: z.string().optional().describe("From address (optional)"), value: z.string().optional().describe("Value in wei (optional)"), gas: z.string().optional().describe("Gas limit (optional)"), gasPrice: z.string().optional().describe("Gas price (optional)"), }, async ({ to, data, blockNumber, from, value, gas, gasPrice }) => { try { const txObject: any = { to, data, }; if (from) txObject.from = from; if (value) txObject.value = value; if (gas) txObject.gas = gas; if (gasPrice) txObject.gasPrice = gasPrice; const result = await makeRPCCall("eth_call", [txObject, blockNumber]); return { content: [ { type: "text", text: formatResponse( { result, to, data, block: blockNumber, }, "Contract Call Result", ), }, ], }; } catch (error: any) { return { content: [ { type: "text", text: `Error: ${error.message}`, }, ], }; } }, ); server.tool( "eth_estimateGas", "Generates and returns an estimate of how much gas is necessary", { to: z .string() .optional() .describe("Contract address (optional for contract creation)"), data: z.string().optional().describe("Data to send (hex string)"), from: z.string().optional().describe("From address"), value: z.string().optional().describe("Value in wei"), gas: z.string().optional().describe("Gas limit"), gasPrice: z.string().optional().describe("Gas price"), }, async ({ to, data, from, value, gas, gasPrice }) => { try { const txObject: any = {}; if (to) txObject.to = to; if (data) txObject.data = data; if (from) txObject.from = from; if (value) txObject.value = value; if (gas) txObject.gas = gas; if (gasPrice) txObject.gasPrice = gasPrice; const result = await makeRPCCall("eth_estimateGas", [txObject]); const gasEstimate = parseInt(result, 16); return { content: [ { type: "text", text: formatResponse( { gas_estimate_hex: result, gas_estimate_decimal: gasEstimate, transaction_object: txObject, }, "Gas Estimate", ), }, ], }; } catch (error: any) { return { content: [ { type: "text", text: `Error: ${error.message}`, }, ], }; } }, ); server.tool( "eth_sendRawTransaction", "Creates new message call transaction or a contract creation", { signedTransactionData: z .string() .describe("Signed transaction data (hex string)"), }, async ({ signedTransactionData }) => { try { const result = await makeRPCCall("eth_sendRawTransaction", [ signedTransactionData, ]); return { content: [ { type: "text", text: formatResponse( { transaction_hash: result, status: "Transaction submitted successfully", }, "Raw Transaction Sent", ), }, ], }; } catch (error: any) { return { content: [ { type: "text", text: `Error: ${error.message}`, }, ], }; } }, ); server.tool( "eth_gasPrice", "Returns the current price per gas in wei", {}, async () => { try { const result = await makeRPCCall("eth_gasPrice"); const gasPrice = parseInt(result, 16); const gasPriceGwei = ethers.formatUnits(gasPrice, "gwei"); return { content: [ { type: "text", text: formatResponse( { gas_price_hex: result, gas_price_wei: gasPrice.toString(), gas_price_gwei: gasPriceGwei, }, "Current Gas Price", ), }, ], }; } catch (error: any) { return { content: [ { type: "text", text: `Error: ${error.message}`, }, ], }; } }, ); server.tool( "eth_getCode", "Returns code at a given address", { address: z.string().describe("Contract address"), blockNumber: z .string() .optional() .default("latest") .describe("Block number or 'latest', 'earliest', 'pending'"), }, async ({ address, blockNumber }) => { try { const result = await makeRPCCall("eth_getCode", [address, blockNumber]); return { content: [ { type: "text", text: formatResponse( { address, code: result, code_length: result.length, block: blockNumber, }, "Contract Code", ), }, ], }; } catch (error: any) { return { content: [ { type: "text", text: `Error: ${error.message}`, }, ], }; } }, ); server.tool( "eth_getStorageAt", "Returns the value from a storage position at a given address", { address: z.string().describe("Contract address"), position: z.string().describe("Storage position (hex string)"), blockNumber: z .string() .optional() .default("latest") .describe("Block number or 'latest', 'earliest', 'pending'"), }, async ({ address, position, blockNumber }) => { try { const result = await makeRPCCall("eth_getStorageAt", [ address, position, blockNumber, ]); return { content: [ { type: "text", text: formatResponse( { address, position, value: result, block: blockNumber, }, "Storage Value", ), }, ], }; } catch (error: any) { return { content: [ { type: "text", text: `Error: ${error.message}`, }, ], }; } }, ); server.tool( "eth_getLogs", "Returns an array of all logs matching a given filter object", { fromBlock: z .string() .optional() .describe("Starting block (hex or 'latest', 'earliest', 'pending')"), toBlock: z .string() .optional() .describe("Ending block (hex or 'latest', 'earliest', 'pending')"), address: z.string().optional().describe("Contract address (optional)"), topics: z .array(z.string()) .optional() .describe("Array of topic filters (optional)"), }, async ({ fromBlock, toBlock, address, topics }) => { try { const filter: any = {}; if (fromBlock) filter.fromBlock = fromBlock; if (toBlock) filter.toBlock = toBlock; if (address) filter.address = address; if (topics) filter.topics = topics; const result = await makeRPCCall("eth_getLogs", [filter]); // Format logs specially for better readability let logText = `**Event Logs**\n\n`; logText += `**Total Logs:** ${result.length}\n`; logText += `**Filter:** ${JSON.stringify(filter, null, 2)}\n\n`; if (result.length > 0) { logText += `**Logs:**\n\n`; result.forEach((log: any, index: number) => { logText += `**Log ${index + 1}:**\n`; logText += ` **Address:** ${log.address}\n`; logText += ` **Block:** ${log.blockNumber} (${parseInt(log.blockNumber, 16)})\n`; logText += ` **Transaction:** ${log.transactionHash}\n`; logText += ` **Log Index:** ${log.logIndex}\n`; logText += ` **Topics:** [${log.topics.length} topics]\n`; if (log.topics && log.topics.length > 0) { log.topics.forEach((topic: string, topicIndex: number) => { logText += ` ${topicIndex}: ${topic}\n`; }); } if (log.data && log.data !== "0x") { logText += ` **Data:** ${log.data}\n`; } logText += ` **Removed:** ${log.removed}\n\n`; }); } else { logText += `No logs found matching the filter criteria.\n`; } return { content: [ { type: "text", text: logText, }, ], }; } catch (error: any) { return { content: [ { type: "text", text: `Error: ${error.message}`, }, ], }; } }, ); server.tool( "eth_chainId", "Returns the chain ID of the current network", {}, async () => { try { const result = await makeRPCCall("eth_chainId"); const chainId = parseInt(result, 16); const chainNames: { [key: number]: string } = { 1: "Ethereum Mainnet", 11155111: "Sepolia Testnet", 5: "Goerli Testnet", 137: "Polygon Mainnet", 80001: "Polygon Mumbai Testnet", 42161: "Arbitrum One", 421614: "Arbitrum Sepolia", 10: "Optimism", 420: "Optimism Sepolia", 56: "BNB Smart Chain", 97: "BNB Smart Chain Testnet", }; return { content: [ { type: "text", text: formatResponse( { chain_id_hex: result, chain_id_decimal: chainId, chain_name: chainNames[chainId] || "Unknown Network", }, "Network Chain ID", ), }, ], }; } catch (error: any) { return { content: [ { type: "text", text: `Error: ${error.message}`, }, ], }; } }, ); server.tool("net_version", "Returns the current network id", {}, async () => { try { const result = await makeRPCCall("net_version"); return { content: [ { type: "text", text: formatResponse( { network_id: result, }, "Network Version", ), }, ], }; } catch (error: any) { return { content: [ { type: "text", text: `Error: ${error.message}`, }, ], }; } }); server.tool( "net_listening", "Returns true if client is actively listening for network connections", {}, async () => { try { const result = await makeRPCCall("net_listening"); return { content: [ { type: "text", text: formatResponse( { is_listening: result, }, "Network Status", ), }, ], }; } catch (error: any) { return { content: [ { type: "text", text: `Error: ${error.message}`, }, ], }; } }, ); server.tool( "net_peerCount", "Returns number of peers currently connected to the client", {}, async () => { try { const result = await makeRPCCall("net_peerCount"); const peerCount = parseInt(result, 16); return { content: [ { type: "text", text: formatResponse( { peer_count_hex: result, peer_count_decimal: peerCount, }, "Connected Peers", ), }, ], }; } catch (error: any) { return { content: [ { type: "text", text: `Error: ${error.message}`, }, ], }; } }, ); async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error(`EVM MCP Server running on stdio`); console.error(`Connected to: ${RPC_URL}`); console.error(`Chain ID: ${CHAIN_ID}`); } main().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/JamesANZ/evm-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server