MCP 协议设计与实现

系统剖析 Model Context Protocol 的协议内核与两套 SDK 实现。

在 MCP 之前,每接一个工具就要写一份适配:这个用 OpenAPI,那个用私有 SDK,换个 Agent 全部重来。MCP 把这件事变成了一个协议——任何工具实现一次 MCP Server,就能被 Claude Code、Claude Desktop、Cursor 等任何 Client 调用。它正在成为 Agent 生态的"USB 接口"。

本专栏从协议规范出发,深入 TypeScript SDKPython SDK 的源码实现。两个 SDK 的实现代码分别是 2.3 万行2.0 万行;把测试、示例与工具脚本都算进去,整个仓库是 8.2 万行与 6.1 万行。行数的统计口径与锚定版本见下面《源码版本》一节——不写口径的行数不是一个事实。

flowchart TB
  subgraph H [Host · Claude Code / Desktop / Cursor]
    direction LR
    C1[Client 1] --- C2[Client 2] --- C3[Client 3]
  end
  C1 -- STDIO --> S1[Server · 本地进程]
  C2 -- Streamable HTTP --> S2[Server · 远程]
  C3 -- SSE / WebSocket --> S3[Server]
  subgraph P [三大原语 · 谁来控制]
    T[Tool<br/>模型控制] --- R[Resource<br/>应用控制] --- PR[Prompt<br/>用户控制]
  end
  S2 -.提供.-> P
  S2 -.认证.-> OA[OAuth 2.1 + PKCE<br/>Resource Indicator]
  S2 -.反向通道.-> BK[Sampling · Elicitation<br/>Roots · Progress · Cancel]

这张图里最容易被忽略的是右下角那条反向通道。 大多数人对 MCP 的理解停在"Client 调 Server 的工具",但协议是双向的:Server 可以反过来请求 Host 调用 LLM(Sampling)、向用户提问(Elicitation)、询问文件系统边界(Roots)、上报进度、被优雅取消。这些才是把 MCP 从"远程函数调用"抬升成"Agent 协议"的部分。

读完你能做到什么

  1. 说清 Host / Client / Server 三层为什么这么分。 四大设计原则、能力协商机制、安全边界,以及 TypeScript SDK 里 Protocol 类怎么把这些落地(第2章 架构总览第4章 生命周期与能力协商)。
  2. 写出模型能正确调用的 Tool。 定义结构与生命周期、SDK 的注册机制、输入校验、Tool Annotations 这套 Agent 安全元数据、错误处理的双层设计、返回值的内容类型,以及 5 种反模式(第5章 Tool)。
  3. 分清三大原语各自归谁控制。 Tool 由模型控制、Resource 由应用控制、Prompt 由用户控制——这个"三大控制平面"的划分决定了你该把一个能力做成哪一种(第6章 Resource第7章 Prompt)。
  4. 选对传输层。 STDIO 的消息帧格式与进程派生安全、Streamable HTTP 的三种 HTTP 方法分工、会话有状态与无状态的权衡、EventStore 的事件持久化与断线重放(第12章 STDIO第13章 Streamable HTTP第14章 SSE 与 WebSocket)。
  5. 接上生产级认证。 为什么是 OAuth 2.1 而不是 API Key、授权服务器发现、客户端注册的三种策略、PKCE、Resource Indicator 与令牌绑定,以及认证怎么和 Streamable HTTP 集成(第15章 OAuth第16章 服务发现)。
  6. 用上反向通道。 Sampling 让 Server 发起 LLM 调用、Elicitation 让 Server 向用户提问、Roots 划定文件边界,以及进度、取消、结构化日志(第17章 Sampling第18章 Elicitation 与 Roots)。
  7. 从零构建一个生产级 Server。 技术选型、Tools/Resources/Prompts 三件套实现、进度追踪、测试策略(第20章 从零构建第21章 设计模式)。

这个专栏的讲法

规范 + 两套 SDK 对照着读。 同一个机制,TypeScript 和 Python 各自怎么实现——差异出现的地方往往就是规范留白的地方,也是你自己实现时会踩的地方。

有一章专门读别人的实现。 第 19 章拆 Claude Code 的 MCP 客户端(1.6 万行,实测)——一个真实的、跑在几百万台机器上的 Client 是怎么处理超时、重连、多 Server 并发的(第19章)。

每章都给反模式与设计指南。 Prompt 的 5 种反模式、Tool 的错误处理双层设计——这些是从别人已经踩过的坑里提出来的。

适合谁读

不适合:只想调用现成 MCP Server 的使用者——那读官方文档的 quickstart 就够了。

源码版本

MCP 的两个官方 SDK 都还在快速演进,文件会搬家(例如 ModelPreferences 已从 mcp/types.py 迁到 mcp-types/mcp_types/_types.py)。所以本专栏不写 latest——latest 不是一个版本,读者拿到的树和写作时的树可能不是同一棵。全部引用锚定在下面这几个精确提交上:

组件 版本 获取方式
MCP 协议规范 2025-11-25 modelcontextprotocol/modelcontextprotocol
TypeScript SDK b8886e7(2026-04-16) modelcontextprotocol/typescript-sdk
Python SDK 3d7b311(2026-04-15) modelcontextprotocol/python-sdk
git clone https://github.com/modelcontextprotocol/typescript-sdk.git
cd typescript-sdk && git checkout b8886e7

照着这两个 commit 拿到的就是核对时用的那棵树——本专栏 104 处带行号的引用,行号越界 0 处。用更新的版本读到行号对不上时,以符号名检索为准rg "class ModelPreferences" -n)。另外规范仓库已从 specification 改名为 modelcontextprotocol,旧地址靠 GitHub 重定向仍能 clone,本专栏一律用改名后的地址。

两个 SDK 有多大:口径与复核命令

开头那句「2.3 万行 / 2.0 万行」是在上面这两个提交上实测的,口径是排除测试的实现代码。一并给出整仓数字,是因为这两个数差了三倍多,而「SDK 有多大」这个问题在不同口径下会得到差三倍的答案——不说口径就等于没说:

实现代码(排除测试) 整仓(含测试 / 示例 / 脚本)
TypeScript SDK b8886e7 22704 82494
Python SDK 3d7b311 20459 61026

复核命令(wc -l 数的是物理行,含空行与注释):

# TypeScript:packages/ 下的 .ts,剔掉 *.test.ts / *.spec.ts / __tests__ / test/
cd typescript-sdk && git checkout b8886e7
find packages -name '*.ts' | grep -vE '\.test\.ts$|\.spec\.ts$|/__tests__/|/test/' | xargs wc -l | tail -1
find . -path ./.git -prune -o -name '*.ts' -print | xargs wc -l | tail -1

# Python:src/ 下的 .py(这棵树里 src/ 只有 mcp 一个包,测试都在顶层 tests/)
cd python-sdk && git checkout 3d7b311
find src -name '*.py' | xargs wc -l | tail -1
find . -path ./.git -prune -o -name '*.py' -print | xargs wc -l | tail -1

两个数字都会随版本变,而且变得很快。 同样这两条命令跑在 2026-08 的 HEAD 上:TypeScript 的实现代码已经是 5.4 万行(仓库拆成了 7 个 package,多出 core-internalcodemodserver-legacy),Python 是 3.8 万行。所以引用规模时必须带上是哪一棵树——这也正是本专栏锚定提交而不写 latest 的原因。

第 19 章是个例外,要单独说明。那一章讲的是 Claude Code 怎么做 MCP 客户端, 引用的 src/query.tssrc/utils/api.tssrc/tools/ToolSearchTool/ToolSearchTool.ts 等路径不属于上面这三个仓库——它们来自 Claude Code 2026.3.31 的源码快照, 即 npm 包 @anthropic-ai/claude-code@2.1.89 短暂随包分发的 source map 所还原 出的 TypeScript 树。

这份快照今天已经拿不到github.com/anthropics/claude-code 是 issue、文档 与插件仓库,里面没有 src/;现在的 npm 包里也不再有 .map 文件。所以第 19 章 的那些行号没法像本专栏其他章节那样 checkout 一棵树逐行核对,读的时候请以 函数名与调用关系为锚点(toolToAPISchemalogContextMetrics 等),不要把结论 压在行号上。《Claude Code 源码深度解析》专栏对这份快照有更完整的说明。

目录

第一部分:开篇

第二部分:协议基础

第三部分:三大原语

第四部分:Client 与 Server 实现

第五部分:传输层

第六部分:认证与安全

第七部分:高级特性

第八部分:实战与总结

版权声明

本专栏内容为 杨艺韬 版权所有,保留一切权利。未经书面许可,不得全文或大段转载、改编、翻译,或用于任何商业用途(含以本专栏内容训练模型、生成衍生课程或商品)。

欢迎分享本专栏的链接。引用少量内容用于评论、教学或研究时,请署名 杨艺韬 并附上原文链接。