UNPKG

@boundless-oss/atlas

Version:

Atlas - MCP Server for comprehensive startup project management

321 lines 10.4 kB
import { getValidator, createValidationError } from './validation.js'; import { getSQLiteManager } from '../storage/sqlite-manager.js'; import { randomUUID } from 'crypto'; /** * Tool Registry - manages all registered tools */ export class ToolRegistry { tools = new Map(); modules = new Map(); validator; constructor() { this.validator = getValidator(); } /** * Register a module with its tools */ registerModule(registration) { // Validate all tool schemas first for (const tool of registration.tools) { this.validateToolDefinition(tool); } // Register the module this.modules.set(registration.module, registration); // Register individual tools with module prefix for (const tool of registration.tools) { const fullName = `${registration.module}--${tool.name}`; this.tools.set(fullName, { ...tool, name: fullName }); } console.error(`🔧 Registered module '${registration.module}' with ${registration.tools.length} tools`); } /** * Get a tool by name */ getTool(name) { return this.tools.get(name); } /** * Get all registered tools */ getAllTools() { return Array.from(this.tools.values()); } /** * Get tools by module */ getToolsByModule(moduleName) { const registration = this.modules.get(moduleName); if (!registration) return []; return registration.tools.map(tool => ({ ...tool, name: `${moduleName}.${tool.name}` })); } /** * Get all modules */ getModules() { return Array.from(this.modules.keys()); } /** * Get tool manifest for MCP */ getToolManifest() { return this.getAllTools().map(tool => ({ name: tool.name, description: tool.description, inputSchema: tool.inputSchema, category: tool.category, readOnly: tool.readOnly })); } /** * Get tool count */ getToolCount() { return this.tools.size; } /** * Get module count */ getModuleCount() { return this.modules.size; } /** * Validate a tool definition */ validateToolDefinition(tool) { if (!tool.name || typeof tool.name !== 'string') { throw new Error('Tool name is required and must be a string'); } if (!tool.description || typeof tool.description !== 'string') { throw new Error('Tool description is required and must be a string'); } if (!tool.inputSchema || typeof tool.inputSchema !== 'object') { throw new Error('Tool inputSchema is required and must be a valid JSON schema'); } if (typeof tool.execute !== 'function') { throw new Error('Tool execute function is required'); } // Validate the schema itself try { this.validator.createValidator(tool.inputSchema); } catch (error) { throw new Error(`Invalid input schema for tool '${tool.name}': ${error instanceof Error ? error.message : 'Unknown error'}`); } } } /** * Tool executor - handles tool execution with validation and error handling */ export class ToolExecutor { registry; validator; constructor(registry) { this.registry = registry; this.validator = getValidator(); } /** * Execute a tool with validation and error handling */ async execute(toolName, input, context) { const startTime = Date.now(); try { // Get the tool const tool = this.registry.getTool(toolName); if (!tool) { return this.createErrorResult({ code: 'TOOL_NOT_FOUND', message: `Tool '${toolName}' not found`, details: { toolName, availableTools: this.registry.getAllTools().map(t => t.name) }, suggestions: [ 'Check the tool name for typos', 'Use list_tools to see available tools', 'Verify the tool module is loaded' ], recoverable: false, category: 'resource' }, context.requestId); } // Validate input const validationResult = this.validator.validate(tool.inputSchema, input); if (!validationResult.valid) { return this.createErrorResult(createValidationError(validationResult.errors), context.requestId); } // Record execution start await this.recordExecution({ toolName, requestId: context.requestId, userId: context.userId, startTime, success: false // Will be updated after execution }); // Execute the tool const result = await tool.execute(input, context); // Calculate execution time const executionTime = Date.now() - startTime; // Update execution record await this.recordExecution({ toolName, requestId: context.requestId, userId: context.userId, startTime, endTime: Date.now(), executionTime, success: result.success, errorCode: result.error?.code }); // Add metadata if (!result.metadata) { result.metadata = {}; } result.metadata.executionTime = executionTime; result.metadata.requestId = context.requestId; return result; } catch (error) { const executionTime = Date.now() - startTime; // Record failed execution await this.recordExecution({ toolName, requestId: context.requestId, userId: context.userId, startTime, endTime: Date.now(), executionTime, success: false, errorCode: 'EXECUTION_ERROR' }); return this.createErrorResult({ code: 'EXECUTION_ERROR', message: `Tool execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`, details: { toolName, error: error instanceof Error ? error.stack : String(error) }, suggestions: [ 'Check tool implementation for bugs', 'Verify input parameters are correct', 'Check system resources and dependencies', 'Review logs for more details' ], recoverable: true, category: 'execution' }, context.requestId, executionTime); } } /** * Create a standardized error result */ createErrorResult(error, requestId, executionTime) { return { success: false, error, metadata: { requestId, executionTime } }; } /** * Record tool execution metrics */ async recordExecution(metadata) { try { const db = getSQLiteManager(); if (metadata.endTime) { // Update existing record await db.run(`UPDATE performance_metrics SET execution_time = ?, success = ?, error_details = ? WHERE request_id = ?`, [ metadata.executionTime || 0, metadata.success ? 1 : 0, metadata.errorCode || null, metadata.requestId ]); } else { // Insert new record await db.run(`INSERT INTO performance_metrics (id, tool_name, execution_time, success, user_id, request_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)`, [ randomUUID(), metadata.toolName, 0, // Will be updated later 0, // Will be updated later metadata.userId || null, metadata.requestId, metadata.startTime ]); } } catch (error) { console.error('Failed to record execution metrics:', error); // Don't fail the tool execution for metrics recording errors } } } /** * Global registry instance */ let globalRegistry = null; let globalExecutor = null; export function getToolRegistry() { if (!globalRegistry) { globalRegistry = new ToolRegistry(); } return globalRegistry; } export function getToolExecutor() { if (!globalExecutor) { globalExecutor = new ToolExecutor(getToolRegistry()); } return globalExecutor; } /** * Utility function to create a tool definition with common patterns */ export function createTool(config) { return { name: config.name, description: config.description, inputSchema: config.inputSchema, outputSchema: config.outputSchema, category: config.category, requiresApproval: config.requiresApproval || false, readOnly: config.readOnly || false, execute: config.execute }; } /** * Utility to create success results */ export function createSuccessResult(data, metadata) { return { success: true, data, metadata }; } /** * Utility to create error results */ export function createErrorResult(error) { return { success: false, error: { code: error.code || 'UNKNOWN_ERROR', message: error.message || 'An unknown error occurred', details: error.details || {}, suggestions: error.suggestions || ['Please try again'], recoverable: error.recoverable !== false, category: error.category || 'execution' } }; } //# sourceMappingURL=tool-framework.js.map