MCP 与 Agent 工具链:把外部系统安全接进来
从一次集成事故说起
去年我接了一个需求:让内部 Agent 能读写 Jira 工单、查询 PostgreSQL 和上传设计稿到 Figma。三个外部系统,三种对接方式——Jira 用自写 REST 封装,PostgreSQL 走内部 RPC 服务,Figma 是社区现成的 plugin。上线两周后,Figma 插件升级了 token 刷新逻辑,RPC 服务的 schema 没跟上,Jira 封装里有个写操作没有二次确认。结果就是:Agent 误删了一个 sprint 的 issue,排查日志时才发现审计字段只记录了 agent ID,没记录是谁触发的任务。
这件事让我意识到,Agent 接外部系统,难点从来不在「能不能调通」,而在于接进来之后怎么管——管权限、管数据流、管异常、管审计。MCP 的价值就在这里。
MCP 解决什么问题
Agent 要进入真实工作流,必须连接文件、数据库、浏览器、设计工具、工单、文档和内部系统。过去,每个连接都是一段定制胶水代码:写 schema、写鉴权、写错误处理、写日志格式。换一个模型提供商,这些代码可能得重写一半。
MCP(Model Context Protocol)提供了一种标准化方式,让工具以统一协议暴露给模型和客户端。它借鉴了 Language Server Protocol 的思路——LSP 让编辑器和语言服务解耦,MCP 让 AI 主机和外部工具解耦。它的价值不在于让模型什么都能做,而在于把工具接入方式变得可管理、可替换、可审计。
到 2026 年,MCP 已经成为事实上的开放标准。Anthropic 在 2024 年底发布协议,2025 年 OpenAI 和 Google 相继原生支持。官方 spec 停在 2025-06-18 版本,但社区生态已经长出 200 多个 MCP server,覆盖数据库、云存储、CI/CD、通讯工具和主流 SaaS。
协议架构:三层原语
MCP 的架构分三层:Host、Client、Server。
Host 是 AI 应用本身,比如 Claude Desktop、Cursor、ChatGPT。Client 住在 Host 内部,负责协议协商、路由和会话管理。Server 暴露能力,包括三种核心原语:
| 原语 | 职责 | 典型场景 |
|---|---|---|
| Resources | 只读数据快照 | 文件内容、数据库查询结果、API 响应 |
| Tools | 可执行动作,有副作用 | 创建工单、部署应用、发消息 |
| Prompts | 可复用任务模板 | 标准化操作流程,减少 token 消耗 |
通信协议基于 JSON-RPC 2.0,传输层有两个选项:stdio 用于本地子进程通信(桌面应用和开发场景),HTTP/SSE 用于远程连接(云端部署的 server)。一个 Host 可以同时维持多个 Client 连接,每个连到不同的 Server。
这个结构的关键在于,工具的发现和执行是动态协商的,不是编译时绑定的。Host 启动时不知道 Server 有哪些工具——它先发送 tools/list 请求,Server 返回工具清单(包括名称、描述、输入 schema),然后 Host 把这些信息注入到模型的上下文中。模型决定要调哪个工具时,Host 发 tools/call 请求,Server 执行后返回结果。
MCP vs Function Calling:它们不在同一层
很多人把 MCP 和 function calling 放在一起比较,这其实有点错位。它们不是竞争关系,而是同一流程的不同阶段。
Function calling 是模型的「前端」能力——把自然语言转成结构化的函数调用指令。MCP 是「后端」的执行编排——负责发现工具、路由请求、管理响应。一个管「模型说了什么」,一个管「系统怎么做」。
| 维度 | Function Calling | MCP |
|---|---|---|
| 职责 | 意图识别,生成结构化调用 | 工具发现,执行编排,响应管理 |
| 标准化 | 各厂商格式不同(OpenAI / Anthropic / Google 各一套) | 统一协议,模型无关 |
| 工具发现 | 编译时硬编码到 prompt | 运行时动态协商 |
| 传输方式 | HTTP(随模型 API) | stdio + HTTP/SSE |
| 可扩展性 | 新增模型或工具需改 schema | 新增任一端无需改另一端 |
| 安全模型 | 应用层管理密钥 | Server 级鉴权,支持 OAuth 2.1 |
| 适用场景 | 单步任务:数据提取、分类、简单 API 调用 | 多步动态场景:工作流自动化、合规操作、跨系统协作 |
实际工程中,两者是串联的。模型用 function calling 格式输出「我要调用 create_jira_issue,参数是 title=xxx, desc=yyy」,然后 MCP 接手,把这个调用路由到 Jira Server,执行鉴权、参数校验、请求发送、结果返回。
这个分层带来一个直接好处:换模型时不用重写工具集成代码,换工具时不用改模型侧的 prompt 逻辑。
安全评估:不是接进来就完了
MCP 解决的是协议标准化,不解决安全问题。安全问题需要你在 server 层面、client 层面和应用层面分别处理。
已知攻击向量
MCP 官方安全文档列出了几类重要攻击,这里摘几个工程中最常遇到的:
混淆代理问题(Confused Deputy):当 MCP server 作为代理连接第三方 API 时,如果用了静态 client ID、允许动态注册 client、且第三方设了 consent cookie,攻击者可以构造恶意链接,绕过用户确认直接获取授权码。防御方式是 MCP server 必须实现独立的 per-client consent,在转发到第三方之前先确认用户同意。
SSRF(服务端请求伪造):OAuth 元数据发现阶段,client 会去获取 MCP server 提供的 URL。恶意 server 可以把这些 URL 指向内网 IP、云元数据端点(http://169.254.169.254/)或本地服务。防御方式是 client 必须验证 URL scheme(只允许 HTTPS,开发环境允许 localhost)、屏蔽私有 IP 段、使用出口代理。
会话劫持:多个有状态 HTTP server 处理 MCP 请求时,攻击者获取 session ID 后可以冒充合法 client。防御方式包括:session ID 用安全随机数生成、绑定用户信息(<user_id>:<session_id>)、定期轮换、不使用 session 做认证。
| 攻击类型 | 触发条件 | 影响 | 防御要点 |
|---|---|---|---|
| 混淆代理 | 静态 client ID + 动态注册 + consent cookie | 未授权获取第三方 API 访问 | MCP 层独立 consent 流程 |
| SSRF | client 不验证 server 提供的 OAuth URL | 内网穿透、云凭据泄露 | URL scheme 白名单、IP 段屏蔽、出口代理 |
| 会话劫持 | session ID 可预测或未绑定用户 | 冒充合法 client 执行操作 | 安全随机数、用户绑定、定期轮换 |
| Token 透传 | server 不过滤直接转发上游 token | 安全控制绕过、审计失效 | server 必须验证 token audience |
| 本地 server 被利用 | 本地 server 无沙箱、无 consent | 任意代码执行、数据泄露 | stdio 传输、沙箱运行、启动前确认 |
| Scope 过大 | 一次性请求所有权限 scope | token 泄露影响面扩大 | 最小权限、渐进式授权 |
安全加固代码示例
先看一个常见的 OAuth 配置错误:
// ❌ 问题:token 透传,server 不验证 audience
async function forwardRequest(token: string, apiUrl: string) {
// 直接把 client 的 token 发给下游 API
const res = await fetch(apiUrl, {
headers: { Authorization: `Bearer ${token}` },
})
return res.json()
}修正方式:
// ✅ 修正:server 用自己的 token 访问下游,验证权限后再转发
async function forwardRequest(clientToken: string, apiUrl: string, userId: string) {
// 1. 验证 client token 确实是发给本 server 的
const claims = await verifyToken(clientToken, { expectedAudience: SERVER_AUDIENCE })
// 2. 检查权限 scope
if (!claims.scopes.includes('read:orders')) {
throw new Error('缺少读权限')
}
// 3. 用 server 自己的凭据访问下游 API
const serverToken = await getServerToken()
const res = await fetch(apiUrl, {
headers: { Authorization: `Bearer ${serverToken}` },
})
// 4. 审计日志
console.log(JSON.stringify({
event: 'mcp.proxy.downstream_call',
userId: claims.sub,
endpoint: apiUrl,
timestamp: new Date().toISOString(),
}))
return res.json()
}MCP 连接器评估清单
接入一个 MCP server 之前,我会逐项检查以下内容。不是所有项都必须满足,但每一条不满足的都需要明确风险接受方。
| # | 检查项 | 评估要点 |
|---|---|---|
| 1 | 工具清单是否明确 | 列出所有 tools,标注哪些是只读、哪些有写操作、哪些涉及删除或资金变动 |
| 2 | 鉴权方式 | 是 API key、OAuth 2.1 还是 token 透传?密钥存储在哪里?是否支持轮换? |
| 3 | 输入校验 | 每个 tool 的输入参数是否有 schema 定义?是否做了服务端校验(不是只靠模型输出格式正确)? |
| 4 | 数据敏感度 | 返回的数据中是否包含 PII、凭据、内部 URL、Token?是否有脱敏处理? |
| 5 | 审计日志 | 每次 tool 调用是否记录了:调用者身份、参数、时间戳、结果摘要?日志能否追溯到具体任务? |
| 6 | 权限隔离 | 是否区分了不同用户/角色的可用工具?管理员和普通用户是否看到同样的工具集? |
| 7 | 降级策略 | server 挂了或超时时,agent 是停下来还是继续?有没有 fallback 逻辑? |
| 8 | 速率限制 | 是否有 rate limit 保护下游系统?agent 循环调用时会不会把下游打挂? |
| 9 | 传输安全 | 本地用 stdio 还是 HTTP?远程是否强制 HTTPS?是否校验了 TLS 证书? |
| 10 | 沙箱隔离 | server 是否运行在容器或受限环境中?文件系统访问范围是否明确? |
| 11 | 版本兼容性 | server 的 MCP 协议版本是否与 client 兼容?breaking change 时如何通知? |
| 12 | 来源可信度 | server 代码是否开源?维护频率如何?是否有已知漏洞记录?npm 包下载量多少? |
| 13 | 环境隔离 | 开发、测试、生产环境的 MCP 配置是否分开?开发环境是否连了生产数据库? |
适用边界:MCP 不能替代什么
MCP 解决的是工具接入的标准化,但它不是万能的。有几个边界需要明确:
MCP 不替代业务权限系统。 Agent 通过 MCP 调用外部系统前,应用层仍然需要判断用户是否有权访问对应资源。MCP server 可以做权限检查,但它检查的是「这个 API token 对应的身份能否执行这个操作」,不是「当前对话的用户能否看到这个数据」。两层权限不能混为一谈。
MCP 不替代数据治理。 它不管数据怎么脱敏、怎么分类、怎么合规流转。这些需要在 MCP server 内部实现,或者在 server 之前的数据层处理。
MCP 不替代工作流编排。 它是工具调用协议,不是流程引擎。多步骤的业务流程(审批链、状态机、Saga)需要在上层编排,MCP 只负责每个步骤里和外部系统的交互。
MCP 和 A2A 是互补的。 MCP 连接 agent 和工具/数据,A2A(Agent-to-Agent)连接 agent 和 agent。两个协议解决不同层面的问题,不是替代关系。
工程落地建议
按环境区分 MCP 配置。开发环境可以连本地文件和测试数据库,生产环境应限制写权限和敏感数据范围。对团队使用场景,还要管理工具白名单。不是所有成员、所有任务都应该拥有同样的外部系统访问能力。
一个实用的起步策略:先把你最常用的一个内部 API 或数据库封装成 MCP server,跑通整个流程——从 server 开发、鉴权、输入校验、审计日志,到 client 接入、模型 prompt 调优、异常处理。这个过程会让你对协议的每个环节都有体感,之后再扩展到其他系统就有章法了。
MCP 的核心价值是让你不用为每个外部系统重写集成代码。但「标准化接入」不等于「放心使用」——标准只管接口形状,不管安全、不管数据边界、不管运维。这些才是工程落地的真正工作量。
参考资料
- MCP 官方安全最佳实践 — 官方安全文档,覆盖混淆代理、SSRF、会话劫持等攻击向量和防御措施
- MCP 协议规格 (2025-06-18) — 协议正式规格,定义了 Host/Client/Server 架构和三种核心原语
- MCP vs Function Calling: 7 Key Differences — 详细对比 function calling 和 MCP 的七个维度差异
- The Complete Guide to MCP in 2026 — 2026 年 MCP 完整开发指南,含架构、代码示例和路线图
- MCP-Zero: Proactive Toolchain Construction for LLM Agents — 大规模工具集动态检索框架,token 消耗降低 98%
- MCP vs Function Calling: How They Differ — 侧重安全、可扩展性和可移植性的对比分析
- OWASP: A Practical Guide for Secure MCP Server Development — OWASP 出品的 MCP server 安全开发指南
- MCP 2026 Roadmap — MCP 维护者公布的 2026 路线图,重点解决生产环境痛点