本文介绍了如何使用Model Context Protocol(MCP)构建一个EVM MCP服务器,使大型语言模型(LLM)能够与多个EVM兼容区块链进行交互。该服务器允许LLM访问链上数据,从而为Web3自动化和分析开辟了新的可能性。文章详细说明了服务器的搭建步骤、关键代码,以及如何配置Claude桌面应用来测试该服务器,最后探讨了未来扩展EVM功能、构建AI特定增强功能的方向。
模型上下文协议 (MCP) 使得大型语言模型 (LLM) 能够使用标准化的基于消息的协议与外部工具(如 HTTP API、文件,甚至区块链)进行交互。可以将其视为 Claude 或 Cursor 等 agent 可以接入的“函数调用”接口,从而将脚本或服务转变为 AI 原生扩展。
在本指南中,你将学习如何构建和部署一个 MCP 服务器,该服务器使 LLM agent 能够跨多个 EVM 兼容网络访问区块链数据。这种强大的集成使 Claude 等 AI 模型能够直接与区块链数据交互,从而为 Web3 自动化和分析开辟新的可能性。
模型上下文协议(MCP)是一个开放标准,允许 AI agent 与外部工具和数据源进行交互。它在语言模型和工具之间创建了一个结构化的通信通道。
如果没有 MCP,AI 模型将仅限于:
MCP 通过创建 AI 访问外部工具和数据的标准化方式来弥补这一差距。
来源(针对我们正在构建的服务器进行了修改):Model Context Protocol
MCP 服务器负责处理来自客户端的传入请求并返回适当的数据。它们使用 MCP 协议与 LLM 通信,并且可以使用任何编程语言或框架来实现。
每个服务器都通过标准通道(stdio、HTTP 或 sockets)与 LLM 通信,并返回结构良好的输出。这允许 LLM 理解请求的上下文和意图,并做出适当的响应。MCP 协议被设计为可扩展的,允许开发人员根据需要添加新的工具、资源和提示。
工具:AI 可以调用以执行特定操作的函数。 它们可以是简单的 API 调用,也可以是复杂的任务。
eth_getBalance
用于检查钱包的余额weather_forecast
用于获取当前天气数据资源:AI 可以引用的静态知识。 这些会影响模型的依据和假设——例如,添加 gas-reference
资源可以帮助 Claude 了解特定链的 gas 价格是低、中等还是高,而无需调用任何工具。
提示:指导 AI 的预先编写的指令。 它们就像可重复使用的思考模板。 每个提示定义了 LLM 应如何使用工具和结构化推理来处理任务(例如,“分析此钱包”)。
当你将 MCP 服务器连接到像 Claude 这样的 AI 时:
以下是相同用户问题在使用和不使用 MCP 服务器的情况下的表现:
没有 MCP | 有 MCP | |
---|---|---|
用户 | “钱包 0x123... 的余额是多少?” | “钱包 0x123... 的余额是多少?” |
AI 进程 | 无法访问外部数据 | [通过 MCP 调用 eth_getBalance 工具] |
AI 响应 | “我无法访问当前的区块链数据,因此无法检查该钱包的余额。” | “该钱包目前在以太坊上持有 0.45 ETH。” |
结果 | ❌ 无法实现用户请求 | ✅ 提供实时区块链数据 |
以下是 agent 通过提示发出请求时发生的情况:
我们了解了 MCP 服务器的工作原理。 现在,让我们构建一个!
项目结构如下:
evm-mcp-server/
├── index.ts # 入口点,设置 MCP 服务器
├── chains.ts # 链配置 + QuickNode 端点映射
├── clients.ts # Viem public client 创建器
├── package.json # 依赖项和脚本
├── prompts.ts # LLM 提示定义
├── resources.ts # 外部参考(gas 价格,浏览器)
├── tools.ts # MCP 工具:getCode,getBalance,gasPrice
└── tsconfig.json # TypeScript 配置
首先,让我们看一下每个文件及其用途。 但是,如果你想直接跳转到代码,请随时跳到 构建你的 EVM MCP 服务器 部分。
项目代码
本指南的所有代码都可以在 QuickNode GitHub 存储库 中找到。 我们在这里通过提供代码的高级概述来解释事物的工作原理,但是我们将在 构建你的 MCP 服务器 部分中使用 GitHub 存储库。
index.ts
此文件是初始化并启动 MCP 服务器的入口点。 它注册工具、提示和资源:
index.ts 文件的一部分
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { registerTools } from './tools'
import { registerPrompts } from './prompts'
import { registerResources } from './resources'
async function main() {
try {
// 创建 MCP 服务器
const server = new McpServer({
name: 'EVM MCP Server',
version: '0.1.0',
description: 'A server for LLM agent to access EVM blockchain data', // 用于LLM agent 访问 EVM 区块链数据的服务器
})
// 注册所有工具、提示和资源
registerTools(server)
registerPrompts(server)
registerResources(server)
// 启动 MCP 服务器
const transport = new StdioServerTransport()
await server.connect(transport)
} catch (error) {
console.error('❌ Failed to start server:', error) // 启动服务器失败
process.exit(1)
}
}
// 运行 main 函数
main().catch(error => {
console.error('❌ Unhandled error:', error) // 未处理的错误
process.exit(1)
})
chains.ts
此文件定义区块链配置,并基于 QuickNode 的多链格式构建 RPC URL。
chains.ts 文件的一部分
// 从环境变量获取端点名称和Token ID
const QN_ENDPOINT_NAME = validateEnvVar('QN_ENDPOINT_NAME')
const QN_TOKEN_ID = validateEnvVar('QN_TOKEN_ID')
// 用于基于网络名称构建 QuickNode RPC URL 的函数
const buildRpcUrl = (networkName: string): string => {
// 以太坊主网的特殊情况
if (networkName === 'mainnet') {
return `https://${QN_ENDPOINT_NAME}.quiknode.pro/${QN_TOKEN_ID}/`
}
// Avalanche 主网的特殊情况
if (networkName === 'avalanche-mainnet') {
return `https://${QN_ENDPOINT_NAME}.${networkName}.quiknode.pro/${QN_TOKEN_ID}/ext/bc/C/rpc`
}
// 对于其他网络,在 URL 中包含网络名称
return `https://${QN_ENDPOINT_NAME}.${networkName}.quiknode.pro/${QN_TOKEN_ID}/`
}
export const CHAINS = {
ethereum: {
network: 'mainnet',
rpc: buildRpcUrl('mainnet'),
name: 'Ethereum',
symbol: 'ETH',
decimals: 18,
},
// 其他链...
}
// 代码的其余部分...
clients.ts
此文件使用 Viem 库中的 createPublicClient
函数为每个链创建客户端,以使用 QuickNode RPC 端点建立与链的连接。
clients.ts 文件的一部分
import { createPublicClient, http } from 'viem'
import { ChainId, getChain } from './chains'
// viem 客户端的缓存,以避免创建重复的客户端
const clientCache = new Map<ChainId, ReturnType<typeof createPublicClient>>()
export const getPublicClient = (chainId: ChainId) => {
// 如果存在则从缓存返回
if (clientCache.has(chainId)) {
return clientCache.get(chainId)!
}
// 获取链配置
const chain = getChain(chainId)
// 创建新的 public client
const client = createPublicClient({
transport: http(chain.rpc),
})
// 缓存以供将来使用
clientCache.set(chainId, client)
return client
}
prompts.ts
此文件定义 LLM agent 要使用的提示。 提示对象包含 description
和 messages
属性,MCP 服务器使用这些属性来生成提示。 提示包括用于调用工具、解释结果和格式化响应的说明。
prompts.ts 文件的一部分
// 注册 check-wallet 提示
server.prompt(
'check-wallet',
checkWalletSchema.shape,
({ address, chain }: { address: string; chain: string }) => ({
description: "Guide for analyzing a wallet's balance and context", // 用于分析钱包余额和上下文的指南
messages: [
{
role: 'user',
content: {
type: 'text',
text: `Please analyze this Ethereum wallet address: ${address} on ${chain} chain.
You need to analyze a wallet address on an EVM blockchain.
First, use the eth_getBalance tool to check the wallet's balance.
Next, use the eth_getCode tool to verify if it's a regular wallet or a contract.
Once you have this information, provide a summary of:
1. The wallet's address
2. The chain it's on
3. Its balance in the native token
4. Whether it's a regular wallet (EOA) or a contract
5. Any relevant observations about the balance (e.g., if it's empty, has significant funds, etc.)
Aim to be concise but informative in your analysis.`, // 请分析以太坊钱包地址:${address} 在 ${chain} 链上。 你需要在 EVM 区块链上分析钱包地址。 首先,使用 eth_getBalance 工具检查钱包的余额。 接下来,使用 eth_getCode 工具验证它是否是常规钱包或合约。 获得此信息后,请提供以下摘要: 1. 钱包的地址 2. 它所在的链 3. 其在本机Token中的余额 4. 它是常规钱包 (EOA) 还是合约 5. 关于余额的任何相关观察(例如,如果它是空的、有大量资金等) 旨在在你的分析中保持简洁但内容丰富。
},
},
],
})
)
// check-wallet 提示的 Schema
const checkWalletSchema = z.object({
address: z.string().refine(isAddress, {
message: 'Invalid Ethereum address format', // 以太坊地址格式无效
}),
chain: z
.string()
.refine((val): val is ChainId => Object.keys(CHAINS).includes(val), {
message:
'Unsupported chain. Use one of: ethereum, base, arbitrum, avalanche, bsc', // 不支持的链。 请使用以下链之一:ethereum、base、arbitrum、avalanche、bsc
}),
})
// 代码的其余部分...
resources.ts
此文件定义可由 LLM agent 使用的外部引用。 在这种情况下,我们让他们可以访问每个链的 gas 价格水平、每个链的区块浏览器链接以及有关链本身的一些详细信息。
resources.ts 文件的一部分
export const registerResources = (server: any) => {
// 注册 gas 参考资源
server.resource(
'gas-reference',
'evm://docs/gas-reference',
async (uri: URL) => {
return {
contents: [
{
uri: uri.href,
text: JSON.stringify(gasReferencePoints, null, 2),
},
],
}
}
)
// 其他资源...
}
// 每个链的 Gas 参考点
const gasReferencePoints = {
ethereum: {
low: 20,
average: 40,
high: 100,
veryHigh: 200,
},
base: {
low: 0.05,
average: 0.1,
high: 0.3,
veryHigh: 0.5,
},
// 其他链...
}
// 代码的其余部分...
tools.ts
此文件定义服务器可以调用的工具。 在这种情况下,我们使用 eth_getBalance
、eth_getCode
和 eth_gasPrice
工具。 每个工具都是一个异步函数,通过 Viem 查询区块链,并返回 LLM 使用的结构化数据。
tools.ts 文件的一部分
// 向 MCP 服务器注册工具
export const registerTools = (server: any) => {
// 注册 eth_getBalance 工具
server.tool(
'eth_getBalance',
balanceSchema.shape,
async (args: z.infer<typeof balanceSchema>) => {
try {
const result = await getBalance(args)
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
}
} catch (error) {
// 处理错误
}
}
)
// 其他工具...
}
// eth_getBalance 工具的 Schema
const balanceSchema = z.object({
address: z.string().refine(isAddress, {
message: 'Invalid Ethereum address format', // 以太坊地址格式无效
}),
chain: z
.string()
.refine((val): val is ChainId => Object.keys(CHAINS).includes(val), {
message:
'Unsupported chain. Use one of: ethereum, base, arbitrum, avalanche, bsc', // 不支持的链。 请使用以下链之一:ethereum、base、arbitrum、avalanche、bsc
}),
})
// 其他工具 Schema...
/**
* 获取指定链上以太坊地址的余额
*/
export const getBalance = async (params: z.infer<typeof balanceSchema>) => {
const { address, chain } = balanceSchema.parse(params)
try {
const client = getPublicClient(chain as ChainId)
const chainInfo = getChain(chain as ChainId)
// 获取 wei 中的余额
const balanceWei = await client.getBalance({ address })
// 将余额格式化为 ETH/本机Token
const balanceFormatted = formatEther(balanceWei)
return {
address,
chain: chainInfo.name,
balanceWei: balanceWei.toString(),
balanceFormatted: `${balanceFormatted} ${chainInfo.symbol}`,
symbol: chainInfo.symbol,
decimals: chainInfo.decimals,
}
} catch (error) {
return {
error: `Failed to get balance: ${(error as Error).message}`, // 无法获取余额
}
}
}
// 代码的其余部分...
我们了解了每个文件如何在 MCP 服务器中发挥作用。 现在,让我们构建并运行我们的服务器。
我们正在构建的 MCP 服务器将支持多个 EVM 链(以太坊、Base、Arbitrum、Avalanche 和 BSC)。 通过利用 QuickNode 的多链格式,我们可以使用单个端点轻松连接到这些链。 如果你没有 QuickNode 帐户,可以在 此处 免费获取一个。
https://{endpoint_name}.quiknode.pro/{token_id}/
或 https://{endpoint_name}.{chain_name}.quiknode.pro/{token_id}
。由于有了多链格式,我们现在可以使用单个端点连接到任何 EVM 链。 端点名称和Token ID 用于标识链及其配置。
现在我们有了端点,让我们设置我们的 MCP 服务器。
git clone https://github.com/quiknode-labs/qn-guide-examples.git
cd qn-guide-examples/AI/evm-mcp-server
npm install
这将安装项目所需的依赖项:
npm run build
这将生成包含已编译的 index.js
文件的 build/
目录,该文件用作服务器的入口点。
服务器使用在 Claude Desktop 的配置文件 (claude_desktop_config.json
) 中定义的环境变量来连接到 QuickNode 并运行服务器。
claude_desktop_config.json
以包含以下内容(如果存在其他配置,请在 mcpServers
下添加):{
"mcpServers": {
"evm": {
"command": "node",
"args": ["/absolute-path-to/build/index.js"],
"env": {
"QN_ENDPOINT_NAME": "your-quicknode-endpoint-name",
"QN_TOKEN_ID": "your-quicknode-token-id"
}
}
}
}
保存文件并重新启动 Claude Desktop。 你的 MCP 服务器的工具、资源和提示现在应该在 Claude 中可用。
让我们开始提问,看看服务器是如何工作的。
使用以下提示或使用我们预先构建的提示来指导 Claude 分析钱包的余额和上下文:
Give the balance of 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 on Ethereum
或者,要求 Claude 分析所有受支持链上的钱包余额:
Give the balance of 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 address across all networks you support.
克劳德会:
eth_getBalance
工具请注意,由于 supported-chains
资源,Claude 将仅针对其支持的链调用 eth_getBalance
工具。
使用以下提示或使用我们预先构建的提示来指导 Claude 检测钱包是否是合约:
What kind of contract is 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2?
克劳德会:
使用以下提示或使用我们预先构建的提示来指导 Claude 评估以太坊网络上的 gas 价格:
Analyze current gas price on Ethereum. Is now a good time to transact?
克劳德会:
eth_gasPrice
工具以从 QuickNode 获取当前 gas 价格gas-reference
资源,该资源包括每个链的低、中、高和非常高的 gas 价格的阈值恭喜! 你已经构建了你自己的 LLM 驱动的区块链数据分析工具。 现在,你可以使用 Claude 分析区块链数据并与之交互,使你能够构建智能应用程序并自动执行流程。
一旦你的 EVM MCP 服务器启动并运行,就可以通过多种方式扩展其功能并加深其与 AI 工作流程的集成。 以下是几个需要探索的领域:
添加更多本机 EVM 方法以增加服务器可以执行的操作:
eth_getLogs
:监视合约事件,例如Token转移或 DAO 投票eth_call
:读取智能合约数据(例如,Token余额、配置)eth_blockNumber
:获取最新的区块以进行链健康/状态跟踪eth_getTransactionByHash
:分析特定交易当 LLM agent 在特定于领域的推理和上下文的指导下,会变得更加有用。 考虑:
.eth
域名以获得更好的用户体验对于经常查询的数据,缓存可以减少延迟并避免重复的 RPC 调用:
eth_getBalance
和 eth_gasPrice
了解你的工具是如何使用的:
此数据可以帮助指导未来的改进,例如优化提示模板或扩展基础架构。
由于 MCP 服务器接受来自 agent 的输入,因此保护表面积非常重要:
EVM MCP 服务器在 LLM agent 和区块链数据之间提供了一个强大的桥梁。 通过实施模型上下文协议,我们创建了一个标准化接口,该接口允许 AI 模型访问和分析多个 EVM 兼容网络上的链上信息。
这个技术基础为 AI 驱动的区块链应用程序开辟了广阔的前景,从自动化监控到智能分析和推荐系统。 无论你是为分析师、dApp 还是内部自动化构建 agent,这都是你的基础——构建一次,然后在任何地方扩展它。
如果你有任何疑问或需要帮助,请随时在我们的 Discord 或 Twitter 上与我们联系。
如果你对新主题有任何反馈或请求,请告诉我们。 我们很乐意听到你的声音。
- 原文链接: quicknode.com/guides/ai/...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!