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
564 lines • 22.1 kB
JavaScript
/**
* Handler for granular debug data access tools
* Provides explicit tools for console logs, network activity, etc.
* that AI agents commonly expect
*/
export class DebugDataHandler {
localEngine;
sessions;
name = 'debug-data';
// Handler methods mapping
handlers = {
get_console_logs: this.getConsoleLogs.bind(this),
get_network_activity: this.getNetworkActivity.bind(this),
get_errors: this.getErrors.bind(this),
get_performance_metrics: this.getPerformanceMetrics.bind(this),
get_dom_snapshot: this.getDomSnapshot.bind(this),
get_local_storage: this.getLocalStorage.bind(this),
get_cookies: this.getCookies.bind(this),
search_logs: this.searchLogs.bind(this),
list_available_debug_data: this.listAvailableDebugData.bind(this)
};
// Implement the required handle method from ToolHandler interface
async handle(toolName, args, sessions) {
const handler = this.handlers[toolName];
if (!handler) {
throw new Error(`Unknown debug data tool: ${toolName}`);
}
return handler(args);
}
tools = [
{
name: 'get_console_logs',
description: 'Get console messages from the debugging session. Returns an array of console log entries with timestamp, level (log/warn/error/info), and message text.',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'Debug session ID'
},
level: {
type: 'string',
enum: ['all', 'log', 'warn', 'error', 'info'],
default: 'all',
description: 'Filter by log level'
},
limit: {
type: 'number',
default: 100,
description: 'Maximum number of logs to return'
}
},
required: ['sessionId']
}
},
{
name: 'get_network_activity',
description: 'Get network requests from the debugging session. Returns an array of network requests with URL, method, status, response time, and headers.',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'Debug session ID'
},
filter: {
type: 'string',
enum: ['all', 'xhr', 'fetch', 'document', 'script', 'stylesheet', 'image', 'font', 'websocket'],
default: 'all',
description: 'Filter by request type'
},
statusFilter: {
type: 'string',
enum: ['all', 'success', 'error', 'pending'],
default: 'all',
description: 'Filter by status code range'
}
},
required: ['sessionId']
}
},
{
name: 'get_errors',
description: 'Get JavaScript errors and exceptions from the debugging session. Returns an array of error objects with message, stack trace, and source location.',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'Debug session ID'
},
includeWarnings: {
type: 'boolean',
default: false,
description: 'Include warnings in addition to errors'
}
},
required: ['sessionId']
}
},
{
name: 'get_performance_metrics',
description: 'Get performance metrics from the debugging session. Returns Core Web Vitals (LCP, FCP, CLS, FID), resource timings, and memory usage.',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'Debug session ID'
},
detailed: {
type: 'boolean',
default: false,
description: 'Include detailed resource timings'
}
},
required: ['sessionId']
}
},
{
name: 'get_dom_snapshot',
description: 'Get current DOM tree snapshot from the debugging session. Returns HTML structure, computed styles, and element properties.',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'Debug session ID'
},
selector: {
type: 'string',
description: 'CSS selector to get specific element (optional, defaults to full page)'
},
includeStyles: {
type: 'boolean',
default: false,
description: 'Include computed styles for elements'
}
},
required: ['sessionId']
}
},
{
name: 'get_local_storage',
description: 'Get localStorage data from the debugging session. Returns key-value pairs stored in browser localStorage.',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'Debug session ID'
},
keys: {
type: 'array',
items: { type: 'string' },
description: 'Specific keys to retrieve (optional, defaults to all)'
}
},
required: ['sessionId']
}
},
{
name: 'get_cookies',
description: 'Get cookies from the debugging session. Returns array of cookie objects with name, value, domain, path, and expiry.',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'Debug session ID'
},
domain: {
type: 'string',
description: 'Filter cookies by domain (optional)'
}
},
required: ['sessionId']
}
},
{
name: 'search_logs',
description: 'Search through console logs, network activity, and errors for specific patterns. Returns matching entries across all debug data.',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'Debug session ID'
},
pattern: {
type: 'string',
description: 'Search pattern (supports regex)'
},
searchIn: {
type: 'array',
items: {
type: 'string',
enum: ['console', 'network', 'errors']
},
default: ['console', 'network', 'errors'],
description: 'Where to search'
}
},
required: ['sessionId', 'pattern']
}
},
{
name: 'list_available_debug_data',
description: 'List all types of debug data available for the current session. Returns summary of console logs, network requests, errors, and other captured data.',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'Debug session ID'
}
},
required: ['sessionId']
}
}
];
constructor(localEngine, sessions) {
this.localEngine = localEngine;
this.sessions = sessions;
}
async getConsoleLogs(args) {
const session = this.sessions.get(args.sessionId);
if (!session || !session.page) {
throw new Error('Invalid session ID or session expired');
}
const allLogs = await this.localEngine.getConsoleMessages();
let filtered = allLogs;
// Filter by level if specified
if (args.level && args.level !== 'all') {
filtered = allLogs.filter((log) => log.level === args.level);
}
// Apply limit
if (args.limit) {
filtered = filtered.slice(-args.limit);
}
return {
content: [
{
type: 'text',
text: `Retrieved ${filtered.length} console messages${args.level !== 'all' ? ` (${args.level} level)` : ''}:`
},
{
type: 'text',
text: JSON.stringify(filtered, null, 2)
}
]
};
}
async getNetworkActivity(args) {
const session = this.sessions.get(args.sessionId);
if (!session || !session.page) {
throw new Error('Invalid session ID or session expired');
}
const allRequests = await this.localEngine.getNetworkRequests();
let filtered = allRequests;
// Filter by type if specified
if (args.filter && args.filter !== 'all') {
filtered = allRequests.filter((req) => {
const resourceType = req.resourceType?.toLowerCase() || '';
return resourceType === args.filter;
});
}
// Filter by status if specified
if (args.statusFilter && args.statusFilter !== 'all') {
filtered = filtered.filter((req) => {
const status = req.status || 0;
switch (args.statusFilter) {
case 'success':
return status >= 200 && status < 300;
case 'error':
return status >= 400;
case 'pending':
return status === 0;
default:
return true;
}
});
}
return {
content: [
{
type: 'text',
text: `Retrieved ${filtered.length} network requests:`
},
{
type: 'text',
text: JSON.stringify(filtered, null, 2)
}
]
};
}
async getErrors(args) {
const session = this.sessions.get(args.sessionId);
if (!session || !session.page) {
throw new Error('Invalid session ID or session expired');
}
const consoleLogs = await this.localEngine.getConsoleMessages();
const errors = consoleLogs.filter((log) => log.level === 'error' || (args.includeWarnings && log.level === 'warning'));
// Also get failed network requests
const failedRequests = await this.localEngine.getFailedRequests();
return {
content: [
{
type: 'text',
text: `Found ${errors.length} console errors${args.includeWarnings ? '/warnings' : ''} and ${failedRequests.length} failed network requests:`
},
{
type: 'text',
text: JSON.stringify({
consoleErrors: errors,
networkErrors: failedRequests
}, null, 2)
}
]
};
}
async getPerformanceMetrics(args) {
const session = this.sessions.get(args.sessionId);
if (!session || !session.page) {
throw new Error('Invalid session ID or session expired');
}
// Get performance metrics from the page
const metrics = await session.page.evaluate(() => {
const navigation = performance.getEntriesByType('navigation')[0];
const paint = performance.getEntriesByType('paint');
return {
navigation: {
domContentLoaded: navigation?.domContentLoadedEventEnd - navigation?.domContentLoadedEventStart,
loadComplete: navigation?.loadEventEnd - navigation?.loadEventStart,
responseTime: navigation?.responseEnd - navigation?.requestStart
},
paint: paint.map(entry => ({
name: entry.name,
startTime: entry.startTime
})),
memory: performance.memory ? {
usedJSHeapSize: performance.memory.usedJSHeapSize,
totalJSHeapSize: performance.memory.totalJSHeapSize,
jsHeapSizeLimit: performance.memory.jsHeapSizeLimit
} : null
};
});
return {
content: [
{
type: 'text',
text: 'Performance metrics:'
},
{
type: 'text',
text: JSON.stringify(metrics, null, 2)
}
]
};
}
async getDomSnapshot(args) {
const session = this.sessions.get(args.sessionId);
if (!session || !session.page) {
throw new Error('Invalid session ID or session expired');
}
const selector = args.selector || 'body';
const snapshot = await session.page.evaluate((sel, includeStyles) => {
const element = document.querySelector(sel);
if (!element)
return null;
const result = {
tagName: element.tagName,
attributes: {},
innerHTML: element.innerHTML.substring(0, 1000) + (element.innerHTML.length > 1000 ? '...' : ''),
textContent: element.textContent?.substring(0, 500)
};
// Get attributes
for (let i = 0; i < element.attributes.length; i++) {
const attr = element.attributes[i];
result.attributes[attr.name] = attr.value;
}
// Get computed styles if requested
if (includeStyles) {
const styles = window.getComputedStyle(element);
result.computedStyles = {
display: styles.display,
position: styles.position,
width: styles.width,
height: styles.height,
color: styles.color,
backgroundColor: styles.backgroundColor,
fontSize: styles.fontSize,
fontFamily: styles.fontFamily
};
}
return result;
}, selector, args.includeStyles || false);
return {
content: [
{
type: 'text',
text: `DOM snapshot for "${selector}":`
},
{
type: 'text',
text: JSON.stringify(snapshot, null, 2)
}
]
};
}
async getLocalStorage(args) {
const session = this.sessions.get(args.sessionId);
if (!session || !session.page) {
throw new Error('Invalid session ID or session expired');
}
const storage = await session.page.evaluate((keys) => {
const result = {};
if (keys && keys.length > 0) {
// Get specific keys
for (const key of keys) {
result[key] = localStorage.getItem(key);
}
}
else {
// Get all keys
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key) {
result[key] = localStorage.getItem(key);
}
}
}
return result;
}, args.keys);
return {
content: [
{
type: 'text',
text: `localStorage data (${Object.keys(storage).length} items):`
},
{
type: 'text',
text: JSON.stringify(storage, null, 2)
}
]
};
}
async getCookies(args) {
const session = this.sessions.get(args.sessionId);
if (!session || !session.page) {
throw new Error('Invalid session ID or session expired');
}
let cookies = await session.page.context().cookies();
// Filter by domain if specified
if (args.domain) {
cookies = cookies.filter((cookie) => cookie.domain === args.domain || cookie.domain === `.${args.domain}`);
}
return {
content: [
{
type: 'text',
text: `Found ${cookies.length} cookies${args.domain ? ` for domain ${args.domain}` : ''}:`
},
{
type: 'text',
text: JSON.stringify(cookies, null, 2)
}
]
};
}
async searchLogs(args) {
const session = this.sessions.get(args.sessionId);
if (!session || !session.page) {
throw new Error('Invalid session ID or session expired');
}
const results = {
console: [],
network: [],
errors: []
};
const regex = new RegExp(args.pattern, 'i');
// Search in console logs
if (args.searchIn.includes('console')) {
const logs = await this.localEngine.getConsoleMessages();
results.console = logs.filter((log) => regex.test(log.text) || regex.test(JSON.stringify(log)));
}
// Search in network activity
if (args.searchIn.includes('network')) {
const requests = await this.localEngine.getNetworkRequests();
results.network = requests.filter((req) => regex.test(req.url) || regex.test(JSON.stringify(req)));
}
// Search in errors
if (args.searchIn.includes('errors')) {
const logs = await this.localEngine.getConsoleMessages();
const errors = logs.filter((log) => log.level === 'error');
results.errors = errors.filter((err) => regex.test(err.text) || regex.test(JSON.stringify(err)));
}
const totalMatches = results.console.length + results.network.length + results.errors.length;
return {
content: [
{
type: 'text',
text: `Found ${totalMatches} matches for "${args.pattern}":`
},
{
type: 'text',
text: JSON.stringify(results, null, 2)
}
]
};
}
async listAvailableDebugData(args) {
const session = this.sessions.get(args.sessionId);
if (!session || !session.page) {
throw new Error('Invalid session ID or session expired');
}
const consoleLogs = await this.localEngine.getConsoleMessages();
const networkRequests = await this.localEngine.getNetworkRequests();
const errors = consoleLogs.filter((log) => log.level === 'error');
const summary = {
consoleLogs: {
total: consoleLogs.length,
byLevel: {
log: consoleLogs.filter((l) => l.level === 'log').length,
warn: consoleLogs.filter((l) => l.level === 'warning').length,
error: consoleLogs.filter((l) => l.level === 'error').length,
info: consoleLogs.filter((l) => l.level === 'info').length
}
},
networkActivity: {
total: networkRequests.length,
successful: networkRequests.filter((r) => r.status >= 200 && r.status < 300).length,
failed: networkRequests.filter((r) => r.status >= 400).length,
pending: networkRequests.filter((r) => !r.status).length
},
errors: {
total: errors.length,
unique: new Set(errors.map((e) => e.text)).size
},
availableDataTypes: [
'console_logs',
'network_activity',
'errors',
'performance_metrics',
'dom_snapshot',
'local_storage',
'cookies'
]
};
return {
content: [
{
type: 'text',
text: 'Available debug data summary:'
},
{
type: 'text',
text: JSON.stringify(summary, null, 2)
}
]
};
}
}
//# sourceMappingURL=debug-data-handler.js.map