阅读需 8 分钟

避免生产误操作的 MCP 工具描述

当 MCP 工具描述用简单语言写明目标、副作用和确认要求时,就能防止代理意外执行生产操作。

避免生产误操作的 MCP 工具描述

MCP 工具描述既可以在不安全的生产调用开始前阻止它,也可能用一个友好的动词把危险隐藏起来。大多数意外的生产操作,并不是代理先决定要造成破坏,而是含糊的描述让破坏性工具看起来和检查工具没有区别。

把每个会改变状态的工具描述写成一份小型操作契约:写明目标系统、写明副作用,并写明确认要求。如果缺少其中任何一项,描述就会要求模型自行推断安全边界,而这本应由代码明确规定。

我审查过足够多的操作接口,因此不再信任「管理」「同步」「部署」和「清理」这类标签。它们对作者很方便,却会让负责解释「为什么一个测试请求落到了线上账户」的人付出代价。好的描述会让那些令人不便的细节无法被忽略。

工具描述是执行警告,不是产品文案

MCP 工具描述应告诉代理及其人类操作员,调用成功后会发生什么。它不应推销能力、概括内部子系统,也不应把工具名称换一种更长的说法再重复一遍。

Model Context Protocol 的工具模式在工具名称和 inputSchema 旁边提供了面向人的 description。MCP 规范还允许使用 readOnlyHintdestructiveHint 等工具注解。这些注解能帮助客户端展示工具,但规范明确要求客户端把它们视为提示,而不是权限检查。因此,描述仍然是操作员在调用到达服务前阅读实际后果的地方。

看看下面两个定义:

{
  "name": "delete_backup",
  "description": "Deletes a backup.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "backup_id": { "type": "string" }
    },
    "required": ["backup_id"]
  }
}
{
  "name": "delete_production_backup",
  "description": "Permanently deletes one backup from the Production PostgreSQL backup store. This removes a recovery point and cannot be undone. Ask the user to confirm the backup ID and its timestamp before calling this tool.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "backup_id": {
        "type": "string",
        "description": "Immutable backup ID returned by list_production_backups."
      }
    },
    "required": ["backup_id"],
    "additionalProperties": false
  },
  "annotations": {
    "destructiveHint": true,
    "readOnlyHint": false
  }
}

第二个定义不只是听起来更谨慎。它告诉代理目标是什么、结果是否不可逆、如何安全识别对象,以及必须停下来进行一次对话确认。审核人员也能据此在查看实现细节前拒绝一次调用。

不要以为换一个更强烈的动词就能解决问题。「摧毁」比「删除」更有警示作用,但它仍然没有说明是哪个账户、哪类数据,以及工具如何处理确认。上下文应由描述负责。

在第一句话中写明目标系统

第一句话应指出受影响的确切系统,包括环境或账户边界。对「数据库」执行操作,可能指可随时丢弃的本地容器、共享测试服务、staging 租户,或生产客户账本。即使 API 端点恰好相同,这些也属于不同操作。

使用操作员在实际工作中能识别的名词。可以写「Production payments account」「staging Kubernetes cluster」「customer tenant northwind」或「repository mobile-api release branch」。除非每位目标操作员都熟悉内部昵称,而且名称也出现在参数中,否则不要使用这类昵称。

下面这种顺序有效,因为它先说风险,再说机制:

[目标系统]。[操作和结果]。[确认规则]。

例如:

Production identity directory. Disables the selected user account and ends active sessions. Ask the user to confirm the username before calling.

目标必须与处理程序实际影响的对象一致,而不是与作者的意图一致。如果工具接受 environment 参数,那么描述声称「更新 staging」会在调用者传入 production 时立即失实。要么按环境拆分工具,要么明确说明参数允许什么。

拆分通常更容易操作:

list_staging_feature_flags
set_staging_feature_flag
list_production_feature_flags
request_production_feature_flag_change

这种设计看起来可能有些重复。但重复的成本远低于工具选择器认为 set_feature_flag 看起来合适,后来才发现某个可选参数把目标指向了 production。

名称也需要同样严谨,但名称无法承担全部警示责任。工具列表可能截断名称。代理在相似名称之间选择时,有时会重点查看描述。人在压力下也会同时扫视两者。只要合适,就把目标放进名称和描述中;即使名称从视野中消失,描述也必须完整。

有一个例外:工具接收不可变的资源 URI,而且 URI 的主机已经固定了环境。即便如此,也应在描述中写出主机或账户类别。UUID 无法告诉人类它代表开发记录还是线上客户数据。

把副作用写成完成后的结果

安全的描述会告诉读者调用成功后世界变成什么样。这会迫使作者区分观察和变更、可逆变更和永久变更,以及请求和执行。

比较一下含糊的动词「管理」:

Manages service deployments.

它掩盖了许多完全不同的结果。部署工具可能创建发布、提升已有发布、重启实例、更改流量分配、回滚代码,或者只获取状态。如果它们有不同的故障模式或审批规则,每种操作都应使用独立工具。

改用明确的结果:

Creates a deployment request for the Production catalog service. It does not change running instances. A release manager must approve the request in the deployment system.

或者:

Changes Production catalog traffic so the specified release receives 100 percent of requests. Existing requests may finish on the prior release. Ask the user to confirm the release version before calling.

创建请求和执行请求的区别,比 HTTP POSTPATCH 的区别重要得多。请求对象仍然可能创建工作、消耗配额或通知人员,因此也要描述这些副作用。但如果它只是打开一个审批事项,就不要把它称作线上部署。

避免委婉说法。「退役」可能意味着归档、禁用、删除或终止计费。「清理」可能意味着移除临时文件,也可能意味着删除客户导出的唯一保留副本。写出实际的动词和对象:删除、禁用、轮换、提升、转移、发送、扣费或发布。

对于延迟生效的操作,要写明延迟。DNS 变更可能在 API 返回后才传播。移除用户可能会阻止后续访问,却保留审计记录。轮换凭据可能使仍在使用旧密钥的客户端失效。代理需要这些上下文,才能判断是否应先检查依赖系统。

如果一次调用会影响很多对象,描述也应说明实际范围。「删除选定记录」和「删除与所提供查询匹配的全部记录」完全不同。用单数动词掩盖批量端点,会带来可预见的问题。

确认语言必须描述真实的控制措施

只有在实现和操作流程都会遵守确认要求时,确认句才有用。一个处理程序会立即执行的工具,却写着「需要确认」,这只是做样子,代理迟早会把它暴露出来。

这里有三种不同模式,描述应写明你实际采用的是哪一种。

  1. 代理先在自己的对话中询问用户,然后调用操作。这依赖代理遵守描述,无法阻止经过修改或粗心的客户端。
  2. 工具创建请求,交给另一位人员或另一个系统审批。调用本身有副作用,但描述中的生产变更会等待审批。
  3. 执行网关暂停操作,在发送凭据或联系目标系统前要求人工批准。

不要把这些模式都压缩成「需要确认」。它们提供的保护和审计证据各不相同。

使用能说明行为者和时机的动词:

Before calling, ask the user to confirm the repository name and release tag.
Calling this tool submits a change request. The deployment system requires a release manager to approve it before any production release begins.
This action gateway asks a human to approve every call before it sends the request to the Production payments API.

最后一种说法描述的是强制执行的边界。第一种说法描述的是给代理的指令。两者都可能合适,但并不等价。

不要只告诉代理请求一次含糊的确认。要告诉它,用户需要确认哪些事实。删除操作可能需要资源名称、账户和保留状态。转账可能需要来源、目的地、金额和币种。发布可能需要服务、版本和流量范围。描述不应要求仪式化的确认,而应要求能够发现目标错误的事实。

确认规则还需要说明范围。如果一次会话在获得一次批准后就能运行十次调用,「生产变更前获得批准」就很弱。若实际控制措施批准的是整个会话生命周期,应在产品文档中说明,并避免声称每次调用都会单独暂停。

处理程序暗中执行工作时,只读声明会失效

先识别进程
审批卡片会先显示调用进程的代码签名权限,然后该进程才能执行操作。

只有当处理程序不会有意改变目标系统时,才能把工具称为只读。这个词描述的是行为,而不是 HTTP 方法、数据库权限名称或作者的期望。

GET 请求可能刷新会话、更新最后访问字段、生成导出、启动报告任务,或触发有实际成本的缓存填充。POST 请求如果只评估 dry run 且不持久化任何内容,也可能是无害的。在选择标签前,应检查处理程序及其下游调用。

MCP 注解 readOnlyHint 对希望降低检查工具使用阻力的客户端很有帮助。但它仍然只是提示,因此服务器必须自行强制执行边界。更重要的是,描述应说明任何会让操作员意外的例外情况。

下面的描述会误导人:

Read-only tool for checking invoice status.

如果端点会创建文档查看事件、刷新第三方令牌或启动远程计算,这个说法就不成立。更诚实的写法是:

Retrieves the current status of one Production invoice. It does not edit the invoice or charge the customer. The billing provider records this request in its access log.

访问日志通常不会影响检查操作。但如果目标有合规规则、每次获取都收费,或存在会对读取作出反应的工作流,访问日志就很重要。应说明这些影响,但不要把每个描述都写成法律声明。

尽可能把 dry run 和执行分成不同工具。带有 dry_run 布尔值的 deploy 工具,会在同一个定义中制造两种安全配置。代理可能遗漏默认值,也可能误解服务器是否遵守默认值,或者复用载荷却忘记修改它。plan_production_deploymentexecute_production_deployment 会让区别在工具选择、日志和审核中都清晰可见。

验证工具也遵循同一规则。「验证配置」听起来安全,但有些提供商会在验证时分配资源或联系实时依赖。如果确实如此,就应把它描述成一种操作,并应用相应的确认规则。

一个宽泛工具会制造审批错误

工具应按共同的后果和审批边界组织,而不是为了方便一个 API 客户端。通用管理工具会把描述变成一长串例外,模型和人类都无法可靠地读完。

应避免下面这种模式:

{
  "name": "admin",
  "description": "Administer users, deployments, secrets, and configuration across environments.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "operation": { "type": "string" },
      "environment": { "type": "string" },
      "payload": { "type": "object" }
    },
    "required": ["operation", "environment", "payload"]
  }
}

这个定义破坏了有用的审核单位。审核人员从工具卡片上看不出调用会获取状态、轮换凭据还是删除用户。operation 字符串把重要语义移到了后面的参数中,很容易被忽略。

应按意图和风险拆分:

get_production_deployment_status
plan_production_deployment
submit_production_deployment_request
rotate_production_service_credential
create_production_user_access_request

不需要为每个端点都创建一个工具。只有当目标、副作用或确认要求发生变化时,才需要拆分。只要批量工具始终操作同一种边界明确的资源,并且始终需要相同审批,它就可以继续作为批量工具存在。但描述必须说明它会影响多个对象,并展示调用者如何限制选择范围。

参数也需要描述。工具描述说明操作做什么,参数描述则限制危险选择。能使用时,为环境和操作类型使用枚举。在服务器端拒绝无法识别的值。不要把「production」放进自由文本字符串,然后指望描述来保护你。

范围更窄的工具也能产生更好的审计记录。日志写着 rotate_production_service_credential 时,调查人员无需打开参数就能理解操作类别。若日志只写 admin,他们必须从载荷中重新推断意图。

在处理程序之前写描述

在 Mac 上运行网关
Sallyport 以签名的 Mac 菜单栏应用运行,保管库核心位于进程内部。

在实现之前起草操作契约,可以在修改接口仍然便宜时暴露含糊需求。如果你无法用一句普通话写出成功后的结果,就说明工具边界还不稳定。

对每个操作工具按下面的顺序审核:

  1. 按操作员识别它的方式写出目标,包括环境、账户或租户。
  2. 用直白的动词写出完成后的结果,并说明变更是否可逆。
  3. 写明确认者、审批发生的节点,以及审批按每次调用还是每个会话生效。
  4. 将这句话与处理程序的行为、默认值、重试和下游 API 进行对照。
  5. 为标识符、范围控制以及任何会改变目标的值补充参数描述。

第四项会发现精美文档容易漏掉的问题。除非下游请求使用幂等机制,否则重试可能导致扣费或消息发送两次。默认值可能把省略的 environment 变成 production。处理程序可能把一个友好名称解析为多个资源。描述无法修复这些实现错误,但写描述会迫使它们暴露出来。

一个有用的内部测试是:去掉工具名称,只把描述和输入模式展示给另一位工程师。让他预测成功调用后会发生什么,以及他认为需要什么审批。如果答案与处理程序不一致,就修正契约或代码。

还要用普通语言提示进行测试。「清掉旧数据」「让新版本上线」和「修好 Jordan 的账户」,正是会让人想使用宽泛工具的请求。安全的代理应先使用检查工具、询问缺少的标识符,或把具体操作提交审批。如果它能从这类提示直接跳到生产删除,问题早在接口设计阶段就已经出现,而不是模型行为导致的。

错误消息和结果必须保留安全边界

让每次使用都经过确认
将密钥设为每次调用都需审批,Sallyport 就会在每次使用该密钥前请求确认。

如果工具结果隐藏了实际执行的目标,或者错误信息诱使代理尝试更宽泛的操作,再谨慎的描述也会失去很多价值。返回足够的证据,让代理和操作员核验发生了什么。

对于成功的状态变更,返回规范目标标识符、执行的操作和结果状态。不要只返回 ok

{
  "status": "completed",
  "target": {
    "environment": "production",
    "service": "catalog",
    "release": "2025.06.14-3"
  },
  "action": "traffic_promoted",
  "traffic_percent": 100,
  "request_id": "relreq_8a2f"
}

如果操作暂停等待审批,要说明还没有任何内容到达目标系统。这个区别可以防止代理把一个只是等待人员处理的调用当成失败操作来补救。

{
  "status": "approval_required",
  "action": "rotate_production_service_credential",
  "target": "production/catalog-api",
  "executed": false,
  "approval_scope": "this call"
}

错误也需要同样谨慎。「Forbidden」在技术上准确,在操作上却毫无帮助。告诉调用者,是目标被拒绝、环境无效、缺少审批,还是请求到达远程系统后失败。解释中绝不能暴露秘密,也不要建议代理盲目重复状态变更请求。

对外部有实际影响的操作,应让结果清楚显示幂等性。如果网络超时发生在远程服务已经接受转账或创建发布之后,代理必须使用稳定的请求 ID 查询请求状态。重试路径不应靠猜。描述无法表达所有重试规则,但执行不可逆调用的工具应配有状态查询工具,并使用支持恢复的结果结构。

描述背后必须有强制措施

纯文本能减少错误选择,但无法阻止一个已经持有不受限制生产令牌的进程。应把凭据和最终网络操作放在能够拒绝、审批并记录调用的边界之后。

Sallyport 为连接 MCP 的代理采用了这种安排:代理使用随附的 sp mcp shim,而应用将 API 和 SSH 凭据保存在加密保管库中,并由自身执行经过批准的操作。按会话授权和可选的按调用密钥审批,让确认文字变成可强制执行的行为,而不是礼貌请求。

这并不能为薄弱的工具设计开脱。网关看到的是到达它的调用。工具模式仍然决定调用写的是「删除这个生产备份」,还是把删除隐藏在通用的 admin 操作之后。在处理程序中强制执行参数验证;如果远程系统支持,就把凭据限制在预期目标上;同时保留能够识别进程和操作的审计记录。

Model Context Protocol 的授权指南在另一层也说明了同一个更大的道理:授权应属于带有明确检查的协议流程,而不是模型指令。把描述当作面向人的契约,把服务器授权、凭据保管和审批当作让契约成真的控制措施。

挑出目前暴露的最危险工具,不看它的名称,重写它的描述。如果你无法在两三句直接的话中写明生产目标、完成后的副作用和审批范围,就暂时不要把这个工具提供给自主代理。

常见问题

MCP 工具描述中应包含哪些生产操作信息?

生产工具的描述应写明确切目标、会发生什么变化,以及是否必须由人员批准调用。「部署服务」之所以不够,是因为它隐藏了环境、操作和审批边界。用一句话写清后果,让疲惫的工程师看一遍就能理解。

MCP 工具名称足以防止意外修改生产环境吗?

工具名称有助于路由,但名称通常会被缩写,也可能随着工具扩展而过时。把安全含义写进描述,因为代理和操作员可以在那里同时看到目标、副作用和审批要求。名称也要保持具体,但不能只依赖名称。

如何在 MCP 工具中清楚描述目标系统?

把目标系统放在开头:「生产计费 API」比「API」清楚。然后写明状态变化,例如禁用客户账户或创建部署。最后用简单语言说明确认规则,包括工具是否只会准备请求。

如何描述不可逆的副作用?

写明调用会改变什么,以及在适用时说明哪些变化无法撤销。「永久删除选定的生产数据库备份」很清楚。「管理备份」则会让代理自行猜测它是在列出、恢复、复制还是销毁数据。

在工具描述中写「需要确认」就够了吗?

不够。如果只写「需要确认」,却不说明谁来确认、何时确认,就会造成虚假的安全感。应说明是用户批准每次调用、外部网关请求审批,还是工具只为另一位操作员创建请求。

确认要求应该使用结构化字段还是纯文本?

代理可能更可靠地解析结构化的确认字段,但描述仍需要为审核工具的人员提供一段清楚的安全说明。条件允许时两者都使用。结构化字段不能取代易读的后果说明。

如果工具会记录访问或刷新令牌,还能称为只读吗?

只要工具不会有意修改目标系统,就可以称为只读。列出资源、获取状态和验证请求,只有在实现不会刷新凭据、创建记录或触发后台工作时,才符合只读定义。应检查处理程序,而不是看名称中的动词。

规划和执行应使用不同的 MCP 工具吗?

当操作后果不同时,应使用不同工具。一个可以规划、发布、回滚和提升版本的「deploy」工具迟早会收到错误参数。将检查、创建请求和执行拆开,让每个描述都做出一个明确承诺。

如何记录一个既能操作 staging 又能操作 production 的工具?

如果目标来自参数,应描述允许的值,并明确指出 production。不要声称开发环境调用也需要确认,而实际情况并非如此。可以拆分工具,也可以让环境规则在工具契约中可见,并由代码强制执行。

如何测试 MCP 描述是否能阻止不安全的工具选择?

用包含「清理」「发布」「修复访问权限」和「删除旧的那个」等含糊词语的提示进行测试。观察代理是否选择正确工具、是否提出有用的澄清问题,以及是否保留确认边界。一个只有在提示完美时才安全的工具,不适合日常使用。

Sallyport

Sallyport 替你的 AI 智能体执行 API 调用和 SSH 命令。密钥留在你 Mac 上的本地密钥库里;每次运行由你批准,每个操作都落入一份密封的审计日志。

© 2026 Sallyport · 依据 Apache-2.0 开源 · Oleg Sotnikov