MCP 解决的是一个很具体的问题:每个 AI 应用都要为每个工具写一遍集成。它把「N 个客户端 × M 个工具」的平方问题,变成「各自实现一次协议」。
为什么需要协议
假设你有 5 个 AI 应用(IDE、聊天客户端、客服系统、内部助手、自动化平台),要接 20 个数据源(文件、数据库、Git、工单、日历……)。没有协议时,最坏需要 100 个集成。
MCP(Model Context Protocol)定义了一套标准接口:工具方实现一次 Server,应用方实现一次 Client,任意组合都能工作。
管理多个 Client
处理协议与生命周期
本地进程或远程服务
三个原语
| 原语 | 控制方 | 用途 | 例子 |
|---|---|---|---|
| Tools | 模型 | 可执行动作 | 查询订单、发消息、跑测试 |
| Resources | 应用 | 可读取的数据 | 文件、数据库记录、日志 |
| Prompts | 用户 | 可复用提示模板 | 「代码审查」「周报生成」 |
理解区别很重要:
- Tool 有副作用,需要权限与审计;
- Resource 是只读上下文,通常由应用决定何时加载;
- Prompt 是用户主动选择的模板,不是模型自动调用。
此外还有客户端能力(Server 可以反向请求 Host):
- Sampling:Server 请求 Host 用模型生成内容;
- Roots:Host 告诉 Server 可访问的目录范围;
- Elicitation:Server 向用户索取补充信息。
传输方式
| 方式 | 场景 | 特点 |
|---|---|---|
| stdio | 本地进程 | 简单、无网络、随 Host 启动 |
| Streamable HTTP | 远程服务 | 可跨网络、支持认证、可多客户端 |
早期的 HTTP+SSE 方式已逐步被 Streamable HTTP 取代。选择原则:本地工具用 stdio,团队共享/云端工具用 HTTP。
一次调用的过程
1. 初始化:协商协议版本与能力
2. 发现:client 拉取 tools/list、resources/list、prompts/list
3. 选择:Host 把相关工具描述放入模型上下文
4. 调用:模型输出 tool call → client 发送 tools/call
5. 执行:server 校验参数、执行、返回 content
6. 回填:结果进入模型上下文,继续循环MCP 只规定「怎么通信」,不规定「模型怎么选工具」。选择仍由 Host 和模型决定。
写一个 MCP Server
用 Python 的 FastMCP,十几行就能跑:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("acme-docs")
DOCS = {
"refund": "退款需在签收后 7 天内申请,商品需保持完好。",
"shipping": "默认顺丰,偏远地区 3–5 天。",
}
@mcp.tool()
def search_docs(query: str, limit: int = 5) -> list[dict]:
"""检索 ACME 内部文档。
Args:
query: 关键词,尽量简短。
limit: 返回条数,1-10。
"""
q = query.lower()
hits = [{"id": k, "text": v} for k, v in DOCS.items() if q in k or q in v.lower()]
return hits[: max(1, min(limit, 10))]
@mcp.resource("docs://{doc_id}")
def read_doc(doc_id: str) -> str:
"""按 ID 读取文档全文。"""
if doc_id not in DOCS:
raise ValueError(f"未知文档:{doc_id}")
return DOCS[doc_id]
@mcp.prompt()
def summarize_doc(doc_id: str) -> str:
"""生成文档摘要的提示模板。"""
return f"请用三条要点总结文档 {doc_id},每条不超过 30 字。"
if __name__ == "__main__":
mcp.run() # 默认 stdio客户端配置(以支持 MCP 的桌面客户端为例):
{
"mcpServers": {
"acme-docs": {
"command": "python",
"args": ["C:/path/to/server.py"],
"env": { "DOCS_TOKEN": "..." }
}
}
}远程服务则配置 URL 与认证头,由客户端完成 OAuth 或 token 交换。
设计一个好 Server
工具要小而清晰
✓ search_orders(query, status, limit)
✓ get_order(order_id)
✓ create_refund(order_id, amount, idempotency_key)
✗ manage_orders(action, payload)一个工具只做一类事。参数少、枚举优先、描述写清「何时不用」。这些原则与〈工具设计〉完全一致。
不要把危险能力直接暴露
- 不要暴露
execute_sql,除非有强沙箱与只读账号; - 不要暴露
run_shell,除非在隔离环境且有人工确认; - 文件访问限制在 roots 指定目录;
- 写操作要求幂等键与权限检查;
- 返回结果裁剪,避免把整页 HTML 塞进上下文。
分页与截断
MCP 的返回会进入模型上下文。列表接口必须支持 limit / cursor,并在结果里标明是否截断。
{
"items": [...],
"next_cursor": "abc",
"truncated": true
}认证与授权
远程 MCP Server 应使用标准的 OAuth 2.1 流程,而不是让用户把长期 token 贴在配置里。要点:
- 每个用户/租户独立授权;
- token 有最小 scope;
- 不要把用户 token 透传给下游(confused deputy);
- 记录调用审计;
- 支持撤销。
安全风险
MCP 扩大了攻击面。常见风险:
| 风险 | 说明 | 缓解 |
|---|---|---|
| 工具投毒 | 工具描述里藏恶意指令,诱导模型调用 | 审查来源、固定版本、限制工具数量 |
| Rug pull | 首次审核后,Server 更新恶意行为 | 锁定版本/哈希,监控变更 |
| 跨 Server 影子 | 恶意 Server 冒充可信工具名 | 命名空间、白名单 |
| 间接提示注入 | Resource 内容包含恶意指令 | 外部内容视为数据、权限隔离 |
| 过度授权 | Server 拿到过多 scope | 最小权限、按用户授权 |
| 数据外泄 | 工具把上下文拼进 URL/邮件 | 出站白名单、DLP、人工确认 |
| 供应链 | 依赖包被篡改 | 依赖审计、签名、沙箱 |
协议只负责连接。权限、沙箱、审计、人工审批必须由 Host 和 Server 自己实现。接入第三方 Server 前,先假设它可能恶意。
什么时候不该用 MCP
- 只有一个应用、一个工具:直接写函数更简单;
- 工具需要极低延迟:多一层进程/网络有开销;
- 工具需要复杂的事务语义:MCP 不提供分布式事务;
- 团队没有能力审计第三方 Server:先别接。
MCP 适合「多个客户端要复用同一批能力」的场景。它是集成层,不是 Agent 框架。
与其他协议的关系
MCP 连接 Agent ↔ 工具/数据
A2A 连接 Agent ↔ Agent
AG-UI 连接 Agent ↔ 前端界面三者解决不同层的问题,可以组合使用。不要把它们混为一谈。
检查清单
- 工具粒度小、描述清晰、参数受控
- 读写分离,写操作幂等且需授权
- 文件/网络访问限制在明确范围
- 返回结果裁剪、分页、标注截断
- 远程服务使用 OAuth,scope 最小化
- 第三方 Server 经过审查并锁定版本
- 调用有审计日志
- 不把 MCP 当安全边界
小结
- MCP 用统一协议解决 N×M 集成问题。
- Host / Client / Server 三角色,Tools / Resources / Prompts 三原语。
- 本地用 stdio,远程用 Streamable HTTP + OAuth。
- 工具设计与普通 Function Calling 原则一致:小、清晰、可恢复。
- 安全靠权限、隔离、审计,不靠协议本身。
下一篇看最接近「通用」也最脆弱的形态:浏览器与计算机使用 Agent。