tool.handlers.ts•15.5 kB
import { CallToolRequest, CallToolResult } from '@modelcontextprotocol/sdk/types.js';
import { DatabaseConnectionManager } from '../../database/connection/connection.manager.js';
import { Logger } from '../../core/logger.js';
import { QueryService } from '../../services/query.service.js';
import { SchemaService } from '../../services/schema.service.js';
import { PerformanceService } from '../../services/performance/performance.service.js';
/**
 * MCP Tool Handler Integration Class
 *
 * Receives all tool calls and routes them to appropriate services.
 */
export class ToolHandlers {
  private queryService: QueryService | null = null;
  private schemaService: SchemaService | null = null;
  private performanceService: PerformanceService | null = null;
  constructor(
    private dbManager: DatabaseConnectionManager,
    private logger: Logger
  ) {
    // Services will be initialized lazily when first needed in handleToolCall
  }
  /**
   * Handle tool calls
   */
  async handleToolCall(request: CallToolRequest): Promise<CallToolResult> {
    const { name, arguments: args } = request.params;
    this.logger.info(`Tool call: ${name}`, { arguments: args });
    try {
      // Wait for services to initialize if not ready
      if (!this.queryService || !this.schemaService || !this.performanceService) {
        await this.initializeServices();
      }
      switch (name) {
        case 'execute_query':
          return await this.handleExecuteQuery(args);
        case 'get_schema':
          return await this.handleGetSchema(args);
        case 'list_tables':
          return await this.handleListTables(args);
        case 'describe_table':
          return await this.handleDescribeTable(args);
        case 'get_connection_pool_status':
          return await this.handleGetConnectionPoolStatus(args);
        case 'start_performance_monitoring':
          return await this.handleStartPerformanceMonitoring(args);
        case 'start_batch_processing':
          return await this.handleStartBatchProcessing(args);
        case 'generate_performance_report':
          return await this.handleGeneratePerformanceReport(args);
        case 'get_query_stats':
          return await this.handleGetQueryStats(args);
        case 'get_schema_statistics':
          return await this.handleGetSchemaStatistics(args);
        case 'clear_caches':
          return await this.handleClearCaches(args);
        default:
          throw new Error(`Unknown tool: ${name}`);
      }
    } catch (error) {
      this.logger.error(`Tool call failed: ${name}`, {
        error: error instanceof Error ? error.message : String(error),
      });
      return {
        content: [
          {
            type: 'text',
            text: `Error: ${error instanceof Error ? error.message : String(error)}`,
          },
        ],
        isError: true,
      };
    }
  }
  /**
   * Initialize services
   */
  private async initializeServices(): Promise<void> {
    try {
      // Create default connection if none exists
      await this.ensureConnection();
      const adapter = this.dbManager.getCurrentConnection();
      // Initialize services
      this.queryService = new QueryService(this.dbManager, this.logger);
      this.schemaService = new SchemaService(this.dbManager, this.logger);
      this.performanceService = new PerformanceService(this.dbManager, this.logger);
    } catch (error) {
      this.logger.error('Service initialization failed', { error });
      throw error;
    }
  }
  /**
   * Ensure connection exists and create if needed
   */
  private async ensureConnection(): Promise<void> {
    try {
      // Check if current connection exists
      if (this.dbManager.getConnectionStats().total === 0) {
        await this.dbManager.createDefaultConnection();
      }
    } catch (error) {
      this.logger.error('Failed to create default connection', { error });
      throw error;
    }
  }
  /**
   * Execute query handler
   */
  private async handleExecuteQuery(args: any): Promise<CallToolResult> {
    const { query, parameters } = args;
    if (!query || typeof query !== 'string') {
      throw new Error('Query was not provided.');
    }
    if (!this.queryService) {
      throw new Error('Query service is not initialized.');
    }
    const result = await this.queryService.executeQuery(query, { parameters });
    if (result.success) {
      return {
        content: [
          {
            type: 'text',
            text:
              `Query execution completed\n\n` +
              `Execution time: ${result.executionTime}ms\n` +
              `Affected rows: ${result.rowsAffected || 0} rows\n\n` +
              `Results:\n${JSON.stringify(result.recordset || result.data, null, 2)}`,
          },
        ],
      };
    } else {
      throw new Error(result.error || 'Query execution failed');
    }
  }
  /**
   * Get schema handler
   */
  private async handleGetSchema(args: any): Promise<CallToolResult> {
    const { include_system_tables = false } = args;
    if (!this.schemaService) {
      throw new Error('Schema service is not initialized.');
    }
    const schema = await this.schemaService.getSchema(include_system_tables);
    return {
      content: [
        {
          type: 'text',
          text:
            `Database schema information\n\n` +
            `Schema name: ${schema.name}\n` +
            `Number of tables: ${schema.tables?.length || 0}\n` +
            `Number of views: ${schema.views?.length || 0}\n` +
            `Number of procedures: ${schema.procedures?.length || 0}\n\n` +
            `Detailed information:\n${JSON.stringify(schema, null, 2)}`,
        },
      ],
    };
  }
  /**
   * List tables handler
   */
  private async handleListTables(args: any): Promise<CallToolResult> {
    const { pattern } = args;
    if (!this.schemaService) {
      throw new Error('Schema service is not initialized.');
    }
    const tables = await this.schemaService.getTables(pattern);
    // Get column counts for each table
    const tablesWithColumnCounts = await Promise.all(
      tables.map(async table => {
        try {
          // Get column count using a simple query
          const columnCountQuery = `
            SELECT COUNT(*) as column_count 
            FROM INFORMATION_SCHEMA.COLUMNS 
            WHERE TABLE_NAME = '${table.name}'
          `;
          const result = await this.dbManager.getCurrentConnection().executeQuery(columnCountQuery);
          const columnCount = (result.success && result.recordset?.[0]?.column_count) || 0;
          return {
            ...table,
            columnCount,
          };
        } catch (error) {
          return {
            ...table,
            columnCount: 0,
          };
        }
      })
    );
    return {
      content: [
        {
          type: 'text',
          text:
            `Table list (total ${tables.length} tables)\n\n` +
            tablesWithColumnCounts
              .map(
                (table, index) =>
                  `${index + 1}. ${table.schema}.${table.name} (${table.type})\n` +
                  `   Columns: ${table.columnCount}`
              )
              .join('\n'),
        },
      ],
    };
  }
  /**
   * Describe table handler
   */
  private async handleDescribeTable(args: any): Promise<CallToolResult> {
    const { table_name } = args;
    if (!table_name) {
      throw new Error('Table name was not provided.');
    }
    if (!this.schemaService) {
      throw new Error('Schema service is not initialized.');
    }
    const tableInfo = await this.schemaService.getTableInfo(table_name);
    return {
      content: [
        {
          type: 'text',
          text:
            `Table detailed information: ${tableInfo.name}\n\n` +
            `Schema: ${tableInfo.schema}\n` +
            `Type: ${tableInfo.type}\n` +
            `Number of columns: ${tableInfo.columns.length}\n` +
            `Primary keys: ${tableInfo.primaryKeys.length}\n` +
            `Foreign keys: ${tableInfo.foreignKeys?.length || 0}\n` +
            `Indexes: ${tableInfo.indexes?.length || 0}\n\n` +
            `Column information:\n` +
            tableInfo.columns
              .map(
                (col, index) =>
                  `${index + 1}. ${col.name}: ${col.dataType}${
                    col.maxLength ? `(${col.maxLength})` : ''
                  }\n` +
                  `   ${col.isNullable ? 'NULL allowed' : 'NOT NULL'}` +
                  `${col.isPrimaryKey ? ' PK' : ''}` +
                  `${col.isIdentity ? ' IDENTITY' : ''}`
              )
              .join('\n'),
        },
      ],
    };
  }
  /**
   * Get connection pool status handler
   */
  private async handleGetConnectionPoolStatus(args: any): Promise<CallToolResult> {
    if (!this.performanceService) {
      throw new Error('Performance service is not initialized.');
    }
    const poolStatus = this.performanceService.getConnectionPoolStatus();
    const connectionStats = this.dbManager.getConnectionStats();
    return {
      content: [
        {
          type: 'text',
          text:
            `Connection pool status\n\n` +
            `Total connections: ${poolStatus.totalConnections}\n` +
            `Active connections: ${poolStatus.activeConnections}\n` +
            `Pool utilization: ${poolStatus.utilization.toFixed(1)}%\n` +
            `Status: ${poolStatus.status}\n\n` +
            `Connection list:\n` +
            connectionStats.connections
              .map((conn, index) => `${index + 1}. ${conn.id}: ${conn.type} (${conn.status})`)
              .join('\n'),
        },
      ],
    };
  }
  /**
   * Start performance monitoring handler
   */
  private async handleStartPerformanceMonitoring(args: any): Promise<CallToolResult> {
    const { interval = 5000 } = args;
    if (!this.performanceService) {
      throw new Error('Performance service is not initialized.');
    }
    this.performanceService.startMonitoring(interval);
    return {
      content: [
        {
          type: 'text',
          text:
            `Performance monitoring started\n\n` +
            `Monitoring interval: ${interval}ms\n` +
            `Real-time tracking CPU, memory, connection status.\n` +
            `Alerts are sent automatically if thresholds are exceeded.`,
        },
      ],
    };
  }
  /**
   * Start batch processing handler
   */
  private async handleStartBatchProcessing(args: any): Promise<CallToolResult> {
    const { queries } = args;
    if (!Array.isArray(queries)) {
      throw new Error('Query array was not provided.');
    }
    if (!this.queryService) {
      throw new Error('Query service is not initialized.');
    }
    const results = await this.queryService.executeBatch(queries);
    const successCount = results.filter(r => r.success).length;
    return {
      content: [
        {
          type: 'text',
          text:
            `Batch processing completed\n\n` +
            `Total queries: ${queries.length}\n` +
            `Success: ${successCount}\n` +
            `Failure: ${queries.length - successCount}\n\n` +
            `Detailed results:\n` +
            results
              .map(
                (result, index) =>
                  `${index + 1}. ${result.success ? 'SUCCESS' : 'FAILED'} ${result.executionTime}ms` +
                  (result.error ? ` - ${result.error}` : '')
              )
              .join('\n'),
        },
      ],
    };
  }
  /**
   * Generate performance report handler
   */
  private async handleGeneratePerformanceReport(args: any): Promise<CallToolResult> {
    const { period = '1h' } = args;
    if (!this.performanceService) {
      throw new Error('Performance service is not initialized.');
    }
    const report = this.performanceService.generatePerformanceReport(period);
    return {
      content: [
        {
          type: 'text',
          text:
            `Performance report (${period})\n\n` +
            `Summary statistics:\n` +
            `  Average CPU usage: ${report.summary.averageCpuUsage.toFixed(1)}%\n` +
            `   Average memory usage: ${report.summary.averageMemoryUsage.toFixed(1)}%\n` +
            `   Average query time: ${report.summary.averageQueryTime.toFixed(1)}ms\n` +
            `   Peak connection count: ${report.summary.peakConnections}\n\n` +
            `Recommendations:\n` +
            report.recommendations.map((rec: string) => `  • ${rec}`).join('\n'),
        },
      ],
    };
  }
  /**
   * Get query stats handler
   */
  private async handleGetQueryStats(args: any): Promise<CallToolResult> {
    if (!this.queryService) {
      throw new Error('Query service is not initialized.');
    }
    const stats = this.queryService.getQueryStats();
    return {
      content: [
        {
          type: 'text',
          text:
            `Query execution statistics\n\n` +
            `Total queries: ${stats.totalQueries}\n` +
            `Successful: ${stats.successfulQueries}\n` +
            `Failed: ${stats.failedQueries}\n` +
            `Average execution time: ${stats.averageExecutionTime}ms\n\n` +
            (stats.slowestQuery
              ? `Slowest query:\n` +
                `   Time: ${stats.slowestQuery.executionTime}ms\n` +
                `   Query: ${stats.slowestQuery.query}\n\n`
              : '') +
            (stats.fastestQuery
              ? `Fastest query:\n` +
                `   Time: ${stats.fastestQuery.executionTime}ms\n` +
                `   Query: ${stats.fastestQuery.query}`
              : ''),
        },
      ],
    };
  }
  /**
   * Get schema statistics handler
   */ private async handleGetSchemaStatistics(args: any): Promise<CallToolResult> {
    if (!this.schemaService) {
      throw new Error('Schema service is not initialized.');
    }
    const stats = await this.schemaService.getSchemaStatistics();
    return {
      content: [
        {
          type: 'text',
          text:
            `Schema statistics\n\n` +
            `Total tables: ${stats.totalTables}\n` +
            `Total views: ${stats.totalViews}\n` +
            `Total procedures: ${stats.totalProcedures}\n` +
            `Total functions: ${stats.totalFunctions}\n` +
            `Total columns: ${stats.totalColumns}\n` +
            `Total indexes: ${stats.totalIndexes}\n` +
            `Average columns per table: ${stats.averageColumnsPerTable}\n\n` +
            (stats.largestTable
              ? `Largest table (by columns):\n` +
                `   Name: ${stats.largestTable.name}\n` +
                `   Column count: ${stats.largestTable.columns?.length || 0}\n` +
                `   Type: ${stats.largestTable.type}`
              : 'Table information not available'),
        },
      ],
    };
  }
  /**
   * Clear caches handler
   */
  private async handleClearCaches(args: any): Promise<CallToolResult> {
    if (!this.schemaService || !this.queryService || !this.performanceService) {
      throw new Error('Services are not initialized.');
    }
    this.schemaService.clearCache();
    this.queryService.clearHistory();
    this.performanceService.clearMetrics();
    return {
      content: [
        {
          type: 'text',
          text:
            `Caches cleared\n\n` +
            `Schema cache cleared\n` +
            `Query history cleared\n` +
            `Performance metrics cleared\n\n` +
            `All caches have been cleared.`,
        },
      ],
    };
  }
}