@dynamicu/chromedebug-mcp 2.6.2 → 2.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dynamicu/chromedebug-mcp",
3
- "version": "2.6.2",
3
+ "version": "2.6.4",
4
4
  "description": "ChromeDebug MCP - MCP server that provides full control over a Chrome browser instance for debugging and automation with AI assistants like Claude Code",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/cli.js CHANGED
@@ -38,6 +38,13 @@ async function main() {
38
38
  // Parse arguments first to get session settings
39
39
  const args = app.parseArguments();
40
40
 
41
+ // Set database path environment variable if provided via CLI
42
+ // This ensures the database singleton uses the correct path BEFORE initialization
43
+ if (args.dbPath) {
44
+ process.env.CHROMEDEBUG_DB_PATH = args.dbPath;
45
+ logger.debug(`[CLI] Database path set: ${args.dbPath}`);
46
+ }
47
+
41
48
  // Detect Windows and provide WSL2 guidance
42
49
  if (process.platform === 'win32' && !process.env.WSL_DISTRO_NAME && !process.env.CHROMEDEBUG_FORCE_WINDOWS) {
43
50
  console.error(`
@@ -74,6 +81,11 @@ For details: https://github.com/dynamicupgrade/ChromeDebug#windows-setup
74
81
  verbose: args.verbose
75
82
  });
76
83
 
84
+ // Kill other instances unless --allow-multiple is set
85
+ if (!args.allowMultiple) {
86
+ await sessionManager.killOtherInstances();
87
+ }
88
+
77
89
  // Register this process for tracking
78
90
  await registerProcess(process.pid, 'mcp-server');
79
91
 
package/src/database.js CHANGED
@@ -3,6 +3,7 @@ import Database from 'better-sqlite3';
3
3
  import path from 'path';
4
4
  import { fileURLToPath } from 'url';
5
5
  import fs from 'fs';
6
+ import os from 'os';
6
7
  import { ProjectManager } from './services/project-manager.js';
7
8
  import logger from './utils/logger.js';
8
9
 
@@ -11,49 +12,90 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
12
  // Initialize project manager for path resolution
12
13
  const projectManager = new ProjectManager();
13
14
 
14
- /**
15
- * Gets the appropriate database path (always use global chromedebug.db)
16
- * @returns {string} Database file path
17
- */
18
- function getDatabasePath() {
19
- // Always use global database for cross-project accessibility
20
- const globalDbPath = path.join(__dirname, '../data/chromedebug.db');
21
-
22
- // Ensure data directory exists
23
- const dbDir = path.dirname(globalDbPath);
24
- if (!fs.existsSync(dbDir)) {
25
- fs.mkdirSync(dbDir, { recursive: true });
26
- }
27
-
28
- logger.debug(`[Database] Using global database: ${globalDbPath}`);
29
- return globalDbPath;
30
- }
31
-
32
- // Get the database path (project-local or global)
33
- const DB_PATH = getDatabasePath();
34
-
35
15
  class ChromePilotDatabase {
36
- constructor() {
16
+ constructor(options = {}) {
37
17
  this.db = null;
38
18
  this.initialized = false;
19
+
20
+ // Priority: options.dbPath > env var > centralized default
21
+ this.dbPath = options.dbPath ||
22
+ process.env.CHROMEDEBUG_DB_PATH ||
23
+ this.getCentralDbPath();
24
+
25
+ logger.debug(`[Database] Using database path: ${this.dbPath}`);
26
+ }
27
+
28
+ /**
29
+ * Gets the centralized database path in user's home directory
30
+ * @returns {string} Centralized database file path
31
+ */
32
+ getCentralDbPath() {
33
+ return path.join(os.homedir(), '.chromedebug', 'chrome-pilot.db');
34
+ }
35
+
36
+ /**
37
+ * Ensures the database directory exists with proper permissions
38
+ */
39
+ ensureDbDirectory() {
40
+ const dir = path.dirname(this.dbPath);
41
+ if (!fs.existsSync(dir)) {
42
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
43
+ logger.debug(`[Database] Created database directory: ${dir}`);
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Migrates from old project-local database to centralized database
49
+ * Only runs if:
50
+ * 1. We're using the central DB location (not a custom path)
51
+ * 2. Old project DB exists
52
+ * 3. Central DB doesn't exist yet
53
+ */
54
+ migrateIfNeeded() {
55
+ const projectDb = path.join(__dirname, '../data/chrome-pilot.db');
56
+ const usingCentralDb = this.dbPath === this.getCentralDbPath();
57
+
58
+ // Only migrate if using centralized path, project DB exists, and central doesn't
59
+ if (usingCentralDb && fs.existsSync(projectDb) && !fs.existsSync(this.dbPath)) {
60
+ logger.info(`[Database] Migrating database from ${projectDb} to ${this.dbPath}`);
61
+
62
+ try {
63
+ // Ensure central directory exists
64
+ this.ensureDbDirectory();
65
+
66
+ // Copy the database file
67
+ fs.copyFileSync(projectDb, this.dbPath);
68
+
69
+ logger.info('[Database] Migration complete. Old database preserved at original location.');
70
+ logger.info('[Database] All future recordings will be stored in the centralized database.');
71
+ } catch (error) {
72
+ logger.error(`[Database] Migration failed: ${error.message}`);
73
+ logger.error('[Database] Will create new database at centralized location.');
74
+ }
75
+ } else if (usingCentralDb && fs.existsSync(this.dbPath)) {
76
+ logger.debug('[Database] Using existing centralized database');
77
+ } else if (!usingCentralDb) {
78
+ logger.debug(`[Database] Using custom database path: ${this.dbPath}`);
79
+ }
39
80
  }
40
81
 
41
82
  init() {
42
83
  if (this.initialized) return;
43
-
44
- // Ensure data directory exists
45
- const dataDir = path.dirname(DB_PATH);
46
- if (!fs.existsSync(dataDir)) {
47
- fs.mkdirSync(dataDir, { recursive: true });
48
- }
49
-
50
- this.db = new Database(DB_PATH);
84
+
85
+ // Ensure database directory exists
86
+ this.ensureDbDirectory();
87
+
88
+ // Migrate from old location if needed
89
+ this.migrateIfNeeded();
90
+
91
+ // Open database connection
92
+ this.db = new Database(this.dbPath);
51
93
  this.db.pragma('journal_mode = WAL'); // Enable WAL mode for better concurrent access
52
-
94
+
53
95
  // Create tables
54
96
  this.createTables();
55
97
  this.initialized = true;
56
- logger.debug(`ChromeDebug MCP database initialized at: ${DB_PATH}`);
98
+ logger.debug(`ChromeDebug MCP database initialized at: ${this.dbPath}`);
57
99
  }
58
100
 
59
101
  createTables() {
@@ -1300,16 +1342,16 @@ class ChromePilotDatabase {
1300
1342
  // Get database stats
1301
1343
  getStats() {
1302
1344
  this.init();
1303
-
1345
+
1304
1346
  const recordingsCount = this.db.prepare(`SELECT COUNT(*) as count FROM recordings`).get().count;
1305
1347
  const framesCount = this.db.prepare(`SELECT COUNT(*) as count FROM frames`).get().count;
1306
1348
  const logsCount = this.db.prepare(`SELECT COUNT(*) as count FROM console_logs`).get().count;
1307
-
1349
+
1308
1350
  return {
1309
1351
  recordings: recordingsCount,
1310
1352
  frames: framesCount,
1311
1353
  logs: logsCount,
1312
- dbPath: DB_PATH
1354
+ dbPath: this.dbPath
1313
1355
  };
1314
1356
  }
1315
1357
 
@@ -2175,5 +2217,8 @@ class ChromePilotDatabase {
2175
2217
  }
2176
2218
  }
2177
2219
 
2178
- // Export singleton instance
2179
- export const database = new ChromePilotDatabase();
2220
+ // Export singleton instance (uses default centralized path)
2221
+ export const database = new ChromePilotDatabase();
2222
+
2223
+ // Export class for custom instantiation if needed
2224
+ export { ChromePilotDatabase };
package/src/index.js CHANGED
@@ -46,6 +46,13 @@ class ChromePilotApp {
46
46
  const args = this.parseArguments();
47
47
  const finalOptions = { ...options, ...args };
48
48
 
49
+ // Set database path environment variable if provided via CLI
50
+ // This ensures the database singleton uses the correct path
51
+ if (finalOptions.dbPath) {
52
+ process.env.CHROMEDEBUG_DB_PATH = finalOptions.dbPath;
53
+ logger.debug(`[App] Database path set via CLI: ${finalOptions.dbPath}`);
54
+ }
55
+
49
56
  // Create Chrome controller instance
50
57
  this.chromeController = new ChromeController();
51
58
 
@@ -144,7 +151,9 @@ class ChromePilotApp {
144
151
  debug: false,
145
152
  sessionId: null,
146
153
  noCleanup: false,
147
- verbose: false
154
+ verbose: false,
155
+ dbPath: null,
156
+ allowMultiple: false
148
157
  };
149
158
 
150
159
  const argv = process.argv.slice(2);
@@ -169,12 +178,23 @@ class ChromePilotApp {
169
178
  process.exit(1);
170
179
  }
171
180
  break;
181
+ case '--db-path':
182
+ if (i + 1 < argv.length) {
183
+ args.dbPath = argv[++i];
184
+ } else {
185
+ logger.error('--db-path requires a value');
186
+ process.exit(1);
187
+ }
188
+ break;
172
189
  case '--no-cleanup':
173
190
  args.noCleanup = true;
174
191
  break;
175
192
  case '--verbose':
176
193
  args.verbose = true;
177
194
  break;
195
+ case '--allow-multiple':
196
+ args.allowMultiple = true;
197
+ break;
178
198
  case '--help':
179
199
  this.showHelp();
180
200
  process.exit(0);
@@ -182,6 +202,8 @@ class ChromePilotApp {
182
202
  default:
183
203
  if (arg.startsWith('--session-id=')) {
184
204
  args.sessionId = arg.split('=')[1];
205
+ } else if (arg.startsWith('--db-path=')) {
206
+ args.dbPath = arg.split('=')[1];
185
207
  } else if (arg.startsWith('--')) {
186
208
  logger.error(`Unknown argument: ${arg}`);
187
209
  this.showHelp();
@@ -216,8 +238,10 @@ Options:
216
238
  --watch Watch for file changes (development mode)
217
239
  --debug Enable debug logging
218
240
  --session-id ID Set custom session ID for resource isolation
241
+ --db-path PATH Set custom database file path (default: ~/.chromedebug/chrome-pilot.db)
219
242
  --no-cleanup Skip cleanup of dead processes on startup
220
243
  --verbose Enable verbose logging including session info
244
+ --allow-multiple Allow multiple ChromeDebug instances (default: kill others on startup)
221
245
  --help Show this help message
222
246
 
223
247
  Session Isolation:
@@ -225,13 +249,20 @@ Session Isolation:
225
249
  This allows multiple Chrome Debug MCP instances to run simultaneously
226
250
  without interfering with each other.
227
251
 
252
+ Database Location:
253
+ By default, ChromeDebug uses a centralized database at ~/.chromedebug/chrome-pilot.db
254
+ This allows recordings from all projects to be accessible in one place.
255
+ Use --db-path to override, or set CHROMEDEBUG_DB_PATH environment variable.
256
+
228
257
  Examples:
229
258
  node src/index.js --session-id claude-session-1 --verbose
230
259
  node src/index.js --no-cleanup --debug
260
+ node src/index.js --db-path /custom/path/to/database.db
231
261
 
232
262
  Environment Variables:
233
- NODE_ENV Set to 'development' for debug mode
234
- CHROME_PILOT_PORT Configure HTTP server port
263
+ NODE_ENV Set to 'development' for debug mode
264
+ CHROME_PILOT_PORT Configure HTTP server port
265
+ CHROMEDEBUG_DB_PATH Set custom database file path
235
266
 
236
267
  For more information, see the documentation in CLAUDE.md
237
268
  `);
@@ -446,6 +446,99 @@ export class UnifiedSessionManager {
446
446
  return sessions;
447
447
  }
448
448
 
449
+ /**
450
+ * Kill all other ChromeDebug instances
451
+ * Uses session files to find PIDs, then kills them gracefully
452
+ * @returns {Array} Array of killed PIDs
453
+ */
454
+ async killOtherInstances() {
455
+ const sessions = await UnifiedSessionManager.findActiveSessions();
456
+ const currentPid = process.pid;
457
+ const killedPids = [];
458
+
459
+ for (const session of sessions) {
460
+ if (session.pid === currentPid) continue; // Don't kill ourselves
461
+
462
+ try {
463
+ // Check if process exists
464
+ process.kill(session.pid, 0); // Signal 0 = check existence
465
+
466
+ // Verify this is actually a ChromeDebug process
467
+ const isChromeDebug = await this.isChromeDebugProcess(session.pid);
468
+ if (!isChromeDebug) {
469
+ console.error(`Skipping PID ${session.pid} - not a ChromeDebug process`);
470
+ continue;
471
+ }
472
+
473
+ console.error(`Killing ChromeDebug instance: PID ${session.pid} (session ${session.sessionId})`);
474
+
475
+ // Send SIGTERM for graceful shutdown
476
+ process.kill(session.pid, 'SIGTERM');
477
+ killedPids.push(session.pid);
478
+
479
+ // Wait 2 seconds, then SIGKILL if still alive
480
+ setTimeout(() => {
481
+ try {
482
+ process.kill(session.pid, 0); // Check if still alive
483
+ console.error(` Process ${session.pid} didn't terminate, sending SIGKILL`);
484
+ process.kill(session.pid, 'SIGKILL');
485
+ } catch (err) {
486
+ // Process already dead, good
487
+ }
488
+ }, 2000);
489
+
490
+ // Clean up session file
491
+ const sessionFile = path.join(this.sessionDir, `${session.sessionId}.json`);
492
+ try {
493
+ await fs.promises.unlink(sessionFile);
494
+ } catch (err) {
495
+ // Ignore errors
496
+ }
497
+
498
+ } catch (error) {
499
+ if (error.code === 'ESRCH') {
500
+ // Process doesn't exist, just clean up the session file
501
+ const sessionFile = path.join(this.sessionDir, `${session.sessionId}.json`);
502
+ try {
503
+ await fs.promises.unlink(sessionFile);
504
+ } catch (err) {
505
+ // Ignore errors
506
+ }
507
+ }
508
+ // Ignore other errors (permission denied, etc.)
509
+ }
510
+ }
511
+
512
+ if (killedPids.length > 0) {
513
+ console.error(`Killed ${killedPids.length} other ChromeDebug instance(s)`);
514
+ }
515
+
516
+ return killedPids;
517
+ }
518
+
519
+ /**
520
+ * Verify if a PID belongs to a ChromeDebug process
521
+ * @param {number} pid - Process ID to check
522
+ * @returns {boolean} True if it's a ChromeDebug process
523
+ */
524
+ async isChromeDebugProcess(pid) {
525
+ try {
526
+ const { execSync } = await import('child_process');
527
+
528
+ // Get process command line
529
+ const cmd = execSync(`ps -p ${pid} -o command=`, { encoding: 'utf8' }).trim();
530
+
531
+ // Check if it's a ChromeDebug process (node running chromedebug or chrome-pilot)
532
+ return cmd.includes('chromedebug') ||
533
+ cmd.includes('chrome-pilot') ||
534
+ cmd.includes('ChromePilot') ||
535
+ cmd.includes('ChromeDebug');
536
+ } catch (error) {
537
+ // If we can't determine, be safe and don't kill
538
+ return false;
539
+ }
540
+ }
541
+
449
542
  /**
450
543
  * Clean up stale sessions
451
544
  */