文章目录
-
- 前言
- 一、先把结论甩出来
- [二、MCP 的基本模型:三步,多一步算我输](#二、MCP 的基本模型:三步,多一步算我输)
-
- [1. 什么是工具服务器](#1. 什么是工具服务器)
- [2. 通信流程就三步](#2. 通信流程就三步)
- 三、配置文件:mcp_servers.json
- [四、连接与发现:register_mcp_servers 干了什么](#四、连接与发现:register_mcp_servers 干了什么)
- 五、MCPToolAdapter:把远端工具包装成本地工具
- 六、完整链路:从配置文件到模型调用
- [七、两种传输方式:本地亲儿子 vs 云端干儿子](#七、两种传输方式:本地亲儿子 vs 云端干儿子)
-
- [1. stdio 模式(本地子进程)](#1. stdio 模式(本地子进程))
- [2. http 模式(远端 HTTP 服务)](#2. http 模式(远端 HTTP 服务))
- [八、单个服务器挂了,agent 不能跟着躺](#八、单个服务器挂了,agent 不能跟着躺)
- 九、设计亮点,抄作业时间
-
- [1. 适配器模式屏蔽了"远端"](#1. 适配器模式屏蔽了"远端")
- [2. namespace 避免命名冲突](#2. namespace 避免命名冲突)
- [3. 配置格式兼容 Claude 桌面端](#3. 配置格式兼容 Claude 桌面端)
- 十、小结

P.S. 目前国内还是很缺AI人才的,希望更多人能真正加入到AI行业,共同促进行业进步,增强我国的AI竞争力。想要系统学习AI知识的朋友可以看看我精心打磨的教程 http://blog.csdn.net/jiangjunshow,教程通俗易懂,高中生都能看懂,还有各种段子风趣幽默,从深度学习基础原理到各领域实战应用都有讲解,我22年的AI积累全在里面了。注意,教程仅限真正想入门AI的朋友,否则看看零散的博文就够了。
前言
Tavily 能联网搜索,Context7 能查最新文档,GitHub CLI 能管仓库------现成的服务堆了一车库,非要自己撸一套轮子。这跟你家里有洗衣机、偏要手搓衣服有什么区别?区别是洗衣机不会笑话你,评论区会。
所以,MCP 登场。全称 Model Context Protocol,翻译成大白话就是"工具界的 USB-C 接口"。以前每家工具服务一个接口,想让 agent 调谁,就得给谁单独写适配器,数据线多到能绕地球一圈。现在协议统一,一根线走天下。
一、先把结论甩出来
接入一个 MCP 工具服务,只需要两步:
| 步骤 | 做什么 |
|---|---|
| 1 | 配置 mcp_servers.json:告诉框架 MCP 服务在哪里、怎么启动 |
| 2 | 启用 MCP:--enable-mcp 启动参数,或 ENABLE_MCP=true 环境变量 |
就这两步,指令长度还不如"下楼买瓶酱油"------而且人家买酱油至少还得问一句"要生抽还是老抽"。
剩下的框架全包:自动连服务、自动发现工具列表、自动把远端工具注册成本地工具,之后的调用链和内置工具完全一致。亲儿子待遇,一个字都不用多说。
二、MCP 的基本模型:三步,多一步算我输
1. 什么是工具服务器
一个独立的进程(或者远端 HTTP 服务),里面注册了一堆工具,每个工具都有自己的名字、描述和参数 schema。
说人话:一个"工具超市"。每个货架上都贴好了标签,写清楚这是什么、怎么用、要什么参数,你推着购物车逛就行。
2. 通信流程就三步
握手:连接服务器
↓
发现:list_tools() → 拿到工具列表(名字 + 描述 + inputSchema)
↓
调用:call_tool(name, params) → 拿到结果
就这三步,没了。协议的价值全在"标准化"三个字------任何 MCP 服务器都支持这三步,任何 MCP 客户端都能和任何 MCP 服务器通信。这感觉就像全世界终于统一了插座标准,以前去朋友家总得带转换头,现在一根线搞定。
MyCodeAgent 实现了 MCP 的客户端侧,一个 MCPClient 类把这三个操作全封装了。
三、配置文件:mcp_servers.json
在项目根目录创建(或修改)一个 mcp_servers.json,一个服务器一段配置:
json
{
"mcpServers": {
"tavily": {
"command": "uvx",
"args": ["mcp-server-tavily"],
"env": {
"TAVILY_API_KEY": "tvly-xxxxx"
}
},
"context7": {
"command": "uvx",
"args": ["--from", "context7-mcp", "context7-mcp"],
"env": {
"CTX7_API_KEY": "your-key"
}
}
}
}
三个关键字段,拿小本本记好:
- command:启动工具服务器的命令(uvx 是 Python 的"免安装直接跑"神器)
- args:传给 command 的参数
- env:工具服务器需要的环境变量,比如 API key
说到 API key,我多说一句:这玩意儿你填错一次、报错一次,就会记得比对象生日还牢。
除了本地进程,还支持远程 HTTP 服务器:
json
{
"mcpServers": {
"remote-tool": {
"transport": "http",
"url": "https://your-mcp-server.com/v1"
}
}
}
读这个文件的是 extensions/mcp/config.py。它还兼容 Claude 桌面端的写法,同一个配置文件两边通用,不用你翻译第二遍。就像同一份简历,字节腾讯通吃,连排版都不用改。
四、连接与发现:register_mcp_servers 干了什么
agent 启动时(带 --enable-mcp),register_mcp_servers() 被调用:
python
# extensions/mcp/bootstrap.py
def register_mcp_servers(tool_registry, project_root):
# 1. 读取 mcp_servers.json
servers = load_mcp_servers(project_root)
# 2. 为每个 server 创建 MCPClient(还没连接)
for server_name, spec in servers.items():
config = _build_client_config(project_root, spec, MCPClientConfig)
client = MCPClient(config)
# 3. 连接 server,发现工具列表,注册到 ToolRegistry
tools_meta = register_mcp_tools(tool_registry, client, namespace=server_name)
注意 namespace=server_name 这个参数,它解决了一个非常真实的痛点:两家不同的 MCP 服务器可能都有一个叫 search 的工具。就像两个小区都叫"幸福家园",你不加前缀,外卖小哥只能靠猜------猜错一次,你就收获一份凉透的麻辣烫。
加了 namespace 之后,一个变成 tavily_search,一个变成 context7_search(冒号会被清理成下划线),谁是谁清清楚楚,模型想调错都难。
五、MCPToolAdapter:把远端工具包装成本地工具
register_mcp_tools() 调完 list_tools_sync() 之后,会为每个远端工具创建一个 MCPToolAdapter:
python
# extensions/mcp/adapter.py
class MCPToolAdapter(Tool):
"""把 MCP 远端工具伪装成本地 Tool。"""
def get_parameters(self) -> list[ToolParameter]:
# 从 MCP 工具的 inputSchema 生成参数定义
schema = self._schema # 从 list_tools 拿到的 JSON Schema
properties = schema.get("properties", {})
required = set(schema.get("required", []))
return [
ToolParameter(name=name, type=spec.get("type", "any"), ...)
for name, spec in properties.items()
]
def run(self, parameters: dict) -> ToolResult:
# 1. 校验参数
invalid = self._validate_params(parameters)
if invalid:
return to_protocol_invalid_param(invalid, ...)
# 2. 调用远端工具
result = self._mcp_client.call_tool_sync(self._remote_name, parameters)
# 3. 把结果转成标准信封
return to_protocol_result(result, ...)
这个 Adapter 就干三件事:
- 参数接口对齐:把 MCP 工具的 JSON Schema 翻译成 ToolParameter 列表,get_openai_tools() 才能生成正确的 Function Calling schema
- 执行转发:调 call_tool_sync() 把请求发给远端
- 结果归一化:把 MCP 格式的响应转成 ToolResult,下游完全不用关心这工具是本地还是远端
注册完之后,tavily_search 和 Read、Bash 在框架眼里没有任何区别:都在 ToolRegistry 里,都会出现在 get_openai_tools() 的列表里,都走同一个 Executor 管道。这就像外包和正式员工,活儿干完结算方式一模一样------唯一的区别是工牌颜色,哦不对,工牌颜色也一样。
六、完整链路:从配置文件到模型调用
把上面几步串起来,一个 MCP 工具从配置到被模型调用,完整链路长这样:
写 mcp_servers.json,填入 server 配置
│
▼
agent 启动(--enable-mcp)
│
▼
bootstrap.py 调用 register_mcp_servers()
├── 读 mcp_servers.json
├── 为每个 server 创建 MCPClient
└── 连接 server,调 list_tools_sync(),拿到工具列表
│
▼
为每个工具创建 MCPToolAdapter
MCPToolAdapter 注册到 ToolRegistry
│
▼
tool_registry.get_openai_tools() 返回的工具列表里包含了 tavily_search
│
▼
模型看到工具列表,决定调用 tavily_search
│
▼
ToolRegistry.execute_tool("tavily_search", params)
→ ToolExecutor 走四关卡(权限/锁/熔断/run)
→ MCPToolAdapter.run()
→ MCPClient.call_tool_sync("search", params)
→ 结果转 ToolResult → 返回给模型
整条链路里,MCP 的特殊性只在 MCPToolAdapter.run() 这一层。权限检查、熔断器、字节预算、结果写入历史------其他部分全部复用内置工具的代码。
说白了,你只是点了个外卖,锅碗瓢盆还是家里那套,菜是外面送的。你不会因为点一次外卖就把厨房拆了重建,对吧?
七、两种传输方式:本地亲儿子 vs 云端干儿子
_build_client_config() 会根据配置决定用哪种传输方式。
1. stdio 模式(本地子进程)
json
{
"command": "uvx",
"args": ["mcp-server-tavily"]
}
框架 fork 一个子进程运行工具服务器,通过 stdin/stdout 管道通信,子进程和 agent 同生命周期------同生共死,相当感人,像极了创业老板对员工许下的承诺。
2. http 模式(远端 HTTP 服务)
json
{
"transport": "http",
"url": "https://your-mcp-server.com/v1"
}
框架通过 HTTP 和远端服务通信,不用启动子进程,适合托管在服务器上的 MCP 工具,比如付费 SaaS API。对 SaaS 来说,你的信用卡才是它真正的"传输层"。
两种模式对上层代码完全透明,MCPToolAdapter.run() 根本不关心底层是子进程还是 HTTP,只管调 call_tool_sync()。
八、单个服务器挂了,agent 不能跟着躺
register_mcp_servers() 里有一个很关键的容错处理:
python
for server_name, spec in servers.items():
try:
tools_meta = register_mcp_tools(tool_registry, client, namespace=server_name)
registered_tools.extend(tools_meta)
except Exception as exc:
logger.warning("MCP tool registration failed for %s: %s", server_name, exc)
continue # 单个失败,继续处理下一个
如果 Tavily 启动失败(比如密钥忘填了),不会影响 Context7 的注册,更不会让整个 agent 启动失败,顶多在日志里留一条 warning。
这个设计思路和 CompositeRuntimeEventSink 一脉相承:可观测性基础设施或外部服务的故障,不能让你 agent 的主路径停下来。翻译成生活语言:不能因为楼下保安昨晚没睡好,你就把整个物业都辞了。
九、设计亮点,抄作业时间
1. 适配器模式屏蔽了"远端"
MCPToolAdapter 让远端工具在框架内部和本地工具无法区分。权限检查、熔断器、字节预算------内置工具享有的保护,MCP 工具自动获得,一行逻辑都不用重写。
2. namespace 避免命名冲突
两个服务器都有 search?加上 namespace 就变成 tavily_search 和 context7_search,模型看得清清楚楚,不会误调。
3. 配置格式兼容 Claude 桌面端
{"mcpServers": {...}} 是 Claude 桌面端的标准写法。MyCodeAgent 兼容它,意味着你给 Claude 配好的文件直接拿过来就能用,不用二次翻译。
十、小结
| 设计选择 | 方案 | 工程价值 |
|---|---|---|
| 接入方式 | JSON 配置文件 + --enable-mcp | 零代码接入,改配置即可 |
| 工具注册 | MCPToolAdapter 实现 Tool 接口 | 远端工具和本地工具走同一管道 |
| 命名空间 | namespace:remote_name 前缀 | 多 server 不冲突 |
| 故障隔离 | 单 server 失败 continue | 不影响其他 server 和 agent 启动 |
| 传输方式 | stdio(本地进程)/ http(远端服务) | 覆盖本地和云端两种部署场景 |
至此,Part 6「从零扩展」系列全部收官。回顾这四篇:
- 20:添加新工具------Tool 基类、协议信封、沙箱检查、注册与测试
- 21:接入新 LLM------表驱动的 provider 路由,加一行 profile 就搞定
- 22:写 Skill------Markdown 定义专家行为,热加载,$ARGUMENTS 参数注入
- 23:接入 MCP------配置文件 + 适配器,外部工具生态一键接入
这四个扩展维度加起来,覆盖了你几乎能想到的所有"让 agent 做新事情"的需求:
- 需要 agent 执行新的具体动作 → 工具
- 需要 agent 用新的 LLM → provider
- 需要 agent 学一种新的处理方式 → Skill
- 需要 agent 调用现成的外部服务 → MCP
最后说句掏心窝的话:干这行久了你会发现,大部分"创新"其实就是"把现成的东西接好"。MCP 干的就是这件事,而你只需要写一个 JSON 文件。所以下次别人问你最近在忙啥,你可以挺起胸膛说:"在做生态对接。"------翻译过来就是改配置文件。
本系列的分析都基于开源项目 MyCodeAgent,源码里按文章顺序在关键位置加了注释,可以对照着看,也可以直接克隆下来跑:
行了,本篇到此为止。如果这篇帮到了你,点个赞再走。下次别再问我"MCP 是不是哪款新出的奶茶"------那叫 Meco。
P.S. 目前国内还是很缺AI人才的,希望更多人能真正加入到AI行业,共同促进行业进步,增强我国的AI竞争力。想要系统学习AI知识的朋友可以看看我精心打磨的教程 http://blog.csdn.net/jiangjunshow,教程通俗易懂,高中生都能看懂,还有各种段子风趣幽默,从深度学习基础原理到各领域实战应用都有讲解,我22年的AI积累全在里面了。注意,教程仅限真正想入门AI的朋友,否则看看零散的博文就够了。