Tool Registry
The tool registry is the catalog of all capabilities available to the agent. It's responsible for defining, discovering, validating, and executing tools safely.
Purpose
- Discovery: Tell the model what tools are available
- Definition: Provide schemas so the model knows how to call them
- Validation: Ensure tool calls are well-formed
- Execution: Run tools with proper error handling
- Security: Enforce permissions and sandboxing
- Extensibility: Support built-in tools and MCP servers
Tool Definition Structure
Every tool needs a clear definition:
interface Tool {
name: string; // Unique identifier (e.g., "read_file")
description: string; // Clear description for the model
parameters: JSONSchema; // Input schema
category: string; // Grouping (filesystem, shell, search, etc.)
requiresPermission: boolean;
riskLevel: 'low' | 'medium' | 'high';
execute: (args: any, context: ToolContext) => Promise<ToolResult>;
}
Tool Schema Design
Good tool schemas help the model call tools correctly:
{
"name": "read_file",
"description": "Read the contents of a file. Returns the file content as text.",
"parameters": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Absolute or relative path to the file"
},
"offset": {
"type": "number",
"description": "Line number to start from (1-based)"
},
"limit": {
"type": "number",
"description": "Maximum number of lines to read"
}
},
"required": ["path"]
}
}
Tool Categories
| Category | Examples | Risk Level | Notes |
|---|---|---|---|
| Read | read_file, glob, grep, search | Low | Generally safe, read-only |
| Write/Edit | write_file, edit, str_replace | Medium | Modify files, need care |
| Execute | bash, run_command | High | Shell access, most dangerous |
| Navigate | cd, list_dir | Low | Directory operations |
| Git | git_status, git_diff, git_commit | Medium-High | Version control changes |
| Web | webfetch, websearch | Medium | Network access |
| Browser | browser_navigate, browser_click | High | Full browser control |
| MCP | Dynamic from servers | Varies | Depends on server |
Registry Implementation
class ToolRegistry:
def __init__(self):
self.tools = {}
self.categories = {}
def register(self, tool: Tool):
self.tools[tool.name] = tool
self.categories.setdefault(tool.category, []).append(tool.name)
def get(self, name: str) -> Tool | None:
return self.tools.get(name)
def get_schemas_for_model(self, allowed_tools=None) -> list:
"""Return tool schemas in model's expected format"""
tools = self.tools.values()
if allowed_tools:
tools = [t for t in tools if t.name in allowed_tools]
return [self._format_schema(t) for t in tools]
async def execute(self, name: str, args: dict, context: ToolContext):
tool = self.get(name)
if not tool:
raise ToolNotFoundError(name)
# Validate args against schema
validate(args, tool.parameters)
# Check permissions
await context.permission_checker.check(tool, args)
# Execute with timeout
return await asyncio.wait_for(tool.execute(args, context), timeout=300)
Tool Context
Tools need access to harness capabilities:
interface ToolContext {
sessionId: string;
workingDirectory: string;
permissionChecker: PermissionChecker;
sandbox: Sandbox;
logger: Logger;
abortSignal?: AbortSignal;
userId?: string;
}
Built-in Tool Examples
Filesystem Tools
@tool(name="read_file", category="filesystem", risk_level="low")
async def read_file(args, ctx):
path = resolve_path(args["path"], ctx.workingDirectory)
content = await ctx.sandbox.read_file(path)
return {"content": content, "path": path}
Shell Tool
@tool(name="bash", category="shell", risk_level="high", requiresPermission=True)
async def bash(args, ctx):
command = args["command"]
# Permission check will handle approval
result = await ctx.sandbox.execute_shell(command, timeout=args.get("timeout", 120))
return {"stdout": result.stdout, "stderr": result.stderr, "exitCode": result.exitCode}
Search Tools
@tool(name="grep", category="search", risk_level="low")
async def grep(args, ctx):
pattern = args["pattern"]
path = args.get("path", ctx.workingDirectory)
results = await ctx.sandbox.grep(pattern, path, args.get("include"))
return {"matches": results}
MCP Integration
The registry should also support tools from MCP servers:
class MCPToolAdapter:
def __init__(self, server_name: str, tool_def: MCPTool):
self.server_name = server_name
self.name = f"{server_name}__{tool_def.name}" # Namespaced
self.description = tool_def.description
self.parameters = tool_def.inputSchema
async def execute(self, args, ctx):
return await ctx.mcp_client.call_tool(
self.server_name,
self.original_name,
args
)
Best Practices
Tool Design
- Clear descriptions - Tell the model when to use the tool and what it does
- Minimal parameters - Fewer required params = fewer mistakes
- Good defaults - Make common cases easy
- Consistent naming - Use verb_noun pattern (read_file, run_command)
- Return useful info - Include enough context for next steps
Registry Design
- Lazy loading - Load MCP tools on demand
- Tool filtering - Show only relevant tools based on context
- Validation - Validate schemas at registration time
- Error messages - Clear errors when tools fail or aren't found
- Namespacing - Avoid collisions between built-in and MCP tools
Security
- Permission gates - High-risk tools need approval
- Input validation - Sanitize paths, commands
- Path restrictions - Don't allow escaping working directory
- Command filtering - Block dangerous shell patterns
- Audit logging - Log all tool executions
Tool Result Format
Consistent result format helps the model understand outcomes:
interface ToolResult {
success: boolean;
output?: any; // Main result data
error?: string; // Error message if failed
metadata?: {
duration: number;
truncated?: boolean;
[key: string]: any;
};
}
Related Notes
- Permission System — Security and approvals
- Function Calling — Model tool calling
- MCP Protocol — MCP integration
- Error Recovery — Handling tool failures
- Filesystem Integration — Filesystem tools
Sources
- OpenAI Function Calling documentation
- Anthropic Tool Use documentation
- MCP Specification
- LangChain Tools
- OpenCode tool implementations