第一节:工具注册
Tool 架构与注册流程

作者:小学子 📚 | 日期:2026年4月2日 | 第二阶段 · 模块二


# 第二阶段 · 模块二 · 第一节:工具注册与权限模型

核心问题

Claude Code 的工具是如何注册的?权限模型是怎样的?canUseTool 的检查流程是什么?内置工具有哪些?


◇ 本节位置


        Claude Code 全局架构
        
        ┌─────────────────────────────────────────────────────────────────────┐
        │  入口层(cli.tsx ──> main.tsx ──> REPL.tsx)                      │
        └──────────────────────────────┬──────────────────────────────────────┘
                                       │
                                       ▼
        ┌─────────────────────────────────────────────────────────────────────┐
        │  查询引擎层(QueryEngine ──> query)                                │
        └──────────────────────────────┬──────────────────────────────────────┘
                                       │
                                       ▼
        ┌─────────────────────────────────────────────────────────────────────┐
        │  工具/服务层                                                        │
        │                                                                      │
        │  tools.ts (40+工具注册) ← 本节内容                                  │
        │  ├── 内置工具(Read, Write, Bash, Grep, Glob...)                  │
        │  ├── MCP 工具                                                        │
        │  └── 自定义工具                                                      │
        └─────────────────────────────────────────────────────────────────────┘
        


一、工具注册

1.1 源码实现

源码位置:`src/tools.ts` 第 1-100 行


        // biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered
        import { toolMatchesName, type Tool, type Tools } from './Tool.js'
        import { AgentTool } from './tools/AgentTool/AgentTool.js'
        import { BashTool } from './tools/BashTool/BashTool.js'
        import { FileEditTool } from './tools/FileEditTool/FileEditTool.js'
        import { FileReadTool } from './tools/FileReadTool/FileReadTool.js'
        import { FileWriteTool } from './tools/FileWriteTool/FileWriteTool.js'
        import { GlobTool } from './tools/GlobTool/GlobTool.js'
        // ... 更多内置工具
        import { WebSearchTool } from './tools/WebSearchTool/WebSearchTool.js'
        import { GrepTool } from './tools/GrepTool/GrepTool.js'
        // ...
        

1.2 内置工具列表

源码位置:`src/tools.ts` 第 271-310 行


        export const getTools = (permissionContext: ToolPermissionContext): Tools => {
          const replSimple = [
            new Read({ ... }),           // 读取文件
            new Glob({ ... }),           // 文件模式匹配
            new Grep({ ... }),           // 文本搜索
            new Bash({ shell: '/bin/bash' }),  // Shell 命令
          ];
        
          const simpleTools = [
            ...replSimple,
            new FileEdit({ ... }),       // 编辑文件
            new FileWrite({ ... }),      // 写入文件
            new WebSearch({ ... }),      // 网络搜索
            new WebFetch({ ... }),       // 获取网页
            // ...
          ];
        
          // 更多工具...
        };
        

1.3 五问分析

问 1:内置工具有哪些?

类别工具作用
文件读取Read读取文件内容
文件写入Write写入文件
文件编辑Edit编辑文件(diff 应用)
文件匹配Glob按模式匹配文件
文本搜索Grep搜索文件内容
Shell 执行Bash执行 shell 命令
网络搜索WebSearch搜索网络
网页抓取WebFetch获取网页内容
子代理Agent启动子代理
技能调用Skill调用技能
.........

问 2:工具是如何动态导入的?


        // Dead code elimination: conditional import for ant-only tools
        const REPLTool =
          process.env.USER_TYPE === 'ant'
            ? require('./tools/REPLTool/REPLTool.js').REPLTool
            : null
        
        // feature flag 控制
        const SleepTool =
          feature('PROACTIVE') || feature('KAIROS')
            ? require('./tools/SleepTool/SleepTool.js').SleepTool
            : null
        

问 3:为什么某些工具需要 feature flag?


        const cronTools = feature('AGENT_TRIGGERS')
          ? [
              require('./tools/ScheduleCronTool/CronCreateTool.js').CronCreateTool,
              require('./tools/ScheduleCronTool/CronDeleteTool.js').CronDeleteTool,
              require('./tools/ScheduleCronTool/CronListTool.js').CronListTool,
            ]
          : []
        

原因:

- 某些功能仅限内部使用(Ant)

- 减少外部构建的产物大小

- 按需启用实验性功能

问 4:工具的注册流程是什么?


        工具注册流程:
        
        1. 静态导入工具类
           import { BashTool } from './tools/BashTool/BashTool.js'
        
        2. 在 getTools() 中实例化
           const tools = [
             new Bash({ shell: '/bin/bash' }),
             new Read({ ... }),
           ]
        
        3. 根据权限过滤
           const allowedTools = filterToolsByDenyRules(tools, permissionContext)
        
        4. 传递给 query()
           const result = await query({ tools: allowedTools, ... })
        

问 5:Tool 接口是什么样的?

源码位置:`src/Tool.ts`


        export interface Tool {
          name: string;
          description: string;
        
          // 构建 MCP 格式的工具定义
          buildTool(): ToolRecord;
        
          // 执行工具
          execute(
            args: ToolArgs,
            context: ToolContext,
          ): Promise<ToolResult>;
        }
        


二、权限模型

2.1 权限模式

源码位置:`src/utils/permissions/PermissionMode.ts`


        export type PermissionMode =
          | 'accept-all'      // 接受所有工具调用
          | 'bypass-killswitch'  // 跳过killswitch
          | 'auto'            // 自动模式
          | 'review-tool'     // 逐个审查
          | 'deny-all';       // 拒绝所有
        

2.2 权限检查流程

源码位置:`src/utils/permissions/permissions.ts` 第 287 行


        export function getDenyRuleForTool(
          permissionContext: ToolPermissionContext,
          tool: Tool,
        ): PermissionRule | undefined {
          // 1. 检查是否是白名单工具(始终允许)
          // 2. 检查是否是黑名单工具(始终拒绝)
          // 3. 检查权限模式
          // 4. 返回拒绝规则或 undefined
        }
        

2.3 五问分析

问 1:权限检查的步骤是什么?


        // 权限检查流程(简化版)
        
        function canUseTool(tool: Tool, context: ToolContext): PermissionResult {
          // Step 1: 检查白名单
          if (isWhitelisted(tool)) {
            return { allowed: true };
          }
        
          // Step 2: 检查黑名单
          if (isBlacklisted(tool)) {
            return { allowed: false, reason: 'blacklisted' };
          }
        
          // Step 3: 根据权限模式决定
          switch (permissionMode) {
            case 'accept-all':
              return { allowed: true };
            case 'deny-all':
              return { allowed: false, reason: 'deny-all mode' };
            case 'auto':
              return autoCheck(tool, context);
            case 'review-tool':
              return { allowed: false, needsUserApproval: true };
          }
        }
        

问 2:filterToolsByDenyRules 的作用?

源码位置:`src/tools.ts` 第 267 行


        export function filterToolsByDenyRules<T extends Tool>(
          tools: readonly T[],
          permissionContext: ToolPermissionContext,
        ): T[] {
          return tools.filter(tool => !getDenyRuleForTool(permissionContext, tool));
        }
        

这个函数根据权限规则过滤掉不允许的工具


        // 示例
        const allTools = [Read, Write, Bash, Grep];
        const permissionContext = { mode: 'deny-all' };
        
        const allowedTools = filterToolsByDenyRules(allTools, permissionContext);
        // 结果:[](所有工具都被过滤)
        

问 3:permissionContext 是什么?


        interface ToolPermissionContext {
          mode: PermissionMode;
          rules: PermissionRule[];
          localOnly?: boolean;  // 仅本地模式
          // ...
        }
        

permissionContext 由 init() 在启动时设置,取决于:

- 用户配置(`~/.claude/settings.json`)

- 命令行选项(`--dangerously-skip-permissions`)

- 环境变量

问 4:权限规则是什么格式?


        interface PermissionRule {
          tool: string | RegExp;  // 工具名或模式
          reason?: string;         // 拒绝原因
          allow?: boolean;         // 是否允许(false 表示拒绝)
        }
        

示例:


        {
          "rules": [
            { "tool": "Bash", "allow": false, "reason": "Security policy" },
            { "tool": "Read", "allow": true }
          ]
        }
        

问 5:auto 模式是如何工作的?


        // auto 模式:根据风险评估自动决定
        
        function autoCheck(tool: Tool, context: ToolContext): PermissionResult {
          // 1. 评估风险
          const riskLevel = assessRisk(tool, context);
        
          // 2. 低风险:自动允许
          if (riskLevel === 'low') {
            return { allowed: true };
          }
        
          // 3. 高风险:需要用户确认
          if (riskLevel === 'high') {
            return { allowed: false, needsUserApproval: true };
          }
        
          // 4. 中风险:检查历史记录
          if (wasPreviouslyApproved(tool)) {
            return { allowed: true };
          }
        
          return { allowed: false, needsUserApproval: true };
        }
        


三、工具执行机制

3.1 工具调用流程


        用户: "读取 package.json"
            │
            ▼
        query() 接收消息
            │
            ▼
        callModel() 调用 Claude API
            │
            ▼
        API 返回 tool_use 块
            │
            ▼
        canUseTool(tool, context) 检查权限
            │
            ├── 允许 → 执行工具
            │
            └── 拒绝 → 返回拒绝消息
            │
            ▼
        StreamingToolExecutor 或 runTools() 执行
            │
            ▼
        返回 tool_result 块
            │
            ▼
        query() 继续循环或返回结果
        

3.2 工具执行源码

源码位置:`src/query.ts` 第 307-400 行


        while (true) {
          // 1. 调用 Claude API
          for await (const message of deps.callModel({
            messages: messagesForQuery,
            tools: toolUseContext.options.tools,
          })) {
            if (message.type === 'assistant') {
              // 处理 tool_use 块
              if (hasToolUseBlocks(message)) {
                needsFollowUp = true;
              }
            }
          }
        
          // 2. 检查退出条件
          if (!needsFollowUp) {
            return { reason: 'completed' };
          }
        
          // 3. 执行工具
          const toolUpdates = streamingToolExecutor
            ? streamingToolExecutor.getRemainingResults()
            : runTools(toolUseBlocks, canUseTool);
        
          for await (const update of toolUpdates) {
            if (update.message) {
              toolResults.push(update.message);
            }
          }
        
          // 4. 状态转移,继续循环
          state = { messages: [...], turnCount: nextTurnCount, ... };
        }
        

3.3 五问分析

问 1:canUseTool 在哪里被调用?

canUseTool 在 query() 内部被调用,用于在执行前检查权限


        // 工具执行前
        const permissionResult = await canUseTool(tool, input, context);
        
        if (!permissionResult.allowed) {
          // 返回拒绝消息
          return {
            type: 'tool_result',
            tool_use_id: toolUseId,
            content: `Permission denied: ${permissionResult.reason}`,
            is_error: true,
          };
        }
        
        // 执行工具
        const result = await tool.execute(args, context);
        

问 2:StreamingToolExecutor 是什么?


        // StreamingToolExecutor 允许工具并行执行
        // 与模型流式输出重叠
        
        const streamingToolExecutor = config.gates.streamingToolExecution
          ? new StreamingToolExecutor(tools, canUseTool, toolUseContext)
          : null;
        

好处:

- 工具执行和模型生成并行

- 减少总等待时间

问 3:runTools 和 StreamingToolExecutor 的区别?

方面runToolsStreamingToolExecutor
执行方式顺序执行并行执行
时机所有工具串行与模型流式并行
适用场景简单场景复杂场景

问 4:工具结果如何返回给模型?


        // 返回 tool_result 块
        const toolResult: ToolResultBlockParam = {
          type: 'tool_result',
          tool_use_id: toolUseId,
          content: result.content,  // 工具的输出
          is_error: result.isError,
        };
        
        // 添加到消息列表
        messages.push(toolResult);
        
        // 继续调用模型
        

问 5:工具执行失败怎么办?


        try {
          const result = await tool.execute(args, context);
        } catch (error) {
          // 返回错误信息给模型
          return {
            type: 'tool_result',
            tool_use_id: toolUseId,
            content: `Error: ${error.message}`,
            is_error: true,
          };
        }
        

模型可以:

1. 尝试修复错误

2. 请求用户帮助

3. 放弃当前任务


四、设计模式

4.1 工厂模式


        // getTools() 是工具的工厂
        const tools = getTools(permissionContext);
        // 根据权限上下文返回不同的工具集
        

4.2 策略模式


        // canUseTool 是可替换的策略
        const wrappedCanUseTool: CanUseToolFn = async (tool, input, context) => {
          const result = await canUseTool(tool, input, context);
          if (!result.allowed) {
            this.permissionDenials.push({ tool, input, ... });  // 记录
          }
          return result;
        };
        

4.3 过滤器模式


        // filterToolsByDenyRules 根据规则过滤工具
        const allowedTools = tools.filter(tool => !getDenyRuleForTool(context, tool));
        


五、思考题

思考题 1:工具注册能否动态扩展?

问题:能否在运行时动态注册新工具,而不是在代码中静态定义?

答案

目前 Claude Code 的工具注册是静态的(在 getTools() 中定义)。动态扩展可以通过:

1. MCP(Model Context Protocol)


           // MCP 允许动态添加工具
           const mcpTools = await mcpClient.getTools();
           const allTools = [...builtInTools, ...mcpTools];
        

2. 插件系统


           // 插件可以注册自定义工具
           plugin.registerTool({
             name: 'myTool',
             execute: async (args) => { ... }
           });
        

3. SkillTool


           // 技能本质上也是工具
           const skillTool = new SkillTool({ skill: 'my-skill' });
        


思考题 2:权限模型的局限性?

问题:当前的权限模型有什么局限?如何改进?

答案

局限性

1. 粗粒度控制:只能按工具名控制,不能按参数控制


           // 当前:无法区分
           Bash({ command: 'ls' })     // 安全
           Bash({ command: 'rm -rf /' })  // 危险,但被视为同一工具
        

2. 静态规则:无法根据上下文动态调整


           // 当前:
           { "tool": "Bash", "allow": false }  // 完全禁止
        
           // 更好的方式:
           { "tool": "Bash", "allow": true, "conditions": { "command": { "pattern": "^ls|^cat" } } }
        

3. 无法回溯:工具执行后的影响无法追踪

改进方向

- 参数级别的细粒度控制

- 基于上下文的动态规则

- 工具执行审计日志


思考题 3:为什么 CLI 模式和 SDK 模式的权限处理不同?

问题:在 CLI 模式(REPL)中,用户可以交互式批准/拒绝工具调用。但在 SDK 模式中,如何处理?

答案

CLI 模式


        // REPL 中的权限处理
        if (!permissionResult.allowed) {
          // 显示交互式提示
          const shouldAllow = await promptUser(
            `Allow ${tool.name}?`,
            permissionResult.reason
          );
        
          if (shouldAllow) {
            // 临时允许并执行
            await tool.execute(args);
          }
        }
        

SDK 模式


        // SDK 中的权限处理
        const wrappedCanUseTool: CanUseToolFn = async (tool, input, context) => {
          const result = await canUseTool(tool, input, context);
        
          if (!result.allowed) {
            // SDK 模式:无法交互,只能记录
            this.permissionDenials.push({ tool, input, toolUseID });
          }
        
          return result;
        };
        
        // 调用者可以通过 engine.permissionDenials 查询被拒绝的工具
        

关键区别

方面CLI 模式SDK 模式
交互可以交互提示无法交互
处理方式实时用户确认记录 + 调用者决定
错误恢复用户可选择重试调用者控制


六、延伸阅读

文件行数核心内容
`src/tools.ts`389工具注册
`src/Tool.ts`?工具接口定义
`src/utils/permissions/permissions.ts`?权限检查
`src/query.ts`1729工具调用流程


七、下节预告

下一节我们将深入 内置核心工具

- Read/Write/Edit 工具的源码分析

- Bash 工具的安全机制

- 工具参数验证


*- 第一轮:□ 事实准确性*

*- 第二轮:□ 深度与洞见*

*- 第三轮:□ 可读性与价值*