# MCP 工具名称冲突与更安全的代理行为

代理不会像谨慎的工程师阅读 SDK 那样阅读 MCP 工具目录。它会把压缩后的指令路由到一个可能的操作。如果你提供 `get_user`、`get_users`、`user_lookup` 和 `admin_get_user`，却想靠一段注意事项来区分它们，那么你实际上是在围绕权限设计一场猜谜游戏。

MCP 工具名称冲突不只是重复标识符导致客户端拒绝目录。更糟糕的是语义冲突：两个可调用操作听起来可以互换，但其中一个可能访问范围更广的系统、携带权限更强的凭据，或会改变状态。我见过团队在代理选错操作后把问题归咎于提示词。多数时候，这是他们自己发布的接口出了问题。

解决办法不是建立庞大的分类体系，也不是成立一个仪式感十足的命名委员会。给每个操作一个能说明它做什么、作用于哪里以及权限边界延伸到哪里的名称。然后用描述明确补上名称无法承载的边界。在歧义变成审批卡片、意外 API 调用或混乱的事故复盘之前，就通过测试把它暴露出来。

## 冲突首先是语义上的，其次才是语法上的

语法冲突发生在两个 MCP 服务器都发布了一个名为 `search` 的工具时。根据客户端的不同，它可能覆盖其中一个条目、强制使用命名空间，或生成令人困惑的目录。你应该修复它，因为不同客户端的行为可能不同。

即使每个标识符在技术上都唯一，语义冲突仍然存在。看看这些工具：

```text
search_customer
search_customer_records
lookup_customer
customer_admin_search
```

编译器可能认为这四个名称都有效，创建它们的团队也可能觉得它们很好懂。但当代理收到「找到 Maya Chen 的客户记录并更新她的地址」时，这些名称提供的路由信号很弱。代理必须推断哪个系统掌握真实数据、操作是否只读、能否搜索整个租户，以及使用管理凭据是否合适。

工具架构无法挽救模糊的目录。模型可以查看参数名称，但相似的架构往往会让歧义更严重。只读目录搜索和生产 CRM 搜索都可能接受 `query`、`limit` 和 `organization_id`。一个工具只返回数据，另一个却可能触发数据丰富操作或写入审计事件，这个事实也许只出现在描述中，而模型对描述的重视程度可能低于表面上的任务匹配度。

把这些问题视为不同缺陷：

- 标识符冲突：客户端无法一致地展示两个工具。
- 意图冲突：两个工具看起来都能满足同一个用户请求。
- 权限冲突：广泛权限的凭据隐藏在听起来范围很窄的工具后面。
- 环境冲突：相似标签掩盖了不同账户、区域或生产状态。

后三种冲突会造成代价高昂的错误。客户端可以拒绝重复名称，却无法可靠地告诉你，`sync_contact` 实际上意味着「使用组织级令牌修改生产 CRM 记录」，而 `update_contact` 意味着「写入本地测试夹具」。

## 工具名称必须携带路由所需的事实

有用的名称应在代理阅读长描述之前，就提供选择所需的事实。对于会触及外部系统的操作，我使用这样的顺序：目标系统、对象、动词；如果范围会改变权限或后果，再加上范围。

`crm_contact_update` 优于 `update_contact`，因为它说明了系统。如果同一目录还包含 sandbox，`crm_production_contact_update` 可能更好。`github_org_member_remove` 比 `manage_member` 清楚，因为它说明了哪个资源会改变，以及结果是移除。

不要把所有实现细节都塞进名称。代理不需要 `crm_v3_contacts_patch_with_bearer_auth`。它需要的是会改变选择结果的区别。版本、传输方式和身份验证通常属于服务器实现或描述。账户、环境、副作用和权限边界通常应该出现在名称中。

一种实用模式如下：

```text
<system>_<object>_<verb>[_<scope>]
```

示例：

```text
billing_invoice_get
billing_invoice_send_customer
billing_production_refund_create
source_control_repo_issue_list
source_control_org_member_remove
warehouse_inventory_adjust
warehouse_inventory_adjust_dry_run
```

这个模式并非不可改变。关键是，相邻名称应该在效果不同的地方体现差异。如果 `billing_invoice_send_customer` 和 `billing_invoice_preview_email` 并列出现，动词和对象会告诉模型哪个操作真的会联系客户。如果唯一的区别藏在架构中的布尔参数里，目录就对路由提出了过高要求。

除非对象本身能让效果变得明确，否则应避免使用 `process`、`manage`、`handle`、`run`、`execute`、`sync` 和 `apply` 这类模糊动词。它们很受欢迎，是因为产品团队常用它们作为多个操作的总称。这也正是它们不适合作为工具名称的原因。模型会把总称理解为可以选择完成请求所需的最宽泛解释。

## 描述应该定义边界，而不是写营销文案

Model Context Protocol 工具规范把工具定义为名称、描述和输入架构。这是一份接口契约，不是产品文案的位置。描述应回答四个操作层面的问题：会发生什么操作、哪个外部目标会接收它、适用什么范围，以及工具拒绝做什么。

比较下面两种描述：

```json
{
  "name": "crm_contact_update",
  "description": "Updates customer contact information in the CRM.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "contact_id": {"type": "string"},
      "address": {"type": "string"}
    },
    "required": ["contact_id"]
  }
}
```

```json
{
  "name": "crm_production_contact_update",
  "description": "Changes address, phone, or email fields for one existing contact in the production CRM. This writes immediately. Use crm_contact_search first when the caller supplies a name rather than a contact ID. It cannot create contacts, merge records, or update more than one contact per call.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "contact_id": {
        "type": "string",
        "description": "Stable production CRM contact ID, not an email address or display name."
      },
      "changes": {
        "type": "object",
        "properties": {
          "address": {"type": "string"},
          "phone": {"type": "string"},
          "email": {"type": "string"}
        },
        "minProperties": 1,
        "additionalProperties": false
      }
    },
    "required": ["contact_id", "changes"],
    "additionalProperties": false
  }
}
```

第二段描述给出了操作顺序，说明了后果，也排除了容易产生的替代方案。它还把消除歧义的细节放在操作附近，而不是藏进代理可能根本看不到的独立运维手册里。

要直接说明副作用。写「立即发送电子邮件」「创建扣款」「删除远程分支」或「写入生产环境」。不要写「持久化更改」或「执行请求的操作」。这些说法可以让审查者听起来很精确，却掩盖了代理和人真正需要注意的事情。

输入字段描述同样重要。如果字段接受资源 ID，就说明显示名称无效。如果日期默认为 UTC，也要写出来。带有可选字符串的宽松架构，会把含义推给散文描述，也会让代理自行编造碰巧能解析的参数。

## 广泛权限绝不能看起来像方便的备用方案

最危险的目录，同时包含一个范围窄的工具和一个范围更广、但看起来能解决同一请求的工具。广泛工具通常有合理存在的原因：管理员需要紧急访问，迁移需要跨账户搜索，或者支持流程需要覆盖权限。错误在于把它作为名称友好的同级工具暴露出来。

想象这些条目：

```text
support_ticket_get
support_ticket_update
support_admin_query
```

代理想了解工单上下文。`support_admin_query` 可能搜索工单、用户、账单历史、内部备注和已删除记录。如果它的描述以「查询支持平台」开头，代理可能因为它覆盖范围广、看起来有用而选中它。工具确实做了你命名它要做的事。设计在调用发生之前就已经失败了。

重新命名并加以限制：

```text
support_internal_cross_account_search
```

它的描述应说明，该工具会跨账户搜索内部支持数据，返回工单记录之外的内容，并且要求明确指出账户边界。如果流程允许，应在架构中要求账户 ID，而不是只接受一个自由文本查询。

我不赞成为了灵活性而暴露一个「万能工具」这一常见建议。它很受欢迎，是因为能减少服务器代码，让有经验的操作人员用更少调用完成更多事情。但对于自主代理来说，它抹去了普通工作和特殊权限之间的区别。权限存在实质差异时，就创建不同的工具。增加目录条目的成本，低于解释一次广泛搜索为何泄露了错误客户历史的成本。

环境也适用同样的原则。不要提供一个带有 `environment` 参数、默认指向 production 的 `deploy`：

```text
release_staging_deploy
release_production_deploy
```

架构枚举仍然有帮助，但不同名称能让生产环境在选择阶段、审批阶段以及后续审计记录中都清晰可见。

## 参数无法承载全部安全含义

参数是在代理选定工具之后才改变操作。名称和描述则会影响工具本身的选择。团队创建一个带有大型参数对象的通用工具时，常常混淆了这两项职责。

这个设计看起来很紧凑：

```json
{
  "name": "repository_action",
  "description": "Performs repository operations.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "operation": {"enum": ["read_file", "create_branch", "delete_branch", "open_pull_request"]},
      "repository": {"type": "string"},
      "branch": {"type": "string"}
    },
    "required": ["operation", "repository"]
  }
}
```

但它把读操作、写操作和破坏性操作放在同一个路由标签后面。代理一旦选择 `repository_action`，就已经越过了真正重要的边界。审查者看到的是一个含义模糊的总括操作审批，还必须在时间压力下检查参数。

应按操作类别拆分：

```text
repository_file_read
repository_branch_create
repository_branch_delete
repository_pull_request_create
```

参数可以保留操作内部会变化的事实，例如仓库 ID、分支名称、文件路径、提交消息或分页游标。不要让参数决定这次调用是读取、写入、发送、扣款、删除，还是访问生产环境。

范围也遵循同一原则。带有 `scope: all_accounts` 的 `report_export`，会把看似无害的导出变成跨账户数据提取。如果范围改变了受影响的人或可以离开的数据，就为该范围建立独立工具，或者要求更强的授权路径。代理不应在填完 JSON 字段之后才发现权限不同。

## 工具选择需要歧义测试套件

不能只检查一次目录就宣布它易于理解。应使用用户真正会提出的请求来测试，尤其是那些迫使代理推断范围的不完整请求。

为每台服务器建立一组小型选择测试。你可以使用支持的代理客户端手动运行，也可以把目录和提示词输入受控的评估工具。记录所选工具、拟定参数，以及人是否会接受这次调用。不要只根据任务最终是否成功来评分。一个广泛工具即使返回了正确答案，在存在更窄工具时仍然是错误选择。

可以使用这样的提示：

1. 「找到订单 1842 的发票。」预期应选择只读的账单查询，而不是通用总账搜索。
2. 「更新 Priya 的电话号码。」如果缺少稳定的联系人 ID，代理应询问是哪一位 Priya，而不是搜索并修改一个看起来匹配的记录。
3. 「部署修复。」如果目录中同时有 staging 和 production 操作，代理应询问环境。
4. 「把 Alex 从仓库中移除。」代理应区分仓库成员资格和组织成员资格。
5. 「发送发票。」代理应选择发送操作，而不是预览生成器或通用发票更新调用。

加入与错误工具描述相似的对抗性措辞。如果提示说「找出我们拥有的关于这个客户的全部信息」时，`internal_cross_account_search` 获胜，那么你的描述也许技术上诚实，却仍然过于诱人。正确行为可能是选择范围受限的搜索，或要求用户指定账户。

重命名工具时保留测试记录。它能暴露架构验证器无法发现的回归问题。目录可以保持有效，但一次无害的重命名就可能把 `billing_invoice_get` 变成 `get_invoice`，让它与采购、物流和法律系统竞争。

## 审批界面应使用直白的语言重复操作

人工审批是最后一道检查，不是让工具标签继续含糊的许可。如果审批界面只展示 `POST /v1/contacts/123` 这样的底层请求，审批人就必须从端点和负载中重新推断意图。让人发现代理选中的是生产 CRM 而不是 sandbox，并不适合放在这个环节。

让相同的业务含义贯穿每一层。工具名称写作 `crm_production_contact_update`。描述说明它会立即写入一条现有的生产记录。审批提示应说明代理希望修改某个生产联系人中的指定字段，能确定时指出目标账户，并展示拟写入的值。审计事件应保留工具身份，以及实际执行的通道和目标。

不要让审批措辞比操作更令人安心。「允许 CRM 更新」掩盖了更改电话号码和替换账户恢复邮箱之间的差别。展示有意义的参数，并移除秘密值。如果参数包含敏感客户数据，应在遵守数据处理规则的前提下展示足够的结构供人审查。

Sallyport 的按会话授权可以确认某个代理进程在运行期间有权执行操作，而按调用密钥则可以要求对每次使用敏感凭据进行单独审批。这种分工只有在操作标签能让审批人立即、准确地理解代理请求的内容时，才能发挥最佳效果。

## 凭据和工具身份解决的是不同问题

让代理无法接触凭据，可以避免一种常见故障：代理从未获得秘密，因此无法把 API 密钥复制到日志、源文件、工单或聊天回复中。但这并不会让每个请求都安全。代理仍然可以要求网关使用合法凭据执行错误的工具。

设计时应分别回答这些问题：

- 代理能否获得或暴露凭据？
- 代理能否请求超出用户预期范围的操作？
- 人能否看到哪个进程发起了操作？
- 运行结束后，调查人员能否确认发生了什么？

工具目录解决第二个问题。会话身份和审批解决第三个问题。防篡改记录解决第四个问题。每一层都有自己的职责，任何一层都不能替代另一层。

Sallyport 将 HTTP 和 SSH 凭据保存在加密保险库中，并在不把秘密交给代理的情况下执行操作。这能减少凭据暴露，但代理仍然需要一个名称清晰的目录，避免仅仅因为某个更广泛的操作听起来相近，就去请求它。

当团队说「代理看不到令牌，所以工具是安全的」时，这个区别很重要。令牌可能受到保护，但操作仍可能权限过大。只读报表凭据和生产退款凭据不应仅仅因为都与模型隔离，就放在几乎相同的条目后面。

## 命名空间有助于操作人员，但不能替模糊操作开脱

许多客户端会使用服务器派生的前缀来展示工具，例如 `crm.search_contacts` 或 `billing.search_contacts`。如果客户端支持命名空间，就使用它。它能为代理和操作人员提供额外的路由线索，也能减少字面上的重复名称。

但不要依赖它作为唯一线索。客户端可能缩短标签、压平服务器目录，或者展示对审批人来说没有意义的服务器名称。如果一台服务器访问测试数据库，另一台访问真实客户数据，那么名为 `search_contacts` 的工具仍然很模糊。

更好的组合是：

```text
crm_production_contact_search
marketing_audience_contact_search
```

即使客户端添加或移除前缀，这些名称仍然容易理解。当代理随着时间连接更多服务器时，它们也能让混合目录更安全。

使用服务器边界来组织相关权限，不要用它隐藏权限。名为 `operations` 的服务器可能同时提供账单退款、生产部署、客户导出和人员变更。对负责它的团队来说，这很方便，却会产生一个拥挤的目录、互不相关的动词以及很大的凭据暴露面。当不同领域拥有不同负责人、凭据、审批要求或审查路径时，应拆分服务器。

## 目录审查可以在部署前发现问题

应把工具目录与自然语言任务列表放在一起审查，而不是孤立查看。一个名称对作者来说很明显，但当来自六台服务器的三十个工具同时出现在代理会话中时，它可能依赖已经消失的上下文。

发布新操作前，进行这项简短审查：

1. 只看名称。人能否判断外部系统、对象、副作用和特殊范围？
2. 把它放在所有相似工具旁边。是否有一个名称用更温和的动词描述了更广泛的访问权限？
3. 暂时忽略描述。架构是否把读与写、sandbox 与 production，或单条记录与跨账户的选择隐藏在参数中？
4. 提出一个含糊的用户请求。代理是否应该提出澄清问题？你是否让提问比猜测更安全？
5. 检查审批和审计标签。它们是否保留了与工具名称相同的区别？

正确答案往往是拒绝一个方便的总括操作请求。这种拒绝只会在实现阶段让某个人感到一次不便。模糊工具则会让之后调查意外调用的人感到困扰，而那时上下文已经消失。

让名称保持具体，用直白的语言写清边界，并让广泛权限难以被意外选中。代理不需要更多看似合理的选择，而需要更少把一种权限误认为另一种权限的机会。
