跳至内容
26/30第 26 章,共 30 章

对照规范解释 MCP:服务器到底是什么

向子进程输入一行 JSON,取回 13 个工具定义,并按移除握手的 2026-07-28 修订版逐条解读。

本页内容

安装一个已发布的 MCP server,给它发送一行 JSON,然后看看返回了什么。

terminalBASH
npm i @modelcontextprotocol/server-everything@2026.8.31
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' \
  | npx @modelcontextprotocol/server-everything stdio
TEXT
{"result":{"tools":[{"name":"echo","title":"Echo Tool","description":"Echoes
back the input string","inputSchema":{"$schema":"http://json-schema.org/draft-07/
schema#","type":"object","properties":{"message":{"type":"string","description":
"Message to echo"}},"required":["message"]},"annotations":{"readOnlyHint":true,
…                                        … 7,663 bytes on one line …
"jsonrpc":"2.0","id":1}

13 个工具定义,单行返回,来自一个只从标准输入读取了一行内容的进程。你已经在说 Model Context Protocol 了:没有 SDK,没有客户端库,也没有框架。全部就是这样:一种传输、一种消息格式,以及一小组具名方法

第 18 章把工具定义为两件事——模型能看到的 JSON Schema,以及你代码里模型永远看不到的端点。第 23 章构建了一个保存工具目录的 harness。两者都没有回答那个决定这一切能否复用的问题:谁来写 schema,又如何把它从编写者那里放进你的 prompt? MCP 是这个问题的一种答案,而且值得读原文,因为关于它的大多数文章描述的都是一个已经不存在的修订版。

你刚才运行的命令有三处是错的,每一处都是本章的一节。它没有携带协议版本,所以符合规范的 server 本应拒绝它。它仍然得到了回答,原因在规范里被称为风险,而不是特性。并且它请求了三个原语中的一个,却从未发现另外两个的存在。

它解决的问题,以及规范自己给出的类比

链接到此部分:它解决的问题,以及规范自己给出的类比

先不看线路,先算账。你有 NN 个 AI 应用,以及 MM 个它们应该能够访问的东西——日历、工单跟踪器、仓库数据库、设计工具。没有共享契约时,就会有人写 N×MN \times M 个集成,而每一个集成都包含一个 schema、一个端点、一套认证方案和一份维护负担。有了共享契约,工具供应商写一个 server,应用供应商写一个 client,总数就是 N+MN + M

这并不是新观察,规范也说明了它借鉴的是谁:

MCP takes some inspiration from the Language Server Protocol, which standardizes how to add support for programming languages across a whole ecosystem of development tools. In a similar way, MCP standardizes how to integrate additional context and tools into the ecosystem of AI applications.1

请把这个比较当真,而不是当成赞美。在那个协议出现之前,要在编辑器里支持一门语言,意味着每个编辑器都要一个插件;之后,语言团队发布一个 server,每个编辑器都能使用。成功的衡量标准不是优雅,而是集成数量停止倍增。这里也一样:价值在实现数量,而不在设计本身。 一个只有两个产品会说的协议,不过是带了更多仪式感的数据格式。

MCP 消息是 JSON-RPC 2.0。请求是一个对象,包含 jsonrpcidmethod 和可选的 params;响应携带同一个 id,以及 resulterror;通知是没有 id 的请求,并且不会得到回复。规范在此之上增加了三条约束:id 必须是字符串或数字,并且不得为 null;不得与仍在处理中的另一个请求冲突;每个结果都必须携带 resultType 字段。2

在 stdio 传输上——也就是上面命令使用的那一种——分帧规则是每条消息一行:

Messages are delimited by newlines, and MUST NOT contain embedded newlines. […] The server MUST NOT write anything to its stdout that is not a valid MCP message.3

最后一句是自制 server 最常见的破坏方式,而且它会静默失败:一个多余的 console.log、一个进度条、依赖项发出的一条弃用警告,都会让 client 的行解析器撞上不是 JSON 的内容。同一节里也给出了逃生口——server maystderr 写入任何内容,client should not 把它视为错误。上面的参考 server 每次启动都会在 stderr 上打印 Starting default (STDIO) server...,所以管道仍然可以工作。

另一种标准传输是 Streamable HTTP:每条消息都是发往单个端点的 POST,回复要么是一个 JSON 对象,要么是一个请求作用域内的 Server-Sent Events 流——也就是第 14 章手动解析过的线路格式。两种传输上的语义相同,因为传输是一种绑定:它定义分帧和投递,而不是含义。4

上面的命令只发送了 tools/list,除此之外什么也没有。在当前修订版下,这个请求格式错误,符合规范的 server 必须拒绝它。

2026-07-28 起,MCP 是无状态协议,规范说得毫不含糊:

The Model Context Protocol (MCP) is a stateless protocol: all the information needed to process a request is contained in the request itself. A server processes each request independently; no state should be inferred from previous requests, even those on the same connection or stream.2

因此,每个请求都在 params 内一个保留的 _meta 对象中携带自己的协议版本和自己的 client 能力。其中两个字段在每一个请求上都是必需的;缺少任意一个的请求都是格式错误,server must 回复 -326022

_meta keyrequiredwhat it is
io.modelcontextprotocol/protocolVersionyes这个请求使用的修订版,例如 "2026-07-28"
io.modelcontextprotocol/clientCapabilitiesyes本请求中 client 能为 server 做什么
io.modelcontextprotocol/clientInfono (but should)client 名称和版本,仅用于展示和日志
io.modelcontextprotocol/logLevelnoserver 应为本请求输出的最低日志级别

完整写出来,正确的 tools/list 是这样——这也是本章最后一次完整展示这些元数据,因为从这里开始它会出现在每个请求上:

one line, split for the pageTEXT
{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{"_meta":{
  "io.modelcontextprotocol/protocolVersion":"2026-07-28",
  "io.modelcontextprotocol/clientCapabilities":{"elicitation":{"form":{}}},
  "io.modelcontextprotocol/clientInfo":{"name":"bare-hands","version":"0.0.1"}}}}

能力对象就是协商。现在不再有单独的协商步骤:client 在每个请求上声明自己能做什么,server 在结果中声明自己能做什么,任何一方都不得使用另一方没有声称支持的特性。如果 server 需要 client 未声明的能力,must 回复 -32021,并在 data.requiredCapabilities 中指明缺失的能力。如果 server 不支持请求的版本,must 回复 -32022,并列出它支持的版本。2

想预先得到答案的 client 可以直接询问:server/discover 是一个必需 RPC,会在一次往返中返回支持的版本、能力、身份信息,以及一个可选的 instructions 块。5 调用它是可选的。实现它不是。

命令成功了。在当前修订版下它本不该成功;它成功的原因,与其用一段话解释,不如做一次测量,因为这一行就概括了整个生态的状态。

按规范建议现代 client 探测的方式探测这个参考 server:

terminalBASH
echo '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{
  "io.modelcontextprotocol/protocolVersion":"2026-07-28",
  "io.modelcontextprotocol/clientCapabilities":{}}}}' \
  | npx @modelcontextprotocol/server-everything stdio
TEXT
{"jsonrpc":"2.0","id":1,"error":{"code":-32601,"message":"Method not found"}}

这是兼容性规则的第三个分支:DiscoverResult 意味着现代;一个可识别的现代错误意味着现代但版本不对;而其他任何东西——包括 -32601——都意味着旧版,应回退到 initialize 握手。3 那就这样做,并请求当前修订版:

TEXT
→ {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2026-07-28",
   "capabilities":{},"clientInfo":{"name":"bare-hands","version":"0.0.1"}}}

← {"result":{"protocolVersion":"2025-11-25","capabilities":{"tools":{"listChanged":true},
   "prompts":{"listChanged":true},"resources":{"subscribe":true,"listChanged":true},
   "logging":{},"tasks":{…},"completions":{}},"serverInfo":{"name":"mcp-servers/everything",
   "title":"Everything Reference Server","version":"2.0.0"},"instructions":"…"}}

client 请求的是 2026-07-28,server 回答的是 2025-11-25。2026 年 9 月 7 日,官方参考 server——npm 包 @modelcontextprotocol/server-everything,版本 2026.8.31,发布于 2026 年 8 月 31 日——并未实现当前修订版。从日期看,它所基于的 TypeScript SDK 也没有:1.30.0 版本发布于 2026 年 7 月 27 日,也就是该修订版发布的前一天。

请读结论,而不是读八卦。几乎所有关于 MCP 的文章,都在描述一个带有 initialize 握手、会话、server 发给 client 的 roots/list 请求,以及 HTTP+SSE 传输的协议。这四项都已经消失,或正在消失。当你阅读任何关于 MCP 的内容时,包括本页,第一件要找的东西就是修订版编号

而最开始那个命令之所以成功,规范将其称为风险,而非特性:

some legacy servers do not validate that a request arrives after initialize and would process an era-ambiguous method (such as tools/call) under legacy semantics. Probing yields a deterministic failure instead.3

实测:在完全没有握手的情况下向该 server 发送 tools/list,会返回完整目录。一个本应被拒绝的方法被服务了,这正是规范要求即使你只支持现代版本,也要先用 server/discover 探测的原因。

三个角色,以及整份文档中最值得引用的一句话

链接到此部分:三个角色,以及整份文档中最值得引用的一句话

MCP 有三方,而前两者的区别正是人们最容易混为一谈的地方:

Host。 应用:聊天产品、编辑器、agent。它拥有对话、模型、凭据和用户同意。它创建 client,并在它们之间执行安全边界。

Client。 host 内部的连接器。每个 client 只与一个 server 通信——严格的 1:1 关系——并将协议版本和能力附加到它路由的每个请求上。

Server。 一个暴露资源、工具和 prompt 的进程或服务。它可以是本地的,也可以是远程的,独立运行,全部职责就是一个聚焦领域。6

“只与一个 server”这条规则不是记账。它让下面的设计原则变得可实现;如果你只从规范中带走一句话,就带走这一句:

Servers should not be able to read the whole conversation, nor "see into" other servers. Servers receive only necessary contextual information. Full conversation history stays with the host. Each server maintains isolation. Cross-server interactions are controlled by the host.6

这会推翻大多数人一开始带来的心智模型。你连接到助手的天气 server 不会看到你问了什么。它看到的是一个 tools/call,其中包含模型选择的参数,仅此而已——没有之前的轮次,没有你的 system prompt,也没有日历 server 片刻前返回的结果。如果两个 server 需要协作,host 会有意地把一个值从一个带到另一个,因为模型提出了这样的请求。这也是第 30 章所依赖的安全属性:被攻陷的 server 只有一个小而明确的爆炸半径,而要扩大它,必须由 host 配合。

第三件事:三个原语,按谁说了算来排序

链接到此部分:第三件事:三个原语,按谁说了算来排序

第一个命令向那个 server 请求工具,并得到了 13 个。再问它另外两个问题,它也会回答:resources/list 返回 7 个,prompts/list 返回 4 个。它们都没有出现,因为没人问。这就引出了 MCP 的教学主线,它在规范中是一张几乎没人引用的表:

PrimitiveControlDescriptionExample
PromptsUser-controlled由用户选择调用的交互式模板斜杠命令、菜单选项
ResourcesApplication-controlled由 client 附加和管理的上下文数据文件内容、git 历史
ToolsModel-controlled暴露给 LLM 以执行操作的函数API POST 请求、文件写入

不是“三种暴露能力的方式”。而是对“谁决定这件事发生”的三个回答。模型决定调用工具。应用决定附加资源。人决定运行 prompt。弄错了,功能仍然能用,但会在错误的时机、出于错误的原因工作。

最容易体会这一点的是日历。下面这个 server 把同一个日历暴露了三次,分别作为三种原语;它是 100 行纯 Node,没有依赖:

calendar.mjs — the parts that matterJS
const TOOL = {
  name: "create_event",
  description: "Create a calendar event. Writes to the calendar.",
  inputSchema: {
    type: "object",
    properties: {
      title:    { type: "string", description: "Event title." },
      startsAt: { type: "string", format: "date-time", description: "Start, ISO 8601 UTC." },
    },
    required: ["title"],
  },
};

switch (method) {
  case "resources/read":                                          
    return ok(id, { contents: [{ uri: "calendar://week",
      mimeType: "application/json", text: JSON.stringify(EVENTS) }],
      ttlMs: 60000, cacheScope: "private" });

  case "prompts/get":                                             
    return ok(id, { description: PROMPT.description, messages: [{ role: "user",
      content: { type: "text", text: `Read calendar://week and draft a plan. ` +
        `Focus: ${params.arguments?.focus ?? "balance"}.` } }] });

  case "tools/list":                                              
    return ok(id, { tools: [TOOL], ttlMs: 300000, cacheScope: "public" });
}

运行它,并用三种方式分别询问。下面是真实输出,线路上每条消息一行,这里为了页面展示做了换行;请求的 _meta 和 server 的身份块已省略:

TEXT
→ resources/read  {"uri":"calendar://week"}
← {"resultType":"complete","contents":[{"uri":"calendar://week",
   "mimeType":"application/json","text":"[{\"id\":\"e1\",\"title\":\"Standup\",
   \"startsAt\":\"2026-09-07T09:00:00Z\"},{\"id\":\"e2\",\"title\":\"Design review\",
   \"startsAt\":\"2026-09-09T15:00:00Z\"}]"}],"ttlMs":60000,"cacheScope":"private"}

→ prompts/get    {"name":"prepare_week","arguments":{"focus":"deep work"}}
← {"resultType":"complete","description":"Read the week and draft a plan.",
   "messages":[{"role":"user","content":{"type":"text",
   "text":"Read calendar://week and draft a plan. Focus: deep work."}}]}

→ tools/call     {"name":"create_event","arguments":{"title":"Dentist",
                  "startsAt":"2026-09-10T08:30:00Z"}}
← {"resultType":"complete","content":[{"type":"text",
   "text":"Created e3: Dentist at 2026-09-10T08:30:00Z"}],
   "structuredContent":{"id":"e3","title":"Dentist","startsAt":"2026-09-10T08:30:00Z"},
   "isError":false}

三种方法,三种形状,一个日历。重点如下:

它由 URI 寻址,是惰性的,并且由应用决定是否把它附加到对话中。协议中没有任何东西允许模型自行伸手去拿。结果携带 ttlMscacheScope,这是本修订版新增的,所以 client 可以把这一周缓存一分钟,而不是轮询。

它有 schema,它有副作用,并且由模型决定何时调用它。它的结果携带 isError,这正是第 18 章所主张的字段:验证失败会作为模型可读并可修正的工具结果返回,而不是协议错误。

它是一个具名、可带参数的模板,由调用——菜单里的斜杠命令。它返回的是消息,不是答案。它让 server 作者可以交付那套与其自有工具配合良好的措辞,而这恰恰是 server 作者拥有、用户没有的知识。

几乎所有人都会把这三者全做成工具。结果就是,一个本应由应用静默附加的读取操作,要和一个需要审批的写入操作一起争夺模型的 attention;而用户本想要一个按钮的东西,却被埋进了 schema。做对这件事不花成本,而且在你写下一行代码之前就已经决定了。

日历工具有一个必需参数 title,以及一个可选参数 startsAt。让它在没有日期的情况下创建事件,会返回一件有意思的东西:

TEXT
→ tools/call {"name":"create_event","arguments":{"title":"Dentist"}}

← {"resultType":"input_required",
   "inputRequests":{"when":{"method":"elicitation/create","params":{"mode":"form",
     "message":"When should \"Dentist\" start?",
     "requestedSchema":{"type":"object",
       "properties":{"startsAt":{"type":"string","format":"date-time"}},
       "required":["startsAt"]}}}},
   "requestState":"eyJ0aXRsZSI6IkRlbnRpc3QifQ=="}

server 没有发送请求。它回答了自己收到的那个请求,带着 resultType: "input_required",并描述了它还需要什么。client 从人那里收集答案,然后重新发送原始调用——使用新的 id,携带 inputResponses,并回显这个不透明的 requestState

TEXT
→ tools/call {"name":"create_event","arguments":{"title":"Dentist"},
   "inputResponses":{"when":{"action":"accept",
     "content":{"startsAt":"2026-09-10T08:30:00Z"}}},
   "requestState":"eyJ0aXRsZSI6IkRlbnRpc3QifQ=="}

← {"resultType":"complete","content":[{"type":"text",
   "text":"Created e3: Dentist at 2026-09-10T08:30:00Z"}],"isError":false}

这就是 Multi Round-Trip Requests,在当前修订版中引入,取代了旧设计中 server 向 client 回发 JSON-RPC 请求的方式。传输规范现在把规则说得很直白:“servers do not initiate JSON-RPC requests and clients do not send JSON-RPC responses”。4 发起权只有一个方向,而且属于 host。

有两个 client 侧特性依托于这个机制,其中一个名字会绊倒你。

Elicitation 是 server 向请求东西:一个带有刻意受限 JSON Schema 的表单——扁平对象、原始类型属性、无嵌套——这样任何 client 都可以在没有布局引擎的情况下渲染它。它有一条硬规则:server must not 使用表单模式请求“passwords, API keys, access tokens, or payment credentials”,并且 must 对这些内容使用 URL 模式,把用户送到一个 client 永远不会读取的页面。7

Sampling 是 server 向 host 的模型请求一次生成,因此 server 可以在不持有 API key 的情况下变得智能。这里要提醒术语,因为这个词在本课程里已经有另一个意思:这不是第 17 章里的 sampling。 这里与 temperature、top-p 或概率分布的形状无关。它是一次通过协议反向传递的嵌套模型调用。

还有第二个理由让你不要急着使用它:截至本修订版,sampling 已被弃用,与 roots 和 logging 一起,在 SEP-2577 下弃用,并给出直白的迁移建议——“integrate directly with LLM provider APIs instead of Sampling”。8 这个想法不是技术上失败了;它是没能证明自己的表面积值得存在。一个能移除东西的协议,比一个不能移除东西的协议更健康。

无状态听起来像是线路格式细节,直到你测试它。把上面的三条消息交换拿出来,让每条消息都在单独进程里运行——一个全新的 node calendar.mjs,没有共享内存,没有任何结转:

TEXT
process A   tools/call (no date)   → resultType: input_required
                                     requestState: eyJ0aXRsZSI6IkRlbnRpc3QifQ==
process B   tools/call (with the answer, same requestState)
                                   → resultType: complete
                                     "Created e3: Dentist at 2026-09-10T08:30:00Z"
process C   resources/read calendar://week
                                   → events: 2  (Standup, Design review)

进程 B 从未见过问题,却完成了进程 A 开始的 multi-round-trip 调用。这就是 requestState 的意义:continuation 随消息传递,所以没有任何东西依赖它是否还是同一个进程。

进程 C 是失败的部分。事件创建了,却不在那里——因为这个玩具 server 把 EVENTS 保存在模块级数组里,而模块级数组就是连接状态。规范的注释准确命名了这个错误:

an open connection, such as a STDIO process, is not a conversation or session: clients may interleave unrelated requests on the same transport, and a server must not treat connection or process identity as a proxy for conversation or session continuity.2

规定的修复方式不是会话,而是一个显式 handle:创建工具返回一个不透明标识符,之后每次调用都把它作为普通参数传入。协议完全没有它的概念——“from the wire's perspective a handle is an ordinary string in a tool result and an ordinary argument to subsequent tool calls”。9 这让模型负责携带它,也让 server 负责在每一次调用中验证这个调用方是否被允许使用它,因为 handle 是一个名字,而不是一种权限。

server 暴露的每个工具都是一个 schema,会在每次请求中进入你的 prompt;第 24 章测量过这会对 context window 造成什么影响。MCP 还增加了第二项容易忽略的成本,所以值得在上面的参考 server 上同时数一数。

o200k_base tokensTEXT
13 tool definitions (name + description + inputSchema):  1,307 tokens
  cheapest tool, get-tiny-image                              52
  costliest tool, gzip-file-as-resource                     235
server `instructions`, returned by discovery:               312 tokens
                                                          ------
one server, connected, before it is used:                 1,619 tokens

两个观察。第一个是算术:连接五个这种大小的 server,你的窗口中大约 8000 个 token 会在每一轮、永远被占用,不管模型是否使用其中任何一个——这就是第 24 章引用的从 150,000 到 2,000 的缩减背后的机制,也是 just-in-time 工具发现存在的原因。

第二个是披着会计外衣的安全提示。instructions由 server 作者编写、会落入 host prompt 的自然语言文本,旁边的工具描述也是一样。规范在自己的安全原则中说明了应该怎么做:工具注解和描述“should be considered untrusted, unless obtained from a trusted server”,host “must obtain explicit user consent before invoking any tool”。1 连接一个 MCP server 不是添加一个依赖。它是把你 system prompt 中的 1,619 个 token,以及被调用的权利,授予一个陌生人。第 30 章讲的就是当这个陌生人有敌意时会发生什么。

带日期的一节:2026-07-28 修订版,以及它破坏了什么

链接到此部分:带日期的一节:2026-07-28 修订版,以及它破坏了什么

本节中的一切都适用于协议修订版 2026-07-28,即当前版本,阅读日期为 2026 年 9 月 7 日。修订版按 YYYY-MM-DD 标日期,而这个日期就是最后一次做出向后不兼容变更的时间。10 规范性文档是一个 TypeScript 文件,schema/2026-07-28/schema.ts;旁边的 JSON Schema 是从它生成的,所以这里按 TypeScript 读规范,也正因如此,用别的东西教 MCP 就是在教一份翻译。

What changedWasIs nowBreaks
握手每个连接一次 initialize + notifications/initialized已移除;每个请求携带 _meta 版本和能力本修订版之前编写的每个 client
会话Mcp-Session-Id header,连接作用域状态已移除;状态通过显式、由 server 铸造的 handle 传递按连接变化的列表端点
发现initialize 结果推断server/discover,server must 实现没有什么,但现在必须实现
server 到 client 调用server 发送 roots/listsampling/createMessageelicitation/createInputRequiredResult 加 client 重试每个向 client 推送请求的 server
结果形状任意对象必需 resultType"complete""input_required"没有什么:缺失字段必须按 "complete" 读取
订阅HTTP GET 流,resources/subscribe一个带有 opt-in 类型的 subscriptions/listenGET 端点已消失
流恢复Streamable HTTP 上的 Last-Event-ID 重放已移除;流中断会丢失请求,请用新的 id 重新发起依赖重新投递的 client
Rootsserver 可请求的 client 特性deprecated(SEP-2577);将路径作为工具参数或 resource URI 传递暂无——12 个月窗口
Sampling 和 loggingclient 特性deprecated(SEP-2577)暂无——12 个月窗口
HTTP+SSE 传输2025-03-26 起 deprecated根据生命周期策略(SEP-2596)标为 Deprecated迁移到 Streamable HTTP
client 注册OAuth 2.0 Dynamic Client Registration,RFC 7591deprecated,转向 Client ID Metadata Documents为没有后者的 authorization server 保留
错误码资源未找到使用 -32002-32602-32020-32099 为规范保留新代码 -32020-32021-32022

这张表背后的治理变化,比任何单独一行都更重要。本修订版采用了特性生命周期和弃用策略:特性分为 Active、Deprecated 或 Removed;被弃用的特性会记录迁移路径,并在规范中保留至少 12 个月,之后才有资格被移除;还有一个 registry 列出当前所有 Deprecated 状态的内容。8 在这项政策之前,AI 协议里的“deprecated”是什么意思,取决于最近一篇博客怎么说。现在它意味着一个日期。

查看详情

Extensions,也就是目前几乎没人写过的部分。

在核心之外,MCP 定义了可选的 extensions——“always opt-in and require explicit support from both client and server”,通过 client 和 server 能力中的 extensions 字段声明。1 有三个值得记住名字:

  • Tasksio.modelcontextprotocol/tasks),在本修订版中从核心协议移入官方 extension:用于长时间运行操作的异步执行,通过 tasks/get 轮询,通过 tasks/update 在执行中途输入,并使用持久 handle。它回答的是一个需要 20 分钟的工具该怎么办;第 23 章用一个进度事件和一个能抵达工具的信号处理了这个问题。
  • Skills over MCP,一个工作组,目标是让 agent skill——第 28 章的主题——可通过协议发现和消费。
  • MCP Apps,在对话中内联渲染的交互式 UI:图表、表单、视频播放器。

还要注意,现在“negotiated”是什么意思:已经没有初始化阶段可供协商,所以 extension 也和其他所有东西一样按请求声明。

MCP 的位置:相对于它常被混淆的那些东西

链接到此部分:MCP 的位置:相对于它常被混淆的那些东西

这一整块的词汇放在一起是这样的。

What it isWho talks to whomWhen it is the answer
普通 API程序的接口你的代码 ↔ 一个服务你在编写调用方。你控制 schema、认证和错误处理,而且没有 discovery 问题要解决。
MCP向 AI 应用暴露工具、数据和模板的协议host ↔ server,每个 server 一个 client能力由别人编写,并且许多 host 都应能在无需定制集成的情况下使用它。
RAG查找文本并把它放进 prompt 的技术你的代码 ↔ 你的索引模型需要知道某件事。第 19 章。MCP 是交付 retriever 的一种方式;它不是 retriever。
Agent skills一个包含 SKILL.md、由模型阅读的文件夹模型 ↔ 文档知识是流程性的——我们如何做这件事——而且是散文,不是函数。第 28 章。
A2A让 agent 作为对等方协作的协议agent ↔ agent另一端会推理、计划,并在长任务中保持状态,而不是只回答一次调用。
ACP曾经是一个独立的 agent 通信协议它不再是一个仍然有效的比较对象。见下文。

其中两个各值得说一句,因为困惑真正发生在那里。

MCP 对比 A2A 不是竞争,两个规范也都这么说。A2A 文档按另一端是什么来划线:MCP “defines how an AI agent interacts with and utilizes individual tools and resources, such as a database or an API”,其中工具执行“specific, often stateless, functions”;A2A 面向 agent,也就是“more autonomous systems”,它们会“reason, plan, use multiple tools, maintain state over longer interactions, and engage in complex, often multi-turn dialogues”。它自己的总结是值得记住的一句:“A2A is about agents partnering on tasks, while MCP is more about agents using capabilities.”11 二者可以嵌套——应用用 A2A 访问其他 agent,而每个 agent 用 MCP 访问自己的工具。第 25 章在单个进程内部画出了这条线,区分询问 sub-agent 和把对话交给它;A2A 则把这条线画在组织之间。

MCP 对比 ACP 是一个前提已经过时的比较,也正因如此值得回答。Agent Communication Protocol 曾是一个独立的 agent-to-agent 消息开放标准。它自己的文档现在开头就有提示:“ACP is now part of A2A under the Linux Foundation!”12 对“选 MCP 还是 ACP?”这个问题,2026 年 9 月的诚实答案是:这个问题可选项比排在搜索结果里的那些页面暗示的少了一个。

而人们最常问的比较,mcp vs api,答案反而最无聊:MCP 是 API。 它增加的不是能力,而是约定——一组固定的方法名、一次 discovery 调用、一个覆盖原语的控制层级,以及一个隔离模型。你放弃设计自己接口的自由,换来每个会说该协议的 host;这是所有协议从来都在提供的交易。

你现在已经可以不靠翻译读规范,可以按谁说了算来区分 resource、工具和 prompt,可以在客户端库骗你时手动敲请求,也可以通过一篇 MCP 文章仍把哪些已弃用特性当作当前特性来判断它的年代。

你还没有做的是发布一个。第 27 章会把同一个 server 写两遍——TypeScript 和 Python 并排,因为 MCP 是本课程中唯一真正双语的领域,数字也在两个方向上证明了这一点。它会正确覆盖两个 live transport、inspector、打包,以及本章故意留下的协议另一半:authorization。因为一旦你的 server 是远程的,而不是你自己笔记本上的子进程,陌生人的 client 就会提交一个 token,而规范关于你可以如何处理它的规则异常严格。

这就提出了下一章必须回答的问题,而且它并不友好:如果一个 token 到达你的 server,而它是为别人的 audience 签发的,到底是什么阻止你把它转发出去?


本章中的每一段引用、每个方法名、错误码和规则,都读取自 Model Context Protocol 规范,修订版 2026-07-28,阅读日期为 2026 年 9 月 7 日。每条 trace 都在 Node 22 上本地产生:玩具日历 server 是 101 行、没有依赖,参考 server 是下面列出的已发布 npm 包。撰写本章没有调用任何付费 API——这里没有任何内容需要模型,而这本身就是重点。

测量对象:@modelcontextprotocol/server-everything@2026.8.31,发布于 2026 年 8 月 31 日,基于 @modelcontextprotocol/sdk@1.30.0,后者发布于 2026 年 7 月 27 日——比本章描述的修订版早一天。它用 -32601 回答 server/discover,在请求 2026-07-28 时协商出 2025-11-25,并在完全没有握手的情况下服务 tools/list。它的目录是 13 个工具,共 7,663 字节;token 计数为 o200k_base,通过 tiktoken 计算,覆盖每个定义的 namedescriptioninputSchema,这才是 provider 渲染进你 prompt 的内容,而不是 JSON-RPC 帧本身的重量。

Anthropic,Code execution with MCP: building more efficient agents,2025 年 11 月 4 日,是 150,000 到 2,000 这个数字的来源;该数字已在第 24 章引用并使用,这里仅作引用。

  1. Specificationmodelcontextprotocol.io/specification/latest(重定向到 /2026-07-28),阅读于 2026 年 9 月 7 日。来源包括 Language Server Protocol 类比;规范“based on the TypeScript schema in schema.ts”的声明;基础协议摘要(“Stateless, self-contained requests”、“Per-request capability negotiation”);extension 列表(Tasks、Skills over MCP、MCP Apps)以及 extensions “are always opt-in and require explicit support from both client and server”的声明;还有 Security 和 Trust & Safety 原则,包括“Hosts must obtain explicit user consent before invoking any tool”以及将工具注解视为不可信的处理方式。 2 3

  2. Base Protocolmodelcontextprotocol.io/specification/2026-07-28/basic。来源包括 JSON-RPC 约束(非 null id、不得复用 id、必需 resultType);Statelessness 一节及其关于开放 stdio 进程不是会话的注释;_meta 保留 key 表,以及每个 per-request 字段的必需/可选状态;缺失必需字段的 -32602 规则;MissingRequiredClientCapability-32021)规则;以及错误码分配策略。 2 3 4 5

  3. stdio transportmodelcontextprotocol.io/specification/2026-07-28/basic/transports/stdio。来源包括以换行分隔的分帧规则、stdout 纯净性要求、stderr 许可,以及三种结果的向后兼容探测——包括关于某些旧版 server 会在没有握手的情况下处理时代歧义方法的警告,本章的测量复现了这一点。 2 3

  4. Transports overviewmodelcontextprotocol.io/specification/2026-07-28/basic/transports。来源包括“a transport is a binding”的表述,以及 server 不发起 JSON-RPC 请求、client 不发送 JSON-RPC 响应的声明。 2

  5. Discoverymodelcontextprotocol.io/specification/2026-07-28/server/discover。来源包括 server/discover 的必需状态、DiscoverResult 的形状,以及被描述为“optional natural-language guidance for LLMs on how to use this server effectively”的 instructions 字段。

  6. Architecturemodelcontextprotocol.io/specification/2026-07-28/architecture。来源包括 host/client/server 定义、client 到 server 的 1:1 规则、四条设计原则,其中这里引用的隔离原则省略了第五个 bullet:“Host process enforces security boundaries”,以及能力协商一节。 2

  7. Elicitation.../client/elicitation,以及 Sampling.../client/sampling。来源包括两种 elicitation 模式及其受限 schema;禁止通过表单模式请求凭据;sampling 定义、其 human-in-the-loop 要求,以及附加其上的弃用警告。

  8. Key Changesmodelcontextprotocol.io/specification/2026-07-28/changelog,以及 Feature lifecycle and deprecation policy.../community/feature-lifecycle。来源包括变更表中的每一行:移除 sessions 和 Mcp-Session-Id header(SEP-2567);无状态化和移除 initialize(SEP-2575);server/discover(SEP-2575);subscriptions/listen(SEP-2575);Multi Round-Trip Requests 和 resultType(SEP-2322);移除流可恢复性(SEP-2575);弃用 Roots、Sampling 和 Logging(SEP-2577);重新分类 HTTP+SSE(SEP-2596);弃用 Dynamic Client Registration,转向 Client ID Metadata Documents;错误码重新编号;以及 12 个月弃用窗口。 2

  9. Toolsmodelcontextprotocol.io/specification/2026-07-28/server/tools,以及 Server Features.../server。来源包括上文复现的控制层级表;tools/listtools/call 的形状;isError 对协议错误与工具执行错误的区分;工具命名规则以及建议“prefixing tool names with a server identifier”的命名空间注释;以及非规范性的“Stateful Tools”显式 handle 指南。

  10. Versioningmodelcontextprotocol.io/specification/versioning。来源包括 YYYY-MM-DD 方案、Draft/Current/Final 修订状态、确认 2026-07-28 为 current,以及 per-request 协商规则。modelcontextprotocol.io/docs/sdk 处的 SDK tier 表列出 TypeScript、Python、C#、Go 和 Rust 为 Tier 1,Java 和 Ruby 为 Tier 2,Swift、PHP 和 Kotlin 为 Tier 3。

  11. A2A Protocol,版本 1.0.0,a2a-protocol.org——规范以及页面 A2A and MCP: Relationship and Distinction,阅读于 2026 年 9 月 7 日。来源包括工具与 agent 的区分、两个协议“address distinct but highly complementary needs”的声明,以及 partnering/using 表述。

  12. Agent Communication Protocol,agentcommunicationprotocol.dev,阅读于 2026 年 9 月 7 日:“ACP is now part of A2A under the Linux Foundation!”,这是加在一份仍完整提供的规范上方的横幅——architecture、agent manifest、agent discovery、message structure、stateful agents、run lifecycle 和 REST endpoint list 仍然全部返回 200。规范没有消失;项目消失了。

准备好让 LIA 替你选模型了吗?

所有 AI 模型都在一处——今天就免费开始。