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

代理不会像谨慎的工程师阅读 SDK 那样阅读 MCP 工具目录。它会把压缩后的指令路由到一个可能的操作。如果你提供 get_user、get_users、user_lookup 和 admin_get_user,却想靠一段注意事项来区分它们,那么你实际上是在围绕权限设计一场猜谜游戏。
MCP 工具名称冲突不只是重复标识符导致客户端拒绝目录。更糟糕的是语义冲突:两个可调用操作听起来可以互换,但其中一个可能访问范围更广的系统、携带权限更强的凭据,或会改变状态。我见过团队在代理选错操作后把问题归咎于提示词。多数时候,这是他们自己发布的接口出了问题。
解决办法不是建立庞大的分类体系,也不是成立一个仪式感十足的命名委员会。给每个操作一个能说明它做什么、作用于哪里以及权限边界延伸到哪里的名称。然后用描述明确补上名称无法承载的边界。在歧义变成审批卡片、意外 API 调用或混乱的事故复盘之前,就通过测试把它暴露出来。
冲突首先是语义上的,其次才是语法上的
语法冲突发生在两个 MCP 服务器都发布了一个名为 search 的工具时。根据客户端的不同,它可能覆盖其中一个条目、强制使用命名空间,或生成令人困惑的目录。你应该修复它,因为不同客户端的行为可能不同。
即使每个标识符在技术上都唯一,语义冲突仍然存在。看看这些工具:
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。它需要的是会改变选择结果的区别。版本、传输方式和身份验证通常属于服务器实现或描述。账户、环境、副作用和权限边界通常应该出现在名称中。
一种实用模式如下:
<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_customer 和 billing_invoice_preview_email 并列出现,动词和对象会告诉模型哪个操作真的会联系客户。如果唯一的区别藏在架构中的布尔参数里,目录就对路由提出了过高要求。
除非对象本身能让效果变得明确,否则应避免使用 process、manage、handle、run、execute、sync 和 apply 这类模糊动词。它们很受欢迎,是因为产品团队常用它们作为多个操作的总称。这也正是它们不适合作为工具名称的原因。模型会把总称理解为可以选择完成请求所需的最宽泛解释。
描述应该定义边界,而不是写营销文案
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_accounts 的 report_export,会把看似无害的导出变成跨账户数据提取。如果范围改变了受影响的人或可以离开的数据,就为该范围建立独立工具,或者要求更强的授权路径。代理不应在填完 JSON 字段之后才发现权限不同。
工具选择需要歧义测试套件
不能只检查一次目录就宣布它易于理解。应使用用户真正会提出的请求来测试,尤其是那些迫使代理推断范围的不完整请求。
为每台服务器建立一组小型选择测试。你可以使用支持的代理客户端手动运行,也可以把目录和提示词输入受控的评估工具。记录所选工具、拟定参数,以及人是否会接受这次调用。不要只根据任务最终是否成功来评分。一个广泛工具即使返回了正确答案,在存在更窄工具时仍然是错误选择。
可以使用这样的提示:
- 「找到订单 1842 的发票。」预期应选择只读的账单查询,而不是通用总账搜索。
- 「更新 Priya 的电话号码。」如果缺少稳定的联系人 ID,代理应询问是哪一位 Priya,而不是搜索并修改一个看起来匹配的记录。
- 「部署修复。」如果目录中同时有 staging 和 production 操作,代理应询问环境。
- 「把 Alex 从仓库中移除。」代理应区分仓库成员资格和组织成员资格。
- 「发送发票。」代理应选择发送操作,而不是预览生成器或通用发票更新调用。
加入与错误工具描述相似的对抗性措辞。如果提示说「找出我们拥有的关于这个客户的全部信息」时,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 的工具仍然很模糊。
更好的组合是:
crm_production_contact_search
marketing_audience_contact_search
即使客户端添加或移除前缀,这些名称仍然容易理解。当代理随着时间连接更多服务器时,它们也能让混合目录更安全。
使用服务器边界来组织相关权限,不要用它隐藏权限。名为 operations 的服务器可能同时提供账单退款、生产部署、客户导出和人员变更。对负责它的团队来说,这很方便,却会产生一个拥挤的目录、互不相关的动词以及很大的凭据暴露面。当不同领域拥有不同负责人、凭据、审批要求或审查路径时,应拆分服务器。
目录审查可以在部署前发现问题
应把工具目录与自然语言任务列表放在一起审查,而不是孤立查看。一个名称对作者来说很明显,但当来自六台服务器的三十个工具同时出现在代理会话中时,它可能依赖已经消失的上下文。
发布新操作前,进行这项简短审查:
- 只看名称。人能否判断外部系统、对象、副作用和特殊范围?
- 把它放在所有相似工具旁边。是否有一个名称用更温和的动词描述了更广泛的访问权限?
- 暂时忽略描述。架构是否把读与写、sandbox 与 production,或单条记录与跨账户的选择隐藏在参数中?
- 提出一个含糊的用户请求。代理是否应该提出澄清问题?你是否让提问比猜测更安全?
- 检查审批和审计标签。它们是否保留了与工具名称相同的区别?
正确答案往往是拒绝一个方便的总括操作请求。这种拒绝只会在实现阶段让某个人感到一次不便。模糊工具则会让之后调查意外调用的人感到困扰,而那时上下文已经消失。
让名称保持具体,用直白的语言写清边界,并让广泛权限难以被意外选中。代理不需要更多看似合理的选择,而需要更少把一种权限误认为另一种权限的机会。
常见问题
MCP 工具名称在所有服务器之间必须全局唯一吗?
不一定。协议允许服务器发布工具,但不会让所有已连接服务器中的名称自动保持全局唯一。客户端会把可用工具集合提供给模型,模型必须根据名称、描述、架构以及客户端保留的其他上下文来区分相似条目。
使用 get_user 这样的通用 MCP 工具名称安全吗?
只有在工具数量少且范围严格限制为只读操作时,get_user 这类名称才勉强合适。一旦另一个工具可以搜索外部身份、修改记录或调用管理 API,这个通用名称就无法为安全选择提供足够信息。应在名称中说明对象、系统、操作和访问边界。
出现冲突时,应该重命名工具还是改进描述?
通常应先重命名工具,而不是继续写更长的描述。模型往往把工具名称作为最初的路由信号,尤其是在用户只用几个词描述操作时。准确的描述仍然重要,因为它能说明工具会做什么以及不会做什么。
服务器前缀能解决 MCP 工具名称冲突吗?
不能。服务器前缀对操作人员有帮助,但如果客户端移除、缩短前缀,或同时展示大量相似前缀的工具,它对代理可能就失去意义。应把关键区别放进操作名称和描述中,再把服务器身份作为辅助上下文。
只读 MCP 操作和可写 MCP 操作应该拆成不同工具吗?
当只读和可写操作在权限、副作用或目标范围上存在差异时,应将它们拆成不同工具。一个带有模式参数的工具,会迫使代理自行判断 dry_run、apply 或 admin 等值所代表的安全含义。不同名称能让模型在填写参数前就看见这项区别。
应该如何向代理暴露 staging 和 production 工具?
如果它们指向同一个目标但权限不同,不要把 staging 和 production 作为两个普通选项暴露出来。为低权限路径使用独立的名称和描述,并为更高权限的路径设置明确的授权边界。相似标签会诱使模型把更高权限的凭据当成方便的替代方案。
如何测试代理是否会选择正确的 MCP 工具?
建立一组固定的、包含歧义自然语言请求的测试语料,并记录所选工具、参数和结果。加入名称相似、范围缺失,以及广泛工具也能完成任务的请求。把错误选择视为接口缺陷,而不只是模型错误。
什么样的 MCP 工具名称很危险?
危险工具的名称应说明副作用、目标系统和权限范围。github_org_remove_member 在描述解释不可逆后果之前,就已经比 manage_member 传达了更多信息。不要用 sync 或 update 这类看似无害的动词掩盖广泛权限。
MCP 工具名称应该与审批和审计标签一致吗?
工具名称、描述、审批提示和审计记录应使用同一套操作词汇。如果代理调用 crm_contacts_search,但审批界面只显示 POST /query,人就无法可靠地发现路由错误。接口应在执行和审查的各个环节保留业务含义。
仅靠凭据隔离就能避免代理执行错误操作吗?
不能。网关可以让凭据远离代理,但仍然可能收到一个选择错误的操作请求。凭据隔离限制了代理能够外泄的内容,而清晰的工具边界和人工授权限制了它能够要求网关执行的操作。