☰
Agent中MCP协议详解:从“为什么要它”到“怎么用它”,最全最完整
2026/10/7 7:07:33 网站建设 项目流程

1. 为什么 Agent 需要一个统一的工具协议

先说一个我踩过的坑。去年做客服 Agent 的时候,团队里三个人分别写了三个工具:一个查订单的 REST 接口、一个跑 Python 脚本的本地命令行、一个连内部知识库的 gRPC 服务。结果 Agent 要调这三个工具,得写三套适配代码,参数格式、错误处理、超时逻辑全不一样。后来想换一个搜索工具,发现又得重写一遍适配层。这种"每个工具一套接口"的状态,就是 MCP 要解决的问题。

MCP 全称 Model Context Protocol,是 Anthropic 提出的 AI 工具通信标准。你可以把它理解成 AI 应用世界的 USB 接口:以前每个设备一个充电口,现在统一成 Type-C,插上就能用。对 Agent 来说,MCP 让"工具"这件事从"每个工具单独适配"变成"所有工具走同一个协议说话"。Claude Desktop、Cursor、以及你自己写的 Agent,只要支持 MCP,就能直接调用任何符合规范的 MCP Server,零适配。

它适合谁?三类人最该关注。第一类是正在写 Agent 的开发者,你手里有一堆工具想接进 Agent,MCP 能省掉大量胶水代码。第二类是做内部工具平台的团队,把工具封装成 MCP Server 后,公司里所有 AI 应用都能复用。第三类是普通用户,想给 Claude Desktop 或 Cursor 加个文件读取、数据库查询能力,社区已经有现成的 MCP Server,一行 npx 命令就能跑起来。

这篇文章不讲空泛概念,我会把 MCP 的通信机制拆开,从 JSON-RPC 消息格式讲到 Stdio 子进程通信,再给你可复制的配置片段和一次端到端调用验证。看完你应该能自己跑通一个 MCP Server 并接进 Agent 工作流。

2. MCP 协议核心机制:JSON-RPC 与 Stdio 通信详解

MCP 底层用的是 JSON-RPC 2.0,这是理解整个协议的关键。JSON-RPC 的规则很简单:每条消息就是一行 JSON,请求带 id,响应带同一个 id,通知不带 id。就像发微信,一条消息一个气泡,不会粘在一起。

三种消息类型先搞清楚。请求是 Client 发给 Server 的,带 id,等回复,比如点菜等服务员确认:

{"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": null}

响应是 Server 回给 Client 的,id 必须和请求一致,Client 才知道这条回复对应哪个请求:

{"jsonrpc": "2.0", "id": 1, "result": {"tools": []}}

通知是不等回复的,没有 id,发了就完,比如握手完成后的确认:

{"jsonrpc": "2.0", "method": "notifications/initialized"}

连接建立后不是直接干活,先握手。Client 发 initialize 请求,告诉 Server 自己支持的协议版本和客户端信息;Server 回 initialize response,告诉 Client 自己的协议版本、能力和服务端信息;最后 Client 发 notifications/initialized 通知,表示可以开始干活了。整个握手有 30 秒超时,防止 Server 卡住导致 Client 无限等待。

握手请求长这样:

{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": {"name": "my-agent", "version": "0.1.0"} } }

Server 的响应:

{ "jsonrpc": "2.0", "id": 1, "result": { "protocolVersion": "2024-11-05", "capabilities": {"tools": {}}, "serverInfo": {"name": "my-mcp-server", "version": "1.0.0"} } }

传输层有两种:Stdio 和 SSE。Stdio 是最常见的,Client 启动 Server 子进程,通过 stdin/stdout 交换 JSON。你启动一个命令行程序,给它输命令,它打印结果,就是这个模式。SSE 用于远程场景,Server 独立运行,Client 通过 HTTP 连过去,先 GET /sse 建立长连接,Server 推送一个 endpoint 事件告诉 Client 发消息用哪个 URL,之后 Client 用 HTTP POST 往那个 URL 发请求。

Stdio 有个关键细节叫 request_lock。并发请求时,"写 stdin 然后读 stdout"这个操作必须原子,不然两个请求同时写,回复错位就乱了。所以 StdioTransport 里会有一个请求级互斥锁,保证同一时刻只有一个请求在通信。stderr 单独开后台任务打印,不阻塞主通信。

一次完整的工具调用,从 JSON 角度看是这样的。Agent 要读 /tmp/hello.txt,Client 发出:

{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"read_file","arguments":{"path":"/tmp/hello.txt"}}}

Server 回复:

{"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"Hello, World!"}],"is_error":false}}

一行 JSON 过去,一行 JSON 回来。没有 HTTP 头、没有 WebSocket 帧、没有 gRPC 序列化,纯粹的文本行协议。这就是 MCP 简单的地方,也是它容易被各种语言实现的原因。

3. 可复制的 MCP Server 配置与 Stdio 启动命令

这一节给你能直接抄的配置。先看 Claude Desktop 的配置,文件路径在 macOS 上是~/Library/Application Support/Claude/claude_desktop_config.json,Windows 上是%APPDATA%\Claude\claude_desktop_config.json。内容格式:

{ "mcpServers": { "filesystem": { "command": "npx", "args": [ "-y", "@modelcontextprotocol/server-filesystem", "/Users/yourname/project" ] }, "calculator": { "command": "/usr/local/bin/my-calculator", "args": [] } } }

这里command是要启动的可执行文件,args是参数。filesystem 这个 Server 用 npx 启动,允许访问/Users/yourname/project目录。calculator 是你自己编译的二进制,直接跑。

如果你用的是 Cursor,配置在~/.cursor/mcp.json,格式一样:

{ "mcpServers": { "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"] } } }

手动测试 Stdio Server 能不能跑,可以直接在终端里启动它,然后手动喂 JSON。比如启动 filesystem Server:

npx -y @modelcontextprotocol/server-filesystem /tmp

启动后它会等 stdin 输入。你手动输入一行 initialize 请求,回车:

{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}

如果 Server 正常,会立刻在 stdout 打印一行 initialize response。这一步能跑通,说明 Server 本身没问题,问题就出在 Client 配置上。

如果你要接的是 TaoToken 这类兼容 OpenAI 接口的服务,在 Agent 侧配置 Base URL 和 Key 的时候,MCP Server 的配置是独立的,两者不冲突。MCP 管的是工具怎么暴露,模型管的是推理怎么走。你可以把 MCP Server 理解成 Agent 的"手",模型是"大脑",手和大脑通过不同的通道连接。

对于 Codex 用户,~/.codex/auth.json里配置的是模型认证信息,MCP Server 配置在 Codex 的 settings 里单独写。Cline 的话,MCP 配置在 VS Code 的 settings.json 里,搜cline.mcpServers就能找到。CC Switch 这类工具切换的是模型端点,MCP Server 列表是另一份配置,别搞混。

一个完整的 MCP Server 配置三件套是:Base URL(如果是远程 SSE Server)、Key(如果需要认证)、Model ID(Agent 侧用的模型)。Stdio Server 不需要 Base URL 和 Key,因为它就是本地子进程,通过 stdin/stdout 通信,不经过网络。

4. 端到端验证:从 tools/list 到 tools/call 跑通一次调用

配置写好了,怎么确认真的通了?我一般分三步验证:先看 Server 能不能列出工具,再手动调一次工具,最后看 Agent 能不能自动调。

第一步,用 Python 写个最小 Client 验证 tools/list。这段代码可以直接跑:

import subprocess import json # 启动 MCP Server 子进程 proc = subprocess.Popen( ["npx", "-y", "@modelcontextprotocol/server-filesystem", "/tmp"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True ) def send_request(method, params=None, req_id=1): msg = {"jsonrpc": "2.0", "id": req_id, "method": method} if params is not None: msg["params"] = params proc.stdin.write(json.dumps(msg) + "\n") proc.stdin.flush() line = proc.stdout.readline() return json.loads(line) # 握手 init_resp = send_request("initialize", { "protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": {"name": "test-client", "version": "1.0"} }) print("initialize:", init_resp) # 发 initialized 通知(无 id) proc.stdin.write(json.dumps({"jsonrpc": "2.0", "method": "notifications/initialized"}) + "\n") proc.stdin.flush() # 列出工具 tools_resp = send_request("tools/list", None, req_id=2) print("tools:", json.dumps(tools_resp, indent=2, ensure_ascii=False)) proc.terminate()

跑起来应该能看到类似这样的输出:

{ "jsonrpc": "2.0", "id": 2, "result": { "tools": [ {"name": "read_file", "description": "Read the contents of a file"}, {"name": "write_file", "description": "Write content to a file"}, {"name": "list_directory", "description": "List directory contents"} ] } }

第二步,调一次 read_file。先往 /tmp/hello.txt 写点内容:

echo "Hello, MCP!" > /tmp/hello.txt

然后在上面代码基础上加一段:

call_resp = send_request("tools/call", { "name": "read_file", "arguments": {"path": "/tmp/hello.txt"} }, req_id=3) print("call result:", json.dumps(call_resp, indent=2, ensure_ascii=False))

预期输出:

{ "jsonrpc": "2.0", "id": 3, "result": { "content": [{"type": "text", "text": "Hello, MCP!"}], "is_error": false } }

看到is_error: false和文件内容,说明端到端通了。

第三步,接进 Agent。如果你用的是支持 MCP 的 Agent 框架,把上面配置里的 Server 加进去,然后问 Agent"读一下 /tmp/hello.txt 的内容"。Agent 会自动走 tools/list 发现 read_file,然后 tools/call 调用它。你可以在 Agent 的日志里看到完整的 JSON-RPC 消息流。

这一步验证通过后,你就有了一个可用的 MCP 工具链。后面加新工具,只需要在 Server 侧注册,Client 侧不用改任何代码。

5. 常见报错排查:401、local proxy failed、reading choices、OAuth

跑 MCP 的过程中,报错基本集中在几类。我按真实遇到的频率排一下。

401 Unauthorized。这个通常出现在远程 SSE Server 上,Server 要求认证但 Client 没带 Key。检查你的 MCP 配置里有没有headers字段,比如:

{ "mcpServers": { "remote-tools": { "url": "https://your-server.com/sse", "headers": { "Authorization": "Bearer your-token-here" } } } }

如果是 Stdio Server 报 401,那多半是 Server 内部去调了某个需要认证的 API,跟 MCP 协议本身无关,去看 Server 的日志。

local proxy failed。这个报错一般出现在 Client 尝试连接本地 Server 但连不上。可能原因有三个:Server 进程没启动、端口被占用、或者配置里的 command 路径写错了。先手动在终端跑一遍配置里的 command,看能不能启动。如果 command 是相对路径,改成绝对路径试试。端口占用的话,换个端口或者杀掉占用进程。

reading choices 相关报错。这个通常出现在 Agent 侧解析模型返回时,模型返回的格式不符合预期。如果你用的是兼容 OpenAI 接口的服务,检查 Base URL 有没有写对,Model ID 是不是服务商支持的。有些服务返回的 choices 字段结构和标准 OpenAI 不一样,需要在 Agent 侧做适配。TaoToken 的接口是兼容 OpenAI 格式的,Base URL 填https://taotoken.net/api,Model ID 填你实际用的模型名,一般不会有这个问题。

OAuth 相关报错。远程 MCP Server 如果用 OAuth 认证,Client 需要先走一遍授权流程。报错通常是 token 过期或者 scope 不对。检查你的 token 有没有过期,scope 是不是包含了要调用的工具权限。有些 Server 要求特定的 scope,比如tools:read、tools:call,配置的时候要看清楚。

Method not found (-32601)。这个说明你调的方法 Server 没实现。MCP 定义了六大原语:tools、resources、prompts、completion、elicitation、roots、sampling。很多 Server 只实现了 tools,你调 resources/list 就会报这个错。解决方法是先 tools/list 看看 Server 到底支持什么,别调没实现的方法。

Parse error (-32700)。这个说明你发的 JSON 不合法。最常见的是少了个引号、多了个逗号、或者换行符处理不对。Stdio 通信要求每条消息一行,消息内部不能有裸换行。用 json.dumps 生成消息的时候,确保没有 indent 参数,不然会插入换行。

握手超时。30 秒内没完成 initialize 握手就会超时。检查 Server 启动是不是太慢,比如 npx 第一次跑要下载包,可能超过 30 秒。可以先手动跑一次 npx 把包缓存下来,或者改用本地安装的二进制。

排查的时候有个通用技巧:把 MCP 通信的原始 JSON 打出来看。在 Client 侧加日志,把每次 send 和 receive 的内容打印出来,对照 JSON-RPC 规范看哪里不对。大部分问题看原始消息就能定位。

6. 把 MCP 接进你的 Agent 工作流

跑通验证之后,下一步是把它接进实际工作流。我自己的做法是分三层:底层是 MCP Server 池,中间是 Agent 的工具适配层,上层是具体的业务 Agent。

底层 Server 池里,常用的工具各起一个 Server。文件操作一个、数据库查询一个、内部 API 一个。每个 Server 独立进程,互不影响。Server 挂了只影响对应的工具,不会拖垮整个 Agent。

中间适配层负责把 MCP 工具转成 Agent 能直接用的格式。大部分 Agent 框架都有现成的适配器,比如把 MCPToolDefinition 转成 BaseTool。适配逻辑很薄,就是包一层,run 方法内部调 client.call_tool。这层的好处是 Agent 代码不用关心工具是本地还是远程,统一按 BaseTool 调。

上层业务 Agent 按场景组合工具。客服 Agent 只需要订单查询和知识库工具,代码 Agent 只需要文件操作和搜索工具。按需加载,不用把所有工具都塞给一个 Agent。

如果你要长期跑 Agent 任务,建议用 Coding Plan 这类方案管理模型调用,把 MCP 工具调用和模型推理分开计费和监控。MCP 工具调用是本地或内网通信,模型推理走 API,两者的稳定性要求不一样,分开管理更容易排查问题。

实际用下来,MCP 最大的价值不是技术多先进,而是把"工具接入"这件事标准化了。以前每接一个新工具都要写适配代码,现在只要 Server 符合 MCP 规范,配置里加一行就能用。社区里现成的 MCP Server 越来越多,文件系统、GitHub、数据库、搜索都有,直接拿来用就行。

最后给一个实用建议:自己写 MCP Server 的时候,工具描述要写清楚。Agent 是靠描述来决定调哪个工具的,描述模糊会导致 Agent 调错工具。参数 schema 也要写全,required 字段标清楚,不然 Agent 可能漏传参数。这两点做好,Agent 调工具的准确率会高很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询