MCP 与 Agent 工具链:把外部系统安全接进来

0阅读8分钟

从一次集成事故说起

去年我接了一个需求:让内部 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。

流程图画布 · 115%
Mermaid 流程图加载中...

这个结构的关键在于,工具的发现和执行是动态协商的,不是编译时绑定的。Host 启动时不知道 Server 有哪些工具——它先发送 tools/list 请求,Server 返回工具清单(包括名称、描述、输入 schema),然后 Host 把这些信息注入到模型的上下文中。模型决定要调哪个工具时,Host 发 tools/call 请求,Server 执行后返回结果。

MCP vs Function Calling:它们不在同一层

很多人把 MCP 和 function calling 放在一起比较,这其实有点错位。它们不是竞争关系,而是同一流程的不同阶段。

Function calling 是模型的「前端」能力——把自然语言转成结构化的函数调用指令。MCP 是「后端」的执行编排——负责发现工具、路由请求、管理响应。一个管「模型说了什么」,一个管「系统怎么做」。

维度Function CallingMCP
职责意图识别,生成结构化调用工具发现,执行编排,响应管理
标准化各厂商格式不同(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 流程
SSRFclient 不验证 server 提供的 OAuth URL内网穿透、云凭据泄露URL scheme 白名单、IP 段屏蔽、出口代理
会话劫持session ID 可预测或未绑定用户冒充合法 client 执行操作安全随机数、用户绑定、定期轮换
Token 透传server 不过滤直接转发上游 token安全控制绕过、审计失效server 必须验证 token audience
本地 server 被利用本地 server 无沙箱、无 consent任意代码执行、数据泄露stdio 传输、沙箱运行、启动前确认
Scope 过大一次性请求所有权限 scopetoken 泄露影响面扩大最小权限、渐进式授权

安全加固代码示例

先看一个常见的 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 的核心价值是让你不用为每个外部系统重写集成代码。但「标准化接入」不等于「放心使用」——标准只管接口形状,不管安全、不管数据边界、不管运维。这些才是工程落地的真正工作量。

参考资料

评论 0

0 / 1000