场景:你刚在终端输入了 claude "帮我修这个 bug",Claude 秒回说要先读文件再跑测试……
我看到 Claude 能读文件、写文件、跑命令、搜索代码……这些能力是写死的还是可以扩展的?
都是可扩展的。每个工具是一个实现了 Tool 接口的对象,注册在 tools.ts 里。就像瑞士军刀——统一的插槽接口,但每个工具头功能不同。
那 Claude 怎么知道什么时候该用哪个工具?总不能 40 多个工具全塞给 API 吧?
claude "帮我修这个 bug" 并按下回车……getTools() 根据权限上下文动态过滤,而不是把所有工具都发给模型。Tool 接口看着有 30 多个方法,写一个新工具岂不是要实现一大堆东西?
不用。核心只有 4 个:call(执行)、inputSchema(输入校验)、checkPermissions(权限检查)、prompt(给模型的说明文字)。
那其他 26 个方法呢?
大多是 UI 渲染和安全相关的,而且有 buildTool() 这个工厂函数帮你填好安全的默认值——就像新电脑出厂预装了防火墙,你只需要改你关心的设置。
export type Tool<
Input extends AnyObject = AnyObject,
Output = unknown,
> = {
call(
args: z.infer,
context: ToolUseContext,
canUseTool: CanUseToolFn,
parentMessage: AssistantMessage,
onProgress?: ToolCallProgress,
): Promise>
readonly inputSchema: Input
checkPermissions(
input: z.infer,
context: ToolUseContext,
): Promise
prompt(options: {
getToolPermissionContext: () => Promise
tools: Tools
agents: AgentDefinition[]
}): Promise
readonly name: string
isReadOnly(input: z.infer): boolean
isDestructive?(input: z.infer): boolean
}
call 是执行入口——接收解析后的参数和完整上下文。
checkPermissions 在 call 之前被调用,决定是否放行(allow/deny/ask)。
prompt 生成给模型看的工具使用说明,每次对话构建 system prompt 时调用。
const TOOL_DEFAULTS = {
isEnabled: () => true,
isConcurrencySafe: (_input?: unknown) => false,
isReadOnly: (_input?: unknown) => false,
isDestructive: (_input?: unknown) => false,
checkPermissions: (
input: { [key: string]: unknown },
_ctx?: ToolUseContext,
): Promise =>
Promise.resolve({
behavior: 'allow',
updatedInput: input
}),
}
export function buildTool(
def: D
): BuiltTool {
return {
...TOOL_DEFAULTS,
userFacingName: () => def.name,
...def,
} as BuiltTool
}
注意 isConcurrencySafe 默认 false——假设不安全(fail-closed)。
isReadOnly 也默认 false——假设会写入。
简单的展开运算符 ...def 覆盖默认值,开发者只需定义差异部分。
getAllBaseTools() 返回全部 40+ 个工具,可普通用户应该看不到内部工具吧?
对,getTools() 像个三层滤网:第一层是编译时 feature() 门控,代码直接不打包;第二层是 USER_TYPE 运行时过滤内部工具;第三层是 filterToolsByDenyRules,根据用户权限配置再筛。
像餐厅菜单系统——完整菜单是 getAllBaseTools,但根据会员等级和过敏禁忌,每桌客人看到的菜单不一样?
完美的比喻!而且还有简单模式短路——CLAUDE_CODE_SIMPLE 为 true 时,直接只给 Bash、Read、Edit 三个工具。
export const getTools = (
permissionContext: ToolPermissionContext
): Tools => {
// Simple mode: only Bash, Read, Edit
if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) {
const simpleTools: Tool[] = [
BashTool, FileReadTool, FileEditTool
]
return filterToolsByDenyRules(
simpleTools, permissionContext
)
}
const tools = getAllBaseTools().filter(
tool => !specialTools.has(tool.name)
)
let allowedTools = filterToolsByDenyRules(
tools, permissionContext
)
// REPL mode filter
if (isReplModeEnabled()) {
/* ... filter REPL_ONLY_TOOLS ... */
}
// Final: isEnabled() runtime check
const isEnabled = allowedTools.map(_ => _.isEnabled())
return allowedTools.filter((_, i) => isEnabled[i])
}
四层过滤管线:Simple 短路 → deny rules → REPL 过滤 → isEnabled 运行时检查。
filterToolsByDenyRules 读取用户权限配置,移除被禁止的工具。
export type ToolPermissionContext =
DeepImmutable<{
mode: PermissionMode
alwaysAllowRules: ToolPermissionRulesBySource
alwaysDenyRules: ToolPermissionRulesBySource
alwaysAskRules: ToolPermissionRulesBySource
isBypassPermissionsModeAvailable: boolean
shouldAvoidPermissionPrompts?: boolean
prePlanMode?: PermissionMode
}>
三组规则的类型都是 ToolPermissionRulesBySource——按来源分组(用户配置、CLAUDE.md、hook 等)。
这使得规则可追溯——调试权限问题时能定位"这条规则是谁设的"。
DeepImmutable 包裹确保权限上下文不可变,防止工具执行过程中篡改权限。
场景切换:Claude 启动了一个子 Agent 来处理子任务……
子 Agent 能用所有工具吗?感觉有些工具(比如 AskUserQuestion)对子 Agent 没意义?
不只是没意义——还很危险。像公司门禁:正式员工有全部权限,实习生不能进机房,外包限制更多。有四组常量控制不同角色的工具访问。
// 所有 Agent 禁用的工具
export const ALL_AGENT_DISALLOWED_TOOLS =
new Set([
TASK_OUTPUT_TOOL_NAME,
EXIT_PLAN_MODE_V2_TOOL_NAME,
ENTER_PLAN_MODE_TOOL_NAME,
ASK_USER_QUESTION_TOOL_NAME,
TASK_STOP_TOOL_NAME,
])
// Coordinator 只有 4 个工具
export const COORDINATOR_MODE_ALLOWED_TOOLS =
new Set([
AGENT_TOOL_NAME,
TASK_STOP_TOOL_NAME,
SEND_MESSAGE_TOOL_NAME,
SYNTHETIC_OUTPUT_TOOL_NAME,
])
ALL_AGENT_DISALLOWED_TOOLS:所有子 Agent 都不能用的工具。AskUserQuestion 在这里,因为子 Agent 没有 UI 通道。
COORDINATOR_MODE_ALLOWED_TOOLS 只有 4 个——Coordinator 只负责调度,不直接操作文件。
如果子 Agent 能用所有工具会怎样?
两个灾难:(1) 子 Agent 调用 AskUserQuestion 会永远挂起,因为它没有 UI 通道接收用户回复;(2) 子 Agent 嵌套创建 Agent 可能形成无限递归,吃光资源。
所以 ALL_AGENT_DISALLOWED_TOOLS 其实是安全护栏,不是功能限制?
正是如此。整个工具系统的设计哲学就是 fail-closed——从 buildTool 的默认值到 Agent 的工具裁剪,都是"不确定就禁止"。
buildTool() 中 isConcurrencySafe 的默认值是什么?为什么?
COORDINATOR_MODE_ALLOWED_TOOLS 只包含 4 个工具,Coordinator 模式的设计意图是什么?
ToolPermissionContext 中三组规则的类型都是 ToolPermissionRulesBySource。为什么要"按来源分组"而不是简单的规则列表?
可选反馈:
已提交!你可以关闭此标签页或返回 Claude Code。