MCP 解决的是「工具太多」:把工具描述从提示词里挪到协议里,按需列出来。 三步就是全部:initialize 握手、tools/list 列工具、tools/call 调用。 但工具描述是常驻上下文——接十个服务器就是六千 token,不管用不用得上。
这一篇是这一章最「协议」的一篇,但结论很朴素:先问要不要接。 Demo 真起了一个 MCP 服务器进程,用标准输入输出跟它对话。 三步报文一来一回,全部真跑——包括最后一次调用返回的「开心」。 沙箱里没有现成的 MCP 服务器,所以这一个是手写的, 但报文形状照规范来,客户端也是真的按行读写。
一、MCP 解决的是工具太多
没有 MCP 的时候,工具怎么给模型?写在提示词里。 十个工具,提示词里就有十段描述加参数说明。 工具一多,这件事就崩了: 五十个工具的描述能占掉大半窗口,而这次任务可能只用一个。 MCP 的做法是把工具描述挪出去,变成「可查询的」。 先握手问「你有什么工具」,它回一份列表;要用哪个再调。 这样描述仍然是上下文,但至少是「列出来之后才知道要占多少」。 注意它没有真正解决「占上下文」这件事。 它解决的是「不用在提示词里硬编码」。 这两件事的差别,决定了「接不接」的判断标准。
二、一次握手的报文长什么样
三步,每一对都是一行 JSON 出去、一行 JSON 回来。
| 方法 | 谁发 | 回什么 |
|---|---|---|
| initialize | 客户端 | 协议版本、能力、服务器名 |
| tools/list | 客户端 | 工具列表与参数模式 |
| tools/call | 客户端 | 调用结果或错误码 |
Demo 里三步全跑通了。
第一次回来的是 protocolVersion 和 serverInfo;第二次回来的是工具列表;
第三次回来的是「开心」两个字。
报文里有两个字段要认得:
id 用来配对请求和响应(因为可以并发发多个);jsonrpc 固定是 2.0。
还有一个细节:没有 id 的消息是通知,不需要回。
这个设计让「单向告知」和「请求响应」用同一套管道。
三、什么时候不该接
| 情形 | 该不该接 | 为什么 |
|---|---|---|
| 一个脚本能干的事 | 不接 | 脚本不进上下文 |
| 只用一次的外部服务 | 不接 | 握手加描述比直接用还贵 |
| 团队都在用的系统 | 接 | 一次配置,所有人省事 |
| 需要鉴权和权限的 | 接 | 凭据集中管,别散在脚本里 |
第一行最关键:一个脚本能干的事,不要接 MCP。
因为脚本的描述不占上下文——
你只要在 AGENTS.md 里写「跑 python tools/fmt.py」,这十几个字就是全部成本。
而接一个 MCP 服务器,工具描述是常驻的。
Demo 里算了:接十个服务器、每个五个工具,常驻六千 token。
六千 token 大概等于十个中等文件的量。
所以判断标准不是「这个能力好不好」,是「它会不会被反复用、而且反复用的方式一样」。
会,值得接;不会,写个脚本。
四、动手 Demo:MCP:先问是否值得接,再问怎么接
先算一笔账:接几个服务器,常驻上下文多少。 然后真起一个服务器进程,把三步报文跑一遍。 服务器是手写的二十行 Python,用标准输入输出收发 JSON-RPC。 客户端也是真的按行读写,不是假装。
p = subprocess.Popen([PY, u"mcp_server.py"], cwd=base,
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
for m in msgs:
line = json.dumps(m, ensure_ascii=False, separators=(",", ":"))
p.stdin.write((line + u"\n").encode(u"utf-8"))
p.stdin.flush()
back = p.stdout.readline().decode(u"utf-8").strip()
P(u" <- " + short(back))
实跑输出:
--------------------------------------------------------------------
MCP:先问是否值得接,再问怎么接
--------------------------------------------------------------------
① 工具太多是什么问题
--------------------------------------------------------------------
假设每个工具的描述加参数模式占一百二十 token。
接几个服务器、每个服务器几个工具,直接乘出「常驻上下文」。
| 接多少 | 工具总数 | 常驻上下文 |
|:---:|:---:|:---:|
| 1 个服务器 | 5 个工具 | 600 |
| 3 个服务器 | 15 个工具 | 1,800 |
| 5 个服务器 | 25 个工具 | 3,000 |
| 10 个服务器 | 50 个工具 | 6,000 |
! 这些描述是常驻的——不管这次任务用不用得上,它都占着窗口。所以「要不要接」比「怎么接」更重要。
② 一次握手的报文长什么样
--------------------------------------------------------------------
-> {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion"…
<- {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-06-18","capabil…
-> {"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
<- {"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"judge_emotion","descr…
-> {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"judge_emo…
<- {"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"开心"}]…
③ 三步:握手、列工具、调用
--------------------------------------------------------------------
| 方法 | 谁发 | 回什么 |
|---|---|---|
| initialize | 客户端 | 协议版本、能力、服务器名 |
| tools/list | 客户端 | 工具列表与参数模式 |
| tools/call | 客户端 | 调用结果或错误码 |
三步都是同一套形状:一行 JSON 出去,一行 JSON 回来。
没有长连接、没有二进制、没有握手状态机以外的东西。
④ 什么时候不该接
--------------------------------------------------------------------
| 情形 | 该不该接 | 为什么 |
|---|---|---|
| 一个脚本能干的事 | 不接 | 脚本不进上下文 |
| 只用一次的外部服务 | 不接 | 握手加描述比直接用还贵 |
| 团队都在用的系统 | 接 | 一次配置,所有人省事 |
| 需要鉴权和权限的 | 接 | 凭据集中管,别散在脚本里 |
! 判断标准:这个能力会不会被反复用、而且反复用的方式一样。会,值得接;不会,写个脚本。
第三步回来的 "text":"开心" 是真算出来的——
服务器收到「我今天很开心」,按关键词判断返回了「开心」。
整个链路是真跑的:主进程写管道、子进程读、子进程写回、主进程读。
报文被截断了,但形状看得清:jsonrpc 加 id 加 result。
请求里多一个 method 和 params。
就这些,没有别的。
① 那张表是这一篇的重点。
十个服务器六千 token 常驻——这个数跟「接不接」的判断直接相关,比报文形状重要。
最后那句判断标准要记住:会不会被反复用、而且用法一样。
两个条件都满足才接,缺一个就用脚本。
30 秒实验
- 数一下你每天调的外部服务有几个。 两个以下的,写脚本;五个以上的,考虑接。
- 找一个你接了的 MCP,看它的工具描述占多少 token。 算一下「这次任务用到了几个」。
- 手写一个最小的 MCP 服务器,只实现三步。 跑通一次,以后再看到「接一下这个 MCP」就不慌了。
常见误区
- 「MCP 能省上下文。」 它不省。工具描述还是常驻的。 它省的是「不用在提示词里硬编码」。
- 「工具越多越强。」 工具多到一定程度,模型选错的概率上升,而且描述一直占着窗口。 能力不是越多越好,是越准越好。
- 「有现成的 MCP 就接上。」 先算常驻成本。 一个只用一次的服务器,握手加描述比直接用还贵。
自测三问(点开对答案)
1. MCP 到底解决了什么问题? —— **把工具描述从提示词里挪到协议里,变成可查询的。** **它不省上下文,省的是「不用硬编码」。** 2. 三步报文分别是什么? —— **initialize(握手,换协议版本与能力)、tools/list(列工具与参数模式)、** **tools/call(调用并拿结果)。** 3. 什么时候不该接 MCP? —— **一个脚本能干的、只用一次的。** **判断标准是「会不会被反复用、而且用法一样」——两个都满足才接。**下一篇 8.4 收这一章:三者怎么协同,以及复杂度预算怎么算。
- ☐ (本篇完,下一篇 8.4 Skill、Subagent 和 MCP 协同协作,而不是堆叠复杂度)