Guide for migrating tools from legacy switch statements to ToolExecutorRegistry handlers in Orient
Installation
Details
Usage
After installing, this skill will be available to your AI coding assistant.
Verify installation:
npx agent-skills-cli listSkill Instructions
name: tool-executor-registry-migration description: 'Guide for migrating tools from legacy switch statements to ToolExecutorRegistry handlers in Orient'
Tool Executor Registry Migration
This skill documents the migration pattern from legacy switch-statement tools to ToolExecutorRegistry handlers in Orient.
Triggers
Use this skill when:
- Migrating a tool from the legacy
mcp-server.tsswitch statement to the modern handler pattern - Debugging why a tool appears in
discover_toolsbut returns "Unknown tool" at runtime - Understanding the two-registry architecture (ToolRegistry vs ToolExecutorRegistry)
- Setting up new tools that need to work with all MCP server types
The Two-Registry Architecture
Orient has two separate systems for tools:
1. ToolRegistry (Discovery Only)
Purpose: Metadata for tool discovery via discover_tools
Location: packages/agents/src/services/toolRegistry.ts
Function: getToolRegistry()
This registry stores tool definitions and metadata for search/discovery. It does NOT execute tools.
// Tools are registered here for discovery
registry.registerTool({
tool: { name: 'my_tool', description: '...', inputSchema: {...} },
category: 'google',
keywords: ['calendar', 'events'],
useCases: ['List upcoming meetings'],
});
2. ToolExecutorRegistry (Execution)
Purpose: Actually run tool code when called
Location: packages/agents/src/services/toolRegistry.ts
Function: getToolExecutorRegistry()
This registry maps tool names to handler functions that execute the tool logic.
// Tools are registered here for execution
registry.registerHandler('my_tool', async (args) => {
// Actual tool implementation
return createToolResult(JSON.stringify({ success: true }));
});
Identifying the Problem
When a tool returns {"error":"Unknown tool: tool_name"}:
Step 1: Check Discovery
# If tool appears here, it's in ToolRegistry
curl -X POST ... -d '{"tool":"discover_tools","args":{"mode":"search","query":"tool_name"}}'
Step 2: Check Executor
Look in packages/agents/src/services/toolRegistry.ts for:
registry.registerHandler('tool_name', ...);
If found in discovery but not in executor, the tool needs migration.
Server Architecture
assistant-server (Used by Slack/WhatsApp bots)
assistant-server.ts
βββ base-server.ts
βββ executeToolCallFromRegistry()
βββ ToolExecutorRegistry.execute() β checks here first
βββ Returns "Unknown tool" error β if not found
CRITICAL: base-server.ts does NOT automatically have access to the legacy mcp-server.ts switch statement. Tools MUST be in ToolExecutorRegistry to work with assistant-server.
coding-server / core-server
Same architecture as assistant-server.
Direct mcp-server.ts (Legacy)
The original monolithic server has a giant switch statement with all tools. But this is only used when running mcp-server.ts directly, not via the modular servers.
Migration Steps
Step 1: Find the Legacy Implementation
Look in packages/mcp-servers/src/mcp-server.ts for:
case 'tool_name': {
// Legacy implementation
}
Step 2: Create Handler in ToolExecutorRegistry
Add to packages/agents/src/services/toolRegistry.ts:
// In registerXyzToolHandlers() function
registry.registerHandler('tool_name', async (args: Record<string, unknown>) => {
const { param1, param2 } = args as { param1: string; param2?: number };
try {
// Your implementation
const result = await doSomething(param1, param2);
return createToolResult(JSON.stringify(result, null, 2));
} catch (error) {
return createToolError(`Failed: ${error instanceof Error ? error.message : String(error)}`);
}
});
Step 3: Call Registration Function
Ensure your registration function is called in getToolExecutorRegistry():
export function getToolExecutorRegistry(): ToolExecutorRegistry {
if (!executorInstance) {
executorInstance = new ToolExecutorRegistry();
registerMediaToolHandlers(executorInstance);
registerConfigToolHandlers(executorInstance);
registerGoogleToolHandlers(executorInstance); // Add your function here
}
return executorInstance;
}
Step 4: Build and Deploy
# Build affected packages
pnpm --filter @orientbot/agents build
pnpm --filter @orientbot/mcp-servers build
# Copy to root dist (CRITICAL - often forgotten!)
cp packages/mcp-servers/dist/*.js dist/mcp-servers/
# Kill old MCP server processes
ps aux | grep "assistant-server\|coding-server" | grep -v grep | awk '{print $2}' | xargs kill
# Test - OpenCode will spawn fresh processes
Common Pitfalls
1. Async Registration Race Condition
WRONG - Handlers registered asynchronously may not be ready:
function registerMyHandlers(registry: ToolExecutorRegistry): void {
const registerAsync = async () => {
const service = await import('my-service');
registry.registerHandler('my_tool', ...); // May not be ready!
};
void registerAsync(); // Fire and forget = race condition
}
CORRECT - Register synchronously, import lazily inside handler:
function registerMyHandlers(registry: ToolExecutorRegistry): void {
registry.registerHandler('my_tool', async (args) => {
const service = await import('my-service'); // Lazy import on call
// ...
});
}
2. Forgetting to Copy dist Files
OpenCode's config points to ./dist/mcp-servers/assistant-server.js (root dist).
Package builds go to packages/mcp-servers/dist/.
Always copy after building:
cp packages/mcp-servers/dist/*.js dist/mcp-servers/
3. Not Killing Old Processes
MCP servers run as child processes of OpenCode. If you don't kill them, they keep running with old code:
ps aux | grep "assistant-server\|coding-server" | grep -v grep | awk '{print $2}' | xargs kill
4. Missing from Server Tool Config
Check packages/mcp-servers/src/types.ts to ensure the tool's category is included:
assistant: {
tools: {
categories: ['jira', 'messaging', 'whatsapp', 'docs', 'google', 'context', 'system'],
// ^^^^^^^^ Must include your category
}
}
Verification Checklist
After migration, verify:
-
Build succeeds:
pnpm --filter @orientbot/agents build -
Handler is registered (check built file):
grep "registerHandler.*tool_name" packages/agents/dist/services/toolRegistry.js -
Files copied to root dist:
ls -la dist/mcp-servers/base-server.js # Check timestamp is recent -
Old processes killed:
ps aux | grep "assistant-server" | grep -v grep # Should return nothing -
Tool works in MCP logs:
tail -f logs/mcp-debug-*.log | grep tool_name # Should NOT see "Unknown tool" error
Files Reference
| File | Purpose |
|---|---|
packages/agents/src/services/toolRegistry.ts | Both registries, handler registration |
packages/mcp-servers/src/mcp-server.ts | Legacy switch statement (DEPRECATED) |
packages/mcp-servers/src/base-server.ts | Uses ToolExecutorRegistry |
packages/mcp-servers/src/tool-executor.ts | Execution routing logic |
packages/mcp-servers/src/types.ts | Server tool configurations |
dist/mcp-servers/*.js | Root dist used by OpenCode |
More by orient-bot
View allComprehensive guide for testing Orient releases. Use this skill when asked to "test the release", "verify installation", "run release tests", "check v0.x.x", "validate installer", or when preparing a new version for release. Covers unit tests, installer E2E tests, CLI verification, integration tests, eval tests, and success criteria validation.
Complete Google OAuth integration architecture including token storage and debugging
Complete workflow for deploying website content changes (documentation, legal pages, blog posts) including creating custom React pages, updating Docusaurus config, local testing with dev server, and understanding the rapid deployment process for website-only changes via GitHub Actions
Clean up development environment issues in the Orient monorepo. Use when asked to "clean build", "rebuild from scratch", "fix build issues", "clean node_modules", or when encountering stale build artifacts, tsbuildinfo issues, or turbo cache problems. NOT for testing the installer - use fresh-install-testing for that.
