引言
LLM 本身只会“想”和“说”。如果想让它读文件、查数据库、调用 GitHub、控制浏览器,就必须把外部系统接到模型身边。
最早的做法很直接:每个应用自己写一套插件接口,每个工具自己适配不同的 AI 客户端。
|
|
工具越多、客户端越多,连接关系就会爆炸:
|
|
MCP(Model Context Protocol,模型上下文协议)解决的正是这个问题。它把 AI 应用和外部工具之间的通信标准化,让工具只需要实现一套协议,就能被不同的 AI 客户端接入。
一句话理解:
MCP 是 AI 应用连接外部工具和数据源的标准协议。
它不是某个具体工具,也不是某个模型能力,而是一套“模型如何发现能力、读取上下文、调用动作”的通信规范。
MCP 要解决什么问题
连接外部世界
大模型的知识来自训练数据和当前上下文。它不知道你本地项目有哪些文件,也不能天然访问数据库、浏览器、GitHub Issue、CI 日志。
要让模型真正做事,必须补上三类能力:
| 能力 | 例子 |
|---|---|
| 获取上下文 | 读取文件、查询数据库、获取网页内容 |
| 执行动作 | 创建 Issue、运行命令、写入文档 |
| 复用流程 | 代码审查模板、发布检查清单、排障流程 |
MCP 把这三类能力抽象成 Resources、Tools、Prompts。
降低集成成本
没有统一协议时,每个工具都要适配每个客户端:
|
|
有了 MCP 后,连接关系变成:
|
|
这就是协议的价值:让集成从“点对点适配”变成“标准接口对接”。
划清模型和工具的边界
MCP 还有一个很重要的工程意义:它把“模型推理”和“外部执行”分开。
|
|
模型不直接碰数据库,也不直接拿 token。它只通过 Client 暴露的能力和 Server 交互。
架构:Host、Client、Server
MCP 的核心架构由三类角色组成。
|
|
Host
Host 是用户真正使用的 AI 应用,比如 Claude Desktop、Claude Code、IDE 插件或自研 Agent 平台。
它负责:
- 管理用户会话
- 调用 LLM
- 展示工具和资源
- 决定哪些 MCP Server 被接入
- 做最终的权限和安全控制
用户感知到的是 Host,而不是底层协议。
Client
Client 是 Host 内部的协议客户端。一个 Host 可以同时连接多个 MCP Server,通常每个 Server 对应一个 Client 连接。
Client 负责:
- 建立连接
- 初始化协议能力
- 发送 JSON-RPC 请求
- 接收工具、资源、提示模板列表
- 把工具结果注入模型上下文
- 执行用户确认和权限策略
可以把 Client 理解成 Host 和 Server 之间的“协议适配层”。
Server
Server 是能力提供方。它把某个外部系统封装成 MCP 能理解的形式。
例如:
| Server | 暴露能力 |
|---|---|
| filesystem | 读取/写入指定目录文件 |
| github | 查询仓库、Issue、PR、Actions |
| postgres | 查询数据库 schema 和执行 SQL |
| browser | 打开网页、截图、提取 DOM |
| figma | 获取设计稿节点和样式 |
Server 不需要关心背后连接的是 Claude 还是其他 Agent,只要遵守 MCP 协议即可。
协议层:JSON-RPC 与生命周期
MCP 底层基于 JSON-RPC 2.0。也就是说,请求和响应都是结构化 JSON 消息。
一个典型请求:
|
|
一个典型响应:
|
|
JSON-RPC 的好处是简单、语言无关、容易调试。Server 可以用 TypeScript、Python、Go、Rust 写,只要能收发 JSON 消息即可。
初始化流程
Client 和 Server 建立连接后,会先进行初始化:
|
|
初始化阶段会协商:
- 协议版本
- Server 支持哪些能力
- Client 支持哪些能力
- Server 名称和版本
这一步很关键。Host 不能假设每个 Server 都支持所有 MCP 能力,而是要看 Server 的能力声明。
能力发现
初始化之后,Client 会按需查询 Server 暴露的能力:
|
|
这使得 MCP Server 具备自描述能力。Host 不需要硬编码每个工具的名称和参数,而是通过协议动态发现。
传输层
MCP 常见传输方式有两种。
| 传输 | 适用场景 |
|---|---|
| stdio | 本地 Server,随 Host 启动,通过标准输入输出通信 |
| Streamable HTTP | 远程 Server,通过 HTTP 连接,适合云服务和多用户场景 |
stdio 最适合本地工具,例如 filesystem、sqlite、git。它简单、隔离性好,不需要额外端口。
Streamable HTTP 适合远程能力,例如企业知识库、SaaS API、集中式工具网关。但它也带来更多安全问题:认证、授权、跨租户隔离、DNS rebinding、防止未授权访问等。
Tools:让模型能行动
Tools 是 MCP 中最像“函数调用”的能力。它表示 Server 暴露给模型的一组可执行动作。
例如 GitHub Server 可以暴露:
|
|
每个 Tool 通常包含:
|
|
Tool 的调用流程
|
|
注意:真正执行工具的是 MCP Server,不是 LLM。
LLM 只是产生“我想调用这个工具,参数是这些”的决策。是否允许执行、如何执行、执行结果怎么返回,都由 Host/Client/Server 共同控制。
Tool 描述为什么重要
模型选择工具时高度依赖 name、description 和 inputSchema。
好的 Tool 设计应该:
- 名称明确,避免多个工具语义重叠
- description 说明什么时候用,而不是只说工具做什么
- inputSchema 尽量严格,减少模型乱填参数
- 对危险操作标注清楚
- 返回结构稳定,方便模型理解
例如:
|
|
前者太模糊,模型不知道边界;后者明确告诉模型这是只读 SQL 查询。
Tool 不是 API 的简单透传
一个常见误区是把后端 API 原样暴露成 MCP Tool。
这通常不是好设计。API 面向程序员,Tool 面向模型。模型需要的是语义清晰、粒度合适、失败信息可理解的工具。
更合理的做法是:
|
|
Tool 可以在 Server 端聚合多个 API,返回更适合模型消费的结构。
Resources:让模型能读取上下文
Resources 是 MCP 暴露上下文数据的方式。
如果说 Tool 是“做动作”,Resource 更像“读资料”。
例如:
|
|
每个 Resource 通常有 URI、名称、描述、MIME 类型等信息。
Resource 的使用方式
Resource 适合描述稳定、可读取、可引用的上下文:
- 文件内容
- 数据库 schema
- 文档页面
- 日志片段
- 设计稿节点
- API 规范
Client 可以先列出资源,再读取具体资源:
|
|
对模型来说,Resource 的价值在于把“有哪些上下文可以用”标准化。
Resource 与 Tool 的区别
| 维度 | Resource | Tool |
|---|---|---|
| 目的 | 提供上下文 | 执行动作 |
| 风险 | 通常较低 | 可能有副作用 |
| 类比 | 文件、网页、数据库记录 | 函数、命令、API 调用 |
| 典型操作 | read/list/subscribe | call |
| 是否改变外部系统 | 通常不改变 | 可能改变 |
不要把所有东西都做成 Tool。只读上下文更适合 Resource,因为它更容易被 Host 管理、展示、缓存和引用。
Resource Template
有些资源不是固定 URI,而是带参数的模式。
例如:
|
|
这类资源可以用 Resource Template 描述。Client 根据模板和参数生成具体资源 URI。
它适合动态资源,例如某个 Issue、某个数据库表、某个日志查询结果。
Prompts:沉淀可复用流程
Prompts 是 MCP 提供的提示模板能力。
它不是模型的系统提示,而是 Server 提供给用户或 Host 的可复用任务模板。
例如一个 Git Server 可以提供:
|
|
一个数据库 Server 可以提供:
|
|
Prompt 通常包含:
- 名称
- 描述
- 参数列表
- 生成的一组消息
Prompts 的价值
Prompts 解决的是“如何把工具组合成稳定流程”的问题。
例如“代码审查”不是单个工具调用,而是一组动作:
|
|
把这套流程沉淀成 Prompt,可以让用户在 Host 中直接选择,而不是每次重新描述。
Prompts 与 Skills 的区别
以 Claude Code 生态为例,Prompts 和 Skills 容易混淆。
| 维度 | MCP Prompts | Skills |
|---|---|---|
| 所属层 | 协议能力 | Agent 工作方法 |
| 来源 | MCP Server 提供 | 本地/插件技能目录 |
| 内容 | 可调用提示模板 | 领域知识、流程、工具说明 |
| 作用 | 复用某个任务入口 | 改变 Agent 做事方式 |
可以这样理解:
|
|
三者经常配合使用,但不是同一层东西。
Sampling:Server 反向请求模型
Sampling 是 MCP 中比较特殊的能力:Server 可以向 Client 请求一次模型生成。
普通工具调用是:
|
|
Sampling 则是:
|
|
为什么 Server 需要调用模型?一个例子是文件系统 Server 想做智能摘要:
|
|
这里有一个非常重要的边界:Server 不能直接调用用户的模型。
它只能向 Client 发起 sampling 请求,是否允许、用哪个模型、上下文给多少,都由 Client/Host 控制。
这避免了 Server 绕过用户可见的 AI 应用私自消耗 token 或读取上下文。
Roots:限制 Server 的可见范围
Roots 用来告诉 Server:当前工作空间或可访问范围在哪里。
例如文件系统 Server 不应该默认读整个磁盘,而应该只知道用户授权的目录:
|
|
Roots 的价值是把“访问边界”显式化。
在本地开发场景中,Roots 尤其重要:
- 文件系统 Server 只能访问项目目录
- Git Server 只操作当前仓库
- 文档 Server 只索引指定知识库
- 数据库 Server 只连接指定实例
没有 Roots 这类边界,MCP Server 很容易变成一个过度授权的后门。
安全边界
MCP 的强大之处在于让 Agent 能行动;危险也在这里。
安全设计要记住一句话:
模型可以建议动作,但不能绕过权限直接执行动作。
Host 是最后的安全闸门
Host 必须承担最终安全责任。
它需要决定:
- 哪些 Server 可以启用
- 哪些 Tools 可以展示给模型
- 哪些调用需要用户确认
- 哪些资源可以进入上下文
- 哪些高风险操作必须拦截
- 工具返回内容如何标记为不可信
不要把安全完全交给 Server。Server 可能来自第三方,也可能被供应链攻击。
Tool 调用需要分级
不同工具风险不同,不能一视同仁。
| 等级 | 示例 | 策略 |
|---|---|---|
| 低风险只读 | 读取 README、查询 Issue | 可自动执行 |
| 中风险写入 | 创建 Issue、更新文档 | 用户确认 |
| 高风险操作 | 删除文件、执行命令、修改生产配置 | 默认禁止或强确认 |
| 敏感数据 | 读取密钥、导出用户数据 | 最小权限,通常禁止 |
Tool schema 只能约束参数结构,不能替代权限系统。
输入和输出都不可信
MCP 有两个方向的数据需要警惕:
|
|
第一类风险是模型构造了危险参数:
|
|
Server 必须做路径白名单、参数校验、权限检查。
第二类风险是工具结果里包含恶意指令:
|
|
Host 和模型都要把工具返回视为“不可信数据”,不能当成系统指令执行。
Prompt Injection
当 MCP Server 读取网页、文档、Issue、PR 评论时,很容易把攻击者写入的内容带进上下文。
例如一个网页里写:
|
|
这就是工具链路中的 prompt injection。
防护思路:
- 明确系统指令优先级高于外部内容
- 对外部内容加来源标记
- 高风险 Tool 必须人工确认
- 不把敏感工具和不可信内容放在同一轮自动执行链路里
- 对 Tool 结果做摘要和过滤
远程 MCP Server 的额外风险
stdio Server 通常运行在本地,风险边界比较清晰。远程 MCP Server 则更复杂。
需要考虑:
- 身份认证
- 用户级授权
- 多租户隔离
- 访问日志
- 速率限制
- TLS
- OAuth token 管理
- DNS rebinding 防护
- Server 推送内容的可信度
远程 Server 不应该因为“接入了 MCP”就默认被信任。它本质上还是一个外部网络服务。
MCP 与 Function Calling 的区别
MCP 和 Function Calling 经常被放在一起讨论,但它们不是同一层。
| 维度 | Function Calling | MCP |
|---|---|---|
| 所属层 | 模型接口能力 | 应用到工具的协议 |
| 关注点 | 模型如何输出函数调用 | 工具如何被发现、调用、返回 |
| 作用范围 | 单个模型 API | Host、Client、Server、外部系统 |
| 工具来源 | 应用代码传入 | MCP Server 动态提供 |
| 是否规定传输 | 不规定 | 规定 JSON-RPC 与传输方式 |
Function Calling 解决的是:
|
|
MCP 解决的是:
|
|
二者可以配合。Host 可以把 MCP Server 暴露的 Tools 转换成模型 API 的 function/tool schema,再把模型生成的调用请求转成 MCP tools/call。
MCP Server 设计原则
工具粒度要面向任务
不要把底层 API 原样暴露给模型。
|
|
前者灵活但危险,后者受限但可靠。
默认只读
Server 初始版本最好先暴露只读能力:
- list
- get
- search
- read
写操作要等权限、确认、日志、回滚设计清楚后再加。
返回结构要适合模型
不要把原始 API 响应一股脑返回给模型。应该返回经过整理的结构:
|
|
模型不擅长从大量噪声 JSON 里稳定提取重点。Server 应该帮它降噪。
错误信息要可恢复
坏的错误信息:
|
|
好的错误信息:
|
|
Agent 能否从错误中恢复,很大程度取决于 Server 返回的信息质量。
能力越大,边界越要小
一个能执行任意 shell 命令的 MCP Server 看起来很强,但也最危险。
更稳妥的设计是把能力收窄:
|
|
让模型在明确边界内行动,而不是拿到一个万能入口。
常见反模式
把 MCP 当成“万能插件系统”
MCP 是协议,不是安全沙箱。接了 MCP 不代表工具天然安全。
暴露过宽的文件系统
文件系统 Server 如果能读整个用户目录,就可能读取 SSH key、浏览器缓存、环境变量文件。
给模型一个万能 execute
execute(command) 这种工具很诱人,但风险极高。除非有强权限、审计和确认机制,否则不应该默认暴露。
忽略工具返回的注入风险
网页、Issue、文档都是外部输入。它们进入上下文后,可能诱导模型改变行为。
Server 返回过多噪声
把几千行日志直接塞给模型,会浪费 token,也会降低推理质量。Server 应该做过滤、分页、摘要。
实践:接入 MCP 的检查清单
接入一个新的 MCP Server 前,可以按这张清单过一遍:
|
|
如果一个 Server 无法回答这些问题,就不应该轻易接入生产环境。
参考资料
- Model Context Protocol: Architecture
- Model Context Protocol: Tools
- Model Context Protocol: Resources
- Model Context Protocol: Prompts
- Model Context Protocol: Transports
小结
MCP 的本质不是“让 AI 多几个插件”,而是把 AI 应用和外部世界之间的连接方式标准化。
它的核心抽象可以概括为:
|
|
真正理解 MCP,要同时看到两面:
- 它扩展了 Agent 的能力边界
- 它也扩大了 Agent 的安全边界
一个好的 MCP 集成,不只是“能调通工具”,还要做到权限清晰、边界明确、结果可追踪、失败可恢复。
MCP 让 Agent 接入现实世界。工程上真正要做好的,是确保这个入口足够有用,也足够可控。