MCP(模型上下文协议)
MCP(Model Context Protocol,模型上下文协议)回答的问题是:当模型需要调用你的函数、读取你的数据、操作你的系统时,用什么统一的方式接入? 在它出现之前,每个 Agent 框架、每家模型厂商都定义一套「工具调用」私有格式,每个系统都要为每个模型重复写一遍集成(N×M 问题)。MCP 把「模型 ↔ 工具/数据」这一层标准化,让工具供应商写一次接入,任何支持 MCP 的客户端都能用。
生态位置:由 Anthropic 于 2024 年 11 月开源,2025 年起被 OpenAI / Google / Microsoft 等采用,2025 年 12 月捐赠给 Linux Foundation(Agentic AI Foundation),成为模型连接外部世界的实际标准。规范以日期版本迭代,最新为 2026-07-28(无状态 + OAuth 2.1 的重大修订,2026-08-28 过渡期结束后旧版废弃)。与 Agent 编排 的关系:Agent 负责「规划与循环」,MCP 负责「工具接入」这一层标准接口。
一、为什么需要 MCP:集成问题的解法
在 MCP 之前,「让模型干活」的接入是碎片化的:
| 问题 | 表现 |
|---|---|
| 接口私有 | 每家模型厂商的 function calling / tool use 格式不同,绑死模型供应商 |
| 重复集成 | 同一能力(查日历 / 发消息 / 查数据库)要为每个框架各写一遍 connector |
| 会话内临时搭 | Agent 的「内置动作」是预置死的,运行时新增能力要靠重写或插件机制 |
MCP 的解法是把连接标准化为「通用插座」:
tools / resources / prompts
模型宿主 (Host) ─── Client(1) ──────► MCP Server A(日历)
↑ Client(2) ──────► MCP Server B(GitHub)
用户使用的主应用 Client(n) ──────► MCP Server C(私有数据)- Host(宿主):用户直接使用的应用(如 Claude / Claude Code / Cursor 等代码与桌面工具),负责「拿模型回答 + 拿用户授权」;
- Client(连接器):Host 内部的协议客户端,一个 Host 可挂多个 Client,每个 Client 连一个 Server;
- Server(服务端):提供能力的一方——暴露工具、数据、模板给模型消费。
类比:MCP 之于 AI 应用,类似 USB-C(Anthropic 官方类比)之于外设——把「接口形态」标准化,接上即用;也常被比作 AI 界的 LSP(语言服务器协议,把编辑器与语言工具解耦)。
二、架构:角色与三大原语
Server 向模型暴露三类能力(即「上下文」的来源):
| 原语 | 是什么 | 谁来触发 | 典型例子 |
|---|---|---|---|
| Tools(工具) | 模型可执行的函数,带输入 Schema | 模型自主决定调用 | 发邮件、执行 SQL、创建 Issue |
| Resources(资源) | 结构化的数据 / 文档,用 URI 寻址 | 模型或用户在上下文里引用 | file:/// 项目文件、https:// 文档、数据库记录 |
| Prompts(提示模板) | 可复用的多轮消息模板 | 用户手动选用 | 「写周报」「按模板生成复盘」 |
客户端(Host)侧可提供的能力(旧版为协议内置):Sampling——Server 反向往模型发起「帮我生成一段内容」的请求。2026-07-28 新版将 Sampling 与 Prompts 移入扩展机制,Resources 入口也并入工具形态(见第四节),但「工具为主、资源/模板为辅」的心智不变。
通用能力贯穿所有消息:进度上报、请求取消、错误码、日志(按级别过滤)。
三、跑通最小 Server:传输、消息与示例
传输:本地与远程两条线
| 传输 | 适用 | 形态 | 2026-07-28 版状态 |
|---|---|---|---|
| stdio | 本地子进程(开发工具场景) | 客户端拉起服务端进程,stdin/stdout 走 JSON-RPC | 保留(本地主力) |
| Streamable HTTP | 远程服务 | 无状态 HTTP + SSE 流式返回,可水平扩展 | 标准化为远程唯一(取代旧版 HTTP+SSE) |
消息层:JSON-RPC 2.0
所有通信都是 JSON-RPC 2.0 消息。旧版需要先 initialize 握手协商版本与能力;2026-07-28 版移除该握手,改为 HTTP 头协商(本地 stdio 由客户端在启动参数中带协议版本与能力声明):
MCP-Protocol-Version: 2026-07-28
MCP-Capabilities: tools,listChanged一次工具调用的两端消息(示意,字段以规范为准):
// 模型方(Host/Client)发起调用
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": { "name": "add", "arguments": { "a": 1, "b": 2 } } }
// Server 返回(内容可带结构化文本,供模型读取)
{ "jsonrpc": "2.0", "id": 1, "result": {
"content": [{ "type": "text", "text": "3" }],
"isError": false } }理解要点:Server 不感知「具体是哪个模型」在调它——协议里只有参数与结果,没有厂商绑定。这正是 MCP 存在的意义。
示例:一个最小 Server
社区最常见的写法是 Python 的 FastMCP(也有官方 TS/Python SDK 与社区多语言实现)。以下示例按 fastmcp 常见形态编写,2026-09-04 已在本地实测(fastmcp 4.0.2 + MCP Python SDK 2.1.1,Python 3.14 venv;fastmcp 迭代快,以你实际安装版本为准):
# 示例依赖:fastmcp(建议 Python>=3.10 的 venv: python -m venv .venv && .venv/bin/pip install fastmcp)
# 实测通过(fastmcp 4.0.2);文件保存为 server.py
from fastmcp import FastMCP
mcp = FastMCP("calculator") # stdio 传输,默认被宿主拉起
@mcp.tool
def add(a: int, b: int) -> int:
"""两数相加(description 会被模型看到,用于决定何时调用)"""
return a + b
@mcp.tool
def list_tables(sql: str) -> list[str]:
"""列出给定 SQL 中的表名(示意:真实实现应执行解析而非字符串处理)"""
import re
return re.findall(r"(?:from|join)\s+([a-zA-Z_][\w]*)", sql, re.I)
if __name__ == "__main__":
mcp.run()配套的 stdio 客户端(与 server.py 放同一目录,命名为 client.py;以子进程拉起 server,走 JSON-RPC 完成发现与调用):
# 实测通过(MCP Python SDK 2.1.1)
import asyncio, sys
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
params = StdioServerParameters(command=sys.executable, args=["server.py"])
async with stdio_client(params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
res = await session.list_tools() # 注意: 2.x 返回 ListToolsResult 对象(.tools), 不是裸列表
print("已发现工具:", [t.name for t in res.tools])
for name, args in [
("add", {"a": 2, "b": 3}),
("list_tables", {"sql": "SELECT * FROM orders JOIN users ON orders.uid = users.id"}),
]:
r = await session.call_tool(name, args)
print(f"call_tool({name}, {args}) ->",
"".join(c.text for c in r.content if hasattr(c, "text")))
if __name__ == "__main__":
asyncio.run(main())
# 实测输出(2026-09-04):
# 已发现工具: ['add', 'list_tables']
# call_tool(add, {'a': 2, 'b': 3}) -> 5
# call_tool(list_tables, {'sql': 'SELECT * FROM orders JOIN users ...'}) -> ["orders","users"]要点:
- 函数的 name / docstring(描述)/ 参数类型 会被自动整理为工具声明与 JSON Schema,模型据此决定「何时、传什么参数」调用——所以描述要写清边界与副作用(见安全节);
- 工具内部是普通代码,可访问数据库 / 网络 / 文件系统,因此工具=任意代码执行入口;
- 本地调试可用官方 MCP Inspector 连接观察消息往返。
四、2026-07-28 新版规范:无状态与 OAuth 2.1
这是自发布以来最大的一次修订(Release Candidate 于 2026-05-21 发布,正式版 2026-07-28,30 天过渡期后旧版废弃)。如果从零搭远程 MCP 服务,直接用新版;旧服务需在过渡期内迁移。
| 维度 | 旧版(如 2025-03-26) | 2026-07-28 新版 |
|---|---|---|
| 会话 | 有状态,维护 session_id | 无状态:协议层无会话;确需跨请求状态由 Server 自行下发 state_token,客户端透传 |
| 握手 | initialize RPC 协商 | 移除,改为 HTTP 头协商(协议版本 + capabilities) |
| 认证授权 | OAuth 可选实现 | OAuth 2.1 强制:Server=资源服务器、Client=OAuth 客户端;PKCE 必选;提供 /.well-known/oauth-authorization-server 元数据端点;支持 OpenID Connect |
| 远程传输 | HTTP + SSE | Streamable HTTP(无状态、水平扩展);SSE 弃用;stdio 保留 |
| 原语位置 | resources/list、prompts/list、sampling 为内置 RPC | Resources 并入工具形态、Prompts 与 Sampling 移入扩展机制 |
| 错误码 | 通用 JSON-RPC 错误 | 标准化并新增认证错误:AUTHENTICATION_REQUIRED=-32000、AUTHORIZATION_FAILED=-32001、TOKEN_EXPIRED=-32002 |
| 扩展 | 无正式机制 | 正式扩展机制,如 mcp-apps/v1(服务端渲染 UI)、tasks/v1(长时间运行任务) |
迁移优先级的实操建议:
- 高:移除
initialize调用与 session_id 依赖;OAuth 2.1 + PKCE;替换resources/list、prompts/list、sampling用法; - 中:远程传输 SSE → Streamable HTTP;错误处理适配新错误码;
- 低:评估 mcp-apps / tasks 扩展是否对你的场景有价值。
五、客户端接入与调试
- 接入形态:主流 AI 客户端工具(Claude Code / Cursor 等)都支持「添加 MCP Server」;本地开发最常用 stdio 配置(指定命令与参数拉起你的 Server 进程),远程 Server 走 HTTP URL + OAuth 授权;
- 开发调试流程:先用 MCP Inspector 单测 Server(无界面直接看请求/响应)→ 再挂到真实客户端验证「模型是否会按描述调用」;
- 工具声明可观测:给工具描述与 schema 后,先在客户端里试问模型能否在正确时机带上正确参数调用——模型用不对工具,八成是描述写得不够好,而不是模型笨;
- 回归影响:Host 工具升级后,模型行为会变(新增工具、改 schema 都是行为变更),建议对关键 Agent 场景留评测回归(见 AI 工程化 的评测门禁)。
六、生产化与安全红线
MCP 的核心安全信条(规范层面明确要求,但需实现者落地):用户同意与控制、数据隐私最小暴露、工具安全、采样批准。落到工程上:
- 工具最小权限:每个工具只开它需要的那点权限;删除、写库、支付等破坏性操作要么不做成工具,要么做成「先干查后干写」的两段式并二次确认;
- 工具=任意代码执行入口:供应链风险最高的一环。只接入来源可信的 Server;Server 的依赖要锁版本 + 审计,就像对待生产服务本身;
- 提示注入的回灌通道:模型读到的任何内容(网页 / 邮件 / 工具返回)都可能携带指令。工具返回不可信内容后,若模型据此再调用敏感工具,就形成「注入→外带」链路——敏感动作前要求用户确认,或在工具间隔离不可信内容(防注入原则见 Prompt 工程);
- 认证与凭据:远程 Server 一律走 OAuth 2.1 + PKCE;密钥放在 Server 侧,绝不下发到 Host 上下文;长期凭据要支持吊销(对应新错误码 TOKEN_EXPIRED 的刷新闭环);
- 部署与可观测:无状态服务天然好扩容;给每个工具调用留结构化日志(谁、何时、带了什么参数、返回什么),这是审计与排障的第一现场;
- 速率与配额:Server 要为「模型并发/循环调用」设限(Agent 步数爆炸时尤其重要,参见 RAG 与 Agent 的可控性忠告)。
七、与 Function Calling / A2A 的边界
| 概念 | 管哪一层 | 与 MCP 的关系 |
|---|---|---|
| Function calling / tool use | 模型厂商 API 里的「让模型输出结构化调用意图」 | 私有实现;MCP 是开放通用替代/补充,两者在 host 内部常配合 |
| MCP | 「模型 ↔ 工具 / 数据」的连接 | 本页主题 |
| A2A(Agent-to-Agent) | 「智能体 ↔ 智能体」的协作(Google 等推动) | 与 MCP 互补:MCP 向下接工具,A2A 横向接 agent |
| 插件 / 内置动作 | 单个宿主应用内的扩展 | 早期形态;MCP 把扩展做成跨宿主的标准 |
一句话定位:MCP 解决「模型的手」(接工具),Agent 解决「模型的脑」(规划循环),A2A 解决「多个脑的协作」。 选型时先想清楚你要打通的是哪一层。
八、踩坑清单
- 把 MCP 当 RPC 框架用:它首先是给模型设计声明式接口——工具描述、Schema 质量决定模型会不会正确用,而非代码能跑就行;
- 忽视无状态化迁移:2026-07-28 后还依赖 session_id / initialize 的远程服务在过渡期结束后不可用;本地 stdio 影响小但也要跟 SDK 版本;
- OAuth 当摆设:远程服务不做 OAuth 2.1 + PKCE,Host 侧授权流程会直接失败(或你被迫绕过规范,留下凭据泄漏口);
- 工具描述与实现脱节:模型按 description 决策调用,描述说「返回最近订单」实现却漏了排序,错误不可见、难排查;
- 工具过宽:一个「执行任意 SQL」的工具 = 把数据库钥匙交给所有能触发它的 prompt;按操作拆细、只读与写分离;
- 忽略注入回灌:工具返回网页/文档内容后直接喂给模型再让模型自由操作,等于给不可信内容开执行通道;
- 没有工具调用日志:Agent 出错时看不到「调了谁、传了什么、返回什么」,只能盲调;
- 不设速率/步数上限:模型自循环高频调用远程 Server,账单与限流双爆炸;
- SDK 示例照抄:fastmcp 与官方 SDK 迭代快、API 易变,示例代码不验证就上生产;
- 把「能连上」当「接得好」:接入只是开始,工具被模型正确调用、副作用受控、行为有回归测试才是生产标准(用 AI 工程化 的评测与可观测闭环)。
检查清单
- [ ] 说清接入层:本地 stdio 还是远程 Streamable HTTP?对应配好 Client 端配置;
- [ ] 远程服务:OAuth 2.1 + PKCE、
/.well-known/oauth-authorization-server元数据、无状态部署; - [ ] 基于 2026-07-28 版规范:无 initialize 握手、无 session 依赖、错误码按新版处理;
- [ ] 工具最小权限、读写分离,破坏性操作用户确认;
- [ ] 每个工具的 name / description / 参数 Schema 经过「模型能否正确调用」验证;
- [ ] 不可信内容(网页/上传/工具返回)与敏感操作之间做了隔离或确认;
- [ ] 工具调用有结构化日志(含参数脱敏),可审计可回放;
- [ ] 设了速率/步数/超时上限,防自循环拖垮服务;
- [ ] Server 依赖锁定与审计,来源不明的 MCP 目录不直接接入生产;
- [ ] 关键 Agent 场景挂了评测回归,工具升级视同行为变更。
相关延伸:RAG 与 Agent(Agent 循环与框架选型)、Prompt 工程(提示注入防线)、AI 工程化(评测门禁与可观测、安全合规)。