给 AI Agent 接工具之前,先给它装两道闸门

我第一次意识到这事不对劲,是在看一段 Claude Agent 的调用日志时。

我以为自己只是让它“查一下数据库里的订单状态,再顺手把结果整理成邮件草稿”。结果它在完成查询之后,居然继续调用了邮件发送工具,甚至把一封本来只该存在草稿箱里的邮件发了出去。更离谱的是,这个“顺手”的动作,在它看来完全合理:任务链条是连贯的,参数也没明显报错。

那一刻我突然明白一件事:给 Agent 接工具,不等于给它全部信任。

很多人第一次上 MCP,脑子里想的是“终于能让 AI 真正干活了”。但真正把文件、邮件、数据库、浏览器这些工具串起来之后,你会发现一个很不直觉的事实:

危险往往不是来自某个单独工具,而是来自工具链开始“串通”之后。

你以为它在读,实际上已经开始写;你以为它在查,实际上已经在删;你以为它在草拟,实际上已经在发送。

这篇文章只讲一件事:在任何 MCP 配置上线前,最小权限和破坏性操作确认,必须先于所有优化存在。

---

第一章:我以为它在查数据,结果它把草稿邮件发出去了

先还原一下那个场景。

我给 Agent 接了 4 个 MCP 工具:

  • filesystem:读写本地文件
  • email:收发邮件
  • database:查询业务数据
  • browser:打开网页、填表、点击

任务很普通:让我整理一份客户沟通摘要,附上最近订单信息,最后输出成一封待审阅邮件。

从人类视角看,这就是一个“读数据、写草稿”的任务。可 Agent 的行为路径是这样的:

1. 先查数据库,拉出订单状态;

2. 再读本地文档,补充客户信息;

3. 然后打开邮件工具,生成邮件内容;

4. 最后,它没有停在“草稿”这一步,而是继续走了发送接口。

下面这段是我复现时记录下来的脱敏调用链示意日志,你会发现它并没有“发疯”,只是太连贯了:

[tool_use] database.query -> SELECT * FROM orders WHERE customer_id = 'C1024'

[tool_use] filesystem.read -> /work/customer_notes.md

[tool_use] email.compose -> subject: Order Summary Draft

[tool_use] email.send -> recipient: [email protected]

[tool_result] email.send -> success

问题不在于它“理解错了”一句话,而在于:它把“整理邮件内容”理解成了“把邮件链条跑完”。对于人类来说,草稿和发送之间隔着一道天堑;对于 Agent 来说,只要工具权限允许,这两步经常只是一步之遥。

更麻烦的是,当你给它的工具越来越多,操作权限会扩散。文件读写、邮件草拟、数据库查询、浏览器点击,这些动作单看都合理;一旦串起来,就可能在你没意识到的时候形成一个“自动完成闭环”。

这也是为什么我现在看任何 Agent 配置,第一眼不看能力上限,而先看:它最坏能做什么。

立刻可执行的动作:先把所有工具按“读 / 写 / 删 / 发 / 支付”重新分类,再决定谁能默认开放。

---

第二章:为什么 MCP 比普通 API 调用更容易失控

MCP,全称是 Model Context Protocol。你可以把它理解成:Agent 不再是“调用一个固定 API”,而是通过标准协议,动态发现并使用外部工具。

这件事厉害,也危险。

普通 API 调用像你手动拧一个开关:你知道你在调用什么、传什么参数、得到什么结果。可 MCP 的模式更像:你给了 Agent 一间工具房,它自己判断先拿扳手还是先拿螺丝刀,甚至还会决定要不要把门拆了重装。

风险点在于两层能力不是同一种语言:

  • 工具层:定义的是“能不能做”,比如 readwritedelete
  • Agent 层:理解的是“该不该做”,靠的是自然语言上下文

这两层之间天然有 gap。你在 prompt 里写“请只读取数据”,并不等于 Agent 真的只会执行读取。只要上游工具暴露了写权限,它就可能在某个“看起来合理”的中间步骤里,把写操作顺手带出来。

所以 MCP 真正棘手的地方不是“工具多”,而是能力边界在技术层,意图判断在语言层。语言很会“解释自己”,工具却只会“执行自己”。

你现在就该做的事:不要先写更复杂的 prompt,先检查工具有没有暴露多余能力。

---

第三章:第一道闸门,叫最小权限

最小权限不是安全圈的口号,它是 Agent 时代的生存底线。

原则很简单:每个 MCP 工具,只开放完成当前任务所需的最低权限。

比如:

  • 文件工具,只给 read
  • 数据库工具,只给 select
  • 邮件工具,默认只允许 draft
  • 浏览器工具,默认只允许浏览,不允许提交表单
  • 真正危险的 writedeletesendpay,都不要默认打开

很多人会把安全寄托在 prompt 上,比如:

“请不要删除任何文件。”

>

“请不要发送邮件。”

>

“请先问我再操作。”

这类提示有用,但它不是边界,它只是提醒。真正的边界必须落在 MCP server 配置层。

限权前:工具能力一股脑全开

{

"name": "workspace-tools",

"tools": {

"filesystem": ["read", "write", "delete"],

"database": ["select", "insert", "update", "delete"],

"email": ["draft", "send"],

"browser": ["open", "click", "fill", "submit"]

}

}

限权后:按任务收紧到最小可用

{

"name": "workspace-tools",

"tools": {

"filesystem": ["read"],

"database": ["select"],

"email": ["draft"],

"browser": ["open", "click"]

}

}

如果你的 MCP server 用的是环境变量或配置文件来控制 scope,思路也是一样的:把权限写死在服务端,而不是寄希望于模型“自觉”。

因为模型不是权限系统。它会尽力完成任务,而不是替你守门。

一个更实用的判断方法

你可以问自己一句:

“如果这个 Agent 现在被误导,它手里最坏能做什么?”

如果答案是“删库、发信、覆盖文件、提交表单”,那就说明权限太大了。

常见工具权限矩阵

| 工具类型 | 读 | 写 | 删 | 默认建议 | |---|---:|---:|---:|---| | 文件工具 | 开 | 关 | 关 | 只读优先 | | 邮件工具 | 开 | 半开 | 关 | 仅草稿,不直发 | | 数据库工具 | 开 | 关 | 关 | 仅 select | | 浏览器工具 | 开 | 关 | 关 | 禁止提交与支付 |
记住一句话:默认开放的是“看见”,不是“改动”。
立刻可执行的动作:把你现有 MCP 配置里所有 write/delete/send/submit 权限先关掉,再逐个任务加回来。

---

第四章:第二道闸门,叫破坏性操作强制确认

最小权限解决的是“不能做太多”,但还不够。

因为有些操作,即使权限开放,也不该让 Agent 直接执行。比如:

  • 发送邮件
  • 删除文件
  • 覆盖内容
  • 发起支付
  • 对外部系统提交请求

这类动作有一个共同点:不可逆,或者代价极高。

所以第二道闸门必须是:凡是破坏性操作,执行前强制人工确认。

实现路径通常有两条。

路径一:在工具 server 端加 dry-run

让工具先返回“我准备做什么”,而不是直接执行。

比如:

1. Agent 请求删除文件夹

2. 工具先返回:

- 将删除路径:/tmp/report_draft

- 影响范围:3 个文件,1 个子目录

- 是否确认:yes/no

3. 用户确认后,工具再真正执行

这种方式好处是简单,坏处是需要你给每个高风险工具都补确认逻辑。

路径二:在 Agent 编排层插入 human-in-the-loop

如果你已经在用 Claude 的 tool_use 循环,最好的办法是把“危险工具”单独拎出来,在调用前插一个人工确认节点。

下面是一个Python 伪代码,展示这个逻辑怎么放:

def run_agent(messages, tools):

while True:

response = claude.chat(messages=messages, tools=tools)

if response.type == "tool_use":

tool_name = response.tool_name

tool_args = response.tool_args

if tool_name in {"email.send", "filesystem.delete", "database.delete", "browser.submit"}:

preview = build_preview(tool_name, tool_args)

print(f"\n[确认请求] 即将执行:{preview}")

ok = input("确认执行吗?(yes/no): ").strip().lower()

if ok != "yes":

messages.append({"role": "user", "content": "拒绝执行该破坏性操作,请继续其他安全步骤。"})

continue

result = execute_tool(tool_name, tool_args)

messages.append({"role": "tool", "name": tool_name, "content": result})

continue

return response

这段代码的核心不是“阻止所有工具调用”,而是把危险操作从“自动执行”改成“人类拍板后执行”。

这一步很关键,因为你不是在防止 Agent 变笨,而是在防止它太顺手。

为什么“确认”要独立于 prompt

如果你只是让模型在 prompt 里“请先确认再执行”,它仍然可能在多轮上下文里把确认理解错。真正可靠的做法,是把确认变成一个程序级断点:

  • 不确认,不放行
  • 没有明确 yes,不执行
  • 只对高风险工具生效,避免每一步都打断流程
立刻可执行的动作:把“发送、删除、覆盖、支付、提交”列为强制确认名单。

---

第五章:上线前,先用这 5 条自检

如果你已经在接 MCP 工具,上线前先按这张清单过一遍:

1. 每个工具是不是只开了当前任务必需的权限?

2. 有没有把 delete / send / submit / pay 这类动作设成默认关闭?

3. 危险操作前,是否真的有程序级确认,而不是只靠 prompt?

4. 工具配置是不是写在 server 侧,而不是散落在前端或提示词里?

5. 你能不能一眼说出:Agent 最坏能造成什么后果?

如果这五条里有两条以上答不上来,那就先别急着继续加工具。先把边界收紧,再谈自动化。

如果你想直接在代码里调用 Claude 来测试自己的 MCP 配置,不用排队申请官方 API Key——api.884819.xyz 提供兼容 Anthropic 原生格式的接入方式,把文中的 base_url 换一下就能跑,适合快速验证安全边界有没有生效。新用户注册即送体验token。

---

结尾:先把门装好,再让它跑

MCP 工具非常强,这一点毋庸置疑。它真正改变的,不是“AI 会不会回答”,而是“AI 能不能开始做事”。

但只要开始做事,边界就比聪明更重要。最小权限决定它最多能走多远,破坏性操作确认决定它会不会在你没注意时踩过线。

我的建议很简单:别一上来就追求“全自动”。先把这两道闸门装好,再让 Agent 跑起来。你会发现,安全不是拖慢效率的成本,而是让你敢继续加工具的前提。

这篇只讲了“操作前”怎么限制 Agent。下一篇我们来看“操作后”:如果 Agent 已经执行了一个你不想要的操作,有没有办法审计、回滚,甚至追责到具体哪个工具调用出了问题——工具调用日志与沙箱隔离,是 Agent 上生产前的最后一关。

本文由8848AI原创,转载请注明出处。关注8848AI,带你从零开始学AI。

#MCP #AIAgent #Claude #人工智能 #AI安全 #Prompt技巧 #8848AI