@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 +1 -1
- package/src/cli.js +12 -0
- package/src/database.js +82 -37
- package/src/index.js +34 -3
- package/src/services/unified-session-manager.js +93 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dynamicu/chromedebug-mcp",
|
|
3
|
-
"version": "2.6.
|
|
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
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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: ${
|
|
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:
|
|
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
|
|
234
|
-
CHROME_PILOT_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
|
*/
|