第6章:工具系统
本章深入分析 Claude Code 的工具系统实现,包括工具基类设计、各类工具的实现细节、工具注册机制和权限检查流程。
6.1 Tool.ts 基类设计
Tool.ts 定义了所有工具的基类接口,是工具系统的核心抽象。
Tool 类型定义
export type Tool<
Input extends AnyObject = AnyObject,
Output = unknown,
P extends ToolProgressData = ToolProgressData,
> = {
// 标识属性
readonly name: string // 工具名称
aliases?: string[] // 别名列表(兼容重命名)
searchHint?: string // 搜索提示(3-10词)
readonly shouldDefer?: boolean // 是否延迟加载
readonly alwaysLoad?: boolean // 是否始终加载
readonly strict?: boolean // 是否启用严格模式
// Schema 定义
readonly inputSchema: Input // Zod 输入 Schema
readonly inputJSONSchema?: ToolInputJSONSchema // JSON Schema(MCP工具)
outputSchema?: z.ZodType<unknown> // 输出 Schema
// 核心方法
call(
args: z.infer<Input>,
context: ToolUseContext,
canUseTool: CanUseToolFn,
parentMessage: AssistantMessage,
onProgress?: ToolCallProgress<P>,
): Promise<ToolResult<Output>>
description(
input: z.infer<Input>,
options: { isNonInteractiveSession: boolean; ... },
): Promise<string>
// 行为判断方法
isEnabled(): boolean // 是否启用
isConcurrencySafe(input): boolean // 是否并发安全
isReadOnly(input): boolean // 是否只读
isDestructive?(input): boolean // 是否破坏性操作
// 权限方法
checkPermissions(input, context): Promise<PermissionResult>
validateInput?(input, context): Promise<ValidationResult>
// UI 渲染方法
userFacingName(input): string
renderToolUseMessage(input, options): React.ReactNode
renderToolResultMessage?(content, progressMessages, options): React.ReactNode
renderToolUseProgressMessage?(progressMessages, options): React.ReactNode
// 其他方法
prompt(options): Promise<string> // 工具提示生成
mapToolResultToToolResultBlockParam(content, toolUseID): ToolResultBlockParam
getToolUseSummary?(input): string | null // 简要描述
getActivityDescription?(input): string | null // Spinner 描述
}
buildTool 工厂函数
const TOOL_DEFAULTS = {
isEnabled: () => true,
isConcurrencySafe: (_input?: unknown) => false, // 默认不安全
isReadOnly: (_input?: unknown) => false, // 默认可写
isDestructive: (_input?: unknown) => false,
checkPermissions: (input, _ctx) =>
Promise.resolve({ behavior: 'allow', updatedInput: input }),
toAutoClassifierInput: (_input?: unknown) => '',
userFacingName: (_input?: unknown) => '',
}
export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> {
return {
...TOOL_DEFAULTS,
userFacingName: () => def.name,
...def,
} as BuiltTool<D>
}
工具默认值采用 fail-closed(失败即关闭)原则:isConcurrencySafe 默认 false,isReadOnly 默认 false。这意味着安全相关属性必须显式声明为安全,而非默认安全。
ToolUseContext 定义
export type ToolUseContext = {
options: {
commands: Command[]
debug: boolean
mainLoopModel: string
tools: Tools
verbose: boolean
thinkingConfig: ThinkingConfig
mcpClients: MCPServerConnection[]
mcpResources: Record<string, ServerResource[]>
isNonInteractiveSession: boolean
agentDefinitions: AgentDefinitionsResult
maxBudgetUsd?: number
customSystemPrompt?: string
appendSystemPrompt?: string
}
abortController: AbortController
readFileState: FileStateCache
getAppState(): AppState
setAppState(f: (prev: AppState) => AppState): void
// 回调函数
setToolJSX?: SetToolJSXFn
addNotification?: (notif: Notification) => void
appendSystemMessage?: (msg: SystemMessage) => void
sendOSNotification?: (opts) => void
// 状态追踪
messages: Message[]
fileReadingLimits?: { maxTokens?: number; maxSizeBytes?: number }
globLimits?: { maxResults?: number }
toolDecisions?: Map<string, { source: string; decision: 'accept' | 'reject'; timestamp: number }>
queryTracking?: QueryChainTracking
// Agent 相关
agentId?: AgentId
agentType?: string
}
6.2 tools/ 目录各工具实现
工具分类总览
graph TD
subgraph "文件操作工具"
READ[FileReadTool<br/>文件读取]
WRITE[FileWriteTool<br/>文件写入]
EDIT[FileEditTool<br/>文件编辑]
end
subgraph "搜索工具"
GLOB[GlobTool<br/>文件匹配]
GREP[GrepTool<br/>内容搜索]
SEARCH[ToolSearchTool<br/>工具搜索]
end
subgraph "执行工具"
BASH[BashTool<br/>Shell 命令]
TASK[TaskOutputTool<br/>任务输出]
TASKSTOP[TaskStopTool<br/>任务停止]
end
subgraph "网络工具"
WEBFETCH[WebFetchTool<br/>网页获取]
WEBSEARCH[WebSearchTool<br/>网络搜索]
end
subgraph "Agent 工具"
AGENT[AgentTool<br/>子 Agent]
SKILL[SkillTool<br/>技能调用]
WORKTREE[EnterWorktreeTool<br/>工作树]
end
subgraph "状态管理工具"
TODO[TodoWriteTool<br/>任务列表]
CONFIG[ConfigTool<br/>配置管理]
PLAN[EnterPlanModeTool<br/>计划模式]
end
subgraph "其他工具"
LSP[LSPTool<br/>语言服务]
NOTEBOOK[NotebookEditTool<br/>Jupyter 编辑]
ASK[AskUserQuestionTool<br/>用户提问]
end
文件操作工具
FileReadTool
// 核心功能:读取文件内容
const FileReadTool = buildTool({
name: 'Read',
inputSchema: z.object({
file_path: z.string(),
offset: z.number().optional(),
limit: z.number().optional(),
}),
async call(args, context, canUseTool, parentMessage, onProgress) {
// 权限检查
const permissionResult = await canUseTool(this, args, context, ...)
if (permissionResult.behavior !== 'allow') {
return { data: { error: permissionResult.reason } }
}
// 读取文件
const content = await readFile(args.file_path, args.offset, args.limit)
// 更新缓存
context.readFileState.set(args.file_path, content)
return { data: { content, path: args.file_path } }
},
isReadOnly: () => true,
maxResultSizeChars: Infinity, // 不持久化结果
})
FileEditTool
// 核心功能:精确编辑文件
const FileEditTool = buildTool({
name: 'Edit',
inputSchema: z.object({
file_path: z.string(),
old_string: z.string(),
new_string: z.string(),
replace_all: z.boolean().optional(),
}),
async call(args, context, ...) {
// 验证 old_string 存在
const currentContent = await readFile(args.file_path)
if (!currentContent.includes(args.old_string)) {
return { data: { error: 'old_string not found' } }
}
// 执行编辑
const newContent = args.replace_all
? currentContent.replaceAll(args.old_string, args.new_string)
: currentContent.replace(args.old_string, args.new_string)
await writeFile(args.file_path, newContent)
return { data: { success: true } }
},
isDestructive: () => true, // 标记为破坏性操作
})
FileWriteTool
// 核心功能:写入新文件
const FileWriteTool = buildTool({
name: 'Write',
inputSchema: z.object({
file_path: z.string(),
content: z.string(),
}),
isDestructive: (input) => {
// 覆盖已存在文件为破坏性
return fs.existsSync(input.file_path)
},
checkPermissions: async (input, context) => {
// 检查是否覆盖重要文件
if (await isProtectedFile(input.file_path)) {
return { behavior: 'deny', reason: 'Protected file' }
}
return { behavior: 'allow', updatedInput: input }
},
})
搜索工具
GlobTool
// 核心功能:文件模式匹配
const GlobTool = buildTool({
name: 'Glob',
inputSchema: z.object({
pattern: z.string(),
path: z.string().optional(),
}),
searchHint: 'find files matching pattern',
async call(args, context, ...) {
const matches = await glob(args.pattern, {
cwd: args.path ?? getCwd(),
absolute: true,
})
return { data: { matches } }
},
isReadOnly: () => true,
isConcurrencySafe: () => true,
isSearchOrReadCommand: () => ({ isSearch: true, isRead: false }),
})
GrepTool
// 核心功能:内容正则搜索
const GrepTool = buildTool({
name: 'Grep',
inputSchema: z.object({
pattern: z.string(),
path: z.string().optional(),
glob: z.string().optional(),
output_mode: z.enum(['content', 'files_with_matches', 'count']).optional(),
-C: z.number().optional(), // 上下文行数
}),
searchHint: 'search file contents regex pattern',
async call(args, context, ...) {
const results = await grep(args.pattern, {
path: args.path ?? getCwd(),
glob: args.glob,
outputMode: args.output_mode ?? 'files_with_matches',
contextLines: args['-C'],
})
return { data: results }
},
isSearchOrReadCommand: () => ({ isSearch: true, isRead: false }),
})
执行工具
BashTool
// 核心功能:执行 Shell 命令
const BashTool = buildTool({
name: 'Bash',
inputSchema: z.object({
command: z.string(),
description: z.string().optional(),
timeout: z.number().optional(),
run_in_background: z.boolean().optional(),
}),
async call(args, context, canUseTool, parentMessage, onProgress) {
// 进度回调
onProgress?.({
toolUseID: '...',
data: { type: 'bash', command: args.command, status: 'running' }
})
// 执行命令
const result = await execa(args.command, {
timeout: args.timeout ?? 120000,
cwd: getCwd(),
shell: true,
})
onProgress?.({
toolUseID: '...',
data: { type: 'bash', command: args.command, status: 'completed' }
})
return {
data: {
stdout: result.stdout,
stderr: result.stderr,
exitCode: result.exitCode,
}
}
},
isDestructive: (input) => {
// 检查是否破坏性命令
const destructivePatterns = ['rm', 'delete', 'drop', 'truncate']
return destructivePatterns.some(p => input.command.includes(p))
},
})
Agent 工具
AgentTool
// 核心功能:派发子 Agent
const AgentTool = buildTool({
name: 'Agent',
inputSchema: z.object({
name: z.string().optional(),
color: z.enum(['blue', 'yellow', 'green', 'purple', 'orange']).optional(),
description: z.string().optional(),
prompt: z.string(),
tools: z.array(z.string()).optional(),
model: z.string().optional(),
isolated: z.boolean().optional(),
}),
async call(args, context, ...) {
// 创建 AgentDefinition
const agentDef: AgentDefinition = {
name: args.name ?? 'default',
color: args.color ?? 'blue',
description: args.description ?? '',
model: args.model,
tools: args.tools,
isolated: args.isolated ?? false,
}
// 如果需要隔离,创建 Worktree
if (args.isolated) {
const worktree = await createWorktree()
// ... 在 worktree 中执行 Agent
}
// 启动子 Agent
const result = await runSubAgent(agentDef, args.prompt, context)
return { data: result }
},
})
6.3 工具注册与调用机制
tools.ts 工具注册
export function getAllBaseTools(): Tools {
return [
AgentTool,
TaskOutputTool,
BashTool,
...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]),
ExitPlanModeV2Tool,
FileReadTool,
FileEditTool,
FileWriteTool,
NotebookEditTool,
WebFetchTool,
TodoWriteTool,
WebSearchTool,
TaskStopTool,
AskUserQuestionTool,
SkillTool,
EnterPlanModeTool,
...(process.env.USER_TYPE === 'ant' ? [ConfigTool, TungstenTool] : []),
...(isEnvTruthy(process.env.ENABLE_LSP_TOOL) ? [LSPTool] : []),
...(isWorktreeModeEnabled() ? [EnterWorktreeTool, ExitWorktreeTool] : []),
// ... 更多工具
]
}
export const getTools = (permissionContext: ToolPermissionContext): Tools => {
// Simple 模式过滤
if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) {
return filterToolsByDenyRules([BashTool, FileReadTool, FileEditTool], permissionContext)
}
// 获取基础工具并过滤
const tools = getAllBaseTools()
let allowedTools = filterToolsByDenyRules(tools, permissionContext)
// REPL 模式处理
if (isReplModeEnabled()) {
allowedTools = allowedTools.filter(tool => !REPL_ONLY_TOOLS.has(tool.name))
}
// isEnabled 过滤
return allowedTools.filter(tool => tool.isEnabled())
}
// 组合工具池(内置 + MCP)
export function assembleToolPool(
permissionContext: ToolPermissionContext,
mcpTools: Tools,
): Tools {
const builtInTools = getTools(permissionContext)
const allowedMcpTools = filterToolsByDenyRules(mcpTools, permissionContext)
// 排序并去重(内置工具优先)
return uniqBy(
[...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)),
'name',
)
}
工具调用流程
sequenceDiagram
participant Model as Claude Model
participant Query as query()
participant Matcher as Tool Matcher
participant Permission as Permission Check
participant Tool as Tool Instance
participant Executor as Tool Executor
Model->>Query: tool_use block
Query->>Matcher: 查找工具
Matcher->>Matcher: toolMatchesName()
Matcher-->>Query: Tool 实例
Query->>Permission: canUseTool()
Permission->>Permission: validateInput()
Permission->>Permission: checkPermissions()
Permission->>Permission: 用户确认(交互模式)
Permission-->>Query: PermissionResult
alt 允许执行
Query->>Tool: call(input, context, ...)
Tool->>Executor: 执行操作
Executor-->>Tool: 结果数据
Tool-->>Query: ToolResult
else 拒绝执行
Query-->>Model: tool_result (error)
end
工具匹配函数
export function toolMatchesName(
tool: { name: string; aliases?: string[] },
name: string,
): boolean {
return tool.name === name || (tool.aliases?.includes(name) ?? false)
}
export function findToolByName(tools: Tools, name: string): Tool | undefined {
return tools.find(t => toolMatchesName(t, name))
}
6.4 权限检查流程
权限检查流程图
flowchart TD
subgraph "权限检查流程"
START[Tool Use Request] --> VALIDATE{validateInput?}
VALIDATE --> |"有"| VALIDATE_EXEC[执行输入验证]
VALIDATE --> |"无"| PERMISSION[进入权限检查]
VALIDATE_EXEC --> VALIDATE_RESULT{验证通过?}
VALIDATE_RESULT --> |"否"| DENY1[返回错误]
VALIDATE_RESULT --> |"是"| PERMISSION
PERMISSION --> RULES[检查 Permission Rules]
RULES --> ALWAYS_ALLOW{AlwaysAllow?}
ALWAYS_ALLOW --> |"是"| ALLOW[允许执行]
ALWAYS_ALLOW --> |"否"| ALWAYS_DENY{AlwaysDeny?}
ALWAYS_DENY --> |"是"| DENY2[拒绝执行]
ALWAYS_DENY --> |"否"| ALWAYS_ASK{AlwaysAsk?}
ALWAYS_ASK --> |"是"| PROMPT[提示用户]
ALWAYS_ASK --> |"否"| DEFAULT{默认规则}
DEFAULT --> MODE{PermissionMode?}
MODE --> |"auto"| AUTO_DECISION[自动决策]
MODE --> |"default"| INTERACTIVE[交互确认]
MODE --> |"plan"| PLAN_CHECK[Plan 模式检查]
AUTO_DECISION --> ALLOW_OR_DENY
INTERACTIVE --> USER_INPUT{用户选择}
USER_INPUT --> |"允许"| ALLOW
USER_INPUT --> |"拒绝"| DENY3
PLAN_CHECK --> PLAN_ALLOW{符合 Plan?}
PLAN_ALLOW --> |"是"| ALLOW
PLAN_ALLOW --> |"否"| DENY4
ALLOW --> EXECUTE[执行工具]
DENY1 --> RETURN[返回结果]
DENY2 --> RETURN
DENY3 --> RETURN
DENY4 --> RETURN
EXECUTE --> RETURN
end
PermissionResult 类型
export type PermissionResult = {
behavior: 'allow' | 'deny'
reason?: string
updatedInput?: { [key: string]: unknown }
decision?: 'accept' | 'reject'
source?: 'rule' | 'user' | 'auto'
}
权限模式说明
| 模式 | 说明 | 行为 |
|---|---|---|
default | 默认模式 | 交互式用户确认 |
auto | 自动模式 | 根据规则自动决策 |
plan | 计划模式 | 仅允许计划内操作 |
bypass | 绕过模式 | 允许所有操作(危险) |
权限规则配置
type ToolPermissionContext = {
mode: PermissionMode
additionalWorkingDirectories: Map<string, AdditionalWorkingDirectory>
alwaysAllowRules: ToolPermissionRulesBySource // 允许规则
alwaysDenyRules: ToolPermissionRulesBySource // 拒绝规则
alwaysAskRules: ToolPermissionRulesBySource // 确认规则
isBypassPermissionsModeAvailable: boolean
shouldAvoidPermissionPrompts?: boolean // 后台 Agent
}
// 规则示例
const alwaysAllowRules = {
userSettings: { Bash: ['git *', 'npm *'] },
projectSettings: { Read: ['.claude/*'] },
}
const alwaysDenyRules = {
projectSettings: { Bash: ['rm *', 'sudo *'] },
}
工具执行包装
// runTools 实现
export async function* runTools(
toolUseBlocks: ToolUseBlock[],
assistantMessages: AssistantMessage[],
canUseTool: CanUseToolFn,
toolUseContext: ToolUseContext,
): AsyncGenerator<ToolUpdate> {
for (const block of toolUseBlocks) {
const tool = findToolByName(toolUseContext.options.tools, block.name)
if (!tool) {
yield {
message: createToolResultMessage(block.id, `Unknown tool: ${block.name}`),
}
continue
}
// 权限检查
const permissionResult = await canUseTool(tool, block.input, toolUseContext, ...)
if (permissionResult.behavior === 'deny') {
yield {
message: createToolResultMessage(block.id, permissionResult.reason ?? 'Denied'),
}
continue
}
// 执行工具
const result = await tool.call(block.input, toolUseContext, canUseTool, ...)
yield {
message: createToolResultMessage(block.id, result.data),
}
}
}
下一章将分析 技能系统 的实现细节。