@dynamicu/chromedebug-mcp 2.4.1 → 2.4.3
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/README.md +49 -2
- package/chrome-extension/icon128.png +0 -0
- package/chrome-extension/icon16.png +0 -0
- package/chrome-extension/icon48.png +0 -0
- package/chrome-extension/manifest.json +3 -3
- package/package.json +3 -2
- package/scripts/package-webstore-extension.js +109 -80
- package/src/cli-http.js +310 -0
- package/src/services/chrome-service.js +29 -4
- package/src/services/process-manager.js +14 -2
package/README.md
CHANGED
|
@@ -30,15 +30,62 @@ npm install
|
|
|
30
30
|
|
|
31
31
|
### As MCP Server
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
#### macOS / Linux
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
36
|
claude mcp add chromedebug -s user -- chromedebug-mcp
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
The MCP server will automatically start both the stdio MCP server and HTTP server for Chrome extension integration.
|
|
40
|
+
|
|
41
|
+
#### Windows
|
|
42
|
+
|
|
43
|
+
On Windows, the MCP server and HTTP server should be run separately for best reliability:
|
|
44
|
+
|
|
45
|
+
**Step 1: Add MCP Server for Claude Code**
|
|
46
|
+
```bash
|
|
47
|
+
claude mcp add chromedebug -s user -- cmd /c chromedebug-mcp
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Step 2: Start HTTP Server for Chrome Extension (separate terminal)**
|
|
51
|
+
```bash
|
|
52
|
+
chromedebug-mcp-server
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Why Separate?** On Windows, the stdio-based MCP server (for Claude Code) works best when running independently from the HTTP server (for Chrome extension). This separation provides better stability and prevents connection issues.
|
|
56
|
+
|
|
57
|
+
**Note for Windows Users:**
|
|
58
|
+
- Process cleanup is currently limited on Windows. The server will start successfully but won't clean up orphaned processes from previous sessions.
|
|
59
|
+
- If you only need Claude Code integration (no Chrome extension), the MCP server alone is sufficient.
|
|
60
|
+
- If you need the Chrome extension, run both servers in separate terminal windows.
|
|
61
|
+
|
|
62
|
+
### Standalone HTTP Server (New in v2.4.3)
|
|
63
|
+
|
|
64
|
+
For Windows users or those who prefer to run services separately, you can start the HTTP server independently:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Start HTTP server only (no MCP)
|
|
68
|
+
chromedebug-mcp-server
|
|
69
|
+
|
|
70
|
+
# With options
|
|
71
|
+
chromedebug-mcp-server --verbose --port 3000
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Available Options:**
|
|
75
|
+
- `--port PORT` - Specify HTTP server port (optional, auto-discovers if not set)
|
|
76
|
+
- `--verbose` - Enable verbose logging
|
|
77
|
+
- `--debug` - Enable debug mode
|
|
78
|
+
- `--no-cleanup` - Skip process cleanup on startup
|
|
79
|
+
- `--session-id ID` - Set custom session ID
|
|
80
|
+
|
|
81
|
+
**Use Cases:**
|
|
82
|
+
- Windows users who want Chrome extension support without running full MCP server
|
|
83
|
+
- Development environments where you want HTTP server running persistently
|
|
84
|
+
- Situations where MCP and HTTP servers should run in separate processes
|
|
85
|
+
|
|
39
86
|
### Embedded HTTP Server
|
|
40
87
|
|
|
41
|
-
Chrome Debug automatically
|
|
88
|
+
Chrome Debug can automatically start an embedded HTTP server for Chrome extension communication. The server uses ports configured in `config/chrome-pilot-config.json` (default: 3001, 3000, 3002, 3028). The server provides these REST API endpoints:
|
|
42
89
|
|
|
43
90
|
| Endpoint | Method | Description | Body |
|
|
44
91
|
|----------|--------|-------------|------|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
|
-
"name": "ChromeDebug MCP Assistant v2.1.
|
|
4
|
-
"version": "2.1.
|
|
5
|
-
"description": "ChromeDebug MCP visual element selector with function tracing [Build: 2025-10-
|
|
3
|
+
"name": "ChromeDebug MCP Assistant v2.1.3",
|
|
4
|
+
"version": "2.1.3",
|
|
5
|
+
"description": "ChromeDebug MCP visual element selector with function tracing [Build: 2025-10-16-v2.1.3]",
|
|
6
6
|
"permissions": [
|
|
7
7
|
"activeTab",
|
|
8
8
|
"scripting",
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dynamicu/chromedebug-mcp",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.3",
|
|
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",
|
|
7
7
|
"bin": {
|
|
8
|
-
"chromedebug-mcp": "./src/cli.js"
|
|
8
|
+
"chromedebug-mcp": "./src/cli.js",
|
|
9
|
+
"chromedebug-mcp-server": "./src/cli-http.js"
|
|
9
10
|
},
|
|
10
11
|
"publishConfig": {
|
|
11
12
|
"access": "public"
|
|
@@ -1,89 +1,118 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Package FREE Chrome extension for Chrome Web Store distribution
|
|
5
|
-
* Creates a .zip file optimized for Chrome Web Store submission
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { createWriteStream, promises as fs } from 'fs';
|
|
9
|
-
import { join, dirname } from 'path';
|
|
10
|
-
import { fileURLToPath } from 'url';
|
|
11
|
-
import { execSync } from 'child_process';
|
|
12
2
|
import archiver from 'archiver';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
13
6
|
|
|
14
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
-
const __dirname = dirname(__filename);
|
|
16
|
-
const rootDir = join(__dirname, '..');
|
|
17
|
-
const freeExtensionDir = join(rootDir, 'scripts', 'dist', 'free');
|
|
18
|
-
const outputDir = join(rootDir, 'dist');
|
|
19
|
-
const outputFile = join(outputDir, 'chromedebug-webstore-free.zip');
|
|
20
|
-
|
|
21
|
-
async function packageWebStoreExtension() {
|
|
22
|
-
console.log('📦 Building and packaging FREE Chrome extension for Web Store...');
|
|
23
|
-
|
|
24
|
-
// Step 1: Build the free version
|
|
25
|
-
console.log('🔨 Building free version...');
|
|
26
|
-
try {
|
|
27
|
-
execSync('npm run build:free', {
|
|
28
|
-
cwd: rootDir,
|
|
29
|
-
stdio: 'inherit'
|
|
30
|
-
});
|
|
31
|
-
} catch (error) {
|
|
32
|
-
console.error('❌ Failed to build free version');
|
|
33
|
-
throw error;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Step 2: Verify the build output exists
|
|
37
|
-
try {
|
|
38
|
-
await fs.access(freeExtensionDir);
|
|
39
|
-
} catch {
|
|
40
|
-
throw new Error(`Build output not found at ${freeExtensionDir}`);
|
|
41
|
-
}
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
42
9
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
// Directory might already exist, that's fine
|
|
48
|
-
}
|
|
10
|
+
const projectRoot = path.join(__dirname, '..');
|
|
11
|
+
const extensionDir = path.join(projectRoot, 'chrome-extension');
|
|
12
|
+
const distDir = path.join(projectRoot, 'dist');
|
|
13
|
+
const outputFile = path.join(distDir, 'chromedebug-extension-webstore.zip');
|
|
49
14
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const output = createWriteStream(outputFile);
|
|
54
|
-
const archive = archiver('zip', {
|
|
55
|
-
zlib: { level: 9 }
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
return new Promise((resolve, reject) => {
|
|
59
|
-
output.on('close', () => {
|
|
60
|
-
const sizeKB = (archive.pointer() / 1024).toFixed(2);
|
|
61
|
-
console.log(`✅ Chrome Web Store package created successfully!`);
|
|
62
|
-
console.log(` Size: ${sizeKB} KB`);
|
|
63
|
-
console.log(` Location: ${outputFile}`);
|
|
64
|
-
console.log(` 📌 This is the FREE version for Chrome Web Store`);
|
|
65
|
-
console.log(` 🌐 Ready to upload to: https://chrome.google.com/webstore/devconsole`);
|
|
66
|
-
console.log(`\n📋 Next steps:`);
|
|
67
|
-
console.log(` 1. Go to Chrome Web Store Developer Dashboard`);
|
|
68
|
-
console.log(` 2. Upload this zip file`);
|
|
69
|
-
console.log(` 3. Fill in store listing details`);
|
|
70
|
-
console.log(` 4. Submit for review`);
|
|
71
|
-
resolve();
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
archive.on('error', reject);
|
|
75
|
-
|
|
76
|
-
archive.pipe(output);
|
|
77
|
-
|
|
78
|
-
// Add all files from the free build directory
|
|
79
|
-
// Chrome Web Store expects all files to be in the root of the zip
|
|
80
|
-
archive.directory(freeExtensionDir, false);
|
|
81
|
-
|
|
82
|
-
archive.finalize();
|
|
83
|
-
});
|
|
15
|
+
// Ensure dist directory exists
|
|
16
|
+
if (!fs.existsSync(distDir)) {
|
|
17
|
+
fs.mkdirSync(distDir, { recursive: true });
|
|
84
18
|
}
|
|
85
19
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
20
|
+
// Create output stream
|
|
21
|
+
const output = fs.createWriteStream(outputFile);
|
|
22
|
+
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
23
|
+
|
|
24
|
+
console.log('📦 Packaging Chrome extension for Web Store...');
|
|
25
|
+
|
|
26
|
+
// Listen for archive events
|
|
27
|
+
output.on('close', () => {
|
|
28
|
+
const sizeKB = (archive.pointer() / 1024).toFixed(2);
|
|
29
|
+
console.log(`✅ Extension packaged successfully!`);
|
|
30
|
+
console.log(` File: ${outputFile}`);
|
|
31
|
+
console.log(` Size: ${sizeKB} KB`);
|
|
32
|
+
console.log(`\n🚀 Ready to upload to Chrome Web Store!`);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
archive.on('error', (err) => {
|
|
36
|
+
throw err;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Pipe archive to output file
|
|
40
|
+
archive.pipe(output);
|
|
41
|
+
|
|
42
|
+
// Files to include in the extension (FREE version for Web Store)
|
|
43
|
+
const filesToInclude = [
|
|
44
|
+
// Manifest (use FREE version and rename to manifest.json)
|
|
45
|
+
{ source: 'manifest.free.json', target: 'manifest.json' },
|
|
46
|
+
|
|
47
|
+
// Background service worker
|
|
48
|
+
'background.js',
|
|
49
|
+
'chrome-session-manager.js',
|
|
50
|
+
'logger.js',
|
|
51
|
+
'extension-config.js',
|
|
52
|
+
'firebase-client.js',
|
|
53
|
+
'firebase-config.js',
|
|
54
|
+
'license-helper.js',
|
|
55
|
+
|
|
56
|
+
// Content scripts (from manifest)
|
|
57
|
+
'content.js',
|
|
58
|
+
'content.css',
|
|
59
|
+
'pako.min.js',
|
|
60
|
+
'web-vitals.iife.js',
|
|
61
|
+
'pii-redactor.js',
|
|
62
|
+
'data-buffer.js',
|
|
63
|
+
'performance-monitor.js',
|
|
64
|
+
'dom-tracker.js',
|
|
65
|
+
'network-tracker.js',
|
|
66
|
+
|
|
67
|
+
// Popup
|
|
68
|
+
'popup.html',
|
|
69
|
+
'popup.js',
|
|
70
|
+
|
|
71
|
+
// Options page
|
|
72
|
+
'options.html',
|
|
73
|
+
'options.js',
|
|
74
|
+
|
|
75
|
+
// Activation manager
|
|
76
|
+
'activation-manager.html',
|
|
77
|
+
'activation-manager.js',
|
|
78
|
+
|
|
79
|
+
// Offscreen
|
|
80
|
+
'offscreen.html',
|
|
81
|
+
'offscreen.js',
|
|
82
|
+
|
|
83
|
+
// Frame capture
|
|
84
|
+
'frame-capture.js',
|
|
85
|
+
|
|
86
|
+
// Icons
|
|
87
|
+
'icon16.png',
|
|
88
|
+
'icon48.png',
|
|
89
|
+
'icon128.png',
|
|
90
|
+
|
|
91
|
+
// Documentation
|
|
92
|
+
'README.md'
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
// Add each file to the archive
|
|
96
|
+
filesToInclude.forEach(file => {
|
|
97
|
+
let sourcePath, targetName;
|
|
98
|
+
|
|
99
|
+
if (typeof file === 'object') {
|
|
100
|
+
// Handle object format { source: 'file.json', target: 'renamed.json' }
|
|
101
|
+
sourcePath = path.join(extensionDir, file.source);
|
|
102
|
+
targetName = file.target;
|
|
103
|
+
} else {
|
|
104
|
+
// Handle simple string format
|
|
105
|
+
sourcePath = path.join(extensionDir, file);
|
|
106
|
+
targetName = file;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (fs.existsSync(sourcePath)) {
|
|
110
|
+
archive.file(sourcePath, { name: targetName });
|
|
111
|
+
console.log(` ✓ ${targetName}${typeof file === 'object' ? ` (from ${file.source})` : ''}`);
|
|
112
|
+
} else {
|
|
113
|
+
console.warn(` ⚠ Skipping missing file: ${typeof file === 'object' ? file.source : file}`);
|
|
114
|
+
}
|
|
89
115
|
});
|
|
116
|
+
|
|
117
|
+
// Finalize the archive
|
|
118
|
+
archive.finalize();
|
package/src/cli-http.js
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ChromeDebug HTTP Server CLI
|
|
5
|
+
* Standalone HTTP server for Chrome extension integration
|
|
6
|
+
* Does not start MCP stdio server - only HTTP API server
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { ChromeController } from './chrome-controller.js';
|
|
10
|
+
import { ChromeService } from './services/chrome-service.js';
|
|
11
|
+
import { ProcessManager } from './services/process-manager.js';
|
|
12
|
+
import {
|
|
13
|
+
initializeSessionManager,
|
|
14
|
+
getSessionInfo,
|
|
15
|
+
registerProcess
|
|
16
|
+
} from './services/unified-session-manager.js';
|
|
17
|
+
import logger from './utils/logger.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Standalone HTTP Server Application
|
|
21
|
+
* Provides Chrome extension API without MCP stdio interface
|
|
22
|
+
*/
|
|
23
|
+
class ChromeDebugHttpServer {
|
|
24
|
+
constructor() {
|
|
25
|
+
this.chromeController = null;
|
|
26
|
+
this.chromeService = null;
|
|
27
|
+
this.processManager = null;
|
|
28
|
+
this.sessionManager = null;
|
|
29
|
+
this.initialized = false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Initializes HTTP server components
|
|
34
|
+
* @param {Object} options - Initialization options
|
|
35
|
+
*/
|
|
36
|
+
async initialize(options = {}) {
|
|
37
|
+
try {
|
|
38
|
+
// Parse command line arguments
|
|
39
|
+
const args = this.parseArguments();
|
|
40
|
+
const finalOptions = { ...options, ...args };
|
|
41
|
+
|
|
42
|
+
logger.info('🚀 Starting ChromeDebug HTTP Server (Extension API)...');
|
|
43
|
+
|
|
44
|
+
// Create Chrome controller instance
|
|
45
|
+
this.chromeController = new ChromeController();
|
|
46
|
+
|
|
47
|
+
// Create Chrome service wrapper with process management
|
|
48
|
+
this.chromeService = new ChromeService(this.chromeController);
|
|
49
|
+
this.processManager = this.chromeService.getProcessManager();
|
|
50
|
+
|
|
51
|
+
// Initialize Chrome service with HTTP server REQUIRED
|
|
52
|
+
const serviceResult = await this.chromeService.initialize({
|
|
53
|
+
...finalOptions,
|
|
54
|
+
startHttp: true, // Force HTTP server on
|
|
55
|
+
singleServer: false, // Ensure HTTP starts
|
|
56
|
+
sessionManager: this.sessionManager
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (serviceResult.httpServer) {
|
|
60
|
+
logger.info(`✅ HTTP Server Started Successfully`);
|
|
61
|
+
logger.info(` Port: ${serviceResult.httpServer.port}`);
|
|
62
|
+
logger.info(` Status: ${serviceResult.httpServer.status}`);
|
|
63
|
+
logger.info('');
|
|
64
|
+
logger.info('📍 Chrome Extension Connection:');
|
|
65
|
+
logger.info(` Use port ${serviceResult.httpServer.port} in extension settings`);
|
|
66
|
+
logger.info('');
|
|
67
|
+
logger.info('💡 Server is ready for Chrome extension connections');
|
|
68
|
+
} else {
|
|
69
|
+
logger.error('❌ HTTP Server Failed to Start');
|
|
70
|
+
if (serviceResult.httpServer?.error) {
|
|
71
|
+
logger.error(` Error: ${serviceResult.httpServer.error}`);
|
|
72
|
+
}
|
|
73
|
+
throw new Error('HTTP server initialization failed');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.initialized = true;
|
|
77
|
+
return {
|
|
78
|
+
status: 'initialized',
|
|
79
|
+
options: finalOptions,
|
|
80
|
+
serviceResult
|
|
81
|
+
};
|
|
82
|
+
} catch (error) {
|
|
83
|
+
logger.error('Failed to initialize ChromeDebug HTTP Server:', error);
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Starts the HTTP server and keeps it running
|
|
90
|
+
*/
|
|
91
|
+
async start() {
|
|
92
|
+
if (!this.initialized) {
|
|
93
|
+
await this.initialize();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
// Clean up any existing ChromeDebug processes (optional, controlled by flag)
|
|
98
|
+
const args = this.parseArguments();
|
|
99
|
+
if (!args.noCleanup) {
|
|
100
|
+
const cleanupResult = await this.processManager.cleanupChromePilotProcesses();
|
|
101
|
+
if (cleanupResult.total > 0) {
|
|
102
|
+
logger.debug(`Cleaned up ${cleanupResult.killed.length} existing processes`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Keep the server alive
|
|
107
|
+
logger.info('🔄 HTTP Server is running. Press Ctrl+C to stop.');
|
|
108
|
+
|
|
109
|
+
// Prevent process from exiting
|
|
110
|
+
process.stdin.resume();
|
|
111
|
+
|
|
112
|
+
} catch (error) {
|
|
113
|
+
logger.error('Failed to start ChromeDebug HTTP Server:', error);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Parses command line arguments
|
|
120
|
+
* @returns {Object} Parsed arguments
|
|
121
|
+
*/
|
|
122
|
+
parseArguments() {
|
|
123
|
+
const args = {
|
|
124
|
+
debug: false,
|
|
125
|
+
sessionId: null,
|
|
126
|
+
noCleanup: false,
|
|
127
|
+
verbose: false,
|
|
128
|
+
port: null
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const argv = process.argv.slice(2);
|
|
132
|
+
for (let i = 0; i < argv.length; i++) {
|
|
133
|
+
const arg = argv[i];
|
|
134
|
+
|
|
135
|
+
switch (arg) {
|
|
136
|
+
case '--debug':
|
|
137
|
+
args.debug = true;
|
|
138
|
+
break;
|
|
139
|
+
case '--session-id':
|
|
140
|
+
if (i + 1 < argv.length) {
|
|
141
|
+
args.sessionId = argv[++i];
|
|
142
|
+
} else {
|
|
143
|
+
logger.error('--session-id requires a value');
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
break;
|
|
147
|
+
case '--no-cleanup':
|
|
148
|
+
args.noCleanup = true;
|
|
149
|
+
break;
|
|
150
|
+
case '--verbose':
|
|
151
|
+
args.verbose = true;
|
|
152
|
+
break;
|
|
153
|
+
case '--port':
|
|
154
|
+
if (i + 1 < argv.length) {
|
|
155
|
+
args.port = parseInt(argv[++i], 10);
|
|
156
|
+
} else {
|
|
157
|
+
logger.error('--port requires a value');
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
break;
|
|
161
|
+
case '--help':
|
|
162
|
+
this.showHelp();
|
|
163
|
+
process.exit(0);
|
|
164
|
+
break;
|
|
165
|
+
default:
|
|
166
|
+
if (arg.startsWith('--session-id=')) {
|
|
167
|
+
args.sessionId = arg.split('=')[1];
|
|
168
|
+
} else if (arg.startsWith('--port=')) {
|
|
169
|
+
args.port = parseInt(arg.split('=')[1], 10);
|
|
170
|
+
} else if (arg.startsWith('--')) {
|
|
171
|
+
logger.error(`Unknown argument: ${arg}`);
|
|
172
|
+
this.showHelp();
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return args;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Shows help information
|
|
184
|
+
*/
|
|
185
|
+
showHelp() {
|
|
186
|
+
console.log(`
|
|
187
|
+
ChromeDebug HTTP Server - Standalone Chrome Extension API
|
|
188
|
+
|
|
189
|
+
This server provides HTTP endpoints for Chrome extension integration.
|
|
190
|
+
It does NOT start the MCP stdio server for Claude Code.
|
|
191
|
+
|
|
192
|
+
Usage: chromedebug-mcp-server [options]
|
|
193
|
+
|
|
194
|
+
Options:
|
|
195
|
+
--debug Enable debug logging
|
|
196
|
+
--session-id ID Set custom session ID for resource isolation
|
|
197
|
+
--no-cleanup Skip cleanup of dead processes on startup
|
|
198
|
+
--verbose Enable verbose logging including session info
|
|
199
|
+
--port PORT Specify HTTP server port (optional, auto-discovers if not set)
|
|
200
|
+
--help Show this help message
|
|
201
|
+
|
|
202
|
+
Examples:
|
|
203
|
+
chromedebug-mcp-server
|
|
204
|
+
chromedebug-mcp-server --verbose --no-cleanup
|
|
205
|
+
chromedebug-mcp-server --port 3000
|
|
206
|
+
chromedebug-mcp-server --session-id extension-session-1
|
|
207
|
+
|
|
208
|
+
Environment Variables:
|
|
209
|
+
NODE_ENV Set to 'development' for debug mode
|
|
210
|
+
CHROME_PILOT_PORT Configure HTTP server port
|
|
211
|
+
|
|
212
|
+
What this server does:
|
|
213
|
+
✓ Starts HTTP REST API for Chrome extension
|
|
214
|
+
✓ Provides endpoints for screen recording uploads
|
|
215
|
+
✓ Handles workflow recording and playback
|
|
216
|
+
✓ Manages Chrome browser instances for extension
|
|
217
|
+
|
|
218
|
+
What this server does NOT do:
|
|
219
|
+
✗ Start MCP stdio server for Claude Code
|
|
220
|
+
✗ Provide AI assistant integration
|
|
221
|
+
|
|
222
|
+
For MCP integration, use: chromedebug-mcp
|
|
223
|
+
|
|
224
|
+
For more information, see the documentation in README.md
|
|
225
|
+
`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Graceful shutdown
|
|
230
|
+
*/
|
|
231
|
+
async shutdown() {
|
|
232
|
+
logger.info('Shutting down ChromeDebug HTTP Server...');
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
if (this.chromeService) {
|
|
236
|
+
await this.chromeService.cleanup();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (this.processManager) {
|
|
240
|
+
await this.processManager.killAllManagedProcesses();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
logger.info('HTTP Server shutdown complete');
|
|
244
|
+
} catch (error) {
|
|
245
|
+
logger.error('Error during shutdown:', error);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Main entry point
|
|
252
|
+
*/
|
|
253
|
+
async function main() {
|
|
254
|
+
const app = new ChromeDebugHttpServer();
|
|
255
|
+
|
|
256
|
+
// Parse arguments first to get session settings
|
|
257
|
+
const args = app.parseArguments();
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
// Initialize unified session manager with options
|
|
261
|
+
const sessionManager = await initializeSessionManager({
|
|
262
|
+
sessionId: args.sessionId || 'http-server',
|
|
263
|
+
skipCleanup: args.noCleanup,
|
|
264
|
+
verbose: args.verbose
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// Register this process for tracking
|
|
268
|
+
await registerProcess(process.pid, 'http-server');
|
|
269
|
+
|
|
270
|
+
// Log session information if verbose
|
|
271
|
+
if (args.verbose) {
|
|
272
|
+
const sessionInfo = getSessionInfo();
|
|
273
|
+
logger.debug('Session Information:');
|
|
274
|
+
logger.debug(` Session ID: ${sessionInfo.sessionId}`);
|
|
275
|
+
logger.debug(` PID: ${sessionInfo.pid}`);
|
|
276
|
+
logger.debug(` Port: ${sessionInfo.port}`);
|
|
277
|
+
logger.debug(` Session File: ${sessionInfo.sessionFile}`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Pass session manager to app
|
|
281
|
+
app.sessionManager = sessionManager;
|
|
282
|
+
|
|
283
|
+
await app.start();
|
|
284
|
+
} catch (error) {
|
|
285
|
+
logger.error('Fatal error starting ChromeDebug HTTP Server:', error);
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Handle graceful shutdown
|
|
291
|
+
process.on('SIGINT', async () => {
|
|
292
|
+
logger.info('Received SIGINT, shutting down gracefully...');
|
|
293
|
+
process.exit(0);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
process.on('SIGTERM', async () => {
|
|
297
|
+
logger.info('Received SIGTERM, shutting down gracefully...');
|
|
298
|
+
process.exit(0);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// Export for testing
|
|
302
|
+
export { ChromeDebugHttpServer };
|
|
303
|
+
|
|
304
|
+
// Run if this is the main module
|
|
305
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
306
|
+
main().catch((error) => {
|
|
307
|
+
logger.error('Unhandled error:', error);
|
|
308
|
+
process.exit(1);
|
|
309
|
+
});
|
|
310
|
+
}
|
|
@@ -26,11 +26,13 @@ export class ChromeService {
|
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Initializes the Chrome service with HTTP server if needed
|
|
29
|
+
* Windows-specific: HTTP server failures are non-fatal to prevent MCP crashes
|
|
29
30
|
* @param {Object} options - Initialization options
|
|
30
31
|
* @returns {Promise<Object>} Initialization result
|
|
31
32
|
*/
|
|
32
33
|
async initialize(options = {}) {
|
|
33
34
|
const { startHttp = true, singleServer = false, sessionManager = null } = options;
|
|
35
|
+
const isWindows = process.platform === 'win32';
|
|
34
36
|
|
|
35
37
|
const result = {
|
|
36
38
|
chromeService: 'initialized',
|
|
@@ -57,7 +59,16 @@ export class ChromeService {
|
|
|
57
59
|
console.error('Falling back to port discovery');
|
|
58
60
|
}
|
|
59
61
|
|
|
60
|
-
|
|
62
|
+
// Platform-specific timeout: Windows needs longer initialization time
|
|
63
|
+
const httpTimeout = isWindows ? 20000 : 10000;
|
|
64
|
+
|
|
65
|
+
// Start HTTP server with timeout protection
|
|
66
|
+
const httpServerPromise = startHttpServer(this.chromeController, targetPort);
|
|
67
|
+
const timeoutPromise = new Promise((_, reject) =>
|
|
68
|
+
setTimeout(() => reject(new Error(`HTTP server startup timeout after ${httpTimeout}ms`)), httpTimeout)
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const { httpServer, serverPort } = await Promise.race([httpServerPromise, timeoutPromise]);
|
|
61
72
|
|
|
62
73
|
this.httpServer = httpServer;
|
|
63
74
|
this.httpServerPort = serverPort;
|
|
@@ -69,11 +80,25 @@ export class ChromeService {
|
|
|
69
80
|
|
|
70
81
|
console.error(`HTTP server started on port ${serverPort}`);
|
|
71
82
|
} catch (error) {
|
|
72
|
-
|
|
83
|
+
// IMPORTANT: HTTP server failures are non-fatal
|
|
84
|
+
// This prevents MCP server crashes when HTTP server can't start
|
|
85
|
+
const errorMessage = error.message || String(error);
|
|
86
|
+
console.error('⚠️ HTTP Server Initialization Failed (non-fatal):', errorMessage);
|
|
87
|
+
|
|
88
|
+
if (isWindows) {
|
|
89
|
+
console.error('ℹ️ On Windows, you can start the HTTP server separately:');
|
|
90
|
+
console.error(' npm run server');
|
|
91
|
+
console.error(' or: chromedebug-mcp-server');
|
|
92
|
+
}
|
|
93
|
+
|
|
73
94
|
result.httpServer = {
|
|
74
|
-
error:
|
|
75
|
-
status: 'failed'
|
|
95
|
+
error: errorMessage,
|
|
96
|
+
status: 'failed',
|
|
97
|
+
platform: process.platform,
|
|
98
|
+
fatal: false // Explicitly mark as non-fatal
|
|
76
99
|
};
|
|
100
|
+
|
|
101
|
+
// Do NOT throw - allow MCP server to continue without HTTP
|
|
77
102
|
}
|
|
78
103
|
}
|
|
79
104
|
|
|
@@ -57,18 +57,30 @@ async function safeKillProcess(pid, signal = 'SIGTERM') {
|
|
|
57
57
|
/**
|
|
58
58
|
* Find ChromeDebug MCP processes using Node.js process list instead of shell commands
|
|
59
59
|
* This prevents command injection while maintaining functionality
|
|
60
|
+
*
|
|
61
|
+
* NOTE: Windows support is intentionally limited - the ps command doesn't exist on Windows.
|
|
62
|
+
* Process cleanup is skipped on Windows to allow the server to start. A proper cross-platform
|
|
63
|
+
* solution will be implemented in a future update.
|
|
60
64
|
*/
|
|
61
65
|
async function findChromePilotProcesses() {
|
|
66
|
+
// Skip process cleanup on Windows - ps command doesn't exist
|
|
67
|
+
// This allows the server to start on Windows without crashing
|
|
68
|
+
// TODO: Implement proper Windows process management using tasklist or Node.js process handles
|
|
69
|
+
if (process.platform === 'win32') {
|
|
70
|
+
console.log('[ChromeDebug] Process cleanup skipped on Windows (not yet implemented)');
|
|
71
|
+
return { pidsToKill: [], processDescriptions: [] };
|
|
72
|
+
}
|
|
73
|
+
|
|
62
74
|
const currentPid = process.pid;
|
|
63
75
|
const pidsToKill = [];
|
|
64
76
|
const processDescriptions = [];
|
|
65
|
-
|
|
77
|
+
|
|
66
78
|
try {
|
|
67
79
|
// Security: Use Node.js process list instead of shell commands
|
|
68
80
|
// This approach uses the 'ps-list' module or similar safe approach
|
|
69
81
|
// For now, we'll use a safer implementation with spawn instead of exec
|
|
70
82
|
const { spawn } = await import('child_process');
|
|
71
|
-
|
|
83
|
+
|
|
72
84
|
return new Promise((resolve) => {
|
|
73
85
|
const ps = spawn('ps', ['aux'], { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
74
86
|
let stdout = '';
|