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
JavaScript
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