vibe-coder-mcp
Version:
Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.
178 lines (177 loc) • 7.07 kB
JavaScript
import { z } from 'zod';
import logger from '../../logger.js';
import { AppError, ValidationError } from '../../utils/errors.js';
let instance = null;
export class ToolRegistry {
tools = new Map();
config;
constructor(config) {
this.config = config;
logger.info('ToolRegistry instance created.');
}
static getInstance(config) {
if (!instance) {
if (!config) {
throw new Error("ToolRegistry requires configuration on first initialization.");
}
instance = new ToolRegistry(config);
logger.info('ToolRegistry initialized with configuration', {
hasLlmMapping: Boolean(config.llm_mapping),
mappingKeys: config.llm_mapping ? Object.keys(config.llm_mapping) : []
});
processPendingToolRegistrations();
}
else if (config) {
instance.config = { ...config };
logger.info('ToolRegistry configuration updated', {
hasLlmMapping: Boolean(config.llm_mapping),
mappingKeys: config.llm_mapping ? Object.keys(config.llm_mapping) : []
});
}
return instance;
}
registerTool(definition) {
if (this.tools.has(definition.name)) {
logger.warn(`Tool "${definition.name}" is already registered. Skipping duplicate registration.`);
return;
}
this.tools.set(definition.name, definition);
logger.info(`Registered tool: ${definition.name}`);
}
getTool(toolName) {
return this.tools.get(toolName);
}
getAllTools() {
return Array.from(this.tools.values());
}
getRegistrationStats() {
return {
totalRegistered: this.tools.size,
toolNames: Array.from(this.tools.keys())
};
}
isToolRegistered(toolName) {
return this.tools.has(toolName);
}
clearRegistryForTesting() {
if (process.env.NODE_ENV !== 'test') {
logger.warn('Attempted to clear tool registry outside of a test environment. Operation aborted.');
return;
}
this.tools.clear();
instance = null;
logger.debug('Tool registry cleared for testing.');
}
}
export function registerTool(definition) {
if (!instance) {
logger.info(`Tool "${definition.name}" registration deferred until ToolRegistry is initialized with proper config.`);
pendingToolRegistrations.push(definition);
return;
}
ToolRegistry.getInstance().registerTool(definition);
}
const pendingToolRegistrations = [];
export function processPendingToolRegistrations() {
if (!instance) {
logger.error("Tried to process pending tool registrations but ToolRegistry is still not initialized");
return;
}
const pendingCount = pendingToolRegistrations.length;
if (pendingCount > 0) {
logger.info(`Processing ${pendingCount} pending tool registrations`);
for (const toolDef of pendingToolRegistrations) {
ToolRegistry.getInstance().registerTool(toolDef);
}
pendingToolRegistrations.length = 0;
logger.info(`Successfully registered ${pendingCount} pending tools`);
}
}
export function getTool(toolName) {
if (!instance) {
logger.warn("Attempted to get tool before ToolRegistry initialization.");
return undefined;
}
return ToolRegistry.getInstance().getTool(toolName);
}
export function getAllTools() {
if (!instance) {
logger.warn("Attempted to get all tools before ToolRegistry initialization.");
return [];
}
return ToolRegistry.getInstance().getAllTools();
}
export async function executeTool(toolName, params, config, context) {
logger.debug({ toolName, receivedConfig: config }, 'executeTool received config object.');
logger.info(`Attempting to execute tool: ${toolName}`);
const toolDefinition = getTool(toolName);
if (!toolDefinition) {
logger.error(`Tool "${toolName}" not found in registry.`);
return {
content: [{ type: 'text', text: `Error: Tool "${toolName}" not found.` }],
isError: true,
};
}
const schemaObject = z.object(toolDefinition.inputSchema);
const validationResult = schemaObject.safeParse(params);
if (!validationResult.success) {
logger.error({ tool: toolName, errors: validationResult.error.issues, paramsReceived: params }, 'Tool parameter validation failed.');
const validationError = new ValidationError(`Input validation failed for tool '${toolName}'`, validationResult.error.issues, { toolName, paramsReceived: params });
return {
content: [{ type: 'text', text: validationError.message }],
isError: true,
errorDetails: {
type: validationError.name,
message: validationError.message,
issues: validationError.validationIssues,
}
};
}
logger.debug(`Executing tool "${toolName}" with validated parameters.`);
try {
logger.debug({ toolName: toolName, sessionId: context?.sessionId }, `Executing tool "${toolName}" executor with context.`);
const result = await toolDefinition.executor(validationResult.data, config, context);
logger.info(`Tool "${toolName}" executed successfully.`);
return result;
}
catch (error) {
logger.error({ err: error, tool: toolName, params: validationResult.data }, `Error during execution of tool "${toolName}".`);
let errorMessage = `Execution error in tool '${toolName}'.`;
let errorType = 'ToolExecutionError';
let errorContext = { toolName, params: validationResult.data };
if (error instanceof AppError) {
errorMessage = `Error in tool '${toolName}': ${error.message}`;
errorType = error.name;
errorContext = { ...errorContext, ...error.context };
}
else if (error instanceof Error) {
errorMessage = `Unexpected error in tool '${toolName}': ${error.message}`;
errorType = error.name;
}
else {
errorMessage = `Unknown execution error in tool '${toolName}'.`;
errorType = 'UnknownExecutionError';
errorContext.originalValue = String(error);
}
return {
content: [{ type: 'text', text: errorMessage }],
isError: true,
errorDetails: {
type: errorType,
message: (error instanceof Error) ? error.message : String(error),
context: errorContext,
}
};
}
}
export function isToolRegistered(toolName) {
const registry = ToolRegistry.getInstance();
return registry.isToolRegistered(toolName);
}
export function clearRegistryForTesting() {
if (!instance) {
logger.debug('Tool registry not initialized, nothing to clear for testing.');
return;
}
instance.clearRegistryForTesting();
}