CLI 客户端

社区译文;来源: MCP 80041ab0

每次 CLI 运行都会连接到一个服务器,执行你用 --method 指定的那一个请求,打印结果,然后退出。因此它非常适合 CI 流水线、shell 一行命令,以及需要立即验证服务器改动的编码代理。

npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/list
需要对同一个服务器运行多条命令,或者要走通跨单个会话的流程(任务、订阅、elicitation)?mcpdo 连接客户端会连接一次,并在多条命令之间保持连接打开。

下面的示例使用已安装的 mcp-inspector 二进制文件。若没有全局安装,请像上面那样给每条命令加上前缀 npx @modelcontextprotocol/inspector。

选择服务器

CLI 接受位置参数形式的命令(stdio)、--server-url(HTTP/SSE),或来自 catalog 或配置文件的具名服务器:

# stdio: everything positional is the command to spawn
mcp-inspector --cli node build/index.js --method tools/list

# HTTP
mcp-inspector --cli https://api.example.com/mcp --transport http --method tools/list

# From a file
mcp-inspector --cli --config ./mcp.json --server myserver --method tools/list

当服务器来自文件时,其逐服务器设置(headers、超时、OAuth、协议世代以及 roots)会作用于该连接,解析方式与 TUI 和 web 客户端完全一致。--header flag 会覆盖该次运行中文件的 headers,同时保留其超时和 OAuth 设置。

后续示例把你选用的上述任一形式,连同它的 --transport 或 --config/--server flag,简写为 <server>。

配置文件是唯一能为一次运行持久提供 roots 的方式: 没有 roots flag,CLI 也没有 roots/set 方法。为某个服务器配置的 roots 会在连接时被声明,因此调用 roots/list 的服务器(例如 @modelcontextprotocol/server-filesystem 用它来获知其允许的目录)就能拿到它们。

关于 --catalog 与 --config 的区别、-- 分隔符以及共享的服务器选择 flag,参见配置与 flag。

方法

--method必需的配套参数说明
initialize无仅连接的探测:{serverInfo, protocolVersion, capabilities, instructions}。
tools/list无--strict 会将其 schema 可移植性检查变成门禁(参见 CI 门禁)。
tools/call--tool-name,以及可选的 --tool-arg / --tool-args-json
resources/list无
resources/read--uri
resources/templates/list无
resources/directory/read--uri,以及可选的 --cursor一次读取一页;回传上一页的 nextCursor。
prompts/list无
prompts/get--prompt-name,以及可选的 --prompt-args
logging/setLevel--log-level仅限 legacy 世代;modern 服务器改为逐请求选择加入。
skills/list无--verify 会检查返回的 skills(参见 CI 门禁)。
skills/get--uri同样支持 --verify 选项。
servers/list无不连接任何服务器即可读取 catalog。
servers/show--server同样不连接,针对单个条目。
servers/add--server,以及一个位置目标或 --server-url不连接即可写入 catalog(参见编辑 catalog)。
servers/edit--server,以及要修改的内容同上。
servers/remove--server同上。

仅支持流或会话的方法(例如 logging/tail)会被拒绝,因为会退出的进程无法保持流打开。

传递参数

--tool-arg 接受 key=value,并会在值能被 JSON 解析时强制转换它,因此 count=1 和 zip=10001 都会发送数字。不是合法 JSON 的值(zip=012、一个裸词)会作为字符串发送,随后该字符串会被转换为工具输入 schema 为该属性声明的类型,因此对数值型 zip 使用 zip=012 仍会发送 12:

mcp-inspector --cli <server> --method tools/call --tool-name mytool \
  --tool-arg key=value --tool-arg count=1 --tool-arg 'options={"format":"json"}'

--tool-args-json 一次性接受完整的参数对象,并跳过 key=value 解析,因此 {"zip":"10001"} 发送的是字符串 "10001" 而不是数字。schema 转换仍会作用于字符串值,所以只有当 schema 把该属性类型声明为字符串(或未声明)时,它才会保持字符串形式。这两个 flag 互斥:

mcp-inspector --cli <server> --method tools/call --tool-name mytool \
  --tool-args-json '{"zip":"10001"}'

输出

--format text(默认)为人类做美化打印。--format json 会在 stdout 上输出单个 JSON 对象且不带任何横幅,因此整段输出可以干净地通过管道:

mcp-inspector --cli <server> --method tools/list --format json | jq '.result.tools[].name'

探测 MCP Apps

--app-info 不调用工具即可报告某个工具是否带有 MCP App UI(其 ui:// 资源、CSP 和权限),因此流水线可以在调用任何东西之前决定是否需要浏览器:

# One tool -> one JSON line
mcp-inspector --cli <server> --method tools/call --tool-name my_tool --app-info
# {"hasApp":true,"toolName":"my_tool","resourceUri":"ui://...","csp":{...},"permissions":{...}}

# Every tool -> NDJSON, one line each, over a single connection
mcp-inspector --cli <server> --method tools/list --app-info | jq -c 'select(.hasApp)'

退出码用于区分不同结果:带有 App 的工具退出 0,没有 App 的工具退出 2,工具不存在退出 5,因此拼写错误不会被误认为"没有 App"。探测失败(UI 资源不可读、resourceUri 格式错误)会记录在 resourceError 字段中而不是中止,因此单个坏工具永远不会毁掉整个列表。

CLI 无法渲染 App,因此默认情况下它不会在 initialize 时声明 MCP Apps 扩展。只向声明支持 App 的客户端暴露 App 工具的服务器,因而会显得"没有 App"(退出 2)。加上 --advertise-apps 即可在该次运行中声明该扩展。

无论 --format 如何设置,tools/list --app-info 始终输出 NDJSON(每个工具一行);--format json 只改变 tools/call --app-info 的单工具输出格式。

退出码与错误信封

每个非零退出都映射到一个稳定的失败类别,因此调用方可以依据原因分支处理,而无需抓取文字描述:

代码含义
0成功。
1用法错误或意外错误(兜底),包括机密存储或 OAuth 状态文件不可读。
2工具上没有找到 MCP App(--app-info 探测)。
3服务器要求身份验证(HTTP 401/403,或 SDK 授权错误)。
4服务器不可达(DNS、连接被拒绝、超时、fetch failed)。
5工具错误:tools/call 返回 isError: true,或未找到该工具。
6--strict 发现严重级别为 error 的工具 schema 可移植性问题。
7--verify 发现某个 skill 违反 SEP-2640(一致性、摘要或大小不匹配)。
8--verify 在其读取边界内无法检查每一个 skill。
9--verify --require-digests 发现某个 skill 未提供任何摘要。

任何非零退出时,CLI 还会向 stderr 写入一行 JSON:

{
  "error": {
    "code": "auth_required",
    "message": "Unauthorized",
    "status": 401,
    "url": "https://api.example/mcp"
  }
}

该信封始终包含 code 和 message,并在已知时包含 cause(底层错误,例如 DNS 失败)、status(HTTP 状态码)和 url。URL 中的查询字符串机密会被脱敏。code 是该失败的稳定名称,例如 auth_required、unreachable、tool_not_found、tool_is_error、no_app、schema_unportable、store_unavailable、oauth_state_unrecognized,以及兜底的 error 等。

因为它只有一行,调用方可以用 2>&1 | tail -1 | jq .error 解析它。

返回 isError: true 的 tools/call 仍会打印其负载,但退出码为 5,因此 && 链不会在调用失败后继续执行。

脚本中的授权

默认情况下,CLI 会运行与 TUI 相同的 loopback OAuth 流程:它打开浏览器并等待 localhost 回调,而 CI 任务无法完成这一回调。有两个 flag 可以让非交互运行变得可预测:

  • --stored-auth-only:绝不启动交互式 OAuth 或 step-up,也绝不自动打开浏览器。若共享存储中存在 token 就使用它,否则立即以 auth_required 失败。这正是 CI 需要的 flag。
  • --use-stored-auth:复用 web Inspector 已在本机获取的 token,若存有 refresh token 则先刷新它。它按 URL 查找 token,因此需要 --server-url。

两者都未使用,且 stdin 或 stderr 上没有 TTY 时,CLI 会快速失败并返回 auth_required,而不是为一个无人完成的回调挂起十五分钟。

完整流程、web 到 CLI 的 handoff 以及 --print-handoff,参见授权。

配方

在 CI 中验证服务器

set -euo pipefail

# Fail the build if the server can't be reached or doesn't expose the tool
mcp-inspector --cli --config ./ci-servers.json --server my-server \
  --stored-auth-only --method tools/list --format json \
  | jq -e '.result.tools | map(.name) | index("get_weather")' > /dev/null

CI 门禁

有三个 flag 能把检查变成非零退出,各自使用不同代码,以便任务区分不同失败:

  • --strict(配合 tools/list):在 stderr 上完整报告工具 schema 可移植性问题(路径、问题、建议修复),只要其中任何一项为 error 严重级别就以 6 退出。不使用它时,tools/list 只打印一行计数。在 --format json 下,发现结果会以 schemaFindings 形式并入输出。
  • --verify(配合 skills/list 或 skills/get):运行 SEP-2640 一致性和摘要检查,为每个 skill 在 stdout 上写一份 JSON 报告,违规时退出 7,若读取边界导致无法检查完所有 skill 则退出 8。--skill-catalog-max-skills 和 --skill-catalog-max-bytes 用于设置这些边界。
  • --require-digests(配合 --verify):对于未提供任何摘要的 skill 退出 9,而不是把它报告为不可验证并退出 0。
mcp-inspector --cli <server> --method tools/list --strict > /dev/null
mcp-inspector --cli <server> --method skills/list --verify --require-digests

按失败类别分支

if out=$(mcp-inspector --cli "$URL" --transport http --method tools/list 2>err.json); then
  echo "$out"
else
  case $? in
    3) echo "needs auth: run the web inspector once to sign in" ;;
    4) echo "server unreachable" ;;
    *) jq .error < err.json ;;
  esac
fi

对所有带 UI 的工具做冒烟测试

mcp-inspector --cli "$URL" --transport http --method tools/list --app-info \
  | jq -r 'select(.hasApp) | .toolName'

不连接即可检查 catalog

mcp-inspector --cli --catalog ~/.mcp-inspector/mcp.json --method servers/list
mcp-inspector --cli --catalog ~/.mcp-inspector/mcp.json --method servers/show --server my-server
servers/show 会脱敏含机密的字段(env 值、敏感 headers、OAuth 客户端 secret),但它不会清洗嵌入在服务器 url 中的凭据(userinfo 或查询 token),也不会清洗 stdio args 中的凭据。在把原始 URL 和 detail 字段粘贴到 issue 之前,请将其视为敏感信息。

编辑 catalog

servers/add、servers/edit 和 servers/remove 在不连接任何服务器的情况下写入 catalog。它们走的是与 web 客户端服务器列表相同的代码路径,因此 env 值和客户端 secret 会完全像从 UI 操作那样落入机密存储,并且正在运行的 web 客户端会收到该变更。

# Add: the positional target or --server-url describes the entry, not a server to connect to
mcp-inspector --cli node build/index.js -e API_KEY=secret --method servers/add --server my-server

# Edit: change the target, -e, --cwd, --header, or --protocol-era, or rename it
mcp-inspector --cli --method servers/edit --server my-server --rename my-renamed-server

# Remove the entry and its stored secrets
mcp-inspector --cli --method servers/remove --server my-renamed-server

只能写入可写的 catalog(--catalog、MCP_CATALOG_PATH 或默认路径);--config 会被拒绝。使用内存机密存储时,提供 -e 值(以及 --rename)的写入会被拒绝,因为运行退出后这些机密就会丢失。

代理

连接远程 HTTP/SSE 服务器时会遵循常规代理变量:HTTPS_PROXY / HTTP_PROXY(及它们的小写形式)选择代理,NO_PROXY 豁免主机。无需任何 Inspector 专用 flag,并且代理 agent 是惰性加载的,因此不使用代理的运行不会有任何开销。web 客户端的后端同样如此。