How Are Tools Assembled in OpenClaude: The Tool Pool Architecture
OpenClaude assembles executable toolsets through a six-stage pipeline that aggregates built-in utilities, MCP server plugins, and dynamically discovered tools, then applies coordinator-mode filtering and deduplication before serializing them into API requests.
Understanding how tools are assembled in OpenClaude is essential for developers extending the Gitlawb/openclaude codebase or debugging permission issues. The repository implements a sophisticated tool pool architecture orchestrated primarily in src/utils/toolPool.ts and src/entrypoints/cli/REPL.tsx. This system dynamically constructs context-aware toolsets while respecting security policies and model token limits.
The Six-Stage Tool Assembly Pipeline
Stage 1: Loading Built-in Tools
The assembly process begins in src/entrypoints/cli/REPL.tsx where the system calls getBuiltInTools() from src/tools/builtInTools.js. These static tools include core utilities such as Bash, FileEdit, FileWrite, SendMessage, and TaskCreate. Each tool exports a constants.ts file defining its name and metadata, alongside a prompt.ts file specifying its input schema for the model.
Stage 2: Discovering MCP Tools
After loading built-ins, the pipeline queries connected MCP (Model-Controlled Plugins) servers via getMcpTools(context.mcpClient). These runtime-discovered tools extend capabilities beyond the static set, allowing integration with external services without modifying the core codebase. The MCP client returns tool definitions that conform to the same schema standards as built-in utilities.
Stage 3: Dynamic Tool Discovery via Tool Search
When the Tool Search feature is enabled, the system invokes discoverDeferredTools() from src/utils/toolSearch.ts. This mechanism sends a special ToolSearchTool request to the model, asking it to enumerate additional tools required for the specific query context. The server responds with a curated list of tool names, which OpenClaude then loads on-demand. This deferred loading strategy avoids pre-declaring hundreds of potentially unused MCP tools, significantly reducing API payload size.
Stage 4: Coordinator Mode Filtering
For multi-agent or remote-worker deployments, the pipeline applies applyCoordinatorToolFilter() defined in src/utils/toolPool.ts. This function filters the aggregated tool array against the COORDINATOR_MODE_ALLOWED_TOOLS constant exported from src/constants/tools.ts. This security layer ensures coordinator nodes restrict worker processes to an explicit whitelist of authorized functions, preventing unauthorized file system or network access in distributed scenarios.
Stage 5: Deduplication by Tool Name
The combined array—containing built-ins, MCP tools, and deferred discoveries—frequently contains duplicate entries when MCP servers expose functions matching built-in names. The pipeline applies uniqBy to deduplicate the collection based on tool name, ensuring each executable capability appears exactly once in the final context.
Stage 6: Serialization for the API Request
Finally, the deduplicated tool list passes to the query engine in src/query/*, which serializes the tools into the Anthropic API request payload. Depending on the model version and capabilities, this serialization may include structured tool_reference blocks or inline tool descriptions with JSON schemas.
Implementation Deep Dive
The following examples demonstrate the actual implementation patterns found in the OpenClaude source code.
Assembling the Tool Set in the REPL
The REPL.tsx entry point orchestrates the aggregation and filtering sequence:
// src/entrypoints/cli/REPL.tsx
import { getBuiltInTools } from '../../tools/builtInTools.js';
import { getMcpTools } from '../../tools/mcpTools.js';
import { discoverDeferredTools } from '../../utils/toolSearch.js';
import { applyCoordinatorToolFilter } from '../../utils/toolPool.js';
async function assembleTools(context) {
// Stage 1: Static built-ins
const builtIns = await getBuiltInTools();
// Stage 2: Runtime MCP discovery
const mcp = await getMcpTools(context.mcpClient);
// Stage 3: Dynamic deferred loading
const deferred = await discoverDeferredTools(context);
// Combine all sources
const allTools = [...builtIns, ...mcp, ...deferred];
// Stage 4: Coordinator security filter
const filtered = applyCoordinatorToolFilter(allTools);
// Stage 5: Deduplication (uniqBy implementation detail)
const uniqueTools = uniqBy(filtered, 'name');
return uniqueTools; // Passed to query engine
}
Coordinator Mode Security Filtering
The filtering logic resides in toolPool.ts and uses a whitelist approach:
// src/utils/toolPool.ts
import { COORDINATOR_MODE_ALLOWED_TOOLS } from '../constants/tools.js';
export function applyCoordinatorToolFilter(tools: Tool[]): Tool[] {
return tools.filter(tool =>
COORDINATOR_MODE_ALLOWED_TOOLS.has(tool.name)
);
}
Dynamic Tool Discovery Implementation
The Tool Search feature defers loading until the model explicitly requests specific capabilities:
// src/utils/toolSearch.ts
export async function discoverDeferredTools(ctx) {
if (!ctx.enableToolSearch) return [];
// Sends ToolSearchTool to model
const { toolNames } = await ctx.client.sendToolSearchRequest();
// Load only requested tools
return toolNames.map(name => loadToolByName(name));
}
Summary
- Built-in tools provide core filesystem and process utilities through static imports in
src/tools/. - MCP tools extend capabilities at runtime via server discovery without code changes.
- Tool Search enables deferred loading that minimizes token usage by only declaring tools the model explicitly requests.
- Coordinator filtering enforces security policies via
COORDINATOR_MODE_ALLOWED_TOOLSwhen operating in distributed modes. - Deduplication ensures clean toolsets by removing name collisions between built-ins and MCP providers.
- Serialization in
src/query/*converts the final array into Anthropic API-compatible tool definitions.
Frequently Asked Questions
What is the difference between built-in and MCP tools in OpenClaude?
Built-in tools are static TypeScript modules shipped with the repository (located in src/tools/) that provide essential capabilities like Bash execution and file editing. MCP tools are discovered at runtime from external Model-Controlled Plugins servers, allowing dynamic extension of capabilities without modifying the OpenClaude source code or redeploying the application.
How does Tool Search optimize the tool assembly process?
Tool Search optimizes assembly by implementing deferred loading: instead of declaring all available MCP tools in the initial system prompt (which consumes significant context window tokens), the model first receives a minimal toolset plus a ToolSearchTool. If the model determines it needs additional capabilities, it explicitly requests them via the Tool Search endpoint, and only those specific tools are loaded via discoverDeferredTools() in src/utils/toolSearch.ts.
What happens when coordinator mode filters remove a requested tool?
When applyCoordinatorToolFilter() processes the tool array, any tool not present in the COORDINATOR_MODE_ALLOWED_TOOLS whitelist defined in src/constants/tools.ts is removed from the collection before the API request is constructed. If the model subsequently attempts to invoke a removed tool, the request will fail at the validation layer, requiring the coordinator to either adjust its whitelist or the worker to request alternative approaches using available tools.
Where does deduplication occur in the assembly pipeline?
Deduplication occurs after coordinator filtering but before serialization to the API. The uniqBy function processes the filtered array in src/utils/toolPool.ts (or the assembly function in REPL.tsx), comparing tool names to ensure that duplicates—such as a FileEdit tool provided by both the built-in set and an MCP server—appear only once in the final payload sent to src/query/*.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →