关于 mcp-tool-discovery
面向使用 ServiceNow MCP 服务的 Agent,讲解如何从搜索工具到成功调用。重点说明按会话启用延迟工具、搜索匹配规则、调用方式及常见失败原因。
主要功能
- 01搜索并启用延迟工具
- 02解释工具可用状态
- 03说明会话 ID 与持久化
- 04解析搜索排序与查询方法
- 05排查调用失败及安全标志
适用场景
- 01首次发现并调用 ServiceNow 工具
- 02诊断工具仍处于延迟状态
- 03按领域或表名定位工具
- 04检查执行结果与调用配置
MCP 相关
内容针对 ServiceNow MCP 服务,说明其工具搜索与执行元工具,以及 stdio 和 HTTP 传输下工具发现、启用和调用的差异。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ serac-labs/ serac --skill mcp-tool-discovery安装后在 Claude Code 中输入/mcp-tool-discovery确认已加载。
详细介绍
MCP 工具发现
此服务器在 80 个领域中注册了 440 多个 snow_* 工具,却不列出任何一个。全新的工具列表中
只有两个元工具;其他所有工具都需要通过 tool_search 查找。本指南将介绍如何从“这里
大概有个工具可以做这件事”找到真正能运行的调用。
这里不引用确切总数,因为有两个总数,而且彼此不一致:
packages/servicenow-mcp/tools.json 是文档站点发布的内容,注册表是运行中的服务器实际可以分发的工具,而已发布的工具中有 9 个不在注册表中(参见“你可以在文档中找到、但服务器上没有的九个工具”)。
冷启动:两个工具
通过 stdio 传输方式新建一次 tools/list,无论存在多少工具,返回的内容都只有下面这些:
tool_search
tool_execute
这是有意为之。服务器自己的说明指出,完整目录的 schema 约占 71k 个 token,而两个元工具约占 2k 个 token。索引会将所有工具标记为延迟加载,而
handlers/list-tools.ts 只返回元工具,以及当前会话已启用的工具。
HTTP 传输(门户)则恰恰相反:它会将目录注册为非延迟加载,因此其
tools/list 会返回所有工具,无需启用任何工具。你可以从 tool_search 结果的状态判断自己使用的是哪种传输:[AVAILABLE] 表示服务器完全不限制已启用工具集。
循环
// 1. Search. Found tools are enabled for this session by default.
tool_search({ query: "update-sets" })
// 2. Read the status on each hit:
// [AVAILABLE] — callable now, nothing had to be enabled
// [ENABLED] — deferred, and enabled for this session. Callable now.
// [DEFERRED] — NOT callable. Nothing was enabled; see the next section.
// 3. Execute.
tool_execute({ tool: "snow_ensure_active_update_set", args: { name: "Feature: SLA fix" } })
如果只想查看而不更改会话状态,请传入 enable: false;这样会执行搜索,但不会更改会话状态。
没有会话 ID,就没有工具
这是会让你白忙一下午的问题。工具启用状态按 (tenant, session) 保存。没有会话 ID,就没有可以关联启用状态的对象,因此 tool_search 不会启用任何工具,所有命中结果都会保持为 [DEFERRED],所有调用都会被拒绝:
⚠ 5 deferred tool(s) were NOT enabled: this request carries no session id, so there is nothing to
attach the enablement to. They stay [DEFERRED] and tool_execute will refuse them.
{
"success": false,
"error": "Tool \"snow_query_table\" is [DEFERRED] and must be enabled first",
"hint": "Use tool_search({query: \"query table\"}) to enable this tool",
"status": "[DEFERRED]"
}
再次搜索也无济于事。会话 ID 按以下顺序解析:JWT 负载中的 sessionId、x-session-id 标头、SNOW_SESSION_ID 环境变量,最后是 Serac CLI 写入的 current-session.json 文件。使用纯 stdio 时,前两种方式都不存在,而该文件也只有在这台机器上运行过 Serac TUI 时才会存在,因此自行启动服务器的客户端(包括 Claude Code 插件的 npx … servicenow-mcp-stdio)都没有会话 ID,除非你为其指定一个:
{
"mcpServers": {
"servicenow": {
"type": "stdio",
"command": "npx",
"args": ["-y", "--package=@serac-labs/servicenow-mcp", "servicenow-mcp-stdio"],
"env": { "SNOW_SESSION_ID": "my-session" }
}
}
}
插件在 packages/skills/.mcp.json 中提供的就是这个配置项,另加了一个 env。编辑时请保留 command 和 args;仅包含 env 的配置项无法启动任何程序。任何稳定字符串都可以用作 ID。请注意以下两点:
- 已启用工具集会持久化到磁盘,按会话分别保存,并在重启时恢复:macOS 上的
~/Library/Application Support/snow-code/enabled-tools/stdio/<session>.json、 Linux 上的~/.local/share/snow-code/enabled-tools/、Windows 上的%APPDATA%\snow-code\。该目录仍名为snow-code,是因为它沿用了重命名前的名称。复用相同的SNOW_SESSION_ID,昨天启用的工具就会继续保持启用;删除该文件即可重新开始。 - HTTP 传输中的存储位于内存中,并按租户分别保存;无法归入任何租户的请求会被拒绝,而不会被放入共享池中。此时提示中会显示
no tenant scope。
按名称调用,以及为什么 tool_execute 更安全
启用工具后,服务端会按名称分派工具——handlers/call-tool.ts 会直接接受
snow_query_table,而 Agent 指令也会告诉你这样做。
但服务端不会通知客户端工具列表有所增加。它声明 capabilities: { tools: {} },
没有 listChanged,也从不发送 notifications/tools/list_changed
(https://modelcontextprotocol.io/specification/2025-06-18/server/tools)。根据规范,该通知会促使客户端重新请求
tools/list。因此,新启用的工具是否会作为可调用工具出现在你的上下文中,完全取决于客户端是否会自行重新列出工具。
tool_execute 在冷启动列表中,因此始终可用。搜索后的首次调用请使用它;
按名称调用只是为了方便,并非保证可用。
查看完整封装,而不只是外层标志
tool_execute 报告的是是否已分派,而非操作是否成功。工具自身的判定嵌套在里面:
{
"success": true,
"tool": "snow_query_table",
"result": { "success": false, "error": "undefined is not an object (evaluating 'context.instanceUrl.replace')" }
}
外层 success: true、内层 success: false 表示操作失败。检查 result.success。
这个特定的内部错误并非工具的缺陷——未配置任何实例时,每个工具都会返回此错误。SNOW_INSTANCE 尚未设置;请设置它,而不是查看执行器。
将防护标志放在 args 中
针对生产级实例的写入,以及没有活动更新集时的配置写入,都会在工具层被拦截;无论通过 tool_execute 还是按名称调用,都是如此。重试标志
__confirmProd 和 __skipUpdateSet 必须放在 args 对象内部,而不是与其并列——它们会从内部参数中读取,并在执行器收到参数前移除:
tool_execute({
tool: "snow_create_business_rule",
args: { table: "incident", name: "Auto-assign", __confirmProd: true },
})
查询:哪些内容会匹配,哪些会悄无声息地落空
索引中,每个工具都包含去掉 snow_ 并按 _ 拆分后的名称(保留长度超过两个字符的部分),以及描述中最前面的十个重要词。如果某个用于区分的名词出现在长描述的后面,就无法通过关键词找到它。
评分按完整单词计算,而不是按子串计算。你的查询会按与索引相同的方式拆分,去掉英文停用词,将常规复数还原为单数,并根据每个剩余单词在目录中的稀有程度加权:一半工具都包含的词几乎不会影响评分,而只有三个工具包含的词则能起决定作用。随后,评分会乘以匹配到任何内容的查询词所占比例,因此,能满足请求三分之二的工具会明显胜过只能满足三分之一的工具。即使某个词只是索引词元的前缀,也会以较低权重计入匹配——这就是 "syntax" 能找到描述中写着 "SyntaxErrors" 的工具的原因。
**领域名称是最好的查询词。**它们能找到一整组相关工具,且不会匹配其他内容:
tool_search({ query: "update-sets" }) // all three: snow_update_set_query, snow_ensure_active_update_set, snow_update_set_manage
tool_search({ query: "blast-radius" }) // the whole impact-analysis family
tool_search({ query: "local-sync" }) // the pull/push/status/cleanup family
tool_search({ query: "atf" }) // test, test step, test suite, discover, execute, results
领域名称就是 packages/servicenow-mcp/src/servicenow-mcp-unified/tools/ 下的目录名称,但有几个不是领域:__tests__ 是测试,meta 存放 tool_search 和 tool_execute 本身,并且刻意排除在注册表之外;adapters 是空占位目录。其他目录都可以搜索。
limit 默认为 10,有七个领域的工具数量超过这个上限:security、integration、automation、operations、agile、cmdb、ui-builder。无需记住各领域的确切数量——每次有新工具加入都会变化——因此,如果一次查询恰好返回 10 个结果,就应将其视为结果已截断并调高 limit。
表名也有效,因为表名由单词组成。sys_script_include 会拆分为 sys、script、include,并根据其中罕见的词进行评分:
| 查询 | 首个结果 |
|---|---|
"sys_script_include" | snow_create_script_include |
"sysevent_email_action" | snow_email_notification_manage |
"sp_widget" | snow_create_sp_widget |
"sys_user" | snow_impersonate_user — 四者中最弱,因为 user 无处不在 |
最后一行展示了剩余问题的典型情况:如果表名中用于区分的部分是目录里频繁出现的词,结果就会充斥着看似合理的噪声。改为描述操作——"deactivate a user" 能找到 snow_user_manage。
**普通英语也有效,通常效果更好。**排名器使用 101 条符合实际表达习惯的请求进行评估(tool-search-eval.test.ts);其中三分之二能在前五个结果中找到正确的工具。它还了解少量目录未收录、但使用者会用到的词汇——搜索“ticket”也会查找 incidents,搜索“column”也会查找 fields,询问谁在“shift”也会找到 on-call 工具。
如果没有任何匹配结果,响应中会附带一个 suggestion,列出“可用领域”。该列表是按字母顺序排列的前十五个领域名称,范围从 access-control 到 calculators。它既不是完整领域列表,也不是按相关性排序的建议——不要据此认为 operations 或 update-sets 不存在。
索引实际返回的内容
随附索引中的热门结果,已针对当前检出版本重新运行。评分采用稳定排序,因此分数相同的工具会按注册顺序返回;每当有工具加入,确切排名都会变化。关注结果中有哪些工具,而不是它们的位置:
| 查询 | 热门结果 |
|---|---|
"query table" | snow_query_table、snow_validate_query、snow_graphql_query、snow_record_manage |
"incident" | snow_analyze_incident、snow_auto_resolve_incident、snow_create_security_incident |
"cmdb" | snow_cmdb_identify_reconcile、snow_search_cmdb、snow_cmdb_search |
"business rule" | snow_create_business_rule、snow_disable_business_rule |
"attachment" | snow_get_attachments、snow_delete_attachment、snow_upload_attachment |
"acl" | snow_acl_explain、snow_create_acl_role、snow_create_acl — 以及会捏造答案的 snow_test_acl,详见下文 |
"flow" | snow_start_workflow、snow_workflow_manage、snow_manage_flow |
"jira" | snow_create_oauth_profile、snow_install_spoke — 详见最后一节 |
注意,"incident" 不会返回你想要的事故 CRUD 工具。该工具是 snow_record_manage
({ action, table: "incident", … }),可通过查询 "record" 或 "query table" 找到。还要注意
snow_search_cmdb 和 snow_cmdb_search——这是两个属于不同领域、功能几乎相同的不同工具。重复的名称在这里很常见;选择前请阅读两者的描述。
排名代表相关性,而非质量
tool_search 按字符串匹配进行排名。它不知道工具是否能正常工作,而且此目录有两种不同的误导方式。两种情况都需要防范,因为针对一种情况的防范措施无法识别另一种。
第一种:它根本不会联系实例。 执行器根据你传入的参数组装一个成功对象并返回。对于明显的查询,排名靠前的结果中有两个属于这种情况:
tool_search({ query: "artifact" })会将snow_clone_instance_artifact排在第一位。它根本不会建立连接。它会返回success,其中"message": "Cross-instance cloning requires additional authentication setup",并附带一系列步骤,提到snow_export_artifact和snow_import_artifact——这两个工具在此服务上都不存在。实例间提升请使用更新集;本地往返则使用snow_pull_artifact/snow_push_artifact。tool_search({ query: "incident" })会将snow_create_security_incident排在第三位。这是一个尚未实现的存根:它会验证并回显你的参数,并返回"summary": "Security incident \"…\" prepared with priority: …"。没有任何内容会发送到实例——请注意其中的单词"prepared"。security域中的全部六个snow_create_*工具都是同类存根。能正常工作的 SIR 工具属于snow_sir_*系列(snow_sir_incident_manage等工具会读取和写入sn_si_incident),而直接针对该表调用snow_record_manage始终有效。
最能说明为什么不能把描述当作证据的例子是:security 中的 snow_create_access_control 声称“会写入 sys_security_acl”,实际上却不会建立连接。真正会向 /api/now/table/sys_security_acl 发送 POST 请求的是 access-control 中的 snow_create_acl。
第二种:它联系了实例,却提出了错误的问题。 一批规模更大的工具会向 /api/now/table/sys_script_execution 发送 POST 请求,并把 Table API 对刚插入行的回显当成脚本的输出。Table API 会插入记录,不会执行脚本。因此什么都不会执行,而且你在第一种情况中学到的所有检查都会通过:确实发出了 HTTP 调用,确实返回了 201,确实有 sys_id,而且 result.success 为 true。
snow_test_acl 是上面 "acl" 搜索结果中的热门项之一,也是最值得了解的工具。它返回
has_access: response.data.result——其中的 result 是插入的行,每次调用都会返回一个真值对象——因此
它无条件报告有访问权限,从不评估 ACL。它会忽略你提供的 operation 和 user。处理 ACL 时,请使用 snow_acl_explain 查看哪些规则匹配以及用户持有哪些角色,并使用
snow_create_acl 写入规则;至于实际判定,请在 UI 中模拟用户,因为平台会针对一条特定记录判定 ACL,而 Table API 中没有任何工具会执行这项操作。
这个工具簇中的工具并非全都在编造结果——有些工具在回显没有输出时会抛出错误,这种失败明确而诚实。关键始终在于:响应描述的是你询问的对象,还是你提交的请求。
两个零成本的习惯:
- 优先选择描述中注明了表名或 API 端点的工具,然后确认这一点,因为仅凭描述无法证明什么。含糊不清的描述(“处理身份验证、依赖项解析和数据迁移”)是一个可靠的警示信号;注明表名只是起点,并非保证。
- 对于你关心的任何写入操作,都使用
snow_query_table或snow_record_manage读回结果——并且读回你想要更改的对象,以及你想要更改它所在的表。响应中的 sys_id 本身并不能作为证据:sys_script_execution工具会返回一个完全可以重新查询的 sys_id,但它属于你从未要求创建的行。如果你创建了业务规则,证明应当是sys_script中存在一行,而不是成功查询到了返回的某个 ID。
供参考,可靠的核心工具包括:用于记录的 snow_query_table 和 snow_record_manage,用于工件的 snow_artifact_manage 和 snow_pull_artifact / snow_push_artifact,用于变更跟踪的 snow_update_set_manage / snow_ensure_active_update_set,用于确认你的身份验证身份的 snow_session_context,以及用于排查看似毫无缘由的调用失败的 snow_diagnose_setup。
搜索结果是摘要,不是架构
每条结果都会经过截断:描述最多显示 200 个字符,参数最多显示五个,每个参数的描述最多显示 80 个字符。has_more_params: true 表示还有更多参数,搜索响应不会显示它们。snow_artifact_manage 有 56 个参数;你只能看到五个。
若要查看完整架构,请启用工具并让客户端重新列出工具(tools/list 包含完整的
inputSchema),或者调用工具并查看验证错误。不要因为 tool_search 没有显示某个参数,就推断该参数不存在。
文档列表与服务端列表会相互校验
tools.json——文档站点和门户所呈现的清单——是通过遍历工具文件生成的。
运行中的服务会从 shared/tool-registry.ts 中硬编码的域映射构建注册表。这是对同一目录树的两种不同读取方式,它们曾经出现偏差:九个工具已发布但无法访问(位于 tools/fsm/、tools/predictive-intelligence/ 的工具以及 snow_create_catalog_variable,它们均未加入映射或重新导出),而 snow_comprehensive_search 则恰好相反——可以调用,却未列入清单。这是 serac-labs/serac#307 的问题,现已修复;如果再次发生,packages/servicenow-mcp/src/__tests__/registry-manifest-parity.test.ts 会检测任一方向的偏差并失败。
因此,tools.json 中的名称就是可以调用的名称。如果工具回复
{
"success": false,
"error": "Tool not found: snow_something",
"suggestion": "Use tool_search to find available tools"
}
说明名称有误,而不是工具缺失——请对照 tools.json 检查名称,而不是重新搜索。
清单中有一类条目虽然列出,但不应调用:标记为 "deprecated": true,且描述以 [DEPRECATED - use <replacement>] 开头的条目。snow_comprehensive_search 就是其中之一——它是 snow_search_artifacts 的向后兼容别名。请改用描述中指定的替代工具。
两个名不副实的环境变量
SNOW_LAZY_TOOLS=false的文档称其会在启动时加载整个目录。但在 stdio 上并非如此: 无论该变量的值如何,传输层都会构建索引,并将所有工具标记为延迟加载,因此tools/list仍只会返回tool_search和tool_execute。启动日志却声称并非如此。 无论如何,都应通过tool_search发现工具。SNOW_TOOL_DOMAINS=cmdb,operations只会筛选tools/list。tool_execute完全不会读取它,因此 即使某个工具属于排除的域,只要已启用,仍然会运行。请将其视为显示筛选器,而非 限制。
企业集成属于另一套服务
jira_*、azdo_*、confluence_*、github_* 和 gitlab_* 都是真实存在的,但不在这里。此服务注册表中的每个工具
都以 snow_ 开头——tool_search 和 tool_execute 除外:它们根本不是注册表工具,而是附加在旁边的两个元工具。集成位于
独立二进制文件
servicenow-mcp-enterprise-proxy 后面;该文件从 Serac 许可证服务器获取目录,并公开自己的一对元工具——
enterprise_tool_search 和 enterprise_tool_execute,其前缀经过精确设计,因此不会与此服务上的两个工具
冲突。
因此,tool_search({ query: "jira" }) 是错误的调用,并非因为这些工具不存在:在此
服务上,它会返回 snow_create_oauth_profile 和 snow_install_spoke。如果你的工具列表中没有
enterprise_tool_search,则说明该服务未为此会话配置——插件的 .mcp.json 只会启动
servicenow-mcp-stdio——并且任何查询都无法触达那些工具。
发现过程应保持静默
不要叙述过程。不要说“让我先启用工具”或“我正在搜索合适的工具”——直接搜索、 启用、调用并报告结果。如果调用失败,就换个词再次搜索,不要出声。
问题不在发现时
如果工具能够解析并启用,但每次调用都失败——出现 401、应为 JSON 的地方返回 HTML,或提示“不是有效的 JSON”——
就停止搜索并调用 snow_diagnose_setup。它会报告使用了哪个凭据来源、实例是否处于唤醒状态,以及 OAuth 交换是否成功。休眠中的开发者实例会返回
HTML 登录页面,导致每个工具看起来都像是存在解析错误。
| 症状 | 原因 |
|---|---|
每次命中都是 [DEFERRED],没有任何工具启用 | 缺少会话 ID——设置 SNOW_SESSION_ID |
Tool "x" is [DEFERRED] and must be enabled first | 此会话中尚未启用;先用 tool_search 搜索它 |
成功查阅文档后出现 Tool not found: x | 已发布但从未注册——参见 serac-labs/serac#307 |
No tools found matching "sys_..." | 查询的是表名;请改为搜索操作 |
| 已启用的工具从未出现在客户端的工具列表中 | 没有 listChanged 通知——请使用 tool_execute |
success: true 但实例上没有任何变化 | 读取 result.success;如果它也为 true,请在其自身的表上重新查询该工件——该工具可能只是存根,或是 sys_script_execution 回显 |
| 工具通过 HTTP 调用时拒绝运行,但在本地可用 | 传输层允许列表——该工具会访问本地文件系统 |