browser-connect-mcp
Version:
MCP server for browser DevTools and backend debugging - analyze console logs, network requests, and backend logs with AI assistance
388 lines ⢠13.9 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const commander_1 = require("commander");
const child_process_1 = require("child_process");
const fs = __importStar(require("fs"));
const chalk_1 = __importDefault(require("chalk"));
const program = new commander_1.Command();
program
.name('browser-connect-backend')
.description('Helper tool for backend debugging with browser-connect-mcp')
.version('1.0.0');
// Start command - launches backend with proper debugging setup
program
.command('start [command]')
.description('Start your backend with debugging enabled')
.option('-p, --port <port>', 'Server port', '3000')
.option('-d, --debug-port <port>', 'Debug port', '9229')
.option('--no-inspect', 'Disable Node.js inspector')
.option('-w, --watch', 'Watch for file changes')
.option('-l, --log-file <path>', 'Log output to file', 'debug.log')
.action(async (command, options) => {
console.log(chalk_1.default.blue('š Starting backend with debugging enabled...\n'));
// Auto-detect command if not provided
if (!command) {
command = detectStartCommand();
if (!command) {
console.error(chalk_1.default.red('ā Could not detect start command. Please specify one.'));
console.log(chalk_1.default.gray('Example: browser-connect-backend start "node server.js"'));
process.exit(1);
}
console.log(chalk_1.default.gray(`Auto-detected command: ${command}`));
}
// Parse and enhance the command
let [cmd, ...args] = command.split(' ');
// Add debugging flags for Node.js
if (cmd === 'node' && options.inspect !== false) {
if (!args.some(arg => arg.includes('--inspect'))) {
args.unshift(`--inspect=${options.debugPort}`);
}
}
// Add watch mode if requested
if (options.watch && cmd === 'node') {
cmd = 'nodemon';
if (!args.some(arg => arg.includes('--inspect'))) {
args.unshift(`--inspect=${options.debugPort}`);
}
}
// Handle npm/yarn commands
if ((cmd === 'npm' || cmd === 'yarn') && args[0] === 'start') {
// Set NODE_OPTIONS for debugging
process.env.NODE_OPTIONS = `--inspect=${options.debugPort}`;
}
console.log(chalk_1.default.green(`ā
Starting: ${cmd} ${args.join(' ')}`));
console.log(chalk_1.default.gray(`š Server Port: ${options.port}`));
console.log(chalk_1.default.gray(`š Debug Port: ${options.debugPort}`));
if (options.logFile) {
console.log(chalk_1.default.gray(`š Logging to: ${options.logFile}`));
}
console.log('');
// Create log file write stream
const logStream = options.logFile ?
fs.createWriteStream(options.logFile, { flags: 'a' }) : null;
// Start the process
const proc = (0, child_process_1.spawn)(cmd, args, {
stdio: ['inherit', 'pipe', 'pipe'],
env: {
...process.env,
PORT: options.port,
NODE_ENV: process.env.NODE_ENV || 'development'
}
});
// Handle output
proc.stdout.on('data', (data) => {
const text = data.toString();
process.stdout.write(text);
if (logStream) {
const timestamp = new Date().toISOString();
logStream.write(`[${timestamp}] [STDOUT] ${text}`);
}
});
proc.stderr.on('data', (data) => {
const text = data.toString();
process.stderr.write(chalk_1.default.yellow(text));
if (logStream) {
const timestamp = new Date().toISOString();
logStream.write(`[${timestamp}] [STDERR] ${text}`);
}
});
proc.on('error', (error) => {
console.error(chalk_1.default.red(`\nā Failed to start process: ${error.message}`));
});
proc.on('exit', (code) => {
if (logStream)
logStream.end();
console.log(chalk_1.default.gray(`\nProcess exited with code ${code}`));
});
// Handle graceful shutdown
process.on('SIGINT', () => {
console.log(chalk_1.default.yellow('\n\nš Shutting down...'));
proc.kill('SIGTERM');
setTimeout(() => proc.kill('SIGKILL'), 5000);
});
// Show tips
console.log(chalk_1.default.cyan('\nš” Tips:'));
console.log(chalk_1.default.gray('- Your backend is running with debugging enabled'));
console.log(chalk_1.default.gray('- Use Claude to "debug my backend" to analyze logs'));
console.log(chalk_1.default.gray('- Chrome DevTools can connect to the debug port'));
console.log(chalk_1.default.gray('- Press Ctrl+C to stop\n'));
});
// Doctor command - diagnose debugging setup
program
.command('doctor')
.description('Check your debugging setup')
.action(async () => {
console.log(chalk_1.default.blue('𩺠Checking backend debugging setup...\n'));
const checks = [];
// Check for Node.js
try {
const nodeVersion = await runCommand('node', ['--version']);
checks.push({
name: 'Node.js',
status: 'ok',
message: `Found ${nodeVersion.trim()}`
});
}
catch {
checks.push({
name: 'Node.js',
status: 'error',
message: 'Not found'
});
}
// Check for package.json
if (fs.existsSync('package.json')) {
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
checks.push({
name: 'package.json',
status: 'ok',
message: `Found (${pkg.name || 'unnamed project'})`
});
// Check for start script
if (pkg.scripts?.start) {
checks.push({
name: 'Start script',
status: 'ok',
message: `"${pkg.scripts.start}"`
});
}
else {
checks.push({
name: 'Start script',
status: 'warning',
message: 'Not found (will look for server.js/app.js/index.js)'
});
}
}
else {
checks.push({
name: 'package.json',
status: 'warning',
message: 'Not found'
});
}
// Check for common entry points
const entryPoints = ['server.js', 'app.js', 'index.js', 'src/index.js'];
const foundEntry = entryPoints.find(f => fs.existsSync(f));
if (foundEntry) {
checks.push({
name: 'Entry point',
status: 'ok',
message: `Found ${foundEntry}`
});
}
else {
checks.push({
name: 'Entry point',
status: 'warning',
message: 'No standard entry point found'
});
}
// Check for log files
const logFiles = findLogFiles();
if (logFiles.length > 0) {
checks.push({
name: 'Log files',
status: 'ok',
message: `Found ${logFiles.length} log file(s): ${logFiles.join(', ')}`
});
}
else {
checks.push({
name: 'Log files',
status: 'info',
message: 'No log files found (logs may go to stdout)'
});
}
// Check for Docker
try {
await runCommand('docker', ['--version']);
checks.push({
name: 'Docker',
status: 'ok',
message: 'Available'
});
}
catch {
checks.push({
name: 'Docker',
status: 'info',
message: 'Not available (optional)'
});
}
// Display results
console.log(chalk_1.default.bold('Check Results:\n'));
for (const check of checks) {
const icon = check.status === 'ok' ? 'ā
' :
check.status === 'error' ? 'ā' :
check.status === 'warning' ? 'ā ļø' : 'ā¹ļø';
const color = check.status === 'ok' ? 'green' :
check.status === 'error' ? 'red' :
check.status === 'warning' ? 'yellow' : 'gray';
console.log(`${icon} ${chalk_1.default[color](check.name)}: ${check.message}`);
}
// Recommendations
console.log(chalk_1.default.bold('\nš Recommendations:\n'));
if (!foundEntry && !fs.existsSync('package.json')) {
console.log(chalk_1.default.yellow('⢠Create a package.json with: npm init -y'));
console.log(chalk_1.default.yellow('⢠Add a start script to package.json'));
}
if (logFiles.length === 0) {
console.log(chalk_1.default.gray('⢠Consider logging to a file for better debugging'));
console.log(chalk_1.default.gray(' Example: node server.js > app.log 2>&1'));
}
console.log(chalk_1.default.green('\n⨠To start debugging:'));
console.log(chalk_1.default.gray(' browser-connect-backend start'));
});
// Logs command - find and display logs
program
.command('logs')
.description('Find and display log files')
.option('-f, --follow', 'Follow log output')
.option('-n, --lines <number>', 'Number of lines to show', '50')
.action(async (options) => {
const logFiles = findLogFiles();
if (logFiles.length === 0) {
console.log(chalk_1.default.yellow('No log files found in current directory'));
console.log(chalk_1.default.gray('\nTip: Start your app with logging:'));
console.log(chalk_1.default.gray(' browser-connect-backend start --log-file debug.log'));
return;
}
console.log(chalk_1.default.blue(`š Found ${logFiles.length} log file(s):\n`));
logFiles.forEach((file, i) => {
console.log(chalk_1.default.gray(` ${i + 1}. ${file}`));
});
// Use the first log file
const logFile = logFiles[0];
console.log(chalk_1.default.green(`\nš Showing ${logFile}:\n`));
if (options.follow) {
// Follow mode
const tail = (0, child_process_1.spawn)('tail', ['-f', '-n', options.lines, logFile]);
tail.stdout.pipe(process.stdout);
tail.stderr.pipe(process.stderr);
process.on('SIGINT', () => {
tail.kill();
process.exit(0);
});
}
else {
// Show last N lines
try {
const output = await runCommand('tail', ['-n', options.lines, logFile]);
console.log(output);
}
catch (error) {
console.error(chalk_1.default.red('Failed to read log file'));
}
}
});
// Helper functions
function detectStartCommand() {
// Check package.json
if (fs.existsSync('package.json')) {
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
if (pkg.scripts?.start) {
return 'npm start';
}
if (pkg.scripts?.dev) {
return 'npm run dev';
}
}
// Check for common entry points
const entryPoints = [
'server.js',
'app.js',
'index.js',
'src/index.js',
'src/server.js',
'src/app.js'
];
for (const entry of entryPoints) {
if (fs.existsSync(entry)) {
return `node ${entry}`;
}
}
return null;
}
function findLogFiles() {
const logPatterns = [
'*.log',
'logs/*.log',
'var/log/*.log',
'.logs/*.log'
];
const files = [];
// Check current directory
const entries = fs.readdirSync('.', { withFileTypes: true });
for (const entry of entries) {
if (entry.isFile() && entry.name.endsWith('.log')) {
files.push(entry.name);
}
}
// Check logs directory
if (fs.existsSync('logs')) {
const logEntries = fs.readdirSync('logs', { withFileTypes: true });
for (const entry of logEntries) {
if (entry.isFile() && entry.name.endsWith('.log')) {
files.push(`logs/${entry.name}`);
}
}
}
return files;
}
function runCommand(command, args) {
return new Promise((resolve, reject) => {
const proc = (0, child_process_1.spawn)(command, args);
let output = '';
proc.stdout.on('data', (data) => {
output += data.toString();
});
proc.on('close', (code) => {
if (code === 0) {
resolve(output);
}
else {
reject(new Error(`Command failed with code ${code}`));
}
});
});
}
program.parse();
//# sourceMappingURL=backend-helper.js.map