第 49 章:开放协议的价值

源码验证日期:2026-05-15,基于 commit
0d81bb6
你在第 48 章看到 agent 架构如何在隔离与共享之间找到平衡。但 agent 只是一个内部组件。当你想让外部世界与 Claude Code 交互——让 AI 调用你们公司的内部 API、读取自定义数据库、操作专用工具——你需要一个更大的决定:用什么方式连接外部工具?
Claude Code 的回答是 MCP(Model Context Protocol)。但 MCP 不是一个简单的 API。它是一个协议。API 和协议的区别不只是用词——API 是”我提供这些接口,你来调用”,协议是”我们约定这样通信,谁都可以实现”。API 有一个拥有者,协议有一个社区。
这个选择的影响远超技术层面。它决定了生态系统的形状。
本章路线图
1 | graph LR |
现状:MCP 在 Claude Code 里的实现
多传输层,统一模型
MCP 支持六种传输方式:stdio、sse、sse-ide、http、ws、sdk。在 types.ts 里,每种传输方式有独立的配置 schema:1
2
3export const TransportSchema = lazySchema(() =>
z.enum(['stdio', 'sse', 'sse-ide', 'http', 'ws', 'sdk']),
)
但不管传输层是什么,上层看到的是统一的接口:一个 MCP 服务器暴露一组工具,每个工具有名字、描述和 JSON Schema 定义的输入。调用工具就是发一个请求、拿一个结果。
这种”多传输、统一模型”的设计是协议思维的产物。它说的是:不管你是本地进程(stdio)、远程服务(http/sse)、WebSocket 实时连接(ws)、还是 SDK 内嵌(sdk),你都是一个 MCP 服务器。差别只在管道,不在内容。
六层配置作用域
config.ts 里的 MCP 配置不是单一来源,而是六个作用域的合并:1
plugin -> claudeai -> user -> project -> local -> enterprise
优先级从低到高。用户在 .mcp.json 里配的 project 级服务器优先于 claude.ai 网页端开的连接器;企业管理员配的 enterprise 级配置优先于一切。这不是随意的排序——它反映了控制权的主张:企业管理 > 本地项目 > 全局用户 > 云端服务 > 插件。
合并之后还有去重。dedupPluginMcpServers 和 dedupClaudeAiMcpServers 通过内容签名(getMcpServerSignature)识别重复的服务器——即使名字不同,只要指向同一个命令或同一个 URL,就认为是同一个。这避免了同一工具被注册两次、浪费上下文空间。
安全策略的纵深
isMcpServerAllowedByPolicy 实现了三层安全检查:
- 拒绝名单(denylist):绝对优先。如果服务器在拒绝名单里,不管其他规则怎么配,都不允许。
- 允许名单(allowlist):如果存在允许名单,只有名单里的服务器才允许连接。
- 默认允许:如果没有配置任何限制策略,所有服务器默认允许。
允许名单支持三种匹配方式:按名称、按命令(stdio 服务器)、按 URL 模式(远程服务器)。URL 模式还支持通配符:1
2
3
4
5function urlPatternToRegex(pattern: string): RegExp {
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&')
const regexStr = escaped.replace(/\*/g, '.*')
return new RegExp(`^${regexStr}$`)
}
这给了企业管理员细粒度的控制:允许所有 https://*.internal.company.com/* 的服务器,拒绝其他一切。
三种方案的对比
方案一:硬编码工具集
最简单的方案。把所有工具(文件读写、命令执行、Web 搜索……)都编译进程序。不需要外部服务器,不需要协议,不需要配置。
这是 Claude Code 最初的做法。最初版本的工具集就是固定的几个。但硬编码有两个致命问题:第一,新工具需要修改核心代码、重新发布;第二,第三方无法扩展——你不能让 Claude Code 调用你们公司的内部 API。
方案二:插件 API
定义一个 JavaScript/TypeScript 插件接口。第三方写一个 JS 模块,导出符合接口的工具函数,Claude Code 加载它。
这在 VS Code、Webpack 等项目里很常见。优点是集成度高——插件可以访问宿主的内部 API。缺点是耦合度高——插件依赖宿主的内部实现,宿主升级可能破坏插件。
更重要的是安全问题。一个 JS 插件运行在 Claude Code 的进程里,可以访问文件系统、网络、环境变量。沙箱化 JS 在理论上可行,在实践中很困难。
方案三:开放协议(MCP)
定义一个通信协议。工具提供者和工具消费者通过这个协议交互,中间的管道可以是任何传输层。第三方不需要知道 Claude Code 的内部实现,只需要实现协议。
为什么选了开放协议
理由一:可组合性——乘法效应
协议的核心价值是可组合性。一个 MCP 服务器不需要知道谁在调用它。同一个文件系统 MCP 服务器可以被 Claude Code 调用,也可以被 Cursor 调用,也可以被任何实现了 MCP 客户端的程序调用。
这意味着生态系统的增长是乘法式的:N 个客户端 x M 个服务器 = N x M 种组合。如果只是 API,增长是加法式的:每新增一个客户端,需要为 M 个服务器写 M 个适配器。
config.ts 里的插件服务器系统已经展示了这个效应。插件(loadAllPluginsCacheOnly)提供自己的 MCP 服务器,这些服务器和用户手动配置的服务器享受同样的生命周期管理——连接、工具发现、权限检查、去重。插件不需要知道 Claude Code 怎么管理这些,只需要提供配置。
理由二:安全边界——进程级隔离
MCP 服务器运行在独立的进程里(stdio 传输)或独立的网络上(http/sse/ws 传输)。这意味着它不能直接访问 Claude Code 的内存、文件系统或 API 密钥。通信只能通过协议定义的消息进行。
这种进程级隔离比 JS 插件的沙箱化强得多。你不需要信任 MCP 服务器的代码,只需要信任它的输入输出。即使一个恶意的 MCP 服务器,它的影响范围也被协议的边界限制。
理由三:生态锁定避免
如果 Claude Code 只有一个私有的工具接口,第三方要么为 Claude Code 专门写适配器,要么不支持它。MCP 作为开放协议意味着:
- 工具服务器只需要实现一次,所有 MCP 客户端都能用
- Claude Code 不需要为每个工具写专门的集成
- 用户不会被锁定在单一客户端——他们可以带着工具配置迁移
config.ts 里的 .mcp.json 文件就是这个理念的体现。它放在项目根目录,可以提交到版本控制,团队里每个人都能用同样的工具配置。不管他们用的是 Claude Code 还是别的 MCP 客户端。
理由四:企业友好——精确控制
企业需要控制。config.ts 里的企业配置作用域(enterprise)给了管理员独占控制权:1
2
3
4if (doesEnterpriseMcpConfigExist()) {
// 企业配置存在时,不加载其他任何作用域
return { servers: filtered, errors: [] }
}
这不是附加功能,是设计核心。如果 MCP 是私有 API,企业要么完全接受,要么完全拒绝。作为协议,企业可以精确控制允许哪些服务器、拒绝哪些传输方式、要求哪些安全策略。
与其他协议的横向对比
| 特征 | MCP | LSP(Language Server Protocol) | OpenAPI | GraphQL |
|---|---|---|---|---|
| 领域 | AI 工具交互 | 代码编辑器功能 | Web API 描述 | 数据查询 |
| 设计者 | Anthropic | Microsoft | 社区 | |
| 传输 | 多种(stdio/http/ws) | 多种(stdio/tcp/ws) | HTTP | HTTP |
| 发现 | 工具列表 + schema | 能力注册 | Schema 文件 | Schema 内省 |
| 生态模式 | 客户端 + 服务器 | 客户端 + 服务器 | 生成器 + 消费者 | 客户端 + 服务器 |
MCP 的设计借鉴了 LSP 的成功经验。LSP 让编辑器和语言服务解耦——一个 Python 语言服务器可以被 VS Code、Neovim、Emacs 同时使用。MCP 试图做同样的事:让 AI 客户端和工具服务解耦。
关键区别在于:LSP 的交互是确定性的——“跳转到定义”总是返回同一个结果。MCP 的交互是非确定性的——工具的输入由 AI 模型生成,可能不完全符合 schema。这就是为什么 MCP 特别强调 schema 校验和错误处理。
如果重新设计
服务器发现
当前的 MCP 配置是静态的——用户或管理员手动指定每个服务器的连接信息。一个更强大的设计是动态发现:MCP 服务器在网络上广播自己的存在,客户端自动发现并连接。
动态发现带来新的安全挑战——你需要验证服务器的身份、加密通信、防止伪装。但作为可选功能,它可以让 MCP 的生态系统更加流畅。
流式工具结果
当前的 MCP 工具调用是请求-响应模式:发一个请求,等一个完整的结果。但有些操作天生是流式的——长时间运行的命令、实时数据查询、大文件的分块处理。
如果协议支持流式响应,工具可以在结果生成时就发送部分数据,而不是等到全部完成。这对用户体验是质的提升。
跨服务器组合
当前的每个 MCP 服务器是独立的。你不能让一个服务器的工具调用另一个服务器的工具。一个更有表达力的设计是允许工具组合——一个”查询数据库”的 MCP 工具可以把结果传给一个”生成图表”的 MCP 工具,形成管道。
试试看
练习一:写一个最小的 MCP 服务器
用 Node.js 写一个最简单的 MCP stdio 服务器——只暴露一个工具,返回 “Hello, MCP!”。在 .mcp.json 中配置它,让 Claude Code 调用。
练习二:追踪配置合并
在 config.ts 中找到 MCP 配置合并的逻辑,追踪六个作用域的合并顺序。在多个作用域中配置同一个服务器(名字不同但 URL 相同),观察去重逻辑如何工作。
练习三:对比 LSP 的设计
阅读 LSP(Language Server Protocol)的规范,对比 MCP 的设计。两者在传输层、发现机制、能力协商上有什么异同?MCP 从 LSP 学到了什么,又做了什么不同的选择?
检查点
- MCP 是协议不是 API:”约定高于实现”——任何人都可以实现客户端或服务器
- 六种传输方式:stdio、sse、sse-ide、http、ws、sdk——管道不同,模型统一
- 六层配置作用域:plugin -> claudeai -> user -> project -> local -> enterprise
- 被否决的方案:硬编码工具集(无法扩展)、插件 API(安全风险高)
- 乘法效应:N 个客户端 x M 个服务器 = N x M 种组合
- 安全边界:进程级隔离比 JS 沙箱更可靠
- 企业控制:denylist/allowlist + URL 通配符 + 企业配置优先
导航
下一章:第 50 章:性能的故事