Three Gating Mechanisms for Tool Registration and Conditional Loading
Claude Code uses a single-source tool registry and three layered gating mechanisms—Feature Flags for experimental tools, environment‑variable SIMPLE mode for minimal tool sets, and fine‑grained permission filtering—to securely control tool visibility, initialization side‑effects, and ordering, ensuring stable prompt caching and safe schema exposure.
01 | Tool Registration: A Single Source of Truth
Problem: In many agent frameworks tool registration is scattered across the codebase, making it hard to know how many tools exist.
Claude Code solves this by exposing a single function getAllBaseTools() that returns an array of all built‑in tool classes. The function does no filtering or permission checks, acting as a true "single source of truth".
export function getAllBaseTools(): Tools {
return [
AgentTool, // create sub‑Agent
TaskOutputTool, // view task output
BashTool, // execute Bash commands
GlobTool, // file pattern search
GrepTool, // content search (ripgrep)
FileReadTool, // read files
FileEditTool, // edit files
FileWriteTool, // write files
WebFetchTool, // fetch web pages
TodoWriteTool, // Todo management
WebSearchTool, // web search
SkillTool, // run Markdown skills
AskUserQuestionTool, // ask user
EnterPlanModeTool, // enter planning mode
// ... conditional tools in section 02
];
}The dependency graph shows tools.ts at the core, linking to concrete tool implementations, type definitions, constant lists, permission utilities, and environment utilities.
02 | First Gating Mechanism: Feature Flag (Compile‑time Pruning)
Problem: How to safely roll out experimental or privileged tools?
Tools like SleepTool and WorkflowTool are wrapped with a feature() call that evaluates at module load time. If the flag is on, the tool module is required; otherwise the constant is null, and later filtering skips it.
const SleepTool = feature('PROACTIVE') || feature('KAIROS')
? require('./tools/SleepTool/SleepTool.js').SleepTool
: null;
const WorkflowTool = feature('WORKFLOW_SCRIPTS')
? (() => {
// side‑effect: initialize built‑in workflows
require('./tools/WorkflowTool/bundled/index.js').initBundledWorkflows();
return require('./tools/WorkflowTool/WorkflowTool.js').WorkflowTool;
})()
: null; feature()runs once during module loading, turning the flag into a compile‑time decision and avoiding runtime overhead. WorkflowTool binds its initialization side‑effect to the loading condition, guaranteeing that the tool is ready whenever the flag is enabled.
When the constant is null, subsequent filtering simply omits the entry.
The design insight: initialization side‑effects are bound to the loading condition instead of a separate init() call, eliminating the risk of a flag being on while the tool remains uninitialized.
03 | Second Gating Mechanism: Environment Variable (Runtime Simplified Mode)
Problem: How to expose only a minimal tool set in embedded or CI environments?
Claude Code defines a CLAUDE_CODE_SIMPLE mode. When the environment variable is truthy, getTools() returns only three core tools; otherwise it returns the full set filtered by deny rules.
export const getTools = (permissionContext: ToolPermissionContext): Tools => {
// Simple mode: only three tools
if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) {
return filterToolsByDenyRules(
[BashTool, FileReadTool, FileEditTool],
permissionContext
);
}
// Full mode: all base tools, then apply deny rules
return filterToolsByDenyRules(getAllBaseTools(), permissionContext);
};The three‑tool minimal set is chosen deliberately: BashTool – core execution capability. FileReadTool – needed to understand context. FileEditTool – core code‑modification action.
Tools such as FileWriteTool, WebFetchTool, or AgentTool are omitted, making the set sufficient for reading, editing, and executing code.
The name CLAUDE_CODE_SIMPLE signals a reduction in overall complexity rather than merely removing tools. The same effect can be triggered via the --bare CLI flag; both are treated equivalently by the helper D5() function.
function D5(): boolean {
return U6(process.env.CLAUDE_CODE_SIMPLE) || process.argv.includes("--bare");
}04 | Third Gating Mechanism: Permission Filtering (Fine‑grained Runtime Control)
Problem: How to disable a tool for a specific user or project?
Permission filtering runs after the previous two layers. The function filterToolsByDenyRules iterates over the tool list and removes any tool that matches a deny rule in the provided ToolPermissionContext.
export function filterToolsByDenyRules<T>(tools: readonly T[], permissionContext: ToolPermissionContext): T[] {
return tools.filter(tool => !getDenyRuleForTool(permissionContext, tool));
}The ToolPermissionContext can contain global deny lists, path‑based allow/deny arrays, read‑only flags, and other policy settings. If a tool is denied, it never reaches the model schema, preventing the model from attempting to use an unavailable tool.
This three‑layer approach—Feature Flag, SIMPLE mode, and Permission Filtering—forms a depth‑defense strategy.
05 | Assemble Tool Pool: MCP Tool Merging
Claude Code also merges tools discovered from Model Context Protocol (MCP) servers. The function assembleToolPool first obtains the base tools (already filtered by permissions), then collects MCP tools, and finally merges them, giving precedence to built‑in tools.
async function assembleToolPool(
mcpServers: MCPServer[],
permissionContext: ToolPermissionContext
): Promise<Tools> {
const baseTools = getTools(permissionContext);
const mcpTools = await collectMCPTools(mcpServers);
// MCP tools do not overwrite built‑in tools (built‑in wins)
return mergeToolPools(baseTools, mcpTools);
}To keep prompt‑cache stability, MCP tools are sorted by server name before merging.
const sortedMCPTools = mcpTools.sort((a, b) =>
a.serverName.localeCompare(b.serverName)
);Built‑in priority prevents malicious MCP servers from hijacking tool names.
06 | Critical Perspective: Design Boundaries
Static registration vs. dynamic discovery: getAllBaseTools() is static; adding a new tool requires code changes, unlike frameworks such as LangChain that support runtime registration.
SIMPLE mode hard‑codes the three‑tool list: extending the minimal set (e.g., adding WebFetchTool) requires a source change.
Feature Flag reliance on GrowthBook: flags need network access to fetch the latest state; local development may see a mismatch, and the service falls back to a closed state for safety.
07 | Reuse Recommendations for Your Own Agent
1. Single tool‑registry function: create a getAllTools() that lists every possible tool in one place.
export function getAllTools(): Tool[] {
return [
searchTool,
calculatorTool,
// experimental tools
...(process.env.ENABLE_EXPERIMENTAL ? [experimentalTool] : []),
];
}2. Three‑layer filtering pipeline:
export function getToolsForSession(userId: string, sessionConfig: SessionConfig): Tool[] {
const allTools = getAllTools();
const featureFiltered = allTools.filter(t =>
!t.requiredFeature || isFeatureEnabled(t.requiredFeature, userId)
);
const modeFiltered = sessionConfig.simple
? featureFiltered.filter(t => MINIMAL_TOOLS.includes(t.name))
: featureFiltered;
return modeFiltered.filter(t => !isDenied(t.name, sessionConfig.permissions));
}3. Stable ordering for dynamic parts: when merging tools from external sources, sort them deterministically to keep prompt‑cache hits high.
// Bad: order may vary each request
const tools = await Promise.all(mcpServers.map(s => s.getTools()))
.then(r => r.flat());
// Good: deterministic sort
const tools = await Promise.all(mcpServers.map(s => s.getTools()))
.then(r => r.flat())
.then(t => t.sort((a, b) => a.name.localeCompare(b.name)));Conclusion
getAllBaseTools()provides a single source of truth for tool registration, simplifying audit and maintenance. The three gating mechanisms—Feature Flag, SIMPLE mode, and Permission Filtering—operate at different layers to achieve depth‑defense. Removing a tool via permission filtering is safer than merely denying its use, because the model never sees the tool in its schema. Finally, stable ordering of MCP‑provided tools is essential for prompt‑cache efficiency and cost control.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
James' Growth Diary
I am James, focusing on AI Agent learning and growth. I continuously update two series: “AI Agent Mastery Path,” which systematically outlines core theories and practices of agents, and “Claude Code Design Philosophy,” which deeply analyzes the design thinking behind top AI tools. Helping you build a solid foundation in the AI era.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
