Skip to main content
Glama

Ethereum MCP Server

mcp.rs11.7 kB
use crate::ethereum::EthereumService; use crate::types::*; use anyhow::Result; use rmcp::handler::server::ServerHandler; use rmcp::service::{RequestContext, RoleServer}; use rmcp::{ErrorData, ServiceExt}; use rmcp::schemars; use std::str::FromStr; use std::sync::Arc; #[derive(Clone)] pub struct MCPServer { ethereum: Arc<EthereumService>, } #[derive(Debug, serde::Serialize,serde::Deserialize, schemars::JsonSchema)] #[serde(rename_all = "camelCase")] pub struct QueryBalanceParams { #[schemars(description = "Ethereum address to query")] pub address: String, #[schemars(description = "Optional ERC-20 token address. If not provided, returns ETH balance")] pub token_address: Option<String>, } #[derive(Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] #[serde(rename_all = "camelCase")] pub struct BuildSwapTransactionParams { #[schemars(description = "Address of the token to swap from (default: USDT)")] pub from_token: Option<String>, #[schemars(description = "Address of the token to swap to (default: WETH)")] pub to_token: Option<String>, #[schemars(description = "Amount of from_token to swap (default: 0.0001)")] pub amount_in: Option<String>, #[schemars(description = "Maximum acceptable slippage percentage")] pub slippage_tolerance: Option<f64>, #[schemars(description = "Address to receive the swapped tokens")] pub recipient: Option<String>, } #[derive(Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] pub struct GetTokenPriceParams { #[schemars(description = "Token address or symbol (e.g., 'ETH', 'USDC', or '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48')")] pub token: String, #[schemars(description = "Currency to return price in (e.g., 'USD', 'ETH'). Defaults to 'USD'")] pub currency: Option<String>, } impl ServerHandler for MCPServer { async fn ping(&self, _context: RequestContext<RoleServer>) -> Result<(), ErrorData> { tracing::info!("Received ping request"); Ok(()) } async fn initialize( &self, _request: rmcp::model::InitializeRequestParam, _context: RequestContext<RoleServer>, ) -> Result<rmcp::model::InitializeResult, ErrorData> { tracing::info!("Initializing MCP server"); Ok(rmcp::model::InitializeResult { protocol_version: rmcp::model::ProtocolVersion::V_2024_11_05, capabilities: rmcp::model::ServerCapabilities { experimental: None, logging: None, prompts: None, resources: None, tools: Some(rmcp::model::ToolsCapability { list_changed: Some(true), }), completions: None, }, server_info: rmcp::model::Implementation { name: "test1".to_string(), title: None, version: "abcde".to_string(), icons: None, website_url: None, }, instructions: None, }) } async fn list_tools( &self, _request: Option<rmcp::model::PaginatedRequestParam>, _context: RequestContext<RoleServer>, ) -> Result<rmcp::model::ListToolsResult, ErrorData> { tracing::info!("Listing available tools"); let query_balance_schema = Arc::new(serde_json::json!({ "type": "object", "properties": { "address": { "type": "string", "description": "Ethereum address to query" }, "tokenAddress": { "type": "string", "description": "Optional ERC-20 token address. If not provided, returns ETH balance" } }, "required": ["address"] }).as_object().unwrap().clone()); let swap_tx_schema = Arc::new(serde_json::json!({ "type": "object", "properties": { "fromToken": { "type": "string", "description": "Address of the token to swap from (default: USDT)" }, "toToken": { "type": "string", "description": "Address of the token to swap to (default: WETH)" }, "amountIn": { "type": "string", "description": "Amount of fromToken to swap (default: 1.0)" }, "slippageTolerance": { "type": "number", "description": "Maximum acceptable slippage percentage (default: 0.5%)" }, "recipient": { "type": "string", "description": "Address to receive the swapped tokens (default: signer)" } }, "required": [] }).as_object().unwrap().clone()); let token_price_schema = Arc::new(serde_json::json!({ "type": "object", "properties": { "token": { "type": "string", "description": "Token address or symbol (e.g., 'ETH', 'USDC', or '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48')" }, "currency": { "type": "string", "description": "Currency to return price in (e.g., 'USD', 'ETH'). Defaults to 'USD'" } }, "required": ["token"] }).as_object().unwrap().clone()); let tools = vec![ rmcp::model::Tool { name: "query_balance".into(), title: None, description: Some("Query ETH or ERC-20 token balance for an Ethereum address".into()), input_schema: query_balance_schema, output_schema: None, annotations: None, icons: None, }, rmcp::model::Tool { name: "build_swap_transaction".into(), title: None, description: Some("Build a transaction for executing a token swap".into()), input_schema: swap_tx_schema, output_schema: None, annotations: None, icons: None, }, rmcp::model::Tool { name: "get_token_price".into(), title: None, description: Some("Get current token price in USD or ETH".into()), input_schema: token_price_schema, output_schema: None, annotations: None, icons: None, }, ]; Ok(rmcp::model::ListToolsResult { tools, next_cursor: None, }) } async fn call_tool( &self, request: rmcp::model::CallToolRequestParam, _context: RequestContext<RoleServer>, ) -> Result<rmcp::model::CallToolResult, ErrorData> { tracing::info!("Calling tool: {}", request.name); match request.name.as_ref() { "query_balance" => { // Convert JsonObject to serde_json::Value let params: QueryBalanceParams = serde_json::from_value(serde_json::Value::Object(request.arguments.unwrap())) .map_err(|e| ErrorData::invalid_params(e.to_string(), None))?; let balance_params = BalanceQuery { address: params.address, token_address: params.token_address, }; let balance = self.ethereum.query_balance(&balance_params).await .map_err(|e| ErrorData::internal_error(e.to_string(), None))?; Ok(rmcp::model::CallToolResult { content: vec![rmcp::model::Content { raw: rmcp::model::RawContent::text(balance), annotations: None, }], structured_content: None, is_error: Some(false), meta: None, }) } "build_swap_transaction" => { // Convert JsonObject to serde_json::Value let params: BuildSwapTransactionParams = serde_json::from_value(serde_json::Value::Object(request.arguments.unwrap())) .map_err(|e| ErrorData::invalid_params(e.to_string(), None))?; // Apply default values let from_token = params.from_token.unwrap_or_else(|| "0xdAC17F958D2ee523a2206206994597C13D831ec7".to_string()); let to_token = params.to_token.unwrap_or_else(|| "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".to_string()); let amount_in = params.amount_in.unwrap_or_else(|| "1".to_string()); let swap_params = SwapParams { from_token, to_token, amount_in, slippage_tolerance: params.slippage_tolerance.map(|s| rust_decimal::Decimal::from_str(&s.to_string()).unwrap_or(rust_decimal::Decimal::ZERO)), recipient: params.recipient, }; self.ethereum.validate_swap_params(&swap_params) .map_err(|e| ErrorData::invalid_params(e.to_string(), None))?; let tx = self.ethereum.build_swap_transaction(&swap_params).await .map_err(|e| ErrorData::internal_error(e.to_string(), None))?; Ok(rmcp::model::CallToolResult { content: vec![rmcp::model::Content { raw: rmcp::model::RawContent::text(tx), annotations: None, }], structured_content: None, is_error: Some(false), meta: None, }) } "get_token_price" => { let params: GetTokenPriceParams = serde_json::from_value(serde_json::Value::Object(request.arguments.unwrap())) .map_err(|e| ErrorData::invalid_params(e.to_string(), None))?; let price = self.ethereum.get_token_price(&params.token, params.currency.as_deref()).await .map_err(|e| ErrorData::internal_error(e.to_string(), None))?; Ok(rmcp::model::CallToolResult { content: vec![rmcp::model::Content { raw: rmcp::model::RawContent::text(price), annotations: None, }], structured_content: None, is_error: Some(false), meta: None, }) } _ => Ok(rmcp::model::CallToolResult { content: vec![rmcp::model::Content { raw: rmcp::model::RawContent::text("None"), annotations: None, }], structured_content: None, is_error: Some(true), meta: None, }), } } } impl MCPServer { pub async fn new(rpc_url: Option<String>) -> Result<Self> { Ok(Self { ethereum: Arc::new(EthereumService::init(rpc_url).await?), }) } pub async fn run(self) -> Result<()> { let stdin = tokio::io::stdin(); let stdout = tokio::io::stdout(); let transport = (stdin, stdout); let services = self.serve(transport).await.expect("Serve failed"); services.waiting().await?; Ok(()) } }

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/Joshuashen022/ethereum-mcp-server'

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