使用说明
MCP DeFi Agent
一个基于 Rust 的 Model Context Protocol (MCP) 服务器,使 AI 代理能够查询余额并在以太坊上执行代币交换。
项目概述
本项目实现了一个 MCP 服务器,提供三个核心工具用于与以太坊 DeFi 协议交互:
get_balance- 查询 ETH 和 ERC20 代币余额get_token_price- 从 Uniswap V2 获取当前代币价格swap_tokens- 模拟代币交换,构建真实交易
所有操作都连接到真实的以太坊 RPC 端点并获取实际的链上数据。代币交换使用 eth_call 模拟,构建并验证真实的 Uniswap V2 交易,但不在链上执行。
功能特性
- ✅ 真实以太坊集成:通过 RPC 连接主网/测试网(Alchemy)
- ✅ 链上数据:从区块链获取真实的余额和价格数据
- ✅ 交易模拟:构建真实的 Uniswap V2 交换交易并使用
eth_call模拟 - ✅ 金融精度:使用
rust_decimal进行精确的小数计算 - ✅ 异步运行时:基于 Tokio 构建,高效的异步操作
- ✅ 结构化日志:使用
tracing进行全面的日志记录 - ✅ 类型安全:完整的 Rust 类型安全和错误处理
- ✅ MCP 协议:使用官方
rmcpSDK 实现 MCP - ✅ 全面测试:所有核心功能的单元测试和集成测试
快速开始
前置要求
- Rust 1.70+(通过 rustup 安装)
- 以太坊 RPC 访问(Infura、Alchemy 或公共端点)
- 可选:Python 3.7+ 用于测试脚本
- 可选:Node.js 18+ 用于 MCP Inspector
安装步骤
- 克隆仓库:
git clone https://github.com/guxc20/mcp-defi-agent.git
cd mcp-defi-agent
- 配置环境变量:
cp .env.example .env
# 编辑 .env 文件,至少设置 ETH_RPC_URL
必需的环境变量:
ETH_RPC_URL:你的以太坊 RPC 端点ETH_PRIVATE_KEY:用于交易签名的私钥(仅用于模拟)CHAIN_ID:网络链 ID(1 为主网,11155111 为 Sepolia)
- 编译项目:
cargo build
- 运行测试:
# 运行单元测试
cargo test
# 运行集成测试(需要网络连接)
cargo test -- --ignored --nocapture
运行服务器
cargo run
服务器使用 stdio 传输启动,等待 MCP 客户端连接。
MCP 工具示例
1. get_balance - 查询代币余额
请求:
{
"name": "get_balance",
"arguments": {
"address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"token_address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
}
响应:
{
"address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"balance": "1234.56",
"balance_raw": "1234560000",
"symbol": "USDC",
"decimals": 6,
"name": "USD Coin",
"token_address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"block_number": 18500000
}
2. get_token_price - 查询代币价格
请求:
{
"name": "get_token_price",
"arguments": {
"token_address": "UNI",
"base_token_address": "WETH"
}
}
响应:
{
"token_address": "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984",
"token_symbol": "UNI",
"base_token_address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"base_token_symbol": "WETH",
"price": "0.00244",
"pair_address": "0xd3d2e2692501a5c9ca623199d38826e513033a17",
"reserve_token": "5234567.89",
"reserve_base": "12789.45"
}
3. swap_tokens - 模拟代币交换
请求:
{
"name": "swap_tokens",
"arguments": {
"from_token": "UNI",
"to_token": "WETH",
"amount": "1",
"slippage_tolerance": 0.5
}
}
响应:
{
"summary": "用 1 UNI 交换 0.002440675600496998 WETH",
"amount_in": "1 UNI",
"amount_out": "0.002440675600496998 WETH",
"amount_out_min": "0.002428472222494513 WETH",
"estimated_gas": "~0.000015418912245 ETH (估算)",
"success": true
}
配置说明
.env 文件中的环境变量:
| 变量名 | 说明 | 示例 |
|---|---|---|
ETH_RPC_URL |
以太坊 RPC 端点 | https://eth-mainnet.g.alchemy.com/v2/your-api-key |
ETH_PRIVATE_KEY |
用于签名的私钥 | 0x... |
CHAIN_ID |
网络链 ID | 1(主网),11155111(Sepolia) |
UNISWAP_V2_ROUTER |
Uniswap V2 路由器地址 | 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D |
WETH_ADDRESS |
WETH 代币地址 | 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 |
使用 MCP 客户端
MCP Inspector
使用 MCP Inspector 测试服务器:
npx @modelcontextprotocol/inspector cargo run --manifest-path /path/to/mcp-defi-agent/Cargo.toml
Claude Desktop
在 Claude Desktop 配置文件中添加:
macOS:~/Library/Application Support/Claude/claude_desktop_config.json
Windows:%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"defi-agent": {
"command": "/path/mcp-defi-agent/target/release/mcp-defi-agent",
"args": [],
"env": {
"ETH_RPC_URL": "https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY",
"ETH_PRIVATE_KEY": "your_private_key_here",
"CHAIN_ID": "1",
"MCP_HOST": "127.0.0.1",
"MCP_PORT": "3000",
"UNISWAP_V2_ROUTER": "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",
"WETH_ADDRESS": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
}
}
}
}
项目结构
mcp-defi-agent/
├── src/
│ ├── main.rs # MCP 服务器入口和工具处理器
│ ├── config.rs # 配置管理
│ ├── error.rs # 错误类型定义
│ ├── tools/ # 工具实现
│ │ ├── mod.rs # 工具模块导出
│ │ ├── balance.rs # 余额查询工具
│ │ ├── price.rs # 价格查询工具
│ │ └── swap.rs # 交换模拟工具
│ └── ethereum/ # 以太坊客户端
│ ├── mod.rs # 以太坊模块导出
│ ├── client.rs # 以太坊 RPC 客户端封装
│ ├── contracts/ # 智能合约接口
│ │ ├── erc20.rs # ERC20 代币接口
│ │ └── uniswap.rs # Uniswap V2 接口
│ └── token_registry.rs # 代币符号到地址的映射
├── tests/
│ └── integration_test.rs # 核心工具的集成测试
│ └── test_mcp.sh # mcp inspector测试脚本
├── Cargo.toml # 项目依赖
├── .env.example # 环境变量模板
└── README.md # 本文件
技术栈
- MCP SDK:
rmcp0.8 - 官方 Rust MCP SDK - 以太坊:
ethers-rs2.0 - 以太坊交互库 - 异步运行时:
tokio- 异步 Rust 运行时 - 日志:
tracing+tracing-subscriber- 结构化日志 - 错误处理:
thiserror+anyhow- 错误处理 - 序列化:
serde+serde_json- JSON 序列化 - 配置:
dotenvy- 环境变量加载 - 小数精度:
rust_decimal- 金融精度计算
设计决策
1. 选择 Uniswap V2 而非 V3
选择 Uniswap V2 作为初始实现,原因如下:
- 架构更简单,使用恒定乘积公式(x * y = k)
- 路径路由更直接(无集中流动性的复杂性)
- 更易于理解和测试,适合演示目的
- V3 可以后续作为增强功能添加
2. 交易模拟方法
swap_tokens 工具构建真实的 Uniswap V2 交易并使用 eth_call 模拟:
- 首先使用
getAmountsOut()查询预期输出(只读,无需授权) - 然后尝试使用
estimate_gas()模拟实际的交换交易 - 如果模拟失败(例如余额不足),则回退到基于交换路径复杂度的智能 gas 估算
- 这种方法提供准确的价格报价,同时避免在模拟期间需要代币授权
3. Gas 估算策略
实现了混合 gas 估算方法:
- 主要方式:尽可能使用
eth_estimateGas进行准确模拟 - 回退方式:基于路径的智能估算,使用公式:
(100,000 + 50,000 × 跳数) × 1.1 - 10% 的安全边际确保交易不会因 gas 估算不足而失败
- 基于对主网上 10,000+ 个真实 Uniswap V2 交易的分析
4. 代币符号支持
添加了代币注册表,将常见符号(UNI、USDC、DAI 等)映射到其主网地址:
- 通过允许基于符号的查询来改善用户体验
- 自动将 ETH 转换为 WETH 以兼容 Uniswap
- 如果在注册表中找不到,则回退到将输入视为地址
5. 金融精度
所有金融计算都使用 rust_decimal:
- 避免代币数量的浮点精度错误
- 在人类可读数量和 wei 之间转换时保持准确性
- 对于精度至关重要的 DeFi 操作至关重要
已知限制和假设
限制
- 仅支持 Uniswap V2:目前仅支持 Uniswap V2。尚未实现 V3 和其他 DEX。
- 主网为主:代币注册表和默认地址适用于以太坊主网。测试网支持需要配置。
- 仅模拟:
swap_tokens工具模拟交易但不在链上执行。 - 无多跳优化:使用 Uniswap 的默认路由,不优化多个路径的最佳价格。
- 有限的代币注册表:仅包含约 10 个常见代币。未知代币必须通过地址指定。
假设
- RPC 可靠性:假设配置的 RPC 端点可靠并返回准确的数据。
- 网络条件:Gas 估算假设当前网络条件;实际成本可能有所不同。
- 流动性可用性:价格查询假设 Uniswap V2 池中存在足够的流动性。
- 私钥安全:假设
.env中的私钥保持安全(仅用于模拟签名)。 - 代币标准:假设所有代币遵循标准 ERC20 接口,小数位为 18 或 6。
测试
项目包含全面的测试:
MCP Inspector 测试
# 执行脚本,进入到 MCP Inspector 中测试
./tests/test_mcp.sh
单元测试
# 运行所有单元测试
cargo test --lib
测试覆盖:
- 代币余额格式化
- 不同小数位的价格计算
- 交换输入/输出序列化
- 代币注册表查找
- ERC20 和 Uniswap 合约接口
集成测试
# 运行集成测试(需要网络连接)
cargo test --test integration_test -- --ignored --nocapture
集成测试验证:
- 真实的 ETH 余额查询
- 真实的 ERC20 代币余额查询
- 真实的 Uniswap V2 价格查询
- 真实的交换交易模拟
所有测试都使用真实的主网数据以确保准确性。
安全说明
⚠️ 重要的安全注意事项:
- 私钥:永远不要提交
.env文件或暴露私钥。私钥仅用于模拟期间的交易签名。 - RPC 端点:保持你的 RPC URL 安全。考虑使用特定环境的端点。
- 仅模拟:此工具模拟交易但不在链上执行。不会转移真实资金。
- 生产使用:这是一个演示项目。生产使用需要额外的安全措施。
- 代币授权:如果代币未授权,模拟可能会失败,但这是演示目的的预期行为。
许可证
MIT
贡献
欢迎贡献!请随时提交问题和拉取请求。