Repository navigation
feat(mcp): 支持基于 mcp.json 的 MCP 工具检查、发现与调用(一期设计) #42
Description
背景与目标
新增 bk-cli mcp,让用户和 Agent 通过命令行连接已配置的 MCP server,检查连接、发现工具、查看工具帮助并显式调用工具。以 MCP server 返回的工具定义为依据,不为每个 server 手工开发 system action。
一期面向远程 Streamable HTTP server,认证来自选定 mcp.json 中各 server 自己的 headers。不实现公共客户端 OAuth 自动登录,不隐式复用 bk-cli context 凭据。
本文是待实现的完整一期设计;创建 Issue 不代表功能已完成或真实服务联调已通过。示例中的域名、server 名称及票据均为占位值。
1. 设计基线与兼容边界
初版设计日期:2026-10-20;评审修订日期:2026-10-22。代码检查基于 779108d13b6693d00cc5a4a7d361b5fe6f9e84a1;创建前远端 master 为 ee4d3eb74e8e70fc233af0b03fb524c020426fe3,两者差异仅为已有 Skill 的 jq 使用文档。
当前可复用的基础:
- 配置目录:默认
~/.bk-cli/,支持BK_CLI_CONFIG_DIR。 - 认证头映射:个人 token 使用 Bearer,原有凭据使用
X-Bkapi-Authorization。MCP 命令只复用校验/脱敏等独立能力,不加载这些 context 凭据来生成请求头。 - body 输入:已有内联 JSON、
@file、stdin 输入习惯;当前函数为包内函数,不能假设存在可直接调用的导出 API。 - 输出封装、jq 投影:复用 envelope、结构化错误和最终结果投影。
- 根命令:新增独立的
mcp命令组,不注册为cmd/system下的蓝鲸业务系统。
现有 api / system action 的认证、URL 拼装、context、输出和退出码契约不随本功能变化。不把 MCP 请求伪装成普通网关 REST 调用。
2. 第一期范围
包含
- 从单个 JSON 文件加载多个命名 server,按名称选择目标。
type: "http"对应 Streamable HTTP;支持其 JSON 和text/event-stream响应形式。- 显式 headers、headers 中声明的环境变量引用,以及无认证 server。
--check、--tools、server 帮助、tool 帮助、tool 调用。- 按工具
inputSchema生成可表达的具名参数,并提供完整 JSON 输入兜底。 - 工具列表完整分页、单次执行超时、取消、连接清理。
- 结构化输出、MCP 错误判定、认证信息脱敏和已有
--jq能力。 - 离线 dry-run:使用完整
--body预览调用,不连接 server。 - 本地 mock 验证和人工触发的真实环境验收清单。
不包含
- OAuth 登录、浏览器跳转、授权码/device flow、token 自动刷新或其他客户端登录缓存导入。
- 当前 context 凭据、tenant、stage、网关 URL 模板、认证环境变量的隐式回退。
- stdio 子进程、旧版 HTTP+SSE transport、自动 transport 探测/降级。Streamable HTTP 内的 SSE 响应仍属一期范围。
- 自动扫描/合并 Cursor、Claude、VS Code 等客户端配置。
- 跨 server 批量调用、自动调用全部工具、定时执行、工作流编排。
- resources/prompts 操作、sampling、elicitation、
input_required补充输入/接续调用、异步 task 轮询、媒体自动落盘。外层 Agent 接管补充输入流程留待后续阶段。 - 常驻 daemon、跨进程连接池、持久化工具 Schema 缓存。
- 自动解释任意业务 JSON 中的
code/result/status,或把任务 ID 视为任务完成。
3. 命令形式
统一采用 bk-cli mcp <server> [选项或工具]。不新增 mcp check <server>、mcp tools <server> 这样的操作子命令。
| 命令 | 行为 |
|---|---|
bk-cli mcp / bk-cli mcp --help |
本地通用帮助、配置位置、示例;不要求配置存在,不连接 server |
bk-cli mcp <server> / bk-cli mcp <server> --help |
读取选定配置,展示该 server 的静态使用说明及 --check/--tools/<tool> --help 指引;不连接 server、不要求票据环境变量已设置 |
bk-cli mcp <server> --check |
校验配置、完成协议连接/协商、完整读取工具列表;输出分阶段结果 |
bk-cli mcp <server> --tools |
完整发现工具,输出名称、描述、inputSchema 及可用的其他工具元数据 |
bk-cli mcp <server> <tool> --help |
连接目标并读取工具 Schema,显示参数、必填项、类型、示例和 JSON 输入指引;不执行工具 |
bk-cli mcp <server> <tool> --param value |
发现工具、解析/校验参数,执行一次 tools/call |
bk-cli mcp <server> <tool> --body '<json>' |
使用完整 arguments 对象调用 |
bk-cli mcp --config <path> <server> ... |
本次只使用指定配置文件 |
示例:
bk-cli mcp report-prod --check
bk-cli mcp report-prod --tools
bk-cli mcp report-prod search_reports --help
bk-cli mcp report-prod search_reports --keyword '网关错误' --limit 20
bk-cli mcp report-prod search_reports --body '{"keyword":"网关错误","limit":20}'
bk-cli mcp report-prod search_reports --body @params.json
bk-cli mcp report-prod search_reports --body - < params.json
bk-cli mcp --config ./.mcp.json report-prod --tools规则:
--check、--tools和<tool>调用三种执行模式互斥;多余位置参数、未知选项报错。--help/-h只进入对应帮助路径,不进入 check/call,不读取 body 文件或 stdin;互斥模式组合仍报使用错误。--check/--tools不接受--body或工具参数。- server/tool 名称大小写敏感,按配置名和协议原名精确匹配,不自动改写下划线、连字符或点号。
- server 配置名应为可稳定作为单个命令词的非空名称,不以
-开头,不含空白/控制字符;配置名只是本地别名。 - 不因某个工具无法生成 flags 而隐藏它;仍在工具目录中展示并允许完整 JSON 调用。
通用 flags
--timeout <duration>:正值 duration,默认沿用现有 60 秒常量,但不读取 context。覆盖本次远程操作总耗时(包括发现分页与调用),不是每页重新获得完整预算。--verbose/-v:诊断输出到 stderr,认证信息脱敏。--insecure:仅显式开启时跳过目标 HTTPS 证书验证,与现有开关语义一致。--jq:用于 check、tools、call、dry-run 的最终 JSON envelope;复用既有解析、预算及失败处理。帮助不做结果投影。--context:在 MCP 路径上显式使用时返回unsupported_option,避免给人“已切换 MCP 认证”的错觉;未显式传入时完全不访问 active context。- 不新增
--header、--stage、--tenant或独立 token flags;一期认证和附加 headers 统一来自选定 server 配置。
4. 配置加载
路径与优先级
只选择一个配置文件,不做合并:
- 显式
bk-cli mcp --config <path>。 - 否则使用
internal/config.BaseDirectory()下的mcp.json:设置BK_CLI_CONFIG_DIR时取该目录,否则取~/.bk-cli/mcp.json。
项目配置通过 --config ./.mcp.json 显式使用;一期不自动向父目录查找,不自动读取当前目录或其他客户端配置。选定文件不存在、不可读或格式错误时直接报错,不再尝试另一个来源。
相对配置路径按当前工作目录解析,诊断展示最终文件位置。--body @file 同样按当前工作目录解析,与配置文件所在目录无关。
不把文件放在 contexts/<name>/ 下;context use 不改变 MCP 连接、认证或默认配置文件。
文件结构
{
"mcpServers": {
"report-prod": {
"type": "http",
"url": "https://mcp.example.com/report/application/mcp/",
"headers": {
"X-Bkapi-Authorization": "${REPORT_BK_AUTH}"
}
},
"report-personal": {
"type": "http",
"url": "https://mcp.example.com/report/application/mcp/",
"headers": {
"Authorization": "Bearer ${REPORT_PERSONAL_TOKEN}"
}
},
"public-docs": {
"type": "http",
"url": "https://mcp.example.com/public/mcp/"
}
}
}REPORT_BK_AUTH 是完整认证 JSON 字符串,例如应用凭据或 {"access_token":"..."}。也允许直接配置认证字符串:
{
"X-Bkapi-Authorization": "{\"bk_app_code\":\"your_app_code\",\"bk_app_secret\":\"your_app_secret\",\"bk_ticket\":\"your_ticket\"}"
}加载契约:
- 一期使用严格 JSON,不支持 JSONC、include、extends 或客户端特有的
inputs/命令执行表达式。 - 要求顶层
mcpServers;servers等其他格式明确提示不支持,不静默转换。 - 拒绝重复 JSON key、重复 server key、大小写不同但等价的 header key;不采用“最后一个覆盖前面”的隐式行为。
type必须显式为http;不支持的类型或会被忽略的未知配置字段报错,避免用户误以为配置生效。url必须为绝对 HTTP(S) URL,不允许 userinfo 和 fragment;路径、尾部斜线和合法 query 保持原样,不套网关模板、不拼 stage 或自动追加/mcp。headers必须是字符串映射,可省略或为空对象。禁止用户覆盖由协议/HTTP 栈管理的Host、Content-Length、Content-Type、Accept、连接控制头和 MCP session/version 等协议头。- 文件语法及配置结构整体校验;环境变量展开和凭据校验仅针对当前选中的 server。无关 server 的未设置环境变量不应阻止目标调用。
- 不自动创建带占位票据的配置,不修改或回写用户原文件。
环境变量
- 一期仅支持所选 server 的 header 值中的
${ENV_NAME};支持同一字符串中的多个引用。环境变量名称遵循常见字母/数字/下划线规则。 - 单次文本展开,不执行 shell、不递归展开,不自动读取
.env,不支持${env:NAME}、${input:NAME}、默认值或命令替换。 - 使用
$${表示字面${;展开结果不再当作模板解释。 - 缺少引用变量时报
missing_environment_variable;空认证值报配置错误,不能降级为匿名。 X-Bkapi-Authorization的展开结果必须仍是合法 JSON 对象;推荐整段认证 JSON 通过一个环境变量传入,避免嵌套 JSON 转义问题。- 即使环境里存在
BK_CLI_PERSONAL_TOKEN等变量,也只在配置显式引用时读取;不自动读取现有 auth login 环境回退来源。 - 含真实票据的配置不提交版本库,使用仅当前用户可读写的文件权限;共享配置使用占位值/环境引用,不把展开后的值写回文件。
5. 认证契约
认证唯一来源是目标 server 的 headers(包括其显式环境引用)。
| 配置 | 行为 |
|---|---|
Authorization: Bearer <personal-token> |
原样使用该凭据,不验证业务有效期、不自动刷新 |
X-Bkapi-Authorization: {"bk_app_code":"x","bk_app_secret":"y","bk_ticket":"z"} |
使用原有应用 + ticket 认证 |
将上述 bk_ticket 换为 bk_token |
使用原有应用 + token 认证 |
X-Bkapi-Authorization: {"access_token":"z"} |
使用原有 access_token 认证,不自动转换成 Bearer |
| 无认证头 | 不附加认证,允许匿名 server |
| 两种认证头同时存在 | 忽略头名大小写后识别冲突,本地报错,不选择某一份身份 |
| 空认证头/空 Bearer/非法蓝鲸认证 JSON | 本地报错,不回退 context 或匿名模式 |
headers 保持通用透传能力:第三方的合法 Authorization scheme、自定义 API key header 可直接传递;只对可识别的 Bearer 和蓝鲸认证做对应格式校验,不凭未知 header 推断身份。
蓝鲸认证一期明确支持上述形态:应用模式要求非空 code、secret,以及 ticket/token 二选一;access_token 模式独立使用,不与应用字段混合。字段均为字符串;错误不打印票据。此本地校验不证明凭据有效或有权限。
所有实际 MCP HTTP 请求统一使用同一份已解析 headers,包括旧协议初始化、工具发现分页、工具调用及需要的会话操作。不得只在 tools/call 时添加认证。
响应处理:
401:认证缺失或失效,提示修改所选文件中对应 server 的 headers/环境变量。不浏览器登录、不读其他客户端 token、不刷新、不换身份重试。403:保留安全的服务端权限错误,提示检查该身份的 server/tool/业务权限;不把所有 403 都描述为 token 过期。- 工具发现成功只证明服务端接受本次发现请求;匿名也可能成功,不单独声称 token 已验证,更不表示所有工具或业务资源均获授权。
凭据仅发送到配置的目标 endpoint;一期不自动跟随 HTTP 重定向,避免手工配置的敏感头随重定向外发,并提示更新 URL。HTTPS 默认校验证书。
在 check、help、dry-run、verbose 和错误诊断中展示配置 headers 时,保留全部 header 名称,value 一律显示固定字符串 ***;不保留原值的前后缀或长度,不依赖敏感 header 名单。实际请求仍发送解析后的原始值。头名匹配大小写不敏感,响应 headers 中与配置同名的字段也必须 mask,其他已知认证头继续脱敏。解析错误不得回显原 header 或整个配置。原始工具业务结果按现有 CLI 原样保留,不承诺通用内容级 DLP;诊断不得主动复制凭据到结果。
6. 参数映射
具名 flags
以 tools/list 返回的 inputSchema 为准,保留字段原名:
| Schema | CLI | JSON arguments |
|---|---|---|
| string | --keyword '网关错误' |
"keyword": "网关错误" |
| integer | --limit 20 |
"limit": 20 |
| number | --ratio 0.5 |
"ratio": 0.5 |
| boolean | --enabled=true / --enabled=false |
"enabled": true/false |
| array | --ids '[1,2,3]' |
"ids": [1,2,3] |
| object | --filter '{"status":"running"}' |
"filter": {"status":"running"} |
| null、联合类型、难以映射的 Schema/字段名 | 完整 --body |
保留 JSON 的原始表达 |
规则:
- 支持
--name value和--name=value;布尔值以显式=true/false为规范写法,不引入隐式取反或--no-*命名规则。 - 只传用户显式提供的字段;未提供不能自动变成
false/0/"",也不自动注入 Schema 的default注解。 - 字符串
"00123"保持字符串,不靠字面量猜类型。JSON 数字不得在 CLI 处理链路中经有损浮点转换而改变数值;大整数、高精度小数及指数表示均需覆盖,不能通过把数字改为字符串来绕过。超出实现支持范围时明确拒绝,不能静默截断。 - 数字保真不仅适用于具名 flags、内联 JSON、文件和 stdin 参数,也覆盖
tools/list的inputSchema/outputSchema与元数据,以及工具结果中的 JSON 数字(包括structuredContent、content元数据和_meta)、最终 envelope 和--jq简单字段提取。文本 content 不重新解析为 JSON;jq 计算表达式沿用现有输出层契约。保证范围是 CLI 收到的数据,不承诺恢复服务端已经丢失的精度。 - 对同一个具名参数重复赋值报错;数组使用 JSON,不用逗号拆分或重复 flag 拼接。
- 必填字段、明确的基础类型和 enum 在调用前校验;不能因 CLI 的默认值造成虚假的必填项通过。
- 能安全识别的本地 Schema 约束应校验;一期不承诺完整 JSON Schema 方言、组合关键字或所有
$ref的本地验证,不为解析$ref自动访问远程网络。 - 无法无歧义映射的字段不生成 flag,帮助注明使用
--body;服务端仍为最终参数校验方。
完整 JSON
--body '<json>'、--body @file、--body - 表示完整的 tools/call.params.arguments 对象,不是 JSON-RPC 报文,也不是直接请求上游业务 API 的 HTTP body。
- 顶层必须是 JSON 对象;空输入、数组、标量、非法 JSON 和重复字段报错。无参数调用使用
{}。 - 提供
--body时,以完整 JSON 为准,不与具名参数合并,不用被覆盖的 flags 生成默认值或判定必填项;识别到的被覆盖参数不发送。未知选项仍报错。 - 与现有 body override 行为保持一致。可识别的具名参数仍需有合法 CLI token 结构;不把拼错的 flag 当成可忽略内容。
- 必填校验针对最终对象;基础校验可以执行时执行。复杂 Schema 使用 JSON 路径并交由服务端验证,不能宣称做过实际上未支持的本地完整验证。
- 文件和 stdin 输入采用相同 JSON 解析及数字保真规则;读取错误发生在
tools/call之前。stdin 只读一次,--help不消耗 stdin。 - JSON 字符串中的中文、引号、换行、反斜杠、
$()和反引号作为数据处理,不交给 shell 再执行。
保留名与冲突
保留 CLI 自身实际存在及约定预留的 flag 名,包括 help/check/tools/config/body/timeout/jq/context/dry-run/verbose/insecure/format 等。实现以统一集合为准,并覆盖短选项。
工具字段与保留名冲突时,不生成对应 flag、不覆盖 CLI 选项、不自动改名;字段通过 --body 原名传入。工具名称/工具的其他参数仍可用,--tools 和 tool 帮助应解释该限制。
7. 执行、协议与生命周期
执行顺序:
- 解析本地模式与通用 flags,先发现互斥组合及非法 jq 表达式。
- 选定唯一配置、查找 server、校验目标及认证;仅按需展开 header 环境变量。
- 创建独立 MCP client/HTTP transport,设置超时与取消;不读取 context。
- 按协商协议完成必要初始化;发现工具时完整处理
tools/list分页。 - 对 help 返回 Schema 帮助;对 check/tools 返回诊断/列表;对 call 构造最终 arguments 后只调用指定工具一次。
- 判断协议/工具结果,生成最终 envelope,再做 jq 投影;结束时关闭连接及本次会话。
协议实现优先评估官方 github.com/modelcontextprotocol/go-sdk,不手写一套 JSON-RPC/Streamable HTTP 协议栈。初始候选 v1.8.0 的 go.mod 要求 Go 1.25.0,与初版代码基线 Go 1.25.5 的声明兼容;这只是静态版本证据,不是已完成依赖兼容验证。
允许临时固定包含所需修复的官方上游具体 commit,通过 Go module pseudo-version 锁入 go.mod/go.sum,不跟随浮动分支。实施前必须验证所选提交实际覆盖本方案使用的数字保真路径及协议能力;固定 commit 本身不证明问题已全部修复。待包含所需修复的正式版发布并通过同一组验收后,再切换正式版本。具体提交在接入前确定,当前不宣称已完成依赖选型和验证。
一期兼容测试明确覆盖 Streamable HTTP 上的 2025-03-26、2025-06-18、2025-11-25 和 2026-07-28 工具发现/同步调用路径。由固定 SDK 版本处理版本协商差异,不把所有版本都强行套到同一初始化/会话流程;不支持的版本报错。SDK 支持其他 transport/扩展不意味着 CLI 一期对外支持它们。
- 收到
input_required时,一期返回mcp_capability_unsupported(退出 2),保留可用的原始输入要求与状态,不自动收集/补充输入或再次调用,不能将未完成结果标为成功。一期不提供inputResponses/requestState接续入口;--body始终仅表示普通 arguments 对象。 - 外层 Agent 可以依据工具契约补齐普通业务参数后发起一次新的 CLI 调用,但这不是通用的
input_required接续能力;是否能重新调用须结合工具语义和副作用判断。未来再考虑显式的外层 Agent 接管流程。 - 显式关闭 SDK 默认的多轮补充输入和负载退避重发等可能再次执行
tools/call的行为,不能依赖“未注册交互 handler”间接保证调用次数。连接恢复或 SSE 重连也不得重新发起工具调用;通过 mock 统计实际收到的tools/call次数。 - 若收到 sampling、elicitation 或异步 task 等其他非一期能力的请求/响应,明确报告能力未支持,并保留可用的原始结果/任务标识;不自动交互、轮询或重复执行,不把任务 ID 视为完成。
tools/list处理nextCursor直到结束,检查重复/循环游标和分页上限。任何中途失败/截断不得输出“完整发现成功”。空 tools 列表可以是成功。- 一期对 JSON 响应(包括错误响应)、单条 SSE 事件、累计工具目录及分页次数采用固定上限,不新增可调 flags 或 server 配置项。具体数值在实现前结合 SDK 行为与代表性的 Schema/结果样例确定,统一记录在文档和测试中;限制须在读取/累积过程中生效,不能先无限量读入后再检查。
- 响应或累计目录大小超限返回
mcp_response_too_large(退出 2);分页次数超限仍为mcp_discovery_incomplete。不得截断后返回成功或标为完整目录。发现阶段超限不执行工具;调用已发出且因超限无法取得完整可判定结果时,标记data.execution_state: "unknown",不重发调用。 - 全量工具目录中重名工具应报协议/目录冲突,不任意选一个。工具集可能随认证变化,一期不做持久缓存。
- discovery 与 call 尽量在同一次 client 生命周期内完成;发现后工具被删除时保留远端错误,不自动刷新后重试调用。
- 超时/取消、结果解析失败、输出失败都不能触发自动重发
tools/call。调用已发出或不能排除已发出,且未取得完整可判定响应时,按下文的机器输出契约报告结果未知;已经收到完整结果后,仅 jq 或输出失败不能将已知执行结果改为未知。 - 不预先连接其他 server,不在根命令注册阶段联网;远端故障不能导致其他 bk-cli 命令不可用。
8. 检查与输出契约
--check
执行本地配置/认证格式校验、协议连接和完整工具发现,不调用任何业务 tool。
成功结果示意:
{
"ok": true,
"data": {
"server": "report-prod",
"config_file": "/home/example/.bk-cli/mcp.json",
"transport": "http",
"checks": {
"config": "passed",
"connection": "passed",
"protocol": "passed",
"tools": "passed"
},
"authentication": {
"source": "server_headers",
"discovery_accepted": true
},
"tool_count": 12,
"complete": true
}
}匿名配置的 source 为 none;discovery_accepted 表示此次请求被接受,不是对 token 有效性的独立验证。进入远程检查后失败时,整体 ok:false,已完成阶段保留,失败阶段标 failed,后续阶段标 not_run,提供结构化错误;文件/配置等早期错误沿用下文的本地错误通道。不能靠 HTTP 200 或 tools 数量单独判定成功。
--tools 与调用
--tools输出完整目录及server/tool_count/complete;保留工具inputSchema、可用的outputSchema/annotations 等元数据,以便 Agent 选择工具。- 调用输出包含
data.server、data.tool,取得有效工具结果时包含data.result;result保留 SDK 支持的 MCP 结果字段,包括content、structuredContent、isError、_meta等,不默认把 text 内容再猜测为业务 JSON。 - 对调用已发出或不能排除已发出、但未获得完整可判定响应的情况,输出
ok:false、data.execution_state: "unknown",保留具体原因的错误码(如mcp_timeout、mcp_connection_error、mcp_protocol_error或mcp_response_too_large),退出 2。提示“服务端可能已执行,请先通过业务查询核实,不要直接重复调用”;外层 Agent 无需解析错误文案即可识别该状态。 unknown不等于“服务端未执行”或“工具执行失败”。本地校验失败、发现阶段失败且工具未调用时,不标为未知;已取得完整工具结果、仅 jq 或输出失败时,按既有输出契约尽可能保留原结果,不改写为未知,也不触发重试。已明确收到input_required/任务标识时,按已知的未完成状态报告能力未支持,不将其混同为丢失结果。ok:true仅表示本次检查/发现完成,或本次 MCP 同步工具调用完成且未报告工具错误;不推断业务成功或异步任务完成。- HTTP 成功但 JSON-RPC error、无效 MCP 响应、
isError:true,均为失败。isError:true的原始结果仍可供调用方检查。 - 不伪造 HTTP status。多请求操作的阶段和失败位置通过 data/error 表达,不能只展示最后一个 200 掩盖先前失败。
- 二进制/图片/音频等 content 保留为协议 JSON,不自动展示、下载或写文件。
输出渠道和退出码:
| 情况 | 输出/退出 |
|---|---|
| 帮助 | stdout 文本,退出 0 |
| 本地输入/配置/参数校验失败(包括发现成功后的参数校验) | stderr 结构化错误,stdout 不输出业务结果,退出 1 |
| check/tools/call 成功 | stdout 一个最终 envelope,退出 0 |
| 连接/认证/协议/工具错误、远端结果读取或发现大小超限 | stdout 一个 ok:false envelope,保留阶段/可用工具结果,退出 2;不重复打印第二份完整错误 |
| jq 运行/输出失败 | 沿用现有输出层契约,退出 2;不能触发工具重试。jq 编译错误属于本地输入错误,退出 1 |
错误分类按来源,不按“是否已经联网”划分:获取工具 Schema 后发现参数缺失、类型错误,或完整发现后确认工具不存在,仍是本地使用错误(stderr,退出 1),且不发送 tools/call。诊断日志走 stderr。新增错误码使用 snake_case,固定如下机器契约,沿用输出层已有 jq 错误码:
- 本地/使用错误,退出 1:
invalid_mcp_config、mcp_config_not_found、mcp_server_not_found、missing_environment_variable、mcp_auth_conflict、invalid_mcp_arguments、unsupported_option、mcp_tool_not_found(完整发现后本地确认工具不存在)。 - 远程/运行错误,退出 2:
mcp_auth_required、mcp_permission_denied、mcp_connection_error、mcp_timeout、mcp_protocol_error、mcp_discovery_incomplete、mcp_response_too_large、mcp_tool_error、mcp_capability_unsupported。 - 配置的 transport 不支持属于
invalid_mcp_config;协议协商失败或发现后远端工具消失属于mcp_protocol_error。已知凭据收到 401 时,同一mcp_auth_required提示凭据可能失效,不伪造具体失效原因。
--jq 仅作用于最终完整结果:工具结果判定/目录聚合先完成,失败 envelope 不被投影隐藏,投影错误保留已完成请求的原结果并明确提示不要盲目重试。本功能不改变其他命令既有的 HTTP/业务退出行为。
离线 dry-run
没有持久 Schema 缓存时,具名 flags 的类型来自远端,不能同时承诺“离线”与“动态类型映射”。
一期明确选择:
- 仅支持
<server> <tool> --body <完整对象> --dry-run,无参数工具使用--body '{}'。 - 不发送初始化、
tools/list或tools/call;只校验本地配置、认证格式和 JSON 结构。 - 输出目标 URL、全部配置 header 名称及固定为
***的 value、工具名、arguments、dry_run:true,明确标注未验证远端工具存在性、Schema、认证有效性及业务权限。 - dry-run 不伪造协商版本、会话 ID 或 JSON-RPC 请求成功;协议管理字段由真实执行时的 SDK 生成。
- dry-run 要求完整
--body,并拒绝所有具名工具 flags(即使同时提供 body),避免在离线模式下猜测哪些选项是工具参数;--check/--tools与 dry-run 互斥。
9. 实现边界与建议分工
以下为拟新增位置,均非当前已有实现:
cmd/mcp/:命令路由、模式互斥、动态帮助/参数入口、输出调用。internal/mcpclient/:配置与 header 解析、MCP transport/session、发现分页、Schema 参数适配、结果分类;按职责拆文件,不引入没有具体用途的 provider/plugin 框架。cmd/root.go:注册独立 MCP 命令。已有 system 专用参数归一化逻辑不能未经检查直接套用。internal/output:复用 envelope/jq/error;只有实际调用需要时才做最小通用改动,不修改既有 APIResponse 的成功语义。- body/file/stdin 解析若需要复用,通过最小共享边界提取并保留旧命令行为测试,不让 MCP 执行器依赖网关 runtime/context。
tests/integration/cases/mcp/:作为新的非 system 命令测试目录,并同步 integration guidance;mock 优先扩展既有mock_api/app.py,不默认新增第二套服务。
实现前应锁定 SDK 正式版本或具体 commit 对应的 pseudo-version,检查许可证、依赖变化、MCP 版本能力、完整数字保真路径和跨平台构建;不能仅因引入 SDK 就开启 OAuth/stdio、多轮补充输入或自动工具调用重试。必要的 JSON Schema 库先评估 SDK 的现有依赖,避免无意引入两套验证器。
实现前还需落实三项技术验收:选定提交覆盖哪些精度修复;固定响应/目录/分页上限的具体数值;SDK 在补充输入、负载退避、超时和连接恢复场景中的真实 tools/call 次数。这些结果须记录到实现文档和测试中;若验证表明需要改变已定行为或兼容范围,应重新讨论受影响的设计,不能静默降低验收要求。
文档同步包含 README.md、README_EN.md、docs/user-guide.md、docs/design.md、AGENTS.md;新增中文使用说明 skills/bk-cli-mcp/SKILL.md,共享 Skill 仅补入口和通用行为。MCP 是根命令组,不应混入业务 system 扩展注册流程。
10. 自动化测试与验收矩阵
使用 Ginkgo/Gomega、临时目录、隔离的 BK_CLI_CONFIG_DIR 和本地 HTTP/mock server。测试必须能断言实际请求数量、请求方法/协议参数及 headers,不能只检查输出文案。
| 编号 | 场景 | 验收要求 |
|---|---|---|
| C01 | 默认目录、配置目录环境覆盖、显式 --config |
路径选择正确,显式文件优先且无隐式合并/扫描 |
| C02 | 缺文件、坏 JSON、重复 key、错误字段/transport/URL/header | 明确本地错误,零网络请求,不回退其他文件 |
| C03 | 切换/缺少/损坏 context,显式 --context |
前三者不影响 MCP;显式 context 报不支持,无凭据加载 |
| C04 | 环境引用、缺变量、空票据、字面 ${、无关 server 缺变量 |
只展开目标;不执行命令、不回写、不泄露原值 |
| A01 | 个人 Bearer、应用+ticket、应用+token、access_token 四种形式 | 请求使用配置值,初始化/分页/call 均一致,不出现另一套凭据 |
| A02 | 无认证头,分别访问匿名和需要认证的 server | 匿名成功;401 明确失败,不回退 context/OAuth |
| A03 | 两个认证头、大小写变体、重复等价头、空 Bearer/非法蓝鲸 JSON | 本地拒绝,零网络请求 |
| A04 | 401、403、跨地址重定向、TLS 失败 | 错误分类正确,不自动登录、换身份、跟随重定向或重试调用 |
| A05 | dry-run/check/help/verbose/错误/响应 headers;自定义 X-API-Key 及大小写变体 |
全部配置 header 名称可见、value 固定为 ***,不泄露前后缀或长度;响应同名字段和已知认证头脱敏;实际请求值不变,配置解析错误不回显密钥 |
| D01 | 根/server 帮助、server 裸命令 | 不联网、不消费 stdin;根帮助无配置也可用 |
| D02 | tool 帮助 | 只发现 Schema,不调用工具;网络/认证失败有明确错误 |
| D03 | 单页、多页、空 tools | 完整列表及数量正确,空列表成功,check 不调用 tool |
| D04 | 重复游标、游标循环、超过上限、中途失败、重名工具 | 不挂死、不伪报完整结果,返回失败与已完成阶段 |
| P01 | string/int/number/bool/array/object | 类型正确;中文、空字符串、false、0 和未传值彼此区分 |
| P02 | 超过 2^53 的整数、高精度小数、指数、前导零字符串;flags/内联/file/stdin | 参数数值不被有损转换,不把数字变为字符串;"00123" 保持字符串,超出支持范围明确拒绝 |
| P03 | required、enum、类型错误、重复 flag、未知 flag | 在 tools/call 前失败,不因默认值掩盖缺参 |
| P04 | 内联 JSON、文件、stdin、空/非法/非对象/重复字段输入 | 同一 arguments 契约;stdin 只读一次,失败不执行 tool |
| P05 | body 覆盖具名参数、保留名字段、复杂 Schema | 不隐式合并;原名字段能用 body 调用;不隐藏工具 |
| P06 | shell 敏感字符/换行/引号/反斜杠 | 参数是数据,不二次执行;文件输入可规避 shell 转义 |
| P07 | tools/list Schema/元数据、结果 structuredContent/content 元数据/_meta、envelope 和 jq 简单提取 |
大整数与高精度数值全链路不被静默改变;验证选定 SDK commit 的实际解码路径,不能只测试调用参数 |
| R01 | 协议版本矩阵、JSON/SSE 响应、旧版 session 生命周期 | 协商与请求格式正确,退出清理,无旧 transport 自动降级 |
| R02 | MCP 成功、HTTP 错误、JSON-RPC error、isError=true、畸形结果 | envelope/退出码正确,工具错误原结果保留 |
| R03 | 调用发出后超时/取消/结果丢失/畸形响应;已取得结果后 jq/输出失败 | 未取得完整可判定响应时输出 data.execution_state: "unknown";已知结果不因投影/输出失败改为未知;所有路径不重发工具,连接释放 |
| R04 | input_required(含空输入请求集合)、sampling/elicitation/异步响应 |
报能力未支持并保留可用输入要求/状态/任务标识;不伪报完成、不自动交互/轮询/接续,不提供 inputResponses/requestState 入口 |
| R05 | SDK 默认多轮补充输入、负载退避和连接/SSE 恢复场景 | 显式关闭会重发工具的 SDK 行为;一次 CLI 调用至多发送一次 tools/call,以 mock 收到的调用次数断言,不能只检查文案 |
| R06 | JSON 正常/错误响应、单条 SSE 事件、累计目录及分页上限的临界值和超限 | 固定上限在读取/累积过程中生效;大小超限报 mcp_response_too_large,分页超限报 mcp_discovery_incomplete;无成功截断,发现超限不调用工具,调用结果超限按 R03 处理 |
| O01 | jq 成功/编译失败/运行失败/失败 envelope | 先校验 jq,最终结果只投影一次,不隐藏错误或重发请求 |
| O02 | 离线 dry-run 及拒绝组合 | 零网络请求、全部配置 header 值 mask、标明未验证远端;拒绝纯 flags 的离线映射 |
| O03 | 发现成功后参数缺失/类型错误,或目录中无目标工具 | 仍按本地错误输出到 stderr,退出 1;不输出中间发现成功 envelope,不发送 tools/call,不标记执行结果未知 |
| I01 | 构建后的 CLI + 集成 mock | 从文件加载到 check/tools/help/call 路径完整执行,不访问真实环境 |
集成场景建议至少建立 MCP-001 基本流程、MCP-002 认证及隔离、MCP-003 参数/错误、MCP-004 分页/超时和 MCP-005 离线行为;按已有 harness 的 scenario 与 case 两种入口执行。mock server 需要支持真正的 MCP wire 交互;普通 HTTP 200 echo 不能代替协议测试。
11. 真实环境验收
真实联调单独人工触发,不纳入默认 CI,不把真实 URL/票据写入测试文件、日志或 Issue。
- 准备一个具备已知只读工具的远程 MCP server,分别配置四种蓝鲸票据形态(服务端允许的模式)。
- 执行
--check,验证连接/协议/目录完成;记录协议版本、工具数量和错误边界,不记录票据。 - 执行
--tools和指定 tool 的--help,确认发现结果、参数和 Schema 一致。 - 显式调用一个已知无副作用的工具,依据该工具声明的业务契约判断结果。
- 用缺失、失效、权限不足的测试身份验证 401/403,不把工具列表成功当作全部权限成功。
- 切换 bk-cli context 后重复检查,确认目标配置和认证不变。
若某种认证或协议版本缺少实际服务验证条件,应明确记录“仅 mock 通过 / 未做真实联调”,不能据此宣称该生产部署已支持。
12. 实施顺序与完成条件
建议在同一功能范围内按以下顺序推进:
- 配置选择、严格解析、环境引用、认证隔离与脱敏。
- MCP SDK/transport 接入,完成协议兼容 fixture、check 和 tools 分页。
- 动态帮助、参数映射、body 输入与显式调用。
- 输出/退出码、jq、离线 dry-run、超时/取消及结果未知处理。
- 补齐集成用例、使用文档及真实只读联调记录。
实际实现的验证门禁(当前仅写设计,以下尚未执行):
# 仓库有 .envrc 时先 source .envrc
make fmt
make lint
make test
make test-cover
make build
go test ./tests/integration/runner -count=1
make test-integration SCENARIO=MCP-001
# 再执行其他新增 MCP 场景,并至少用 CASE=实际文件路径执行一次新增 internal 包需要满足仓库现有 coverage 门槛;不得修改阈值来代替行为验证。只有改动影响相关检查时才重复执行,不运行真实生产工具作默认测试。
完成条件:
- 命令形态保持
bk-cli mcp <server> --check/--tools/--help与<server> <tool>。 - 第一阶段的全部范围有实现与测试;排除项没有通过隐式行为被引入。
- 配置和认证来源可解释,切换 context 不改变 MCP 身份;全部配置 header 的展示值统一 mask 为
***,实际请求值不变。 - tools 完整发现、复杂 arguments、保留名冲突和参数/Schema/结果/jq 简单提取的数字保真均有覆盖;SDK 固定版本或提交有验证记录。
- JSON/SSE/累计目录/分页固定上限已记录并测试;超限不返回截断成功。
- 不把 HTTP 成功、发现成功、
input_required或任务 ID 当作业务完成;失败退出、原始结果和data.execution_state: "unknown"可用于自动化判断,已知结果不因输出失败变为未知。 - 帮助/检查不执行业务工具,离线 dry-run 零网络请求,任何失败都不自动重发调用。
- 仓库门禁与 mock 集成通过;真实联调完成情况和缺口分别记录。
- 中英文 README、设计/使用文档和中文 MCP Skill 与命令保持一致。
参考与相关需求
- MCP Tools:工具目录、Schema、调用与错误语义。
- Input-required tool results:补充输入/接续协议;一期仅识别并报告不支持。
- MCP Authorization:认证能力与协议传输层的边界;本期只使用预配置凭据。
- 官方 Go SDK v1.8.0:初始协议与依赖评估基线;最终可固定包含修复的上游 commit,接入前验证版本、依赖、精度及隐式调用行为。
- 上游数字保真跟踪:structuredContent #1239、工具 Schema/元数据 #1311、服务端参数 Schema 路径 #1244。不同路径需分别核实;这些链接不表示相应修复均已合并、发布或完成本项目验证。
- Codestz/mcpx、MCPorter:动态 CLI、配置与调用的实现参考,不据此承诺相同功能范围。
- Claude Code 配置、Cursor 配置:配置位置与环境引用是客户端约定,没有统一的全行业文件路径。
- bk-cli auth login 支持个人 token 登录及使用 #29:已有个人 token 能力;本期复用认证约定,不复用 context 登录态。
- feat(output): 增加内置 --jq 结果筛选支持 #34:已有 jq 投影;本期复用最终输出能力。
- feat(output): 区分请求成功、业务成功与异步任务完成 #32:更广的请求/业务/异步结果语义另行演进;本期只定义新 MCP 命令自身可证明的结果,不改变其他命令契约。
Activity