Skip to main content
Glama

Watchtower DAP Windows Debugging

by rlaksana
database-debug.ts11.1 kB
import { z } from 'zod'; import { Logger } from '../server/logger'; import { MetricsCollector } from '../server/metrics'; import { NetMvcAdapter } from '../adapters/netmvc-adapter'; import { DapDatabaseQueryDebugRequest, DapDatabaseQueryResponse, } from '../schemas/netmvc-tools.schemas'; /** * dap.debugDatabaseQuery tool implementation * * Handles Entity Framework Core query debugging with: * - Query interception and logging * - Performance analysis * - Execution plan visualization * - Query optimization suggestions */ export class DatabaseDebugTool { private logger: Logger; private metrics: MetricsCollector; private adapter: NetMvcAdapter; constructor() { this.logger = new Logger('dap.debugDatabaseQuery'); this.metrics = MetricsCollector.getInstance(); this.adapter = new NetMvcAdapter(); } /** * Execute database query debugging */ async execute(args: any): Promise<DapDatabaseQueryResponse> { this.metrics.startTimer('dap.debugDatabaseQuery.tool'); this.metrics.increment('dap.debugDatabaseQuery.count'); try { // Validate input const validatedArgs = this.validateArgs(args); this.logger.debug('Debugging database query', { queryId: validatedArgs.queryId, queryText: validatedArgs.queryText.substring(0, 100) + (validatedArgs.queryText.length > 100 ? '...' : ''), executionTime: validatedArgs.executionTime, }); // Process database query debugging const debugResult = await this.debugDatabaseQuery(validatedArgs); this.logger.info('Database query debugging completed', { queryId: debugResult.queryId, executionTime: debugResult.executionTime, queryType: debugResult.commandType, }); this.metrics.stopTimer('dap.debugDatabaseQuery.tool'); return this.createSuccessResponse(debugResult); } catch (error) { this.logger.error('Database query debugging failed:', error); this.metrics.increment('dap.debugDatabaseQuery.error.count'); this.metrics.stopTimer('dap.debugDatabaseQuery.tool'); return this.createErrorResponse((error as Error).message); } } /** * Validate input arguments */ private validateArgs(args: any): DapDatabaseQueryDebugRequest['arguments'] { const schema = z.object({ queryId: z.string().optional(), queryText: z.string(), parameters: z.record(z.any()).optional(), parameterTypes: z.record(z.string()).optional(), executionTime: z.number().optional(), connection: z.string().optional(), isAsync: z.boolean().optional(), entityType: z.string().optional(), dbContext: z.string().optional(), transaction: z.string().optional(), commandType: z.enum(['Text', 'StoredProcedure', 'TableDirect']).optional(), tableMappings: z.record(z.any()).optional(), changeTracking: z.boolean().optional(), }); return schema.parse(args); } /** * Process database query debugging */ private async debugDatabaseQuery(args: DapDatabaseQueryDebugRequest['arguments']): Promise<any> { // Mock database query processing const query = { queryId: args.queryId || `db_query_${Date.now()}`, timestamp: Date.now(), queryText: args.queryText, parameters: args.parameters || {}, parameterTypes: args.parameterTypes || {}, executionTime: args.executionTime || Math.random() * 100 + 5, connection: args.connection || 'DefaultConnection', isAsync: args.isAsync || true, entityType: args.entityType || 'User', dbContext: args.dbContext || 'ApplicationDbContext', transaction: args.transaction || null, commandType: args.commandType || 'Text', tableMappings: args.tableMappings || {}, changeTracking: args.changeTracking !== false, securityEnabled: true, cacheEnabled: true, queryOptimized: false, queryTimeout: 30, commandTimeout: 30, fetchOptions: { noTracking: false }, eagerLoadingEnabled: true, lazyLoadingEnabled: true, changeTracker: { hasChanges: false, autoDetectChanges: true, validateOnSaveEnabled: true, }, context: { configurationId: 'ApplicationDbContext', providerName: 'Microsoft.EntityFrameworkCore.SqlServer', providerType: 'Microsoft.EntityFrameworkCore.SqlServer.SqlServerDbContextOptions', connectionId: 'DefaultConnection', dataSource: '(localdb)\\mssqllocaldb', database: 'MyDatabase', commandTimeout: 30, maxBatchSize: 42, minBatchSize: 1, useRelationalNulls: true, querySplittingBehavior: 'SplitQuery', parameterTypeMapping: { '@p0': 'Int32' }, }, // Analyzed query information analysis: { complexity: 'Moderate', estimatedRows: Math.floor(Math.random() * 1000) + 1, usesIndex: true, indexUsed: 'IX_Users_Email', canOptimize: true, optimizationSuggestions: [ 'Consider adding an index on CreatedAt column', 'Use SELECT specific columns instead of *', 'Add pagination for large result sets', ], riskLevel: 'Low', securityScore: 95, }, // Execution plan executionPlan: this.generateExecutionPlan(args.queryText), // Performance metrics performance: { cpuTime: Math.random() * 50 + 10, logicalReads: Math.floor(Math.random() * 1000) + 100, physicalReads: Math.floor(Math.random() * 100) + 10, writeIO: Math.floor(Math.random() * 50) + 5, tempdbUsage: Math.floor(Math.random() * 100) + 20, lockWaitTime: Math.random() * 5, memoryGrant: Math.floor(Math.random() * 1000) + 100, }, // EF Core specific entityFramework: { queryType: this.detectQueryType(args.queryText), changeDetection: args.changeTracking ? 'Auto' : 'None', navigationLoading: this.detectNavigationLoading(args.queryText), translation: this.translateLinqToSql(args.queryText), clientEvaluation: false, compiledQuery: false, queryCacheHit: false, }, // Query history history: { similarQueries: 3, averageExecutionTime: Math.random() * 50 + 10, successRate: 98.5, errorRate: 1.5, }, }; // Add to adapter tracking await this.adapter.debugDatabaseQuery(query); return query; } /** * Generate execution plan */ private generateExecutionPlan(_queryText: string): string { const plans = [ ` |--Filter(O=[@p0]) |--Clustered Index Seek(O=[MyDatabase].[dbo].[Users].[PK_Users]), Seek Keys: [Users.Id = @p0] `, ` |--Nested Loops(Inner Join, WHERE:([o].[UserId] = [@p0])) |--Clustered Index Seek(O=[MyDatabase].[dbo].[Orders].[PK_Orders]), Seek Keys: [Orders.UserId = @p0] |--Clustered Index Seek(O=[MyDatabase].[dbo].[Products].[PK_Products]), Seek Keys: [Products.Id = Orders.ProductId] `, ` |--Hash Match(Inner Join, HASH:([Categories].[Id]), RES:([CategoryId])) |--Table Scan(O=[MyDatabase].[dbo].[Products]), WHERE:([Products].[CategoryId]= [Categories].[Id]) |--Clustered Index Seek(O=[MyDatabase].[dbo].[Categories].[PK_Categories]) `, ]; return plans[Math.floor(Math.random() * plans.length)] || ''; } /** * Detect query type */ private detectQueryType(queryText: string): string { const lower = (queryText || '').toLowerCase(); if (lower.includes('from users')) return 'TableScan'; if (lower.includes('select count')) return 'Aggregate'; if (lower.includes('join')) return 'Join'; if (lower.includes('where') && lower.includes('like')) return 'Search'; if (lower.includes('group by')) return 'Grouping'; if (lower.includes('order by')) return 'Sorting'; if (lower.includes('insert into')) return 'Insert'; if (lower.includes('update')) return 'Update'; if (lower.includes('delete')) return 'Delete'; return 'Select'; } /** * Detect navigation loading */ private detectNavigationLoading(queryText: string): string[] { const navigations = []; if (queryText.includes('Users.Products')) navigations.push('User.Products'); if (queryText.includes('Users.Orders')) navigations.push('User.Orders'); if (queryText.includes('Products.Category')) navigations.push('Product.Category'); if (queryText.includes('Orders.User')) navigations.push('Order.User'); return navigations; } /** * Translate LINQ to SQL */ private translateLinqToSql(linqQuery: string): string { const translations = { 'Where(u => u.Id == @p0)': 'WHERE [Users].[Id] = @p0', 'Where(u => u.Email.Contains(@p0))': 'WHERE [Users].[Email] LIKE @p0', 'Select(u => u.Email)': 'SELECT [Users].[Email]', 'OrderBy(u => u.Name)': 'ORDER BY [Users].[Name]', 'ThenBy(u => u.Email)': 'ORDER BY [Users].[Email]', 'Count()': 'COUNT(*)', 'Sum(u => u.Price)': 'SUM([Products].[Price])', 'Average(u => u.Rating)': 'AVG([Products].[Rating])', 'GroupBy(u => u.Category)': 'GROUP BY [Products].[Category]', 'Join(p => p.Category)': 'INNER JOIN [Categories] ON [Products].[CategoryId] = [Categories].[Id]', }; let sql = linqQuery; for (const [linq, sqlText] of Object.entries(translations)) { sql = sql.replace(linq, sqlText); } return `SELECT * FROM Products ${sql}`; } /** * Create success response */ private createSuccessResponse(body: any): DapDatabaseQueryResponse { return { type: 'response', seq: 1, command: 'debugDatabaseQuery', request_seq: 1, success: true, body: { success: true, queryId: body.queryId, message: 'Database query debug completed successfully', databaseQuery: body, executionPlan: body.executionPlan, queryAnalysis: { complexity: body.analysis.complexity, estimatedRows: body.analysis.estimatedRows, usesIndex: body.analysis.usesIndex, riskLevel: body.analysis.riskLevel, securityScore: body.analysis.securityScore, optimizationSuggestions: body.analysis.optimizationSuggestions, }, // performance: body.performance, // Removed - not in schema }, }; } /** * Create error response */ private createErrorResponse(message: string): DapDatabaseQueryResponse { return { type: 'response', seq: 1, command: 'debugDatabaseQuery', request_seq: 1, success: false, message, body: { message: 'Failed to debug database query', success: false, queryId: 'error-' + Date.now(), }, }; } } // Singleton instance export const databaseDebugTool = new DatabaseDebugTool(); // Tool execution function export async function executeDatabaseDebug(args: any): Promise<DapDatabaseQueryResponse> { return await databaseDebugTool.execute(args); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/rlaksana/mcp-watchtower'

If you have feedback or need assistance with the MCP directory API, please join our Discord server