MCP

ZCode 的 MCP 客户端:配置从用户、项目、插件与内置宿主四处合并,支持 stdio、http、sse 三种传输;会话一创建就开始连接,首次模型请求前注册工具;工具命名与图片归一、OAuth 两段式授权、官方 MCP 的身份头注入,以及工作区级 MCP 自动连接的安全含义。

作者 David更新于 36 篇(共 47 篇)

ZCode 只实现了 MCP 的客户端一侧,而且只用它的一项能力:把外部服务器暴露的工具接进 Agent 的工具表。端口 McpPort 只有连接、断开、探活、查状态、列工具、调工具、关闭这几个方法(apps/zcode-cli/packages/contracts/src/interfaces/mcp.port.ts:290),资源(resources)与提示词(prompts)都不在其中;客户端构造时只传了版本协商选项,没有声明 roots、sampling 之类的客户端能力(apps/zcode-cli/packages/adapters/src/mcp/index.ts:1042),代码里也没有处理 tools/list_changed 通知的地方,工具表在连接时一次定型。

代码分四层。apps/zcode-cli/packages/adapters/src/mcp 的 19 个文件约 6000 行是适配器,基于官方 TypeScript SDK @modelcontextprotocol/client 2.0.0(apps/zcode-cli/packages/adapters/package.json:105),管传输、连接、进程回收、OAuth 与官方鉴权;apps/zcode-cli/packages/core/src/mcp 把工具描述投影成运行时的工具条目,并归一结果里的图片;apps/zcode-cli/packages/core/src/runtime/methods/mcp.ts 决定何时连接、何时注册;bootstrap 把各路配置合并成一张服务器表。端口类型经 @zcode/contracts/mcp 子路径导出,指向的就是上面那个 mcp.port.ts。浏览器控制所用的 node_repl 也是一台 MCP 服务器,留给下一篇

怎么用

配置写在主配置文件里,用户级默认是 ~/.zcode/cli/config.jsonapps/zcode-cli/packages/adapters/src/config/file-config.adapter.ts:61),服务器放在 mcp.servers 下;features.mcp 缺省为真(apps/zcode-cli/packages/adapters/src/config/index.ts:289)。README 的示例(apps/zcode-cli/README.md:144):

{
  "features": {
    "mcp": true
  },
  "mcp": {
    "servers": {
      "filesystem": {
        "type": "stdio",
        "command": "npx",
        "args": ["-y", "@modelcontextprotocol/server-filesystem", "."],
        "cwd": ".",
        "timeoutMs": 30000
      },
      "docs": {
        "type": "http",
        "url": "https://mcp.example.com/mcp",
        "headers": {
          "Authorization": "Bearer <token>"
        }
      },
      "legacy-sse": {
        "type": "sse",
        "url": "https://mcp.example.com/sse",
        "enabled": false
      }
    }
  }
}

字段以 schema 为准(apps/zcode-cli/packages/adapters/src/config/schema.ts:46):

传输必填可选
stdiocommandargscwdenv
httpurlheadersoauth
sseurlheadersoauth
三者共有——enabledtimeoutMsprotocolVersionautolegacy2026-07-28

README 的字段说明(apps/zcode-cli/README.md:176)漏了 oauthprotocolVersion。解析前还有一层宽容的归一(schema.ts:331):不写 type 时有 command 就当 stdio、有 url 就当 httptype: "remote" 改成 http;旧字段 environmentenablehttp_headers 分别映射到 envenabledheaders,别家配置里的 timeoutstartup_timeout_sec 直接丢弃。单台服务器校验不过只跳过它并记一条诊断,不连累整份配置(schema.ts:488)。

在 CLI 里查看和管理用 /mcpapps/zcode-cli/packages/cli/src/command-center/handlers/mcp.ts:5):

命令作用
/mcp/mcp list/mcp status三者相同,逐台列出状态、传输、工具数与错误
/mcp connect <server>按已配置的条目重新连接,未配置的名字直接报错
/mcp disconnect <server>断开当前连接

TUI 侧栏的 MCP 分区每 5 秒刷新一次状态,出错时改为 10 秒(apps/zcode-cli/packages/tui/src/app-mcp-status.ts:5),最多列出 5 台(apps/zcode-cli/packages/tui/src/app-sidebar-mcp.tsx:16)。

配置从哪来

图表加载中…

项目配置的发现规则与 Hook 共用:从工作目录向上找到含 .git 的目录为止,沿途每一级的 zcode.json.zcode/config.json 都算(packages/shared/src/workspace-hook-config.ts:174workspace-hook-config.ts:335),找不到 .git 就只看工作目录本身。全局配置按系统默认、用户、项目、环境变量、命令行的顺序覆盖(apps/zcode-cli/packages/adapters/src/config/config-factory.ts:122),MCP 服务器却单列一条规则:同名时用户配置压过项目配置(config-factory.ts:391)。环境变量层没有任何 MCP 相关的变量(apps/zcode-cli/packages/adapters/src/config/env-config.adapter.ts:14),CLI 也没有对应参数,所以文件层实际只有用户与项目两级。README 说当前 CLI 不会自动发现插件以外的 mcp.json.mcp.jsonapps/zcode-cli/README.md:141),这没错,但它没提项目目录里的 zcode.json.zcode/config.json 同样会被读进来。

文件层之外还有三路,在 bootstrap 里合并(apps/zcode-cli/packages/bootstrap/src/app/runtime-config.ts:90):

  // Protocol session/create 传入的 mcp.servers 只包含 UI MCP 设置里的用户配置,
  // 不包含插件注册的 MCP。宿主内建 server 最后合并并保留其 identity,避免用户或第三方
  // 用同名配置劫持 mcp__node_repl__*;普通 plugin MCP 仍允许显式用户配置覆盖。
  const configuredMcpServers = {
    ...pluginMcpServers,
    ...(options.runtimeConfig?.mcp?.servers ?? configResult.config.mcp.servers),
    ...builtInMcpServers,
  };
  // ...
  // 产品决定 workspace MCP 开箱即用:project 作用域 MCP 默认 trusted,并自动连接。
  const untrustedProjectMcpServers = new Set<string>();
  const autoConnectMcpServers = omitMcpServers(
    configuredMcpServers,
    untrustedProjectMcpServers,
    cuaBridgeServerNames,
  );
  • 插件:服务器键被加上 plugin:<插件名>: 前缀(apps/zcode-cli/packages/adapters/src/plugins/mcp.ts:67),处在最底层,用户可以用同名条目覆盖。清单写法与变量展开见插件与官方市场
  • 桌面端:经 ZCode Protocol 创建会话时带上设置页的服务器表,有它就整体替换文件配置(runtime-config.ts:95)。这张表由桌面主进程读 ~/.zcode/cli/config.json、工作区 .zcode/config.json,并把 .agents/mcp.jsonmcpServers 当兜底来源(packages/desktop/src/main/mcpUserDirectory/index.ts:37mcpUserDirectory/index.ts:52);桌面端还会不落盘地把当前工作区路径追加到 @modelcontextprotocol/server-filesystem 的参数里(packages/services/src/session/mcpWorkspaceScope.ts:46),设置页也能把本机条目同步到远程工作区(packages/services/src/mcp-sync/mcpSync.ts:18);界面一侧的 MCP 类型另在 packages/shared/src/mcp.ts:24
  • 内置node_repl 最后合并,同名的用户或插件条目劫持不了 mcp__node_repl__*

omitMcpServers 的第二个参数本是“未受信任的项目服务器”名单,这里恒为空集,后文再谈它的含义。

三种传输与超时

createTransporttype 直接选传输,不存在失败后换一种再试的回退(apps/zcode-cli/packages/adapters/src/mcp/index.ts:1407):

传输实现要点
stdioProcessTreeStdioClientTransport,继承 SDK 的 StdioClientTransportcwd 相对工作目录解析,缺省即工作目录;stderr 走管道,末尾 4000 字符脱敏后才进失败日志(adapters/src/mcp/index.ts:97adapters/src/mcp/index.ts:1901
httpSDK 的 StreamableHTTPClientTransportheaders 作静态请求头,fetch 遵循 ZCode 的代理与 CA 设置
sseSDK 的 SSEClientTransport协议时代强制 legacy

README 说 stdio 进程继承 zcode 的环境再叠加 env,实际并非原样继承:先剔除一批 ZCode 私有的运行时变量(代理、证书、CUA 凭据等),再按网络设置重新写入代理与 CA 变量,最后才叠加配置里的 envapps/zcode-cli/packages/adapters/src/mcp/network.ts:9);如果当前 Agent 由 node 可执行文件启动,还会把它所在目录补进 PATH,让插件里写的 command: "node" 在远程主机上也能起来(network.ts:78)。清洗规则见执行边界

SDK 2.0 区分 modern 与 legacy 两个协议时代,由 protocolVersion 决定协商方式:2026-07-28 钉死 modern,legacy 或 sse 传输走 legacy,其余一律 autoapps/zcode-cli/packages/adapters/src/mcp/index.ts:1773)。auto 先发一次探测,预算是连接超时的一半、封顶 5 秒;钉死版本时没有回退,探测就是唯一的握手,所以用满整个连接超时(adapters/src/mcp/index.ts:1815)。协商失败单独归为 protocol_negotiation_failed,不会再被误报成“网络不可达”(adapters/src/mcp/index.ts:1232)。各项超时:

数值出处
连接握手、列工具各 30000 毫秒,可被 timeoutMs 覆盖adapters/src/mcp/index.ts:93adapters/src/mcp/index.ts:1057adapters/src/mcp/index.ts:1067
工具调用30000 毫秒,收到进度通知时重新计时apps/zcode-cli/packages/core/src/mcp/index.ts:37apps/zcode-cli/packages/adapters/src/mcp/index.ts:703
存活探测 ping5000 毫秒与 timeoutMs 取小adapters/src/mcp/index.ts:95adapters/src/mcp/index.ts:401
会话启动时等 OAuth15000 毫秒apps/zcode-cli/packages/core/src/runtime/methods/mcp.ts:11
OAuth 授权事务300000 毫秒apps/zcode-cli/packages/adapters/src/mcp/oauth-interactive.ts:38
连接池空闲回收30000 毫秒apps/zcode-cli/packages/adapters/src/mcp/pool.ts:16

连接生命周期

图表加载中…

何时连接AgentRuntime 构造的最后一步就调用 startMcpStartupapps/zcode-cli/packages/core/src/runtime/agent-runtime.ts:307),在后台对整张服务器表发起连接,并把等待 OAuth 的时间压到 15 秒;注释说以前没人授权时会等满 5 分钟,模型请求迟迟发不出去(apps/zcode-cli/packages/core/src/runtime/methods/mcp.ts:73)。回合循环在组装工具表之前等它完成并注册工具(apps/zcode-cli/packages/core/src/runtime/methods/turn-loop.ts:106),这就是 README 所说的“在第一次模型请求前注册”(apps/zcode-cli/README.md:180);手动压缩与插件引用提醒也会先等它(apps/zcode-cli/packages/core/src/runtime/methods/compact-active.ts:253)。注册只做一次,initializeMcpmcpToolsRegistered 标记保证幂等(methods/mcp.ts:126)。连接开始得比多数人以为的更早:TUI 主界面挂载后(不需要登录时)立即开始轮询 MCP 状态(apps/zcode-cli/packages/tui/src/app-view.tsx:110),轮询经 getApp() 把会话建出来(apps/zcode-cli/packages/cli/src/tui-prompt-handler-queries.ts:61),所以打开 TUI、还没输入第一句话,配置里的 stdio 命令就已经跑起来了。

逐台连接connectConfiguredServers 是“替换”语义:先断开不在新表里的服务器,再并行连接其余各台(adapters/src/mcp/index.ts:253)。单台的 openServerConnection 依次建传输、构造 Client、带超时 connect、带超时列工具,把工具描述规范化后存进记录,最后挂上 oncloseadapters/src/mcp/index.ts:1002)。

失败。任何一步出错都进 failConnection:关掉客户端与传输,记录置为 failed、清空工具,并附一个 failureKindadapters/src/mcp/index.ts:1327)。分类共 19 种(packages/shared/src/zcode-protocol/index.ts:663),常见的是 process_start_failednetwork_unreachableconnection_timeoutprotocol_negotiation_failedtool_list_failedoauth_authorization_failed。整次启动的异常也被吞掉、退化成空快照(apps/zcode-cli/packages/core/src/runtime/methods/mcp.ts:100),MCP 出问题不会挡住回合,只是少了工具。

断连与重连onclose 把意外断连记成 disconnected,分类 unexpected_disconnect,并把 stdio 子进程的退出码与 stderr 尾部写进日志(apps/zcode-cli/packages/adapters/src/mcp/index.ts:1119)。恢复发生在下一次调用(adapters/src/mcp/index.ts:455):

    // stdio MCP 子进程死亡后(如 node_repl 被异步错误击穿),此前没有任何恢复路径:
    // 连接只在 session 创建时建立一次,session resume 也不重建,该会话的工具从此永远失败。
    // 这里在调用前对已断连的 record 重连一次;server 进程内状态(如 REPL 变量)不可恢复,
    // 但工具本身恢复可用。
    const disconnected = this.records.get(request.serverName);
    if (disconnected && disconnected.status.status === "disconnected") {
      await waitWithinMcpDeadline(
        this.reconnectForCall(request.serverName, disconnected.config),
        deadline,
        timeoutMessage,
        options.signal,
      );
    }

SDK 可能在 onclose 派发之前先抛出裸的 Not connected,对这一种错误也重连后重试一次(adapters/src/mcp/index.ts:497)。HTTP/SSE 服务停掉时没有常驻流可断,状态会一直停在 connected,所以桌面端设置页刷新时带上 revalidate,先 ping 一下再决定是否重连(apps/zcode-cli/packages/adapters/src/mcp/pool.ts:111)。

关闭。主动关闭先摘掉 onclose,再按进程树回收 stdio 服务器,最后才调 SDK 的 close,因为 SDK 只保证直接子进程退出,npx 之类包装器拉起的后代会残留(adapters/src/mcp/index.ts:1625)。POSIX 上依次发 SIGINT、等 250 毫秒、SIGTERM、等 750 毫秒、SIGKILL(apps/zcode-cli/packages/adapters/src/mcp/process-tree.ts:196);Windows 上启动时把子进程放进一个“句柄关闭即终止”的 Job Object(apps/zcode-cli/packages/adapters/src/mcp/windows-job-object.ts:4),回收时再用 taskkill /T /F 兜底,超时 2 秒(process-tree.ts:173)。独立 CLI 的会话自己拥有适配器,关会话时它与执行端口、浏览器会话并行关闭,各给 6 秒(apps/zcode-cli/packages/bootstrap/src/app/session-facade.ts:301session-facade.ts:82)。

桌面端的连接池。app-server 进程里是一个连接池,每个会话领一份租约(apps/zcode-cli/packages/bootstrap/src/zcode-protocol-entrypoint.ts:271)。连接默认按会话隔离,只有声明了 isolation: "workspace" 的服务器才在同一工作区的多个会话间共享(pool.ts:447),租约全部释放后再等 30 秒才真正关闭。isolation 只由宿主生成,用户配置的 schema 不接受它。桌面端还用 McpTelemetryTracker 跟踪每个 stdio 子进程的启动、崩溃与资源占用(apps/zcode-cli/packages/adapters/src/mcp/telemetry.ts:41),相关上报见遥测、调试与提示词轨迹

两个命令的边界/mcp connect 只在端口层重连(session-facade.ts:361),工具注册却只在会话里做一次(apps/zcode-cli/packages/core/src/runtime/methods/mcp.ts:138):从代码看,启动时就失败的服务器事后连上,它的工具不会补进当前会话,要换新会话才出现。/mcp disconnect 也不是封禁,记录变成 disconnected 之后,模型下一次调用它的工具就会触发上面的自动重连。

工具命名与模型契约

模型看到的名字在适配器里生成(apps/zcode-cli/packages/adapters/src/mcp/descriptor.ts:14):

  const toolName = typeof record.name === "string" ? record.name : "unknown";
  return {
    serverName,
    toolName,
    name: `mcp__${sanitizeMcpName(serverName)}__${sanitizeMcpName(toolName)}`,
  // ...
function sanitizeMcpName(name: string): string {
  const sanitized = name.replace(/[^a-zA-Z0-9_-]/g, "_").replace(/_+/g, "_");
  return sanitized.length > 0 ? sanitized : "unknown";
}

规则就两条:[a-zA-Z0-9_-] 以外的字符换成下划线,连续下划线压成一个;清洗后为空用 unknown。没有长度上限,也没有撞名检测,core 里另有一份同样的实现(apps/zcode-cli/packages/core/src/mcp/name.ts:14)。插件服务器键里的冒号同样被替换,plugin:foo:bar 的工具于是叫 mcp__plugin_foo_bar__<tool>。两个工具清洗后同名时,后注册的覆盖先注册的,只留一条 console.warnapps/zcode-cli/packages/core/src/tool/registry.ts:46)。对照 MiniMax Code 的 MCP:那边限长 80、撞名加后缀,并用持久名册防止新来源继承旧名字。

createMcpToolEntry 再把描述投影成工具条目(apps/zcode-cli/packages/core/src/mcp/index.ts:102):

MCP 描述ZCode 工具条目
description原样放进 metadata.description,随工具契约交给模型
inputSchema强制 type: "object";缺失时给空 properties 并允许任意附加字段(descriptor.ts:30
outputSchema读入但不用,条目统一挂 McpToolOutputJsonSchemacontentstructuredContentisError_meta
readOnlyHintdestructiveHint决定 readOnlydestructive;风险等级只读为 low、破坏性为 high、其余 medium(apps/zcode-cli/packages/core/src/mcp/index.ts:116
idempotentHint与只读任一成立即 concurrentSafe,可与其他工具并发
openWorldHint读入,未被使用
服务器 node_repljs例外:副作用范围记为 system、风险 high,其余 MCP 一律记为 networkapps/zcode-cli/packages/core/src/mcp/index.ts:115

不论注解怎么写,needsApproval 恒为真(apps/zcode-cli/packages/core/src/mcp/index.ts:123)。超时不许调用方覆盖;结果预算是给模型 50000 字节、内联 100000 字节,超出从头截断(apps/zcode-cli/packages/core/src/mcp/index.ts:144)。权限键是 mcp,规则可以按工具名、输入与网络目标匹配(apps/zcode-cli/packages/core/src/mcp/index.ts:189)。于是 build 与 edit 模式下 MCP 调用总要询问(有允许规则时除外);Plan 模式反倒例外,非破坏性的 MCP 工具直接放行(apps/zcode-cli/packages/core/src/permission/service.ts:417),细节见权限模式与规则。会话级禁用名单(如 CLI 的 --disallowed-tools)在注册时就把命中的 MCP 工具滤掉(apps/zcode-cli/packages/core/src/mcp/index.ts:78)。

结果与图片

formatMcpToolResult 把结果转成模型内容(apps/zcode-cli/packages/core/src/mcp/index.ts:369):文本块原样保留;图片块变成带 data URL 的图片;音频不交给模型,只留一行带 MIME 类型的“已省略”说明;内嵌资源被整段序列化成文本;非空的 structuredContent 另起一块附在后面;isError 为真时加前缀 MCP tool returned an error:,除非结果在 _meta 里声明了 message-only

图片还要过一道归一(apps/zcode-cli/packages/core/src/mcp/image-normalization.ts:27)。handler 必须在这一步处理,因为结果预算只看得到图片的占位文本,大图原样进请求会把 provider 的请求体撑爆(apps/zcode-cli/packages/core/src/mcp/index.ts:245)。内联上限是 base64 后 200 KiB(image-normalization.ts:21),超限时:

  • 普通 MCP:原图存成会话级工件,模型看到的是一段说明加工件路径与 URI(image-normalization.ts:170);没配工件存储就只留“已省略”说明。
  • 宿主 node_repljs:先用统一的图片端口压到 200 KiB 以内、长边不超过 2000 像素(image-normalization.ts:133image-normalization.ts:25),压不下来再走上面的路;模型显式截的浏览器截图另存一份原图,并在图片之后补一行 Browser screenshot saved to: 加绝对路径,保持图片在前的顺序(image-normalization.ts:80)。

官方 Computer Use 的“帧”另有一条精确保真的路径,但开源版的判定函数恒为假(packages/zcode-cua/frame-contract.js:7),这条分支不会生效。

OAuth

http 与 sse 服务器支持两种 OAuth。client_credentials 直接交给 SDK 的 ClientCredentialsProvideradapters/src/mcp/index.ts:1547)。authorization_code 则是隐式默认:只要没写 oauth、请求头里也没有 Authorization,就按授权码流程准备,由服务端的 401 与发现机制触发(adapters/src/mcp/index.ts:1851)。它拆成两段:

阶段做什么出处
被动AuthProvidertoken() 读凭据,临近过期 30 秒内主动刷新;401 时 onUnauthorized() 刷新,不做发现、不注册客户端、不开回调端口apps/zcode-cli/packages/adapters/src/mcp/oauth-provider.ts:34apps/zcode-cli/packages/adapters/src/mcp/oauth-credentials.ts:177
刷新跨进程文件锁单飞,最多等 45 秒,避免多个进程拿同一个 refresh token 撞上轮换检测apps/zcode-cli/packages/adapters/src/mcp/oauth-refresh.ts:35oauth-refresh.ts:69
交互抢授权租约;领头者 listen(0) 起本机回调,没配静态 clientId 时每次重新动态注册客户端,用 SDK 的 auth() 走完发现、授权、换码;跟随者每 500 毫秒轮询结果oauth-interactive.ts:77oauth-interactive.ts:121
扩权403 带 insufficient_scope 时,scope 取配置、现有 token 与 challenge 三者并集,强制重新授权adapters/src/mcp/index.ts:1274
存储~/.zcode/v2/credentials.json,键前缀 mcp:oauth: 加服务器名、地址与授权参数的哈希,CLI 与桌面端共用apps/zcode-cli/packages/adapters/src/auth/shared-credentials.ts:289apps/zcode-cli/packages/adapters/src/mcp/oauth.ts:16

回调路径缺省为 /oauth/callback/mcp/<server>oauth-interactive.ts:459)。领头者只发布授权地址,不自动打开浏览器,注释说那会打断当前操作(oauth-interactive.ts:405)。地址写进服务器状态的 authorization.authorizationUrl,由桌面端设置页展示;CLI 的 /mcp 输出与 TUI 侧栏都不显示它(apps/zcode-cli/packages/cli/src/command-center/handlers/mcp.ts:97app-sidebar-mcp.tsx:104),所以从代码看,纯 CLI 下需要浏览器授权的服务器没有现成的授权入口。

官方 MCP

“官方 MCP”指由 ZCode 自家后端提供、按用户的 ZCode 登录与 Coding Plan 套餐鉴权的 MCP 服务。只有插件的 MCP 配置(.mcp.json 或清单里的 mcpServers)能声明它;用户配置的 schema 是严格模式,不认这个字段,写了整台服务器都会因校验失败被跳过(apps/zcode-cli/packages/adapters/src/config/schema.ts:94)。插件里的声明方式是:auth 写成 { "type": "zcode_official", "provider": "jwt_token" },传输只能是 http 或 stdio,不能与 oauth 同时出现,静态请求头里也不许带身份头(apps/zcode-cli/packages/adapters/src/plugins/mcp.ts:190plugins/mcp.ts:267)。服务器归属(插件 id、原始键)由加载器生成,清单里写了也会被覆盖(apps/zcode-cli/packages/adapters/src/plugins/mcp-official-auth.ts:39)。身份头是这几个(packages/shared/src/official-mcp-auth.ts:18):

export const OFFICIAL_MCP_AUTH_HEADER_NAMES = {
  authorization: "Authorization",
  codingPlanAuthorization: "X-Bigmodel-Authorization",
  targetType: "Bigmodel-Target-Type",
  organization: "Bigmodel-Organization",
  project: "Bigmodel-Project",
} as const;

Authorization 携带 ZCode 登录 JWT,X-Bigmodel-Authorization 携带 MaaS 登录 JWT,套餐类型是 PERSONALTEAM,团队套餐再成对附上组织与项目(packages/services/src/official-mcp/officialMcpCredentials.ts:397)。信任判定只有一条:目标 origin 必须逐字等于当前 ZCode API 的 https origin 且不带用户名密码,另有环境变量 ZCODE_OFFICIAL_MCP_DEV_TRUSTED_ORIGINS 只放开本机 http 回环地址供自测(official-mcp-auth.ts:190official-mcp-auth.ts:138)。两种传输的投递通道不同:

  • http:适配器包一层 fetch,逐请求校验 origin、覆盖写入身份头,禁止跟随重定向;带了凭据的请求遇到 401 只重试一次,401、403 与 3xx 各自归类报错(apps/zcode-cli/packages/adapters/src/mcp/official-auth.ts:95official-auth.ts:297)。
  • stdio:请求由插件进程自己发出,身份头随每条出站协议消息放进 _metacom.zcode/official-mcp-auth 键(apps/zcode-cli/packages/adapters/src/mcp/stdio-transport.ts:81adapters/src/mcp/index.ts:1428),拿不到时也下发 ok: false 与原因。

Agent 进程不是身份的权威:身份头经反向请求 interaction/requestOfficialMcpAuthHeaders 向宿主索取,凭据不落运行时配置(apps/zcode-cli/packages/bootstrap/src/zcode-protocol/official-mcp-auth-port.ts:1)。独立 CLI 没有这个端口,也没有可信 origin 表,http 形态的官方 MCP 因此在第一个请求就以 official_auth_unavailable 失败(adapters/src/mcp/index.ts:1487),stdio 形态收到的则是同名原因(adapters/src/mcp/index.ts:602)。只有 http 形态的官方工具被标记为 official,界面才会信任结果里的 quota_exceededcoding_plan_required 并弹出相应提示(adapters/src/mcp/index.ts:1079packages/shared/src/official-mcp-tool-error.ts:20);stdio 结果由插件自己产出,可以伪造,故不标记。开源仓库里没有带这种声明的插件实体,官方插件定义表中的 image-search 注明“认证仍由官方 MCP adapter 注入”(apps/zcode-cli/packages/bootstrap/src/app/official-plugin-definitions.ts:186)。账号与套餐见账号、Coding Plan 与闲时计划

工作区级 MCP 的安全含义

根目录 NOTICE 的风险表里有这样一句(NOTICE.md:19):

当前 Agent 运行配置将工作区级 MCP 纳入自动连接范围;启用 MCP 并启动运行时时,可能使用配置中的命令、环境变量、认证头或 OAuth 连接服务。

对应的代码就是上面那个恒为空的 untrustedProjectMcpServersapps/zcode-cli/packages/bootstrap/src/app/runtime-config.ts:110)。把几处事实连起来看:

  • 一个仓库只要在 zcode.json.zcode/config.json 里写了 mcp.servers,打开会话(TUI 一挂载就算)时其中的 stdio command 就会在工作区里以当前用户身份启动,没有任何确认。
  • 工具审批管的是 tools/call;连接、协商、列工具和 OAuth 刷新都不经过它(NOTICE.md:42)。工作区 Hook 那套按摘要信任的机制也不覆盖 MCP 配置(NOTICE.md:18),见生命周期 Hooks 与工作区信任
  • 旧设计的痕迹还在:状态枚举里有 untrustedmcp.port.ts:110),状态投影会给它配上“Project MCP server requires explicit connection before use.”的说明(apps/zcode-cli/packages/bootstrap/src/mcp-config.ts:211),连接池刷新时也会跳过它(pool.ts:130),只是名单为空,这些分支走不到。
  • 能用的开关:features.mcp: false 全关;由于同名时用户压过项目,在用户配置里写一个同名且 enabled: false 的条目,从代码看就能压住项目里的那一台。

子 Agent 不自己连 MCP,只借用父会话启动快照里已连上的服务器,能调工具,改连接的操作一律被拒(apps/zcode-cli/packages/core/src/subagent/borrowed-mcp-port.ts:34);profile 声明必需、父会话却没连上的服务器会直接报错(apps/zcode-cli/packages/core/src/runtime/methods/subagent.ts:596),“必需”按模型可见名做不分大小写的包含匹配(apps/zcode-cli/packages/core/src/subagent/mcp-config.ts:4apps/zcode-cli/packages/core/src/mcp/name.ts:19)。细节见子 Agent

下一篇:node_repl、Browser Use 与 Computer Use——浏览器控制为什么只给模型一个 js 工具,宿主进程、bridge 与 Playwright 怎样串起来,以及开源版里 Computer Use 的占位实现。

本页目录