第十八章:运行时辅助工具到底在改什么
看 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.tssrc/tools/ConfigTool/prompt.tssrc/tools/ConfigTool/supportedSettings.tssrc/tools/EnterWorktreeTool/EnterWorktreeTool.tssrc/tools/EnterWorktreeTool/prompt.tssrc/tools/ExitWorktreeTool/ExitWorktreeTool.tssrc/tools/ExitWorktreeTool/prompt.tssrc/tools/ListMcpResourcesTool/ListMcpResourcesTool.tssrc/tools/ReadMcpResourceTool/ReadMcpResourceTool.ts
配套还看了:
src/tools.tssrc/utils/worktree.tssrc/services/mcp/client.tssrc/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 是整套设计的核心。
每个设置项至少有:
sourcetypedescriptionoptions或getOptions- 可选
appStateKey - 可选
validateOnWrite - 可选
formatOnRead
这让 ConfigTool 具备一个很好的性质:
- 可支持的配置项是声明式的
- prompt 文档可以从 registry 自动生成
- 读写逻辑可以统一复用
这对 C# 版特别值得抄,因为这类工具最怕越写越碎。
5.2 它把“读”和“写”当成两种不同风险等级
ConfigTool.checkPermissions() 的策略很清楚:
- 读配置:自动
allow - 写配置:必须
ask
而 isReadOnly(input) 也直接根据有没有 value 来决定。
这说明 Claude Code 对这类宿主级变更的态度是:
- 观察可以宽松
- 修改必须显式审批
这不是业务数据,而是在改 agent 自己的工作环境。
5.3 它不只是改文件,还会同步运行时状态
这部分是 ConfigTool 最容易被低估的地方。
写配置以后,它不只是落盘,还会在必要时立刻同步到 AppState:
verbosemainLoopModelthinkingEnabledremoteControlAtStartup
还有 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 keep 和 remove 是两种完全不同的语义
输入参数里最关键的是:
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 层当成独立上下文通道
ListMcpResources 和 ReadMcpResource 这一组特别有意思,因为它们说明 Claude Code 对 MCP 的理解不是“只有 tool call”。
它明确把 MCP server 的能力拆成了几层:
- tools
- commands / skills
- resources
其中 resources 是独立通道,不和普通 tools 混成一团。
8.1 资源工具不是基础工具池常驻成员
这点很关键。
在 tools.ts 里:
ListMcpResourcesToolReadMcpResourceTool
虽然出现在 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 推断扩展名
- 返回磁盘路径和一段说明文字
这和 WebFetch、TaskOutput 的处理方式很像,核心目标都是:
不要把大 blob 或 base64 直接塞爆模型上下文。
8.5 它甚至会针对 mime type 选择后续可消费格式
mcpOutputStorage.ts 里专门把 mime type 映射到扩展名:
pdfjsoncsvpngjpgdocxxlsx
这样后面 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 backendExitWorktree(remove)会做变更统计,并在状态不明时 fail-closedExitWorktree只处理当前 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);
}
我会特别建议你保留这些概念:
SupportedSettingRegistryImmediateAppStateSyncWorkspaceIsolationSessionDestructiveExitGuardMcpResourceCapabilityBinaryArtifactPersistence
其中最关键的是两个:
WorkspaceIsolationSession因为 worktree 不是一个路径字符串,而是一整套需要恢复的会话状态BinaryArtifactPersistence因为 resource/blob 这种东西一旦直接塞上下文,很快就把系统拖死
12. 我的总体评价
拆完这一层以后,我对 Claude Code 的理解又更完整了一点:
它并不只把“帮用户干活的动作”做成工具,也把“Claude Code 自己所处环境的安全入口”做成工具。
也就是:
- 配置怎么改
- 隔离环境怎么切
- 扩展资源怎么读
这些都不是放任模型自由发挥,而是通过受控入口接进 runtime。
这类设计往往不显眼,但对 C# 版非常重要。
因为你真正要抄的不是几个 API,而是这条原则:
模型可以碰宿主环境,但必须只通过被治理过的入口去碰。
这正是 Claude Code 这一组辅助工具最有价值的地方。