Module Structure
MCP 运行时由每个 SessionRuntime 独占:配置先按 source project 合并,Registry 管理
多个 client 的安全投影,动态工具和内容能力再进入该 Session 的 ToolRegistry 与事件流。
Directory Layout
packages/cli/src/mcp/— client、registry、目录、交互、恢复、日志、任务与结果归一化packages/cli/src/mcp/auth/— OAuth 策略、回调 provider 与凭据账本packages/cli/src/tools/builtin/mcp/— Resources、Prompts、Completion 与 Tasks 工具适配packages/cli/src/commands/mcp.ts— 进程外 MCP 管理命令packages/cli/src/server/routes/mcp.ts— 当前 Web Session 的 MCP 管理 APIpackages/cli/src/slash-commands/mcp.ts— 活动 TUI/ACP Session 的 MCP 命令packages/cli/web/src/components/mcp/— Web MCP 状态与操作面板
Key Entry Points
resolveWorkspaceMcpConfig()inpackages/cli/src/mcp/resolveWorkspaceMcpConfig.ts— 合并并规范化单个 Session 的 MCP 配置McpRegistry.createIsolated()inpackages/cli/src/mcp/McpRegistry.ts— 创建 Session 独占 RegistryMcpClient.connect()andMcpClient.callTool()inpackages/cli/src/mcp/McpClient.ts— 建连、能力协商与工具调用createMcpTool()inpackages/cli/src/mcp/createMcpTool.ts— 将协议工具映射为 Blade ToolSessionRuntime.registerMcpTools()inpackages/cli/src/agent/runtime/SessionRuntime.ts— 注册动态目录、内容工具和事件监听
Gotchas
- MCP 配置覆盖顺序是 workspace → plugin →显式 Session → CLI,
strictCliConfig会跳过前三类来源;相对 stdiocwd始终按 source project 解析而不是 task worktree(packages/cli/src/mcp/resolveWorkspaceMcpConfig.ts) --mcp-config的单个非法文件或 JSON 只记录 warning 并跳过,后续参数仍继续覆盖;调用方不能假设解析失败会使 Session 创建失败(packages/cli/src/mcp/loadMcpConfig.ts)McpRegistry.registerServer()默认吞掉初始连接异常并保留 ERROR server,Session 可继续启动;需要通过状态或显式 reconnect 区分“已注册”与“已连接”(packages/cli/src/mcp/McpRegistry.ts)- 同一 client 同时只允许一个交互式工具调用或 task 创建,因为 Elicitation/Sampling 缺少可靠的父调用关联;并发重叠会在发请求前拒绝(
packages/cli/src/mcp/McpClient.ts) - transport 进入恢复前会立即撤销旧 tools、resources、prompts 和 instructions;持有旧
Tool对象或旧 catalog identity 重试会命中死亡 connection generation(packages/cli/src/mcp/McpClient.ts,packages/cli/src/mcp/McpRegistry.ts) list_changed通知只登记有界合并刷新,完整新目录全部校验成功后才替换;notification 刷新失败保留上一有效目录,而手动刷新会把错误返回调用方(packages/cli/src/mcp/McpClient.ts,packages/cli/src/mcp/McpToolCatalog.ts)- Resource subscription 分为 desired 与 active:异常断连只清 active 并在新目录确认 URI 后恢复,手动 disconnect、unsubscribe 或 Session dispose 才清 desired(
packages/cli/src/mcp/McpClient.ts) - MCP Tasks 默认关闭;
taskSupport=required的动态工具会自动后台化,optional仍以前台调用为默认,只有显式StartMcpTask才进入 task 路径(packages/cli/src/mcp/createMcpTool.ts,packages/cli/src/mcp/McpTasks.ts) - 模型和用户只能看到 Blade 生成的
mcp_task_*,原始 server task ID 必须留在 manager 内部;ownership 同时校验 Session ID 与 canonical execution workspace(packages/cli/src/mcp/McpTaskManager.ts,packages/cli/src/tools/builtin/mcp/mcpTaskTools.ts) - 二进制结果绝不能把 base64 直接注入模型;大文本超过 inline budget 后写 Session 私有 artifact,写入失败也只能返回 hash/size/omitted 元数据(
packages/cli/src/mcp/McpToolResult.ts,packages/cli/src/mcp/McpToolArtifactStore.ts) - MCP 日志是用户诊断事件而非模型输入,server instructions 才会以明确标注“不可信外部文档”的 scoped reminder 进入 provider boundary;二者不能共用注入路径(
packages/cli/src/mcp/McpLogging.ts,packages/cli/src/mcp/McpServerInstructions.ts)
Architecture
- 每个 Session 创建独立
McpRegistry、client、目录 revision 和 artifact writer,进程级 Store 仅用于启动表面的配置投影,不能作为运行时连接来源(packages/cli/src/agent/runtime/SessionRuntime.ts,packages/cli/src/mcp/McpRegistry.ts,git:6d712a03) - 动态工具身份固定为
mcp__<server>__<tool>;不安全或过长片段经 NFKC 与摘要后缀规范化,避免 server 同名工具覆盖彼此或覆盖内置工具(packages/cli/src/mcp/McpToolCatalog.ts) - Agent 发起下一次 provider 请求前等待 catalog barrier,随后把完整 MCP 投影一次替换到基础 registry 和每个 executor,并重新应用 whitelist/blacklist(
packages/cli/src/agent/runtime/SessionRuntime.ts,packages/cli/src/tools/registry/ToolRegistry.ts) - 工具调用同时受父
AbortSignal、idle timeout 和 hard total timeout 约束;合法 progress 刷新 idle timeout,但不能延长 hard timeout,也不会写入 durable transcript(packages/cli/src/mcp/McpClient.ts,packages/cli/src/mcp/McpCallLifecycle.ts) - Resources、templates 和 prompts 使用独立 revision 与 delta;资源与模板同批获取后提交,读取和 prompt 解析只接受当前 catalog 已声明的 identity(
packages/cli/src/mcp/McpContentCatalog.ts,packages/cli/src/mcp/McpClient.ts) - Elicitation 在展示前和回传前分别经过 Hooks,最终响应仍按原始 requested schema 校验;没有交互面、取消、超时或异常统一收敛为 cancel/decline(
packages/cli/src/mcp/McpClient.ts,packages/cli/src/mcp/McpElicitation.ts) - Sampling 使用当前 Session 冻结模型,按每次请求重新审批,并拒绝 tools、context 扩展、task sampling 和并发 nested request(
packages/cli/src/mcp/McpSampling.ts,packages/cli/src/mcp/McpClient.ts) - 异步 Task watcher 在 transport 不可用时进入
interrupted并等待同一 client 恢复;恢复后必须验证原始 task ID 与createdAt未变化才继续取状态或结果(packages/cli/src/mcp/McpTaskManager.ts,packages/cli/src/mcp/McpTasks.ts)
Decisions
- MCP 全协议生命周期在一次集中改造中引入,并将目录、内容、交互、日志、任务和 OAuth 都纳入 Session 隔离,而不是继续扩展进程全局 registry(
packages/cli/src/mcp/McpRegistry.ts,packages/cli/src/mcp/McpClient.ts,git:7894b6e6) - Sampling 和 Tasks 均选择默认关闭、按 server 显式启用,因为二者会分别产生额外模型调用和后台生命周期,不能从普通工具授权隐式继承(
packages/cli/src/mcp/McpSampling.ts,packages/cli/src/mcp/McpTasks.ts) - Completion 被建模为只读候选数据而非控制消息,必须先绑定当前 catalog 中的 prompt 参数或 URI template variable,再做字符和结果预算归一化(
packages/cli/src/mcp/McpCompletion.ts) - tool result 与 MCP content 都采用“文本有界保留、二进制只给 provenance”的策略,避免协议返回绕过模型上下文预算和 Web 事件 allowlist(
packages/cli/src/mcp/McpToolResult.ts,packages/cli/src/mcp/McpContentCatalog.ts) - MCP artifact 后续迁移到共享的私有 Session 存储实现,新增结果类型应复用该存储而不是另建可见目录(
packages/cli/src/mcp/McpToolArtifactStore.ts,git:4553d089)
Patterns
- 所有会跨异步边界返回的协议对象先
structuredClone或重新归一化,事件再附加单调 revision,防止 listener 修改 Registry 内部状态(packages/cli/src/mcp/McpRegistry.ts) - 目录分页统一限制页数、条目数、重复 cursor、identity 和总字节;超限按整个目录失败处理,不提交部分目录(
packages/cli/src/mcp/McpClient.ts,packages/cli/src/mcp/McpToolCatalog.ts,packages/cli/src/mcp/McpContentCatalog.ts) - 恢复、Completion、Tasks 和 resource subscription 都捕获当前 client/generation,异步结果返回时发现 client 已变化就拒绝,避免旧 transport 的迟到结果污染新连接(
packages/cli/src/mcp/McpClient.ts,packages/cli/src/mcp/McpConnectionRecovery.ts)
Dependencies
- MCP transport、schema 和实验性 Tasks 依赖
@modelcontextprotocol/sdk;Blade 在 SDK 校验后仍执行自己的目录、结果、Unicode 和生命周期边界(packages/cli/src/mcp/McpClient.ts,packages/cli/package.json) - 动态 MCP 工具复用 Blade 的 TypeBox Tool contract、权限确认、进度事件和
SessionArtifactStore,因此协议层修改必须同时检查 ToolRegistry 与 SessionRuntime 投影(packages/cli/src/mcp/createMcpTool.ts,packages/cli/src/mcp/McpToolArtifactStore.ts)
Security Considerations
- OAuth 只允许 HTTPS,loopback 可用 HTTP;禁止 URL credentials、手写 Authorization header 和旧式 secret/endpoint 字段,网络请求不跟随 redirect(
packages/cli/src/mcp/auth/McpOAuthPolicy.ts) - OAuth 凭据身份由 endpoint、client ID 和排序 scopes 哈希生成,账本与锁文件必须是当前用户拥有的普通
0600文件,写入使用进程内 mutex、跨进程排他锁和原子替换(packages/cli/src/mcp/auth/OAuthTokenStorage.ts) - ACP Session 不读取宿主 OAuth 凭据、不暴露本地 roots 或 artifact path,也不向模型投影 server instruction/log 正文(
packages/cli/src/agent/runtime/SessionRuntime.ts,docs/reference/mcp-session-isolation.md) - Form Elicitation 拒绝原型污染字段、额外属性和不安全数字;URL Elicitation 只接受无凭据的 HTTP(S) URL,敏感数据应留在外部 URL 流程(
packages/cli/src/mcp/McpElicitation.ts) - Prompt 与 Completion 参数必须来自当前 catalog 声明,未知参数、缺失 required 参数和
__proto__/constructor/prototype在协议请求前拒绝(packages/cli/src/mcp/McpClient.ts,packages/cli/src/mcp/McpCompletion.ts) - 连接错误、Task status/error 与工具错误分别执行 URL、Bearer、API key、控制字符和 UTF-8 字节上限清理,不能把 transport 文本直接用作模型指令(
packages/cli/src/mcp/McpConnectionRecovery.ts,packages/cli/src/mcp/McpTasks.ts,packages/cli/src/mcp/McpToolResult.ts)
Error Handling & Recovery
- 自动恢复是 single-flight generation 状态机;手动 disconnect 或 dispose 会提升 generation、取消连接和退避,旧 close/error 回调不能覆盖新状态(
packages/cli/src/mcp/McpClient.ts,packages/cli/src/mcp/McpConnectionRecovery.ts) - 只有 session-not-found、连续终端 transport 错误和健康检查阈值进入恢复;普通协议校验错误不会触发重连风暴(
packages/cli/src/mcp/McpClient.ts,packages/cli/src/mcp/HealthMonitor.ts) - Session dispose 先取消当前 Session 的 MCP Tasks,再解除 Registry listener 并断开全部 client,防止终态事件写入已释放的执行器目录(
packages/cli/src/agent/runtime/SessionRuntime.ts,packages/cli/src/mcp/McpTaskManager.ts)