Book 第十八章:运行时辅助工具到底在改什么
第四部分:扩展与协作

第十八章:运行时辅助工具到底在改什么

看 Config、Worktree、MCP Resource 这一组运行时辅助工具怎样改运行边界。

1. 为什么这一组要单独拆

这一组工具有个共同特点:

  • 它们通常不直接替用户“完成业务任务”
  • 但它们会直接改 Claude Code 自己所处的运行环境

也就是说,它们不是业务工具,而是 runtime helper tools

具体看:

  • ConfigTool 在改 Claude Code 的配置和宿主状态
  • EnterWorktree / ExitWorktree 在改 Claude Code 当前工作的隔离环境
  • ListMcpResources / ReadMcpResource 在改 Claude Code 理解和读取外部上下文的方式

所以这一组工具真正体现的,不是“Claude 会不会多几个命令”,而是:

Claude Code 会不会把自己的运行边界也纳入正式工具协议。

2. 先说结论

我对这组工具的总体判断是:

Claude Code 把“改宿主配置”“切执行沙箱”“读扩展资源”都做成了 runtime 一级能力,而不是隐藏在 CLI 命令或内部代码里的私有逻辑。

换句话说,它在明确做三件事:

  • 让模型可以安全地改宿主配置,但只改白名单
  • 让模型可以进入/退出隔离工作副本,但只在明确用户要求时做
  • 让模型可以把 MCP server 的资源层当成独立信息通道,而不是只会调 MCP tools

这说明 Claude Code 的“工具系统”并不只服务业务动作,也服务 Claude Code 自己的运行时治理。

3. 源码锚点

这次主要看的源码是:

  • src/tools/ConfigTool/ConfigTool.ts
  • src/tools/ConfigTool/prompt.ts
  • src/tools/ConfigTool/supportedSettings.ts
  • src/tools/EnterWorktreeTool/EnterWorktreeTool.ts
  • src/tools/EnterWorktreeTool/prompt.ts
  • src/tools/ExitWorktreeTool/ExitWorktreeTool.ts
  • src/tools/ExitWorktreeTool/prompt.ts
  • src/tools/ListMcpResourcesTool/ListMcpResourcesTool.ts
  • src/tools/ReadMcpResourceTool/ReadMcpResourceTool.ts

配套还看了:

  • src/tools.ts
  • src/utils/worktree.ts
  • src/services/mcp/client.ts
  • src/utils/mcpOutputStorage.ts

4. 总体结构图

flowchart TD
    A["Claude Code runtime helper layer"] --> B["ConfigTool"]
    A --> C["Worktree tools"]
    A --> D["MCP resource tools"]

    B --> B1["支持设置白名单 registry"]
    B1 --> B2["只读 get / 需审批 set"]
    B2 --> B3["同步写 global config 或 settings.json"]
    B3 --> B4["必要时立即同步 AppState"]

    C --> C1["EnterWorktree"]
    C --> C2["ExitWorktree"]
    C1 --> C3["创建隔离 worktree / hook-based sandbox"]
    C3 --> C4["切换 cwd、清缓存、保存 session state"]
    C2 --> C5["keep 或 remove"]
    C5 --> C6["恢复原目录、恢复 prompt/cache/session 状态"]

    D --> D1["MCP server capabilities.resources"]
    D1 --> D2["ListMcpResources"]
    D1 --> D3["ReadMcpResource"]
    D2 --> D4["列资源索引"]
    D3 --> D5["读具体 resource URI"]
    D5 --> D6["文本直接回灌 / 二进制落盘后回引用"]

这张图里最重要的一点是:

这些工具都不是“多做一步工作”,而是在改 Claude Code 后续如何运行、在哪运行、能看到什么上下文。

5. ConfigTool:不是“随便改配置”,而是白名单化配置编辑器

ConfigTool 看起来像个简单设置工具,但实际设计得很谨慎。

它不是开放一个任意 key-value 写口,而是:

  • 先通过 supportedSettings.ts 注册支持项
  • 动态生成 prompt 文档
  • 再在运行时做类型、选项、异步校验、权限审批

所以它的本质不是“通用配置修改器”,而是:

受治理的配置白名单工具。

5.1 支持哪些配置,不是代码里写死一堆 if/else

SUPPORTED_SETTINGS 这个 registry 是整套设计的核心。

每个设置项至少有:

  • source
  • type
  • description
  • optionsgetOptions
  • 可选 appStateKey
  • 可选 validateOnWrite
  • 可选 formatOnRead

这让 ConfigTool 具备一个很好的性质:

  • 可支持的配置项是声明式的
  • prompt 文档可以从 registry 自动生成
  • 读写逻辑可以统一复用

这对 C# 版特别值得抄,因为这类工具最怕越写越碎。

5.2 它把“读”和“写”当成两种不同风险等级

ConfigTool.checkPermissions() 的策略很清楚:

  • 读配置:自动 allow
  • 写配置:必须 ask

isReadOnly(input) 也直接根据有没有 value 来决定。

这说明 Claude Code 对这类宿主级变更的态度是:

  • 观察可以宽松
  • 修改必须显式审批

这不是业务数据,而是在改 agent 自己的工作环境。

5.3 它不只是改文件,还会同步运行时状态

这部分是 ConfigTool 最容易被低估的地方。

写配置以后,它不只是落盘,还会在必要时立刻同步到 AppState

  • verbose
  • mainLoopModel
  • thinkingEnabled
  • remoteControlAtStartup

还有 voice 相关设置,会触发:

  • settings change detector

也就是说,ConfigTool 不是“改完下次启动生效”,而是尽量让当前会话立刻生效。

5.4 它对特殊配置项有专门 runtime 逻辑

这里面至少有几个很典型的例子:

  • model 会走动态可选项和异步模型校验
  • remoteControlAtStartup 支持 "default",语义是删 key 回退到平台默认
  • voiceEnabled 不只是布尔校验,还会做运行时环境探测

比如开 voice 时,它会额外检查:

  • growthbook gate
  • 登录状态
  • 录音能力
  • 系统依赖
  • 麦克风权限

这说明 Claude Code 的配置项不是“静态偏好值”,有些已经和运行时前提绑定了。

5.5 它还会按 feature / user type 动态裁掉配置项

supportedSettings.ts 能看到:

  • 有些配置只有 USER_TYPE === ant 才出现
  • 有些配置受 VOICE_MODE 控制
  • 有些配置受 BRIDGE_MODE 控制
  • 有些配置受 push notification 相关 gate 控制

也就是说,Claude Code 不是“全量配置永远暴露给模型”,而是会按当前产品能力裁剪可见面。

6. EnterWorktree / ExitWorktree:worktree 不是 git 小技巧,而是受控隔离环境

这两个工具非常能体现 Claude Code 的边界意识。

它不是让模型随便 git worktree add,而是做了一个正式的 worktree runtime。

6.1 它只在用户明确提到 worktree 时使用

EnterWorktree 的 prompt 写得非常死:

  • 只有用户明确提到 “worktree” 才能用
  • 用户只是说切分支、建分支、改功能,都不要自动用它

这说明 Claude Code 认为 worktree 是强语义操作,不该“自作主张替用户上隔离环境”。

6.2 它不是简单 git worktree add,而是完整会话切换

EnterWorktreeTool.call() 干的事情包括:

  • 检查当前 session 是否已经在 worktree
  • 先解析到主仓库根,而不是在嵌套 worktree 里继续建
  • 校验 slug
  • 创建或恢复 worktree session
  • process.chdir(...)
  • setCwd(...)
  • 记录 originalCwd
  • 保存 worktree state
  • 清理 system prompt sections
  • 清理 memory file caches
  • 清理 plans directory cache

这说明在 Claude Code 眼里,进入 worktree 不是“新建一个目录”,而是:

整个会话的执行上下文切到了另一个项目副本。

6.3 worktree 名称验证做得很严格

validateWorktreeSlug() 这块非常细。

它专门防:

  • 绝对路径
  • .. 路径逃逸
  • 空 segment
  • 非白名单字符
  • 过长名称

而且还对嵌套 slug 做了 flatten 处理,避免:

  • git ref 的目录/文件冲突
  • worktree 目录嵌套导致父 worktree 删除时误删子 worktree

这说明 Claude Code 不是把 worktree 当“给懂 git 的用户自己兜底”,而是把路径安全和 repo 结构坑都提前收了。

6.4 它支持 hook-based worktree,不强绑 git

这点非常值得注意。

虽然默认是 git worktree,但 prompt 和实现都明确提到:

  • 如果配置了 WorktreeCreate / WorktreeRemove hooks
  • 那也可以走 hook-based 隔离创建

这意味着 Claude Code 的抽象其实不是 GitWorktreeTool,而是更高层的:

Isolated Workspace Tool

git 只是默认后端。

6.5 它会主动做 post-creation setup

utils/worktree.ts 里在新建 worktree 之后还会额外做很多事:

  • 复制 settings.local.json
  • 尝试配置 hooksPath
  • 可选 symlink 大目录,避免磁盘爆炸
  • 复制 .worktreeinclude 里指定的 gitignored 文件
  • 在需要时补提交归属相关 hook

这些都不是 git worktree 原生会帮你做的。

这说明 Claude Code 不是只想“建一个副本”,它想建的是:

一个尽量像主项目环境、但又被隔离开的可工作副本。

7. ExitWorktree:不是简单 cd 回去,而是带安全语义的退出流程

ExitWorktree 也不是反向的 chdir,而是完整的退出协议。

7.1 它只处理“本 session 创建的 worktree”

这一点很重要。

它专门声明自己不会碰:

  • 手动建的 worktree
  • 以前 session 建的 worktree
  • 当前根本没调过 EnterWorktree 的目录

也就是说,它不是一个“泛 worktree 管理器”,而是:

本次 Claude session 所创建隔离环境的退出器。

7.2 keepremove 是两种完全不同的语义

输入参数里最关键的是:

  • action: "keep" | "remove"

这不是 UI 选项而已,而是真正的状态分叉:

  • keep:保留目录和分支,只是当前 session 退出
  • remove:销毁目录和分支

这也是为什么 isDestructive(input) 直接看 action === 'remove'

7.3 remove 默认是 fail-closed 的

这部分设计得很稳。

如果要 remove,而且没有 discard_changes: true,它会先统计:

  • 未提交文件数
  • worktree 分支上的新增 commit 数

只要有变化,就拒绝删除,并明确要求:

  • 先确认
  • 再带 discard_changes: true 重调

更关键的是,如果状态根本无法可靠判断,它也会拒绝,走 fail-closed。

也就是说,Claude Code 宁可麻烦一点,也不愿意在不确定时误删工作。

7.4 退出时会对 session 做一次完整恢复

退出时除了 keepWorktree() / cleanupWorktree() 本身,还会调用:

  • restoreSessionToOriginalCwd(...)

把这些东西一起恢复:

  • cwd
  • originalCwd
  • 必要时 projectRoot
  • hooks config snapshot
  • worktree state
  • system prompt section cache
  • memory file cache
  • plans directory cache

这说明 worktree 对 Claude Code 影响的不只是当前目录,而是:

  • prompt 生成
  • memory 读取
  • plan 存放
  • 项目身份识别

所以退出时必须做真正的环境还原。

8. MCP resource 工具:Claude Code 把 resource 层当成独立上下文通道

ListMcpResourcesReadMcpResource 这一组特别有意思,因为它们说明 Claude Code 对 MCP 的理解不是“只有 tool call”。

它明确把 MCP server 的能力拆成了几层:

  • tools
  • commands / skills
  • resources

其中 resources 是独立通道,不和普通 tools 混成一团。

8.1 资源工具不是基础工具池常驻成员

这点很关键。

tools.ts 里:

  • ListMcpResourcesTool
  • ReadMcpResourceTool

虽然出现在 base tools 里,但又会被 specialTools 过滤掉。

真正把它们加回来的地方是在 MCP client 聚合阶段:

  • 只有某个 server capabilities.resources 为真
  • 才会把 resource tools 注入工具池
  • 而且只注入一份

这说明 Claude Code 的设计是:

resource 工具不是“内建工具永远存在”,而是 MCP 资源能力出现时才激活的桥接工具。

8.2 ListMcpResources 是资源目录,不是资源内容

这个工具做的事情很干净:

  • 可选按 server 过滤
  • 对每个已连接 server 调 resources/list
  • 把 server 名补到每个 resource 上

还有一个很好的设计点:

  • 某一个 server 拉资源失败,不会把整次列表拉取都弄挂

也就是说,它把 resource discovery 当成“尽量给出可用总览”,而不是一报错就全盘失败。

8.3 它依赖缓存,但缓存是带失效策略的

fetchResourcesForClient 用的是:

  • LRU cache

同时源码里明确写了:

  • onclose 会清缓存
  • resources/list_changed 也会清缓存
  • ensureConnectedClient() 会在需要时重连

这说明 Claude Code 在这层做的是:

  • 尽量复用资源目录结果
  • 但尽量不返回已断连连接上的脏缓存

8.4 ReadMcpResource 说明 resource 不是普通 JSON 字符串

这个工具做得非常重要的一点,是它不把 resource 结果一股脑塞进上下文。

处理逻辑大致是:

  • 文本资源:直接回传 text
  • 二进制资源:base64 解码后落盘
  • 根据 mime type 推断扩展名
  • 返回磁盘路径和一段说明文字

这和 WebFetchTaskOutput 的处理方式很像,核心目标都是:

不要把大 blob 或 base64 直接塞爆模型上下文。

8.5 它甚至会针对 mime type 选择后续可消费格式

mcpOutputStorage.ts 里专门把 mime type 映射到扩展名:

  • pdf
  • json
  • csv
  • png
  • jpg
  • docx
  • xlsx

这样后面 Read 或其他本地工具就能按文件类型继续消费。

这说明 Claude Code 在设计 resource 层时,考虑的不是“这次读完了”,而是“后续还能继续处理这个 artifact”。

9. 这一组工具真正体现了 Claude Code 的什么设计思路

把这三组东西放一起看,会发现一个共同模式:

9.1 它不把 runtime 边界藏起来

很多系统会把这些能力放在宿主私有逻辑里:

  • 改配置只能 CLI 命令
  • worktree 只能用户自己建
  • MCP resource 只是 SDK 底层细节

Claude Code 不这么做。

它把这些 runtime 边界显式工具化了。

9.2 它允许模型操作宿主,但只允许操作被治理过的宿主

表现就是:

  • ConfigTool 只能改白名单设置
  • EnterWorktree 只能在明确用户要求时用
  • ExitWorktree(remove) 必须显式确认
  • ReadMcpResource 二进制必须落盘而不是直接塞上下文

这是一种很稳的产品判断:

不是不让模型碰宿主,而是让模型只碰“被规范过的宿主入口”。

9.3 它对 prompt / cache / cwd / projectRoot 的联动非常敏感

特别是 worktree 这组工具,清楚地说明 Claude Code 知道这些东西是绑在一起的:

  • cwd
  • projectRoot
  • system prompt sections
  • memory file caches
  • plan directory

这也是为什么 worktree 进出都要清 cache、改 state、存 session snapshot。

10. Claude Code 做了哪些额外处理

把零散细节收一下,大概有这些:

  • ConfigTool 从 registry 动态生成 prompt 文档,不手写说明
  • ConfigTool 对读和写用不同权限策略
  • ConfigTool 有类型校验、枚举校验、异步校验、运行时前置检查
  • ConfigTool 会把部分设置立即同步到 AppState,避免“改了但当前会话没反应”
  • EnterWorktree 只允许显式用户请求触发
  • EnterWorktree 会把当前 session 真的切进隔离副本,并清一整套依赖 cwd 的缓存
  • EnterWorktree 不只支持 git,还支持 hook-based worktree backend
  • ExitWorktree(remove) 会做变更统计,并在状态不明时 fail-closed
  • ExitWorktree 只处理当前 session 自己创建的 worktree
  • MCP resource 工具不是基础工具池常驻成员,而是 capability-driven 动态注入
  • ListMcpResources 对单个 server 的失败做局部容错
  • ReadMcpResource 对二进制资源做落盘和 mime-aware 扩展名映射

这些都说明:

Claude Code 的运行时辅助层不是零散工具集合,而是一套“受控触达宿主环境”的设计。

11. 转成 C# 时我建议怎么拆

如果你后面做 C# 版,我建议这一层至少拆成三块服务:

public interface IConfigurationGateway
{
    Task<ConfigQueryResult> GetAsync(string key, CancellationToken ct);
    Task<ConfigMutationResult> SetAsync(string key, object value, CancellationToken ct);
}

public interface IWorkspaceIsolationGateway
{
    Task<WorktreeEnterResult> EnterAsync(WorktreeEnterRequest request, CancellationToken ct);
    Task<WorktreeExitResult> ExitAsync(WorktreeExitRequest request, CancellationToken ct);
}

public interface IMcpResourceGateway
{
    Task<IReadOnlyList<McpResourceDescriptor>> ListAsync(string? server, CancellationToken ct);
    Task<McpResourceReadResult> ReadAsync(string server, string uri, CancellationToken ct);
}

我会特别建议你保留这些概念:

  • SupportedSettingRegistry
  • ImmediateAppStateSync
  • WorkspaceIsolationSession
  • DestructiveExitGuard
  • McpResourceCapability
  • BinaryArtifactPersistence

其中最关键的是两个:

  • WorkspaceIsolationSession 因为 worktree 不是一个路径字符串,而是一整套需要恢复的会话状态
  • BinaryArtifactPersistence 因为 resource/blob 这种东西一旦直接塞上下文,很快就把系统拖死

12. 我的总体评价

拆完这一层以后,我对 Claude Code 的理解又更完整了一点:

它并不只把“帮用户干活的动作”做成工具,也把“Claude Code 自己所处环境的安全入口”做成工具。

也就是:

  • 配置怎么改
  • 隔离环境怎么切
  • 扩展资源怎么读

这些都不是放任模型自由发挥,而是通过受控入口接进 runtime。

这类设计往往不显眼,但对 C# 版非常重要。

因为你真正要抄的不是几个 API,而是这条原则:

模型可以碰宿主环境,但必须只通过被治理过的入口去碰。

这正是 Claude Code 这一组辅助工具最有价值的地方。