UNPKG

ai-debug-local-mcp

Version:

🎯 ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh

407 lines 19.9 kB
import { BaseToolHandler } from './base-handler.js'; export class AuditHandler extends BaseToolHandler { auditEngine; localEngine; tools = [ { name: 'run_audit', description: 'Run comprehensive audits on the web page for accessibility, performance, SEO, security, and best practices. Returns detailed scores and actionable recommendations.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID' }, categories: { type: 'array', items: { type: 'string', enum: ['all', 'accessibility', 'performance', 'seo', 'security', 'bestPractices'] }, description: 'Audit categories to run (default: all)', default: ['all'] }, crossBrowser: { type: 'boolean', description: 'Enable professional cross-browser testing (Chrome, Firefox, Safari)', default: false }, enterpriseMode: { type: 'boolean', description: 'Enable enterprise-grade reporting and standards', default: false }, professionalStandards: { type: 'object', description: 'Professional quality standards configuration', properties: { minimumAccessibilityScore: { type: 'number', description: 'Minimum accessibility score required (default: 90)', default: 90 } } } }, required: ['sessionId'] } }, { name: 'mock_network', description: 'Mock network responses to test different scenarios. Useful for testing error handling, slow responses, or specific data conditions.', inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID' }, action: { type: 'string', enum: ['add', 'remove', 'clear', 'list'], description: 'Mock action to perform' }, urlPattern: { type: 'string', description: 'URL pattern to mock (supports * wildcards)' }, response: { type: 'object', properties: { status: { type: 'number', description: 'HTTP status code (default: 200)' }, body: { description: 'Response body (string or JSON)' }, headers: { type: 'object', description: 'Response headers' }, delay: { type: 'number', description: 'Delay in milliseconds before responding' } } } }, required: ['sessionId', 'action'] } } ]; constructor(auditEngine, localEngine) { super(); this.auditEngine = auditEngine; this.localEngine = localEngine; } getTools() { return this.tools; } async handle(toolName, args, sessions) { try { const session = this.getSession(args.sessionId, sessions); switch (toolName) { case 'run_audit': return await this.runAudit(args, session); case 'mock_network': return await this.mockNetwork(args, session); default: return this.createErrorResponse(new Error(`Unknown tool: ${toolName}`)); } } catch (error) { return this.createErrorResponse(error); } } async runAudit(args, session) { try { const categories = args.categories || ['all']; const crossBrowser = args.crossBrowser || false; if (crossBrowser) { // Professional cross-browser testing return await this.runCrossBrowserAudit(session, categories, args); } // Standard single-browser audit const auditResult = await this.auditEngine.performAudit(session.page, categories); // Calculate overall score with null safety const scores = auditResult && Object.values(auditResult) ? Object.values(auditResult).map((cat) => cat.score) : []; const overallScore = scores.length > 0 ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) : 0; // Count issues by severity with null safety let error = 0, warning = 0, info = 0; if (auditResult && Object.values(auditResult)) { Object.values(auditResult).forEach((category) => { category.issues?.forEach((issue) => { switch (issue.severity) { case 'error': error++; break; case 'warning': warning++; break; case 'info': info++; break; } }); }); } // Format the report let report = `# 🔍 Audit Results\n\n`; report += `**URL**: ${await session.page.url()}\n`; report += `**Overall Score**: ${overallScore}%\n\n`; if (error > 0 || warning > 0 || info > 0) { report += `## Issues Found\n`; if (error > 0) report += `- 🔴 Error: ${error}\n`; if (warning > 0) report += `- 🟠 Warning: ${warning}\n`; if (info > 0) report += `- 🟡 Info: ${info}\n`; report += '\n'; } // Category results report += `## Category Scores\n`; if (auditResult && typeof auditResult === 'object') { for (const [category, result] of Object.entries(auditResult)) { const categoryScore = Math.round(result.score); const emoji = categoryScore >= 90 ? '✅' : categoryScore >= 70 ? '⚠️' : '❌'; report += `- ${emoji} **${this.formatCategoryName(category)}**: ${categoryScore}%\n`; } } // Detailed results for each category if (auditResult && typeof auditResult === 'object') { for (const [category, result] of Object.entries(auditResult)) { const issues = result.issues || []; if (issues.length > 0) { report += `\n### ${this.formatCategoryName(category)} Issues\n`; issues.forEach((issue) => { report += `- **${issue.severity}**: ${issue.message}\n`; if (issue.selector) report += ` - Element: \`${issue.selector}\`\n`; if (issue.recommendation) report += ` - Fix: ${issue.recommendation}\n`; }); } } } // Return both text response AND structured data for Elixir compatibility return { content: [{ type: 'text', text: report }], // Add structured data for programmatic access accessibility: auditResult?.accessibility || { score: 0, issues: [], suggestions: [] }, performance: auditResult?.performance || { score: 0, issues: [], suggestions: [] }, seo: auditResult?.seo || { score: 0, issues: [], suggestions: [] }, security: auditResult?.security || { score: 0, issues: [], suggestions: [] }, bestPractices: auditResult?.bestPractices || { score: 0, issues: [], suggestions: [] }, overallScore }; } catch (error) { throw new Error(`Failed to run audit: ${error instanceof Error ? error.message : error}`); } } async runCrossBrowserAudit(session, categories, args) { try { const browsers = ['chromium', 'firefox', 'webkit']; const url = await session.page.url(); // Import Playwright for launching separate browser instances const { chromium, firefox, webkit } = await import('playwright'); const browserLaunchers = { chromium, firefox, webkit }; // Run audits across all browsers with separate instances const crossBrowserResults = {}; let totalScore = 0; let browserCount = 0; for (const browserName of browsers) { let browser = null; let page = null; try { // Launch separate browser instance for each browser type const launcher = browserLaunchers[browserName]; browser = await launcher.launch({ headless: true }); page = await browser.newPage(); // Navigate to the target URL await page.goto(url, { waitUntil: 'networkidle', timeout: 30000 }); // Run audit on the separate browser instance const auditResult = await this.auditEngine.performAudit(page, categories); // Validate audit result before processing if (!auditResult || typeof auditResult !== 'object') { throw new Error('Audit engine returned invalid result'); } crossBrowserResults[browserName] = auditResult; // Calculate browser-specific score const scores = auditResult && Object.values(auditResult) ? Object.values(auditResult).map((cat) => cat.score) : []; const browserScore = scores.length > 0 ? scores.reduce((a, b) => a + b, 0) / scores.length : 0; totalScore += browserScore; browserCount++; } catch (error) { crossBrowserResults[browserName] = { error: error instanceof Error ? error.message : String(error) }; } finally { // Clean up browser resources if (page) { try { await page.close(); } catch (e) { // Silently handle page close errors } } if (browser) { try { await browser.close(); } catch (e) { // Silently handle browser close errors } } } } // Prevent NaN when all browsers fail (division by zero) const overallScore = browserCount > 0 ? Math.round(totalScore / browserCount) : 0; // Generate professional cross-browser report let report = `# 🔍 Professional Cross-Browser Audit Results\n\n`; report += `**URL**: ${url}\n`; if (browserCount === 0) { report += `**Overall Score**: N/A (Cross-browser testing unavailable)\n`; report += `**Browser Status**: 0 of ${browsers.length} browsers succeeded\n\n`; // Add browser failure details report += `## Browser Failures\n`; report += `All browsers failed to launch. This may indicate:\n`; report += `- Missing browser installations\n`; report += `- System compatibility issues\n`; report += `- Resource constraints\n\n`; } else { report += `**Overall Score**: ${overallScore}%\n`; report += `**Browser Status**: ${browserCount} of ${browsers.length} browsers succeeded\n\n`; } report += `**Browsers Tested**: ${browsers.join(', ')}\n\n`; // Browser matrix results report += `## Cross-Browser Compatibility Matrix\n\n`; report += `| Browser | Status | Score |\n`; report += `|---------|-----------|-------|\n`; for (const [browserName, result] of Object.entries(crossBrowserResults)) { if (result.error) { report += `| ${this.formatBrowserName(browserName)} | ❌ Failed | - |\n`; } else { const scores = result && Object.values(result) ? Object.values(result).map((cat) => cat.score) : []; const browserScore = scores.length > 0 ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) : 0; const status = browserScore >= 90 ? '✅ Excellent' : browserScore >= 70 ? '⚠️ Good' : '❌ Needs Work'; report += `| ${this.formatBrowserName(browserName)} | ${status} | ${browserScore}% |\n`; } } // Professional standards compliance report += `\n## Professional Standards\n`; const professionalStandards = args.professionalStandards || {}; const minAccessibilityScore = professionalStandards.minimumAccessibilityScore || 90; if (args.enterpriseMode) { report += `- 🏢 **Enterprise Mode**: Enabled\n`; if (browserCount === 0) { report += `- 📊 **Quality Grade**: N/A (Browser testing failed)\n`; report += `- 🎯 **Cross-Browser Consistency**: Failed\n`; report += `- 🚨 **Recommendation**: Check browser installation and system compatibility\n`; } else { report += `- 📊 **Quality Grade**: ${overallScore >= 95 ? 'A+' : overallScore >= 90 ? 'A' : overallScore >= 80 ? 'B' : 'C'}\n`; report += `- 🎯 **Cross-Browser Consistency**: ${browserCount === browsers.length ? 'Complete' : 'Partial'}\n`; } } if (categories.includes('accessibility') || categories.includes('all')) { report += `- ♿ **Accessibility Compliance**: Minimum ${minAccessibilityScore}% required\n`; } // Detailed results for each browser for (const [browserName, result] of Object.entries(crossBrowserResults)) { if (!result.error && result && typeof result === 'object') { report += `\n### ${this.formatBrowserName(browserName)} Details\n`; for (const [category, categoryResult] of Object.entries(result)) { const categoryScore = Math.round(categoryResult.score); const emoji = categoryScore >= 90 ? '✅' : categoryScore >= 70 ? '⚠️' : '❌'; report += `- ${emoji} **${this.formatCategoryName(category)}**: ${categoryScore}%\n`; } } } return this.createTextResponse(report); } catch (error) { throw new Error(`Failed to run cross-browser audit: ${error instanceof Error ? error.message : error}`); } } formatBrowserName(browser) { const names = { chromium: 'Chrome', firefox: 'Firefox', webkit: 'Safari' }; return names[browser] || browser; } async mockNetwork(args, session) { try { const { action, urlPattern, response } = args; switch (action) { case 'add': { if (!urlPattern) { throw new Error('URL pattern required for add action'); } await this.localEngine.addNetworkMock(urlPattern, response || {}); return this.createTextResponse(`✅ Added network mock for \`${urlPattern}\`\n\n` + `**Status**: ${response?.status || 200}\n` + `**Body**: ${JSON.stringify(response?.body || {}, null, 2)}\n` + (response?.delay ? `**Delay**: ${response.delay}ms\n` : '') + '\n_Use `action: "list"` to see all active mocks_'); } case 'remove': { if (!urlPattern) { throw new Error('URL pattern required for remove action'); } const removed = this.localEngine.removeNetworkMock(urlPattern); if (removed) { return this.createTextResponse(`✅ Removed network mock for \`${urlPattern}\``); } else { return this.createTextResponse(`⚠️ No mock found for \`${urlPattern}\``); } } case 'clear': { const mocksBeforeClear = this.localEngine.getMockedUrls(); const count = mocksBeforeClear.length; this.localEngine.clearAllMocks(); return this.createTextResponse(`✅ Cleared ${count} network mocks`); } case 'list': { const mocks = this.localEngine.getMockedUrls(); if (mocks.length === 0) { return this.createTextResponse('No active network mocks'); } let message = `## Active network mocks (${mocks.length})\n\n`; mocks.forEach((pattern, index) => { message += `${index + 1}. \`${pattern}\`\n`; }); return this.createTextResponse(message); } default: throw new Error(`Invalid action: ${action}. Must be one of: add, remove, clear, list`); } } catch (error) { throw new Error(`Failed to ${args.action} network mock: ${error instanceof Error ? error.message : error}`); } } formatCategoryName(category) { const names = { accessibility: 'Accessibility', performance: 'Performance', seo: 'SEO', security: 'Security', bestPractices: 'Best Practices' }; return names[category] || category; } } //# sourceMappingURL=audit-handler.js.map