# 第二阶段 · 模块二 · 第一节:工具注册与权限模型
Claude Code 的工具是如何注册的?权限模型是怎样的?canUseTool 的检查流程是什么?内置工具有哪些?
Claude Code 全局架构
┌─────────────────────────────────────────────────────────────────────┐
│ 入口层(cli.tsx ──> main.tsx ──> REPL.tsx) │
└──────────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 查询引擎层(QueryEngine ──> query) │
└──────────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 工具/服务层 │
│ │
│ tools.ts (40+工具注册) ← 本节内容 │
│ ├── 内置工具(Read, Write, Bash, Grep, Glob...) │
│ ├── MCP 工具 │
│ └── 自定义工具 │
└─────────────────────────────────────────────────────────────────────┘
源码位置:`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'
// ...
源码位置:`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:内置工具有哪些?
| 类别 | 工具 | 作用 |
| 文件读取 | 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>;
}
源码位置:`src/utils/permissions/PermissionMode.ts`
export type PermissionMode =
| 'accept-all' // 接受所有工具调用
| 'bypass-killswitch' // 跳过killswitch
| 'auto' // 自动模式
| 'review-tool' // 逐个审查
| 'deny-all'; // 拒绝所有
源码位置:`src/utils/permissions/permissions.ts` 第 287 行
export function getDenyRuleForTool(
permissionContext: ToolPermissionContext,
tool: Tool,
): PermissionRule | undefined {
// 1. 检查是否是白名单工具(始终允许)
// 2. 检查是否是黑名单工具(始终拒绝)
// 3. 检查权限模式
// 4. 返回拒绝规则或 undefined
}
问 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 };
}
用户: "读取 package.json"
│
▼
query() 接收消息
│
▼
callModel() 调用 Claude API
│
▼
API 返回 tool_use 块
│
▼
canUseTool(tool, context) 检查权限
│
├── 允许 → 执行工具
│
└── 拒绝 → 返回拒绝消息
│
▼
StreamingToolExecutor 或 runTools() 执行
│
▼
返回 tool_result 块
│
▼
query() 继续循环或返回结果
源码位置:`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, ... };
}
问 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 的区别?
| 方面 | runTools | StreamingToolExecutor |
| 执行方式 | 顺序执行 | 并行执行 |
| 时机 | 所有工具串行 | 与模型流式并行 |
| 适用场景 | 简单场景 | 复杂场景 |
问 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. 放弃当前任务
// getTools() 是工具的工厂
const tools = getTools(permissionContext);
// 根据权限上下文返回不同的工具集
// 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;
};
// filterToolsByDenyRules 根据规则过滤工具
const allowedTools = tools.filter(tool => !getDenyRuleForTool(context, tool));
问题:能否在运行时动态注册新工具,而不是在代码中静态定义?
答案:
目前 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' });
问题:当前的权限模型有什么局限?如何改进?
答案:
局限性:
1. 粗粒度控制:只能按工具名控制,不能按参数控制
// 当前:无法区分
Bash({ command: 'ls' }) // 安全
Bash({ command: 'rm -rf /' }) // 危险,但被视为同一工具
2. 静态规则:无法根据上下文动态调整
// 当前:
{ "tool": "Bash", "allow": false } // 完全禁止
// 更好的方式:
{ "tool": "Bash", "allow": true, "conditions": { "command": { "pattern": "^ls|^cat" } } }
3. 无法回溯:工具执行后的影响无法追踪
改进方向:
- 参数级别的细粒度控制
- 基于上下文的动态规则
- 工具执行审计日志
问题:在 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 工具的安全机制
- 工具参数验证
*- 第一轮:□ 事实准确性*
*- 第二轮:□ 深度与洞见*
*- 第三轮:□ 可读性与价值*