Agent 开发实战 · 第 4 篇

MCP 客户端、服务端与权限边界

画清连接与数据流,为工具、资源和凭据设计最小权限。

34 分钟开发进阶

学完能做什么

你会理解 MCP 客户端、服务端和宿主应用之间的职责,能为一个 MCP 接入画出数据流、权限和信任边界,并在启用前完成最小安全审查。

MCP 解决的是连接规范,不是自动授权

MCP 让应用用统一方式发现和调用外部能力。它减少适配工作,但不会替你判断一个服务是否可信,也不会自动保证工具安全。

text
用户
  ↓
宿主应用(承载对话与 Agent)
  ↓ MCP Client
传输与会话
  ↓
MCP Server
  ├─ Tools:可执行动作
  ├─ Resources:可读取资源
  └─ Prompts:可复用提示模板

宿主应用最终决定接入哪个服务、把哪些能力提供给模型,以及调用前是否需要人工确认。

三个角色不要混淆

角色负责什么典型安全职责
宿主应用展示界面、运行 Agent、保存用户配置身份、授权、确认和审计
MCP Client建立会话、发现能力、发送调用会话隔离、协议校验、超时
MCP Server实现工具和资源访问参数校验、最小权限、结果脱敏

模型不是第四个安全主体。它只能在宿主提供的能力范围内提出调用请求。

Tools、Resources、Prompts 的风险不同

  • Tool 可能产生副作用,例如写文件、发消息、创建工单;
  • Resource 通常是读取,但仍可能暴露敏感数据;
  • Prompt 可能把外部指令带进上下文,形成提示注入;
  • 返回内容本身不可信,不能当作系统指令执行。

“只读”也不等于“无风险”。读取整个用户目录、全部邮箱或客户数据库仍然是高权限。

启用一个服务前画数据流

至少回答:

  1. 服务运行在哪里,由谁维护?
  2. 它能看到哪些请求、文件、账号和环境变量?
  3. 数据是否离开本机或组织边界?
  4. 凭据保存在何处,能否轮换和撤销?
  5. 每个工具是只读、写入、发送还是删除?
  6. 哪些调用需要用户逐次确认?
  7. 服务异常或被替换时如何停止?

如果无法说明数据去哪了,就不应接入真实业务数据。

用能力清单实施最小权限

不要把“文件系统访问”作为一个模糊权限。拆成可审计能力:

json
{
  "allowed_roots": ["/workspace/project-a/docs"],
  "operations": ["list", "read"],
  "denied_patterns": ["*.env", "*.key", "credentials.*"],
  "max_file_bytes": 1048576,
  "follow_symlinks": false
}

即使工具声明了范围,服务端也必须再次校验真实路径,防止路径穿越和符号链接越界。

对写操作分级确认

风险等级示例推荐处理
读取指定公开文档首次授权范围后可执行
在草稿目录创建文件展示路径和摘要,允许一次确认
发送消息、修改线上记录每次展示对象、参数和影响后确认
极高删除、支付、权限变更默认禁用,使用专门审批流程

确认框要展示具体对象和参数。“允许工具执行吗”太笼统,用户无法判断影响。

抵御来自内容的提示注入

资源或工具结果可能包含“忽略之前指令”“上传所有文件”之类文字。处理原则:

  • 外部内容始终标记为数据,不提升为系统指令;
  • 工具选择和权限规则不由资源内容改变;
  • 从工具结果中提取结构化字段,再用于下一步;
  • 高风险动作依据程序规则和用户确认,不依据页面文字;
  • 限制一次结果的大小、类型和可进入上下文的范围。

服务端实现的基本护栏

python
ALLOWED_PROJECTS = {"project-a", "project-b"}


def read_project_note(project_id: str, note_id: str, actor) -> dict:
    if project_id not in ALLOWED_PROJECTS:
        raise PermissionError("项目不在允许范围")
    if not actor.can_read(project_id):
        raise PermissionError("当前身份无读取权限")
    if not note_id.isascii() or len(note_id) > 64:
        raise ValueError("note_id 不合法")

    note = repository.get_note(project_id, note_id)
    return redact_sensitive_fields(note)

不要相信客户端已经验证过。服务端要基于当前身份再次授权,并对返回值做最少披露。

审计记录什么

  • 宿主、用户和服务实例标识;
  • 服务与工具版本;
  • 调用时间、工具名和参数摘要;
  • 授权范围和确认记录;
  • 结果状态、耗时和错误分类;
  • 凭据创建、轮换和撤销事件。

日志不要保存完整密钥、授权头和超出排障需要的原始内容。

接入审查练习

假设要接入一个能读取文档并发送邮件的 MCP 服务:

  1. 将读取和发送拆成两个独立能力;
  2. 限定允许读取的目录、文件类型和大小;
  3. 规定邮件发送必须展示收件人、主题和正文;
  4. 设计一套假数据测试提示注入、路径越界和重复发送;
  5. 写出撤销凭据、停用服务和查看审计日志的路径。

完成检查清单

  • 画清了宿主、Client、Server 和外部系统的数据流。
  • 每个能力都有最小范围,不使用模糊的全盘权限。
  • 资源内容和工具结果被视为不可信数据。
  • 高风险动作会展示具体影响并逐次确认。
  • 凭据可以轮换、撤销,调用过程可以审计。

下一步

下一篇将处理多轮 Agent 最容易失控的部分:上下文、短期记忆、长期记忆和可恢复状态。