Rust AI Agents 开发:MCP(Model Context Protocol)详解笔记
MCP 通过标准化工具发现(List Tools)与调用(Call Tool),把模型与工具集成的 M×N 工作量降为 M+N,并用 Host/Client/Server 三角色和 STDIO/HTTP/WebSocket 传输支撑可组合的 Agent 工具生态。
1. 背景:工具集成为什么需要标准化
上一集手写了一个 web search tool。完成后自然会产生疑问:每新增一个工具,是否都要像这样重新写一遍集成代码?原文提到,上一集结尾提出了四个与“写工具”相关的痛点,但本集没有逐条展开;本集的重点是说明如何通过标准化解决这些问题。
在没有 MCP 之前,如果想把三个不同模型接到四个不同工具上,通常会写出几十套互不相同的胶水代码。原因在于:
- 每个模型的工具调用格式不同。
- 每个工具的认证参数和返回格式也不同。
- 当模型数量乘以工具数量增长时,集成工作量会急剧上升。
- 这就是所谓的 M×N 问题。
MCP 的作用类似 HTTP 对 Web 的作用:
- 过去用 HTTP 浏览器,不需要为每个网站单独设计一套协议。
- 现在有了 MCP,Agent 不需要为每个工具写单独的集成代码。
- 只要模型和工具双方都说 MCP 这门共同语言,就可以自由组合。
- 原来的 M×N 工作量变成 M+N。
2. MCP 核心架构:三个角色
MCP 的核心架构很简单,只包含三个角色:Host、Client、Server。
| 角色 | 定位 | 职责 | 例子或说明 |
|---|---|---|---|
| Host | 大脑 | 直接与用户交互,运行大语言模型,理解用户意图,进行推理,决定是否使用工具以及使用哪个工具 | Cloud Desktop(原文如此,可能指 Claude Desktop)、Codex、Google 的 Anti gravity(原文如此)、自己写的 agent 程序 |
| Client | 桥 | 当 Host 决定使用工具时,实际与 Server 对接;每个 Client 只维护到一个 Server 的连接;负责询问工具列表、转发执行请求、把结果带回 Host | 连接 Host 与某个 Server |
| Server | 实际工作处 | 工具实际在 Server 中运行;只回答两个问题:有哪些工具、如何执行工具 | 背后可以是数据库、内部系统或任何外部 API |
2.1 Host 的职责
Host 是直接面向用户的应用,运行 LLM,负责:
- 理解用户说了什么。
- 判断是否要调用工具。
- 判断要调用哪个工具。
2.2 Client 的职责
MCP Client 是“桥”,在 Host 决定使用工具时与 Server 交互。每个 Client 只保持到一个 Server 的连接。它主要做两件事:
- 询问 Server:“你有哪些工具?”
- 把具体执行请求发送给 Server,再把 Server 的结果带回 Host。
2.3 Server 的职责
MCP Server 是工具实际运行的地方。它只回答两个问题:
- 你有哪些工具?返回工具名称、描述和参数格式。
- 执行那个工具。进行实际计算并返回结果。
Server 背后可以是数据库、内部系统或任何外部 API。
3. 两个标准化接口与传输方式
3.1 工具发现:List Tools
MCP Client 调用 Server 的 List Tools,Server 返回它有哪些工具以及参数长什么样,格式统一。
3.2 工具执行:Call Tool
MCP Client 调用 Call Tool,传入工具名称和参数;Server 运行工具并把结果发回。
以前这两层都要手写,现在 MCP 已经把它们标准化了。
3.3 传输方式
MCP 支持多种传输方式:
| 传输方式 | 适用场景 | 机制与特点 |
|---|---|---|
| STDIO | 本地开发与测试 | 最简单。MCP Client 将 Server 作为子进程启动,双方通过 STDIN 和 STDOUT 交换信息。同机运行,无网络开销。示例使用此方式 |
| HTTP | Server 需要运行在另一台机器,或被多个 Client 共享 | 适合跨机器或共享 Server 场景 |
| WebSocket | Server 需要向 Client 推送消息,例如状态更新 | 支持真正的双向通信 |
3.4 为什么 LLM 提供商不原生支持 MCP 格式
即使 MCP 是标准,为什么所有大语言模型提供商不直接原生支持它的格式?原因如下:
- MCP 标准化的是工具如何被发现和调用。
- 大模型 API 本身要求的工具参数格式由各厂商设定,并且早于 MCP 出现。
- 幸运的是,MCP 的参数描述本身就是 JSON Schema,与各模型提供商要求的格式非常接近。
- 因此 Client 只需要包一层,匹配厂商的 envelope。这个转换步骤很薄,几乎是机械性的。
3.5 MCP 定义的三类能力
MCP 定义了三种能力:
- Tools:模型可以调用的函数。这是本文的主要焦点。
- Resources:可以读取的数据。
- Prompts:可复用的提示模板。
构建 Agent 时,Tools 是最核心的能力,因此本文只聚焦 Tools。
4. 实践准备:Expense Tracker API
4.1 项目定位与技术栈
原文先建立了一个项目,名为 Expense Tracker API,本质上是一个追踪支出的系统。
技术栈与定位:
- 使用 Rust 2024 edition。
- 使用 AXUM、TOKIO 等 crates 构建 Web API 系统。
- 它类似于公司内部系统,可以理解为 ERP 或 CRM 类型的内部系统。
- 这个系统暴露一些 Web API,供 AI Agent 使用。
4.2 暴露的主要 Web API
该项目暴露的主要 Web API 包括:
- 几个 GET。
- 一个 Health Check。
- expenses,并且带有筛选选项,返回列表。
- 查看单个 expense。
- 添加 expense。
- 编辑 expense。
- 删除 expense。
- summary。
4.3 测试与验证
原文使用 REST Client VS Code 扩展,创建 test.http 文件来测试 Web API。
运行方式:
- 已通过
cargo run运行。 - 服务监听端口 3000。
- 所有 Web API 都有认证。
- 使用
X-API-Theader,其值为原文中指定的值(原文未给出具体值)。
测试结果如下:
| 测试项 | 操作或条件 | 结果 |
|---|---|---|
| Health Check | 发送请求 | 返回 200 OK,服务正常运行 |
| 查看所有 expense 记录 | 请求列表 | 返回内存中存储的所有数据 |
| 带筛选条件 | 按 category 和 month 筛选,是 AND 关系 | 返回两条记录 |
| 添加新记录 | 创建一条 expense | 成功,获得 ID |
| 用 ID 查询 | 复制新增的 ID 后查询 | 找到该记录 |
| 部分更新 | 对同一 ID 更新,amount 改为 11 | 无问题 |
| 删除 | 删除该记录 | 返回 204 No Content success(原文为 2.4 Number Content,疑为 204 No Content) |
| Summary | 查看某月 summary | 返回 summary、month 和 details |
| 无 API key | 不提供 key | 返回 401 Unauthorized |
| 负数 | 传入负数 | 返回 400 Bad Request |
| 查询不存在的 ID | 使用不存在的 ID | 返回 404 |
原文总结:这个项目没有大问题。读者也可以自己构建类似项目,可以借助 AI 生成,Rust 或其他语言均可。
5. 回到 AI Agent 项目
在上一课之前,原文对代码做了一些调整,清理了一些 warnings。可以从 GitHub repo 拉取最新代码。
当时已经完成的 expense 管理工具:
- 已运行,监听端口 3000。
- 暴露端点包括查询成本、创建成本、更新成本、删除 expenses、统计 summaries 等 API。
下一步是处理 MCP 部分:
- 先构建一个 MCP Server,即 Server 侧,它会暴露一些工具。
- 然后让 Rust AI Agent 程序连接到这个 MCP Server,并自动把它的工具加入 Agent 的工具箱。
6. 构建 MCP Server
6.1 依赖与文件
需要添加一些 crates:
- 一个是 RMCP(原文 R M C P),用于实现 MCP Server 和 Client。
- 需要启用一些 features。
- 第二个 crate 用于时间相关功能,需要添加到 Chrono(原文出现 Crono/Corona,应为 chrono)。
回到 Cargo.toml:
- 添加这些 features。
- MCP 当前版本是 2.2,但 3.0 beta 已经发布,也可以使用 3.0。
文件位置:
- 在
tools文件夹添加一个文件,名为mcp.rs(原文说S C P dot R S,结合上下文应为mcp.rs)。 - 但 MCP Server 不在这个文件夹中。
- 在
bin文件夹中创建文件:expense_mcp_server.rs。
6.2 整体架构变化
目标不是只添加一个或多个工具,而是为工具系统打造更开放的架构。
原有结构:
- Agent 可以找到本地工具,例如 calculator 或 web search,并调用它们。
加入 MCP 后:
- 工具箱中不仅有本地工具,还有来自 MCP 的部分。
- 项目内有一个 MCP Client。
- MCP Client 与 MCP Server 交互。
- MCP Server 适配 Expense Tracker API。
- Agent 不会直接知道如何调用 expense API。
- Agent 只知道工具箱中新增了一些工具。
- 这些工具通过 MCP Client 获取。
- MCP Client 从 MCP Server 获取工具列表,列表包含每个工具的名称和描述。
- Agent 通过 MCP Client 调用具体工具,工具在 MCP Server 上运行。
- 工具本质上是对 Expense Tracker API 特定部分的调用、翻译或适配。
6.3 expense_mcp_server.rs 实现要点
代码量较大,原文直接粘贴。其结构和逻辑要点如下:
- 参数、请求参数类型、structs 都需要加 JSON Schema。
- 对 GET 请求列出 expenses,其 params 两个都是 optional。
- 还有 summary 和 single entry 的 params。
- 以及 delete、create、update 的 params。
- 以上都是 MCP 的参数。
- 另外还有一个 struct 用于 updates,它对应实际 HTTP 请求,具体是请求 body 中的字段。
- MCP 参数和 HTTP body 不同:MCP 参数有 ID,而发给 web 后端的 HTTP body 没有 ID。
- 原因:MCP 工具调用需要知道更新哪条 expense 记录;但按 HTTP 协议,后端 API 的 ID 应该放在 URL 中,body 不应包含 ID。因此 HTTP struct 缺少 ID。
MCP Server 本身:
- 有一个 struct 叫
ExpenseServer。 - 它有三个字段:HTTP Request Client、Base URL、API Key。
- 有
new函数。 - 调用
new时,HTTP 部分来自RequestClient::new。 base_url和api_key会先尝试从环境变量获取;如果失败,就使用默认值。- 示例中没有设置 ENV vars。
工具实现:
- 后端暴露 6 个 API,需要创建 6 个对应工具。
- 这 6 个工具共享同一套响应处理逻辑,避免重复。
- 统一响应逻辑:检查请求是否失败,以及响应是否成功;成功则返回成功结果,否则处理错误。
- 每个工具用类似
#[tool]的注解(原文为 hashtag bracketed to to,疑为#[tool])。 - 注解中有 key parameter
description,即工具描述,尽量详细,并注明需要的特定格式。 - MCP 参数使用 RMCP parameters package。
- 参数类型来自刚创建的 struct。
- 返回类型是
Result,其中需要CallToolResult,来自 RMCP model。 - 错误必须是 MCP Server,也来自 RMCP model。
- 工具内部逻辑是翻译过程:
- 把传入的 MCP 工具参数转换为对应的 HTTP URL 参数和请求 body 参数(如有)。
- 发送 HTTP 请求。
- 请求返回后,用统一响应处理函数处理,然后返回。
- 其他工具同理,包括查询单条、创建、更新、删除、汇总 expenses。
main 函数:
- 在最底部,通过 STDIO 启动 MCP Server。
- 这个进程会由 AI Agent 作为子进程启动。
- MCP Client 通过标准输入/输出与 MCP Server 通信。
- 注意事项:日志必须写 STDERR,不能写 STDOUT。
- 因为使用 STDIO transport 时,STDOUT 保留给 MCP 通信。混入普通日志会破坏协议通信。
- MCP Server 全部写在一个文件中:
expense_mcp_server.rs。
7. 构建 MCP Client
7.1 文件与模块
MCP Client 负责启动 MCP Server,并处理所有通信。
模块设置:
- 在
tools中添加模块。 - 在
tools文件夹下创建mcp文件夹。 - 在里面创建
client.rs和tools.rs。 - 不要忘记创建模块声明。
7.2 client.rs 要点
client.rs 中有一个 MCP Client,负责 spawn MCP Server 子进程并与其通信。
结构:
MCPClientstruct 包装到 MCP Server 的连接。- 它内部会 spawn MCP Server,即前面创建的
expense_mcp_server,并通过 STDIO 通信。
方法:
connect:
- 将
expense_mcp_server作为子进程启动并建立连接。 - 使用
tokio child_process生成新 command:cargo run,加参数,启动二进制expense_mcp_server。
list_tools:
- 获取 MCP 暴露的所有工具,返回工具列表。
call_tool:
- 当模型决定调用 MCP 工具时,Agent 会调用这个方法。
- 参数:
name是工具名,arguments是 JSON object,即工具调用所需参数。 - 它做三件事:
- 根据工具名构造 MCP Call Tool 请求。
- 把模型传来的 JSON 参数转发给 MCP Server。
- 取 MCP Server 返回的
CallToolResult,格式化为字符串,最后传给 Agent loop。
原文评价:MCP Client 相当简单。
7.3 tools.rs:把 MCP 工具适配到 Tool trait
这部分很关键。
原有 Agent loop 已经知道 Tool trait。为避免重写 Agent loop,需要把 MCP tools 包装成 Agent 能识别的 Tool trait。
关键映射:
- 一个 MCP Tool 映射到 MCP Server 中的一个工具。
- MCP Tool 字段包括:MCP Client、工具名称、描述、参数。
- 实现
new函数,用于创建 MCP Tool。 new接收来自 RMCP 的 tool,转换为自己的 MCP Tool struct。- 然后实现
Tooltrait:name、description、parameters、execute。 execute中:- 传入的是 JSON。
- 先解析为
Value类型。 - 转发给 MCP Client 的
call_tool方法,传入name和 params。 - 然后返回。
- 原文总结:只是加一些兼容性。
7.4 将工具构建改为 async
原来的工具都是同步的,函数也是同步的。现在必须改为 async,并返回 Result。
原因:
- 连接 MCP Server 的整个过程,包括建立连接和握手,是 async,且可能失败。
- 获取工具列表的过程本身也是 async,且可能失败。
- 本地 calculator 和 web search 可以直接
new,同步 setup 不同,需要调整。
核心逻辑:
- 先连接
expense_mcp_server,获取它暴露的工具列表,即所有工具。 - 每个工具包装为 MCP Tool,放入工具箱。
- 从这一刻起,对 Agent 和 Agent loop 来说,MCP 中的工具与 calculator、web search 基本相同。
改为 async 后,一些文件报错:
to_call_completetoolbox 需要加await。- Simple Agent Loop 也需要加
await。
8. Simple Agent Loop 与案例运行
8.1 当前时间与 system prompt
在 Simple Agent Loop 示例中,代码首先获取当前时间。原因是后续问题涉及“today”概念,而所选模型 GPT-4o-mini 对“today”不够清楚,会弄错日期。因此使用 Chrono 获取当前时间,加入 system prompt 并传给 LLM。
system prompt 由 AI 详细生成,包含当前时间。
8.2 测试 prompt
第一个测试的注释有误,但测试 prompt 如下:
I want to buy a Mac Mini M4 help me analyze it. First, use a tool to look up its price, then find my software spending over the past three months. then see how many times the M4 price is of my three month software spend. If I save five hundred RMB each month, how many months would it take to buy it? then, based on my spending habits, offer me advice on whether I should buy it. Lastly, all price and spending data must come from tools, no guessing.
中文含义:
- 我想买 Mac Mini M4,帮我分析。
- 先用工具查它的价格。
- 再查我过去三个月的软件支出。
- 然后看 M4 价格是我三个月软件支出的多少倍。
- 如果我每个月存 500 RMB,需要多少个月才能买?
- 然后根据我的消费习惯,给我是否应该买的建议。
- 最后,所有价格和支出数据必须来自工具,不能猜测。
运行前要确保 expense management API 已运行。Agent loop 中多次调用工具,并让 AI 分析结果。
8.3 运行流程与结果
日志打印出了整个 Agent loop 流程:
- Agent loop 启动。
- MCP Server 初始化。
- 握手,建立连接。
- 用户提出购买分析查询。
- 大模型请求调用工具。
- 先调用 web search 查价格。
- 再拉取过去三个月的 consumption summaries,共三次。
- 把结果发回大模型。
- 大模型再次请求调用 summary agent,检查 June 和 July 的数据。
- 结果再次发回大模型。
- 最终答案。
最终答案结构:
- 价格是多少。
- 过去三个月支出。
- 然后计算。
- 然后分析。
- 最后:除非有迫切需求,否则先不要买。
原文表示,这一部分的代码也会推送到 GitHub repo。
9. 今日回顾
本集覆盖了:
- MCP 的核心思想:标准化工具如何被发现和调用。
- 三个角色:Host、Client、Server。
- 两个接口:List Tools 和 Call Tool。
- 多种可选传输方法:STDIO、HTTP、WebSocket。
- 这些内容构成整个 MCP。
10. 限制与待确认问题
- 原文提到上一集结尾提出四个与写工具相关的痛点,但没有逐条列出,因此具体四个痛点内容待确认。
- 原文没有给出
X-API-Theader 的具体值,只说“this right here”。 - 原文中部分术语可能是语音转写或拼写问题:Cloud Desktop、Google’s Anti gravity、RMCP、Crono/Corona、2.4 Number Content、hashtag bracketed to to 等。本笔记保留原文表述或标注可能对应,不编造确定含义。
- 原文没有提供完整代码,只描述了代码结构、方法名和实现逻辑。
- 原文没有说明 HTTP 和 WebSocket transport 的具体配置或实现细节。
- 原文没有列出 RMCP 需要启用的具体 feature 名称。
- 原文没有说明 MCP 3.0 beta 与 2.2 的差异。
- 原文没有展开 Resources 和 Prompts 的具体用法,因为本文只聚焦 Tools。
11. 行动清单
- 从 GitHub repo 拉取最新代码,注意已清理 warnings。
- 确保 Expense Tracker API 已通过
cargo run运行在端口 3000。 - 在
Cargo.toml添加 RMCP 和 Chrono,并启用所需 features。 - 创建
bin/expense_mcp_server.rs,实现 MCP Server: - 暴露 6 个工具。
- 使用 JSON Schema 描述参数。
- 共享统一响应处理逻辑。
- 日志写 STDERR,不写 STDOUT。
- 创建
tools/mcp/client.rs和tools/mcp/tools.rs,实现: - MCP Client 的
connect、list_tools、call_tool。 - 把 MCP Tool 包装成现有
Tooltrait。 - 将工具构建函数改为 async,并返回
Result。 - 在调用处补上
await,包括to_call_completetoolbox 和 Simple Agent Loop。 - 在 Simple Agent Loop 中加入当前时间到 system prompt,避免 GPT-4o-mini 对“today”理解错误。
- 用给定 prompt 测试:查 Mac Mini M4 价格、过去三个月软件支出、计算倍数和储蓄月数,并给出购买建议。
- 理解并关注 MCP 的两个核心接口:List Tools 与 Call Tool。
- 根据场景选择传输方式:本地测试用 STDIO,跨机器或共享用 HTTP,需要服务端推送用 WebSocket。
12. 总结
MCP 类似 HTTP 对 Web 的标准化作用,解决模型与工具集成中的 M×N 问题,将其降为 M+N。它的核心架构只有 Host、Client、Server 三个角色,核心接口是 List Tools 和 Call Tool,并支持 STDIO、HTTP、WebSocket 等传输方式。MCP 还定义 Tools、Resources、Prompts 三类能力,但本文聚焦最核心的 Tools。
实践中,原文用 Rust 构建了 Expense Tracker API,再实现 MCP Server 和 MCP Client,并把 MCP 工具适配进已有 Agent loop。示例中,Agent 自动调用 web search 和 expense summary 工具,完成了关于是否购买 Mac Mini M4 的购买分析。