阅读需 8 分钟

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

MCP 工具名称冲突会让相似操作掩盖不同权限,导致代理行为混乱。了解工具命名、描述、架构和测试规则,让代理更安全地选择操作。

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

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

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

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

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

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

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

search_customer
search_customer_records
lookup_customer
customer_admin_search

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

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

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

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

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

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

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

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

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

一种实用模式如下:

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

示例:

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_customerbilling_invoice_preview_email 并列出现,动词和对象会告诉模型哪个操作真的会联系客户。如果唯一的区别藏在架构中的布尔参数里,目录就对路由提出了过高要求。

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

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

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

比较下面两种描述:

{
  "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"]
  }
}
{
  "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,也要写出来。带有可选字符串的宽松架构,会把含义推给散文描述,也会让代理自行编造碰巧能解析的参数。

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

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

想象这些条目:

support_ticket_get
support_ticket_update
support_admin_query

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

重新命名并加以限制:

support_internal_cross_account_search

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

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

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

release_staging_deploy
release_production_deploy

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

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

避免再增加一个规则引擎
使用一套固定的决策流程,不要试图用规则引擎编码每个模糊的工具边界。

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

这个设计看起来很紧凑:

{
  "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,就已经越过了真正重要的边界。审查者看到的是一个含义模糊的总括操作审批,还必须在时间压力下检查参数。

应按操作类别拆分:

repository_file_read
repository_branch_create
repository_branch_delete
repository_pull_request_create

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

范围也遵循同一原则。带有 scope: all_accountsreport_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_contactsbilling.search_contacts。如果客户端支持命名空间,就使用它。它能为代理和操作人员提供额外的路由线索,也能减少字面上的重复名称。

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

更好的组合是:

crm_production_contact_search
marketing_audience_contact_search

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

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

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

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

发布新操作前,进行这项简短审查:

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

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

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

常见问题

MCP 工具名称在所有服务器之间必须全局唯一吗?

不一定。协议允许服务器发布工具,但不会让所有已连接服务器中的名称自动保持全局唯一。客户端会把可用工具集合提供给模型,模型必须根据名称、描述、架构以及客户端保留的其他上下文来区分相似条目。

使用 get_user 这样的通用 MCP 工具名称安全吗?

只有在工具数量少且范围严格限制为只读操作时,get_user 这类名称才勉强合适。一旦另一个工具可以搜索外部身份、修改记录或调用管理 API,这个通用名称就无法为安全选择提供足够信息。应在名称中说明对象、系统、操作和访问边界。

出现冲突时,应该重命名工具还是改进描述?

通常应先重命名工具,而不是继续写更长的描述。模型往往把工具名称作为最初的路由信号,尤其是在用户只用几个词描述操作时。准确的描述仍然重要,因为它能说明工具会做什么以及不会做什么。

服务器前缀能解决 MCP 工具名称冲突吗?

不能。服务器前缀对操作人员有帮助,但如果客户端移除、缩短前缀,或同时展示大量相似前缀的工具,它对代理可能就失去意义。应把关键区别放进操作名称和描述中,再把服务器身份作为辅助上下文。

只读 MCP 操作和可写 MCP 操作应该拆成不同工具吗?

当只读和可写操作在权限、副作用或目标范围上存在差异时,应将它们拆成不同工具。一个带有模式参数的工具,会迫使代理自行判断 dry_runapplyadmin 等值所代表的安全含义。不同名称能让模型在填写参数前就看见这项区别。

应该如何向代理暴露 staging 和 production 工具?

如果它们指向同一个目标但权限不同,不要把 staging 和 production 作为两个普通选项暴露出来。为低权限路径使用独立的名称和描述,并为更高权限的路径设置明确的授权边界。相似标签会诱使模型把更高权限的凭据当成方便的替代方案。

如何测试代理是否会选择正确的 MCP 工具?

建立一组固定的、包含歧义自然语言请求的测试语料,并记录所选工具、参数和结果。加入名称相似、范围缺失,以及广泛工具也能完成任务的请求。把错误选择视为接口缺陷,而不只是模型错误。

什么样的 MCP 工具名称很危险?

危险工具的名称应说明副作用、目标系统和权限范围。github_org_remove_member 在描述解释不可逆后果之前,就已经比 manage_member 传达了更多信息。不要用 syncupdate 这类看似无害的动词掩盖广泛权限。

MCP 工具名称应该与审批和审计标签一致吗?

工具名称、描述、审批提示和审计记录应使用同一套操作词汇。如果代理调用 crm_contacts_search,但审批界面只显示 POST /query,人就无法可靠地发现路由错误。接口应在执行和审查的各个环节保留业务含义。

仅靠凭据隔离就能避免代理执行错误操作吗?

不能。网关可以让凭据远离代理,但仍然可能收到一个选择错误的操作请求。凭据隔离限制了代理能够外泄的内容,而清晰的工具边界和人工授权限制了它能够要求网关执行的操作。

Sallyport

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

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