/**
* Tool Registry
*
* Central registry for managing MCP tools.
* Supports dynamic tool registration and lookup.
*/
import { ToolDefinition, ToolResult } from '../../domain/types.js';
import { ILogger } from '../../infrastructure/logging/logger.js';
/**
* Tool handler function signature
*/
export type ToolHandler = (args: unknown) => Promise<ToolResult>;
/**
* Registered tool with definition and handler
*/
interface RegisteredTool {
readonly definition: ToolDefinition;
readonly handler: ToolHandler;
}
/**
* Tool registry for managing available tools
*/
export class ToolRegistry {
private readonly tools: Map<string, RegisteredTool> = new Map();
private readonly logger: ILogger;
constructor(logger: ILogger) {
this.logger = logger;
}
/**
* Register a tool with its handler
*
* @param definition - Tool definition
* @param handler - Tool execution handler
*/
register(definition: ToolDefinition, handler: ToolHandler): void {
if (this.tools.has(definition.name)) {
this.logger.warn('Tool already registered, overwriting', {
toolName: definition.name,
});
}
this.tools.set(definition.name, {
definition,
handler,
});
this.logger.info('Tool registered', {
toolName: definition.name,
});
}
/**
* Unregister a tool
*
* @param toolName - Name of tool to remove
* @returns true if tool was removed, false if not found
*/
unregister(toolName: string): boolean {
const removed = this.tools.delete(toolName);
if (removed) {
this.logger.info('Tool unregistered', { toolName });
} else {
this.logger.warn('Attempted to unregister non-existent tool', { toolName });
}
return removed;
}
/**
* Get all registered tool definitions
*
* @returns Array of tool definitions
*/
getToolDefinitions(): ReadonlyArray<ToolDefinition> {
return Array.from(this.tools.values()).map((tool) => tool.definition);
}
/**
* Get a tool handler by name
*
* @param toolName - Name of tool
* @returns Tool handler or undefined if not found
*/
getHandler(toolName: string): ToolHandler | undefined {
return this.tools.get(toolName)?.handler;
}
/**
* Check if a tool is registered
*
* @param toolName - Name of tool
* @returns true if tool exists
*/
has(toolName: string): boolean {
return this.tools.has(toolName);
}
/**
* Get count of registered tools
*
* @returns Number of tools
*/
count(): number {
return this.tools.size;
}
/**
* Execute a tool by name
*
* @param toolName - Name of tool to execute
* @param args - Tool arguments
* @returns Tool result
* @throws Error if tool not found
*/
async execute(toolName: string, args: unknown): Promise<ToolResult> {
const handler = this.getHandler(toolName);
if (!handler) {
this.logger.error('Tool not found', new Error(`Tool not found: ${toolName}`), {
toolName,
availableTools: Array.from(this.tools.keys()),
});
throw new Error(`Tool not found: ${toolName}`);
}
this.logger.debug('Executing tool', {
toolName,
args,
});
try {
const result = await handler(args);
this.logger.debug('Tool executed successfully', {
toolName,
});
return result;
} catch (error) {
this.logger.error(
'Tool execution failed',
error instanceof Error ? error : new Error(String(error)),
{
toolName,
args,
}
);
throw error;
}
}
}
/**
* Factory function to create tool registry
*/
export function createToolRegistry(logger: ILogger): ToolRegistry {
return new ToolRegistry(logger);
}