@dynamicu/chromedebug-mcp 2.2.0 → 2.2.1
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 +16 -36
- package/chrome-extension/manifest.json +3 -3
- package/chrome-extension/popup.html +2 -1
- package/chrome-extension/popup.js +44 -3
- package/package.json +4 -4
- package/scripts/migrate-dual-ids.js +135 -0
- package/scripts/webpack.config.free.cjs +74 -0
- package/scripts/webpack.config.pro.cjs +80 -0
- package/src/port-discovery.js +28 -155
- package/chrome-extension/README.md +0 -41
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ npm install
|
|
|
33
33
|
To add Chrome Debug to your Claude desktop app:
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
|
-
claude mcp add
|
|
36
|
+
claude mcp add chromedebug -s user -- chromedebug-mcp
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
### Embedded HTTP Server
|
|
@@ -42,35 +42,27 @@ Chrome Debug automatically starts an embedded HTTP server for Chrome extension c
|
|
|
42
42
|
|
|
43
43
|
| Endpoint | Method | Description | Body |
|
|
44
44
|
|----------|--------|-------------|------|
|
|
45
|
-
| `/
|
|
46
|
-
| `/
|
|
47
|
-
| `/
|
|
48
|
-
| `/
|
|
49
|
-
| `/
|
|
50
|
-
| `/
|
|
51
|
-
| `/
|
|
52
|
-
| `/
|
|
53
|
-
| `/
|
|
54
|
-
| `/
|
|
55
|
-
| `/
|
|
56
|
-
| `/
|
|
57
|
-
| `/
|
|
58
|
-
| `/chrome-pilot/workflow-recording/:id` | GET | Get workflow recording | - |
|
|
59
|
-
| `/chrome-pilot/restore-point` | POST | Save restore point | `{ "workflowId": "...", "domSnapshot": {...}, ... }` |
|
|
60
|
-
| `/chrome-pilot/restore-point/:id` | GET/DELETE | Get or delete restore point | - |
|
|
61
|
-
| `/chrome-pilot/restore-points/:workflowId` | GET | List restore points for workflow | - |
|
|
45
|
+
| `/chromedebug/evaluate` | POST | Evaluate expression | `{ "expression": "window.user" }` |
|
|
46
|
+
| `/chromedebug/scopes` | GET | Get scope variables | - |
|
|
47
|
+
| `/chromedebug/logs` | GET | Get recent console logs | - |
|
|
48
|
+
| `/chromedebug/screenshot` | GET | Take screenshot | `{ "type": "jpeg", "fullPage": true, "path": "/path/to/save.jpg" }` |
|
|
49
|
+
| `/chromedebug/dom-intent` | POST | Process natural language DOM commands | `{ "selector": "#element", "instruction": "make it blue" }` |
|
|
50
|
+
| `/chromedebug/status` | GET | Check server and Chrome connection status | - |
|
|
51
|
+
| `/chromedebug/upload/:dataType` | POST | Upload screen recording with logs | FormData with video file, logs JSON, and duration |
|
|
52
|
+
| `/chromedebug/recording/:id` | GET | Retrieve a recording by ID | - |
|
|
53
|
+
| `/chromedebug/workflow-recording` | POST | Save workflow recording | `{ "sessionId": "...", "actions": [...], "logs": [...] }` |
|
|
54
|
+
| `/chromedebug/workflow-recording/:sessionId` | GET | Get workflow recording | - |
|
|
55
|
+
| `/chromedebug/restore-point` | POST | Save restore point | `{ "workflowId": "...", "domSnapshot": {...}, ... }` |
|
|
56
|
+
| `/chromedebug/restore-point/:id` | GET/DELETE | Get or delete restore point | - |
|
|
57
|
+
| `/chromedebug/restore-points/:workflowId` | GET | List restore points for workflow | - |
|
|
62
58
|
|
|
63
59
|
### MCP Tools
|
|
64
60
|
|
|
65
61
|
When used as an MCP server, Chrome Debug exposes these tools:
|
|
66
62
|
|
|
67
63
|
- `launch_chrome` - Launch a Chrome browser instance
|
|
68
|
-
- `pause_execution` - Pause Chrome execution
|
|
69
|
-
- `resume_execution` - Resume Chrome execution
|
|
70
|
-
- `step_over` - Step over in debugger
|
|
71
64
|
- `evaluate_expression` - Evaluate JavaScript expression
|
|
72
65
|
- `get_scopes` - Get scope variables
|
|
73
|
-
- `set_breakpoint` - Set a breakpoint
|
|
74
66
|
- `get_logs` - Get console logs
|
|
75
67
|
- `take_screenshot` - Take a screenshot
|
|
76
68
|
- `get_page_content` - Get page content (text, HTML, and DOM structure)
|
|
@@ -98,8 +90,8 @@ npm start
|
|
|
98
90
|
# 🔧 MCP tools will use this port for API calls
|
|
99
91
|
|
|
100
92
|
# In another terminal, test the API (use the port shown in startup)
|
|
101
|
-
curl -X POST http://localhost:3000/
|
|
102
|
-
curl -X POST http://localhost:3000/
|
|
93
|
+
curl -X POST http://localhost:3000/chromedebug/pause
|
|
94
|
+
curl -X POST http://localhost:3000/chromedebug/evaluate \
|
|
103
95
|
-H "Content-Type: application/json" \
|
|
104
96
|
-d '{"expression": "document.title"}'
|
|
105
97
|
```
|
|
@@ -131,12 +123,6 @@ Chrome Debug includes a Chrome extension that enables visual element selection f
|
|
|
131
123
|
- Console logs can be included in the recording
|
|
132
124
|
- Workflows can be replayed later for debugging
|
|
133
125
|
|
|
134
|
-
3. **Restore Points** (NEW)
|
|
135
|
-
- During workflow recording, click "📍 Save Restore Point" to capture browser state
|
|
136
|
-
- Captures DOM, form values, storage, cookies, and scroll position
|
|
137
|
-
- Restore to any saved point to retry workflows from that state
|
|
138
|
-
- Perfect for debugging multi-step forms or complex interactions
|
|
139
|
-
- See [RESTORE_POINTS.md](RESTORE_POINTS.md) for details
|
|
140
126
|
|
|
141
127
|
## Development
|
|
142
128
|
|
|
@@ -154,12 +140,6 @@ Once Chrome Debug is installed as an MCP server, you can use these prompts with
|
|
|
154
140
|
- "Get all the links on the current page"
|
|
155
141
|
- "Take a screenshot of the current page"
|
|
156
142
|
|
|
157
|
-
### Debugging JavaScript
|
|
158
|
-
- "Set a breakpoint on line 42 of app.js and tell me what variables are in scope when it hits"
|
|
159
|
-
- "Pause execution and show me the current call stack"
|
|
160
|
-
- "Step through the code and watch how the 'counter' variable changes"
|
|
161
|
-
- "Evaluate localStorage.getItem('user') in the browser console"
|
|
162
|
-
|
|
163
143
|
### Web Scraping & Automation
|
|
164
144
|
- "Navigate to https://news.ycombinator.com and get the titles of the top 5 stories"
|
|
165
145
|
- "Fill out the search form with 'JavaScript' and submit it"
|
|
@@ -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-
|
|
3
|
+
"name": "ChromeDebug MCP Assistant v2.1.2",
|
|
4
|
+
"version": "2.1.2",
|
|
5
|
+
"description": "ChromeDebug MCP visual element selector with function tracing [Build: 2025-10-14-v2.1.2]",
|
|
6
6
|
"permissions": [
|
|
7
7
|
"activeTab",
|
|
8
8
|
"scripting",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
border-radius: 4px;
|
|
33
33
|
margin-bottom: 15px;
|
|
34
34
|
font-size: 14px;
|
|
35
|
+
min-height: 40px;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
.instructions {
|
|
@@ -129,7 +130,7 @@
|
|
|
129
130
|
</style>
|
|
130
131
|
</head>
|
|
131
132
|
<body>
|
|
132
|
-
<div style="position: absolute; top: 5px; right: 5px; font-size: 10px; color: #999; font-family: monospace;">v2.
|
|
133
|
+
<div style="position: absolute; top: 5px; right: 5px; font-size: 10px; color: #999; font-family: monospace;">v2.1.2</div>
|
|
133
134
|
<h2>Chrome Debug Assistant</h2>
|
|
134
135
|
|
|
135
136
|
<!-- License Section -->
|
|
@@ -288,12 +288,53 @@ async function checkServerStatus() {
|
|
|
288
288
|
|
|
289
289
|
if (connected) {
|
|
290
290
|
statusEl.className = 'server-status connected';
|
|
291
|
-
statusTextEl.
|
|
291
|
+
statusTextEl.innerHTML = `Server connected (port ${connectedPort})`;
|
|
292
292
|
} else {
|
|
293
293
|
statusEl.className = 'server-status disconnected';
|
|
294
|
-
|
|
294
|
+
|
|
295
|
+
// Enhanced disconnected state with install instructions
|
|
296
|
+
statusTextEl.innerHTML = `
|
|
297
|
+
<span style="display: block; margin-bottom: 8px;">Server not installed</span>
|
|
298
|
+
<div style="display: flex; gap: 5px; align-items: center;">
|
|
299
|
+
<button id="copyInstallCmd" style="padding: 4px 8px; font-size: 11px; background: #2196F3; color: white; border: none; border-radius: 3px; cursor: pointer; flex-shrink: 0;">
|
|
300
|
+
📋 Copy Install Command
|
|
301
|
+
</button>
|
|
302
|
+
<a href="https://www.npmjs.com/package/@dynamicu/chromedebug-mcp" target="_blank" style="font-size: 11px; white-space: nowrap;">
|
|
303
|
+
View Installation Guide
|
|
304
|
+
</a>
|
|
305
|
+
</div>
|
|
306
|
+
`;
|
|
307
|
+
|
|
308
|
+
// Add click handler for copy button
|
|
309
|
+
setTimeout(() => {
|
|
310
|
+
const copyBtn = document.getElementById('copyInstallCmd');
|
|
311
|
+
if (copyBtn) {
|
|
312
|
+
copyBtn.addEventListener('click', async (e) => {
|
|
313
|
+
e.preventDefault();
|
|
314
|
+
const installCommand = 'npm install -g @dynamicu/chromedebug-mcp';
|
|
315
|
+
|
|
316
|
+
try {
|
|
317
|
+
await navigator.clipboard.writeText(installCommand);
|
|
318
|
+
const originalText = copyBtn.innerHTML;
|
|
319
|
+
copyBtn.innerHTML = '✅ Copied!';
|
|
320
|
+
copyBtn.style.background = '#4CAF50';
|
|
321
|
+
|
|
322
|
+
setTimeout(() => {
|
|
323
|
+
copyBtn.innerHTML = originalText;
|
|
324
|
+
copyBtn.style.background = '#2196F3';
|
|
325
|
+
}, 2000);
|
|
326
|
+
} catch (err) {
|
|
327
|
+
console.error('Failed to copy:', err);
|
|
328
|
+
copyBtn.innerHTML = '❌ Failed';
|
|
329
|
+
setTimeout(() => {
|
|
330
|
+
copyBtn.innerHTML = '📋 Copy Install Command';
|
|
331
|
+
}, 2000);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}, 0);
|
|
295
336
|
}
|
|
296
|
-
|
|
337
|
+
|
|
297
338
|
// Return connection status and port for other functions to use
|
|
298
339
|
return { connected, connectedPort };
|
|
299
340
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dynamicu/chromedebug-mcp",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.1",
|
|
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",
|
|
@@ -49,11 +49,11 @@
|
|
|
49
49
|
"test:migration": "NODE_OPTIONS='--experimental-vm-modules' jest tests/dual-id-migration.test.js",
|
|
50
50
|
"test:regression": "NODE_OPTIONS='--experimental-vm-modules' jest tests/recording-regression.test.js",
|
|
51
51
|
"test:recording-all": "NODE_OPTIONS='--experimental-vm-modules' jest tests/recording-system-comprehensive.test.js tests/chrome-extension-recording.test.js tests/dual-id-migration.test.js tests/recording-regression.test.js tests/dual-recording-id.test.js",
|
|
52
|
-
"run-migration": "node migrate-dual-ids.js",
|
|
52
|
+
"run-migration": "node scripts/migrate-dual-ids.js",
|
|
53
53
|
"test:mcp": "NODE_OPTIONS='--experimental-vm-modules' jest tests/mcp-server.test.js",
|
|
54
54
|
"tbv:microtest": "cd chrome-extension && npm run tbv:microtest",
|
|
55
|
-
"build:free": "webpack --config webpack.config.free.cjs",
|
|
56
|
-
"build:pro": "webpack --config webpack.config.pro.cjs",
|
|
55
|
+
"build:free": "webpack --config scripts/webpack.config.free.cjs",
|
|
56
|
+
"build:pro": "webpack --config scripts/webpack.config.pro.cjs",
|
|
57
57
|
"build:both": "npm run build:free && npm run build:pro"
|
|
58
58
|
},
|
|
59
59
|
"keywords": [
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Migration script to convert from dual ID system to base ID system
|
|
3
|
+
// This script updates existing recordings with prefixed IDs to use base IDs
|
|
4
|
+
|
|
5
|
+
import Database from 'better-sqlite3';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const DB_PATH = path.join(__dirname, 'data/chrome-pilot.db');
|
|
11
|
+
|
|
12
|
+
console.log('Chrome Debug ID Migration Script');
|
|
13
|
+
console.log('================================');
|
|
14
|
+
console.log(`Database path: ${DB_PATH}`);
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const db = new Database(DB_PATH);
|
|
18
|
+
|
|
19
|
+
// Disable foreign key constraints during migration
|
|
20
|
+
db.pragma('foreign_keys = OFF');
|
|
21
|
+
console.log('Foreign key constraints temporarily disabled');
|
|
22
|
+
|
|
23
|
+
console.log('\n1. Analyzing current database state...');
|
|
24
|
+
|
|
25
|
+
// Get all recordings with prefixed IDs
|
|
26
|
+
const prefixedRecordings = db.prepare(`
|
|
27
|
+
SELECT id, session_id, type, timestamp, total_frames
|
|
28
|
+
FROM recordings
|
|
29
|
+
WHERE id LIKE 'frame_session_%'
|
|
30
|
+
`).all();
|
|
31
|
+
|
|
32
|
+
console.log(`Found ${prefixedRecordings.length} recordings with prefixed IDs`);
|
|
33
|
+
|
|
34
|
+
if (prefixedRecordings.length === 0) {
|
|
35
|
+
console.log('No prefixed recordings found. Migration not needed.');
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Show what will be migrated
|
|
40
|
+
console.log('\nRecordings to migrate:');
|
|
41
|
+
for (const record of prefixedRecordings) {
|
|
42
|
+
const baseId = record.id.replace('frame_session_', '');
|
|
43
|
+
console.log(` ${record.id} -> ${baseId} (${record.type}, ${record.total_frames} frames)`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log('\n2. Starting migration transaction...');
|
|
47
|
+
|
|
48
|
+
const migration = db.transaction(() => {
|
|
49
|
+
let migratedCount = 0;
|
|
50
|
+
|
|
51
|
+
for (const record of prefixedRecordings) {
|
|
52
|
+
const oldId = record.id;
|
|
53
|
+
const newId = record.id.replace('frame_session_', '');
|
|
54
|
+
|
|
55
|
+
// Check if base ID already exists
|
|
56
|
+
const existingBase = db.prepare('SELECT id FROM recordings WHERE id = ?').get(newId);
|
|
57
|
+
|
|
58
|
+
if (existingBase) {
|
|
59
|
+
console.log(` WARNING: Base ID ${newId} already exists. Checking frame counts...`);
|
|
60
|
+
|
|
61
|
+
// Count frames for both records
|
|
62
|
+
const oldFrameCount = db.prepare('SELECT COUNT(*) as count FROM frames WHERE recording_id = ?').get(oldId)?.count || 0;
|
|
63
|
+
const newFrameCount = db.prepare('SELECT COUNT(*) as count FROM frames WHERE recording_id = ?').get(newId)?.count || 0;
|
|
64
|
+
|
|
65
|
+
console.log(` Old record frames: ${oldFrameCount}, New record frames: ${newFrameCount}`);
|
|
66
|
+
|
|
67
|
+
if (oldFrameCount > 0 && newFrameCount === 0) {
|
|
68
|
+
// Old record has frames, new doesn't - migrate frames and delete old
|
|
69
|
+
console.log(` Migrating frames from ${oldId} to ${newId}`);
|
|
70
|
+
db.prepare('UPDATE frames SET recording_id = ? WHERE recording_id = ?').run(newId, oldId);
|
|
71
|
+
db.prepare('UPDATE recordings SET total_frames = ? WHERE id = ?').run(oldFrameCount, newId);
|
|
72
|
+
db.prepare('DELETE FROM recordings WHERE id = ?').run(oldId);
|
|
73
|
+
migratedCount++;
|
|
74
|
+
} else if (oldFrameCount === 0) {
|
|
75
|
+
// Old record has no frames - just delete it
|
|
76
|
+
console.log(` Deleting empty old record ${oldId}`);
|
|
77
|
+
db.prepare('DELETE FROM recordings WHERE id = ?').run(oldId);
|
|
78
|
+
migratedCount++;
|
|
79
|
+
} else {
|
|
80
|
+
// Both have frames - manual resolution needed
|
|
81
|
+
console.log(` ERROR: Both records have frames. Manual resolution needed for ${oldId} vs ${newId}`);
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
// Simple case - just update the recording ID
|
|
85
|
+
console.log(` Migrating ${oldId} -> ${newId}`);
|
|
86
|
+
|
|
87
|
+
// Update the recording ID
|
|
88
|
+
db.prepare('UPDATE recordings SET id = ? WHERE id = ?').run(newId, oldId);
|
|
89
|
+
|
|
90
|
+
// Update related frames
|
|
91
|
+
db.prepare('UPDATE frames SET recording_id = ? WHERE recording_id = ?').run(newId, oldId);
|
|
92
|
+
|
|
93
|
+
// Update related screen interactions (if any)
|
|
94
|
+
db.prepare('UPDATE screen_interactions SET recording_id = ? WHERE recording_id = ?').run(newId, oldId);
|
|
95
|
+
|
|
96
|
+
migratedCount++;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return migratedCount;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const migratedCount = migration();
|
|
104
|
+
|
|
105
|
+
console.log(`\n3. Migration completed successfully!`);
|
|
106
|
+
console.log(` Migrated ${migratedCount} recordings`);
|
|
107
|
+
|
|
108
|
+
// Verify migration
|
|
109
|
+
console.log('\n4. Verifying migration...');
|
|
110
|
+
const remainingPrefixed = db.prepare(`
|
|
111
|
+
SELECT COUNT(*) as count FROM recordings WHERE id LIKE 'frame_session_%'
|
|
112
|
+
`).get().count;
|
|
113
|
+
|
|
114
|
+
console.log(`Remaining prefixed recordings: ${remainingPrefixed}`);
|
|
115
|
+
|
|
116
|
+
if (remainingPrefixed === 0) {
|
|
117
|
+
console.log('✅ Migration verification passed - no prefixed IDs remain');
|
|
118
|
+
} else {
|
|
119
|
+
console.log('⚠️ Migration verification failed - some prefixed IDs still exist');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Re-enable foreign key constraints
|
|
123
|
+
db.pragma('foreign_keys = ON');
|
|
124
|
+
console.log('Foreign key constraints re-enabled');
|
|
125
|
+
|
|
126
|
+
console.log('\nMigration completed successfully!');
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
// Close database connection
|
|
130
|
+
db.close();
|
|
131
|
+
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.error('Migration failed:', error);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const CopyPlugin = require('copy-webpack-plugin');
|
|
3
|
+
const webpack = require('webpack');
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
mode: 'production',
|
|
7
|
+
entry: {
|
|
8
|
+
background: './chrome-extension/background.js',
|
|
9
|
+
content: './chrome-extension/content.js',
|
|
10
|
+
popup: './chrome-extension/popup.js',
|
|
11
|
+
options: './chrome-extension/options.js'
|
|
12
|
+
},
|
|
13
|
+
output: {
|
|
14
|
+
path: path.resolve(__dirname, 'dist/free'),
|
|
15
|
+
filename: '[name].js',
|
|
16
|
+
clean: true
|
|
17
|
+
},
|
|
18
|
+
plugins: [
|
|
19
|
+
new webpack.DefinePlugin({
|
|
20
|
+
'__TIER__': JSON.stringify('free'),
|
|
21
|
+
'__FEATURES__': JSON.stringify({
|
|
22
|
+
enhancedCapture: false,
|
|
23
|
+
frameEditor: false,
|
|
24
|
+
advancedSettings: false,
|
|
25
|
+
functionTracing: false
|
|
26
|
+
})
|
|
27
|
+
}),
|
|
28
|
+
new CopyPlugin({
|
|
29
|
+
patterns: [
|
|
30
|
+
// Use free manifest
|
|
31
|
+
{
|
|
32
|
+
from: 'chrome-extension/manifest.free.json',
|
|
33
|
+
to: 'manifest.json'
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
// Copy HTML files
|
|
37
|
+
{ from: 'chrome-extension/popup.html', to: 'popup.html' },
|
|
38
|
+
{ from: 'chrome-extension/options.html', to: 'options.html' },
|
|
39
|
+
{ from: 'chrome-extension/offscreen.html', to: 'offscreen.html' },
|
|
40
|
+
|
|
41
|
+
// Copy icons
|
|
42
|
+
{ from: 'chrome-extension/*.png', to: '[name][ext]' },
|
|
43
|
+
|
|
44
|
+
// Copy CSS
|
|
45
|
+
{ from: 'chrome-extension/*.css', to: '[name][ext]' },
|
|
46
|
+
|
|
47
|
+
// Copy third-party libraries (as-is, no bundling)
|
|
48
|
+
{ from: 'chrome-extension/pako.min.js', to: 'pako.min.js' },
|
|
49
|
+
{ from: 'chrome-extension/web-vitals.iife.js', to: 'web-vitals.iife.js' },
|
|
50
|
+
|
|
51
|
+
// Copy supporting modules (these are imported by bundled scripts)
|
|
52
|
+
{ from: 'chrome-extension/pii-redactor.js', to: 'pii-redactor.js' },
|
|
53
|
+
{ from: 'chrome-extension/data-buffer.js', to: 'data-buffer.js' },
|
|
54
|
+
{ from: 'chrome-extension/performance-monitor.js', to: 'performance-monitor.js' },
|
|
55
|
+
{ from: 'chrome-extension/dom-tracker.js', to: 'dom-tracker.js' },
|
|
56
|
+
{ from: 'chrome-extension/network-tracker.js', to: 'network-tracker.js' },
|
|
57
|
+
{ from: 'chrome-extension/upload-manager.js', to: 'upload-manager.js' },
|
|
58
|
+
{ from: 'chrome-extension/extension-config.js', to: 'extension-config.js' },
|
|
59
|
+
{ from: 'chrome-extension/logger.js', to: 'logger.js' },
|
|
60
|
+
{ from: 'chrome-extension/license-helper.js', to: 'license-helper.js' },
|
|
61
|
+
{ from: 'chrome-extension/firebase-client.js', to: 'firebase-client.js' },
|
|
62
|
+
{ from: 'chrome-extension/firebase-config.js', to: 'firebase-config.js' },
|
|
63
|
+
{ from: 'chrome-extension/frame-capture.js', to: 'frame-capture.js' },
|
|
64
|
+
{ from: 'chrome-extension/chrome-session-manager.js', to: 'chrome-session-manager.js' },
|
|
65
|
+
|
|
66
|
+
// Copy README
|
|
67
|
+
{ from: 'chrome-extension/README.md', to: 'README.md' }
|
|
68
|
+
]
|
|
69
|
+
})
|
|
70
|
+
],
|
|
71
|
+
optimization: {
|
|
72
|
+
minimize: false // Don't minimize for easier debugging
|
|
73
|
+
}
|
|
74
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const CopyPlugin = require('copy-webpack-plugin');
|
|
3
|
+
const webpack = require('webpack');
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
mode: 'production',
|
|
7
|
+
entry: {
|
|
8
|
+
background: './chrome-extension/background.js',
|
|
9
|
+
content: './chrome-extension/content.js',
|
|
10
|
+
popup: './chrome-extension/popup.js',
|
|
11
|
+
options: './chrome-extension/options.js',
|
|
12
|
+
'pro/frame-editor': './chrome-extension/pro/frame-editor.js'
|
|
13
|
+
},
|
|
14
|
+
output: {
|
|
15
|
+
path: path.resolve(__dirname, 'dist/pro'),
|
|
16
|
+
filename: '[name].js',
|
|
17
|
+
clean: true
|
|
18
|
+
},
|
|
19
|
+
plugins: [
|
|
20
|
+
new webpack.DefinePlugin({
|
|
21
|
+
'__TIER__': JSON.stringify('pro'),
|
|
22
|
+
'__FEATURES__': JSON.stringify({
|
|
23
|
+
enhancedCapture: true,
|
|
24
|
+
frameEditor: true,
|
|
25
|
+
advancedSettings: true,
|
|
26
|
+
functionTracing: true
|
|
27
|
+
})
|
|
28
|
+
}),
|
|
29
|
+
new CopyPlugin({
|
|
30
|
+
patterns: [
|
|
31
|
+
// Use pro manifest
|
|
32
|
+
{
|
|
33
|
+
from: 'chrome-extension/manifest.pro.json',
|
|
34
|
+
to: 'manifest.json'
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
// Copy HTML files
|
|
38
|
+
{ from: 'chrome-extension/popup.html', to: 'popup.html' },
|
|
39
|
+
{ from: 'chrome-extension/options.html', to: 'options.html' },
|
|
40
|
+
{ from: 'chrome-extension/offscreen.html', to: 'offscreen.html' },
|
|
41
|
+
{ from: 'chrome-extension/pro/frame-editor.html', to: 'pro/frame-editor.html' },
|
|
42
|
+
|
|
43
|
+
// Copy icons
|
|
44
|
+
{ from: 'chrome-extension/*.png', to: '[name][ext]' },
|
|
45
|
+
|
|
46
|
+
// Copy CSS
|
|
47
|
+
{ from: 'chrome-extension/*.css', to: '[name][ext]' },
|
|
48
|
+
|
|
49
|
+
// Copy third-party libraries (as-is, no bundling)
|
|
50
|
+
{ from: 'chrome-extension/pako.min.js', to: 'pako.min.js' },
|
|
51
|
+
{ from: 'chrome-extension/web-vitals.iife.js', to: 'web-vitals.iife.js' },
|
|
52
|
+
|
|
53
|
+
// Copy supporting modules (these are imported by bundled scripts)
|
|
54
|
+
{ from: 'chrome-extension/pii-redactor.js', to: 'pii-redactor.js' },
|
|
55
|
+
{ from: 'chrome-extension/data-buffer.js', to: 'data-buffer.js' },
|
|
56
|
+
{ from: 'chrome-extension/performance-monitor.js', to: 'performance-monitor.js' },
|
|
57
|
+
{ from: 'chrome-extension/dom-tracker.js', to: 'dom-tracker.js' },
|
|
58
|
+
{ from: 'chrome-extension/network-tracker.js', to: 'network-tracker.js' },
|
|
59
|
+
{ from: 'chrome-extension/upload-manager.js', to: 'upload-manager.js' },
|
|
60
|
+
{ from: 'chrome-extension/extension-config.js', to: 'extension-config.js' },
|
|
61
|
+
{ from: 'chrome-extension/logger.js', to: 'logger.js' },
|
|
62
|
+
{ from: 'chrome-extension/license-helper.js', to: 'license-helper.js' },
|
|
63
|
+
{ from: 'chrome-extension/firebase-client.js', to: 'firebase-client.js' },
|
|
64
|
+
{ from: 'chrome-extension/firebase-config.js', to: 'firebase-config.js' },
|
|
65
|
+
{ from: 'chrome-extension/frame-capture.js', to: 'frame-capture.js' },
|
|
66
|
+
{ from: 'chrome-extension/chrome-session-manager.js', to: 'chrome-session-manager.js' },
|
|
67
|
+
|
|
68
|
+
// Copy Pro features
|
|
69
|
+
{ from: 'chrome-extension/pro/function-tracker.js', to: 'pro/function-tracker.js' },
|
|
70
|
+
{ from: 'chrome-extension/pro/enhanced-capture.js', to: 'pro/enhanced-capture.js' },
|
|
71
|
+
|
|
72
|
+
// Copy README
|
|
73
|
+
{ from: 'chrome-extension/README.md', to: 'README.md' }
|
|
74
|
+
]
|
|
75
|
+
})
|
|
76
|
+
],
|
|
77
|
+
optimization: {
|
|
78
|
+
minimize: false // Don't minimize for easier debugging
|
|
79
|
+
}
|
|
80
|
+
};
|
package/src/port-discovery.js
CHANGED
|
@@ -1,110 +1,49 @@
|
|
|
1
1
|
// Port discovery utilities for Chrome Debug
|
|
2
2
|
// This helps Chrome extension and MCP clients find the running HTTP server
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// Uses a single .chromedebug-port file that gets overwritten by each server startup.
|
|
5
|
+
// The last-started server's port is what gets discovered.
|
|
6
|
+
//
|
|
7
|
+
// Note: Session isolation is handled by unified-session-manager.js, which uses
|
|
8
|
+
// atomic file-based locks in /tmp/chromedebug-sessions/port-locks/ to ensure
|
|
9
|
+
// each Claude session gets a unique port. This port file is just a hint for
|
|
10
|
+
// external discovery and is not used for session isolation.
|
|
7
11
|
|
|
8
12
|
import fs from 'fs';
|
|
9
13
|
import path from 'path';
|
|
10
14
|
import { fileURLToPath } from 'url';
|
|
11
|
-
import crypto from 'crypto';
|
|
12
15
|
|
|
13
16
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
14
17
|
|
|
15
|
-
// Session management for port discovery
|
|
16
|
-
let currentSessionId = null;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Get or generate a session ID for port discovery
|
|
20
|
-
*/
|
|
21
|
-
function getSessionId() {
|
|
22
|
-
if (!currentSessionId) {
|
|
23
|
-
currentSessionId = crypto.randomBytes(8).toString('hex');
|
|
24
|
-
}
|
|
25
|
-
return currentSessionId;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Set a custom session ID for port discovery
|
|
30
|
-
*/
|
|
31
|
-
export function setPortDiscoverySessionId(sessionId) {
|
|
32
|
-
if (typeof sessionId === 'string' && sessionId.match(/^[a-zA-Z0-9_-]+$/)) {
|
|
33
|
-
currentSessionId = sessionId;
|
|
34
|
-
console.log(`Port discovery session ID set to: ${sessionId}`);
|
|
35
|
-
} else {
|
|
36
|
-
throw new Error('Session ID must be alphanumeric with dashes/underscores only');
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
18
|
/**
|
|
41
|
-
* Get the
|
|
19
|
+
* Get the port file path
|
|
42
20
|
*/
|
|
43
21
|
function getPortFilePath() {
|
|
44
|
-
const sessionId = getSessionId();
|
|
45
|
-
return path.join(__dirname, `../.chromedebug-port-${sessionId}`);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Get the legacy port file path (for backward compatibility)
|
|
50
|
-
*/
|
|
51
|
-
function getLegacyPortFilePath() {
|
|
52
22
|
return path.join(__dirname, '../.chromedebug-port');
|
|
53
23
|
}
|
|
54
24
|
|
|
55
25
|
/**
|
|
56
|
-
* Write the current port to
|
|
26
|
+
* Write the current port to file for discovery
|
|
27
|
+
* @param {number} port - The port number to write
|
|
57
28
|
*/
|
|
58
|
-
export function writePortFile(port
|
|
59
|
-
const { sessionId, writeLegacy = true } = options;
|
|
60
|
-
|
|
61
|
-
// Set session ID if provided
|
|
62
|
-
if (sessionId) {
|
|
63
|
-
setPortDiscoverySessionId(sessionId);
|
|
64
|
-
}
|
|
65
|
-
|
|
29
|
+
export function writePortFile(port) {
|
|
66
30
|
const portFilePath = getPortFilePath();
|
|
67
|
-
const legacyPortFilePath = getLegacyPortFilePath();
|
|
68
31
|
|
|
69
32
|
try {
|
|
70
|
-
// Write to session-specific file
|
|
71
33
|
fs.writeFileSync(portFilePath, port.toString());
|
|
72
|
-
console.log(`Port ${port} written to ${portFilePath}
|
|
73
|
-
|
|
74
|
-
// Optionally write to legacy file for backward compatibility
|
|
75
|
-
if (writeLegacy) {
|
|
76
|
-
try {
|
|
77
|
-
fs.writeFileSync(legacyPortFilePath, port.toString());
|
|
78
|
-
} catch (legacyError) {
|
|
79
|
-
console.warn(`Could not write legacy port file: ${legacyError.message}`);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
34
|
+
console.log(`Port ${port} written to ${portFilePath}`);
|
|
82
35
|
} catch (error) {
|
|
83
36
|
console.error('Failed to write port file:', error);
|
|
84
37
|
}
|
|
85
38
|
}
|
|
86
39
|
|
|
87
40
|
/**
|
|
88
|
-
* Read the port from
|
|
41
|
+
* Read the port from file
|
|
42
|
+
* @returns {number|null} The port number, or null if not found
|
|
89
43
|
*/
|
|
90
|
-
export function readPortFile(
|
|
91
|
-
// If session ID provided, use that specific session
|
|
92
|
-
if (sessionId) {
|
|
93
|
-
try {
|
|
94
|
-
const sessionPortFile = path.join(__dirname, `../.chromedebug-port-${sessionId}`);
|
|
95
|
-
if (fs.existsSync(sessionPortFile)) {
|
|
96
|
-
const port = parseInt(fs.readFileSync(sessionPortFile, 'utf8').trim());
|
|
97
|
-
if (!isNaN(port)) {
|
|
98
|
-
return port;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
} catch (error) {
|
|
102
|
-
console.error(`Failed to read session port file for ${sessionId}:`, error);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Try current session file
|
|
44
|
+
export function readPortFile() {
|
|
107
45
|
const portFilePath = getPortFilePath();
|
|
46
|
+
|
|
108
47
|
try {
|
|
109
48
|
if (fs.existsSync(portFilePath)) {
|
|
110
49
|
const port = parseInt(fs.readFileSync(portFilePath, 'utf8').trim());
|
|
@@ -113,63 +52,37 @@ export function readPortFile(sessionId = null) {
|
|
|
113
52
|
}
|
|
114
53
|
}
|
|
115
54
|
} catch (error) {
|
|
116
|
-
console.error('Failed to read
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Fall back to legacy file
|
|
120
|
-
const legacyPortFilePath = getLegacyPortFilePath();
|
|
121
|
-
try {
|
|
122
|
-
if (fs.existsSync(legacyPortFilePath)) {
|
|
123
|
-
const port = parseInt(fs.readFileSync(legacyPortFilePath, 'utf8').trim());
|
|
124
|
-
if (!isNaN(port)) {
|
|
125
|
-
return port;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
} catch (error) {
|
|
129
|
-
console.error('Failed to read legacy port file:', error);
|
|
55
|
+
console.error('Failed to read port file:', error);
|
|
130
56
|
}
|
|
131
57
|
|
|
132
58
|
return null;
|
|
133
59
|
}
|
|
134
60
|
|
|
135
61
|
/**
|
|
136
|
-
* Remove the port
|
|
62
|
+
* Remove the port file on shutdown
|
|
137
63
|
*/
|
|
138
64
|
export function removePortFile() {
|
|
139
65
|
const portFilePath = getPortFilePath();
|
|
140
|
-
const legacyPortFilePath = getLegacyPortFilePath();
|
|
141
66
|
|
|
142
67
|
try {
|
|
143
68
|
if (fs.existsSync(portFilePath)) {
|
|
144
69
|
fs.unlinkSync(portFilePath);
|
|
145
|
-
console.log(`Removed
|
|
70
|
+
console.log(`Removed port file: ${portFilePath}`);
|
|
146
71
|
}
|
|
147
72
|
} catch (error) {
|
|
148
|
-
console.error('Failed to remove
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Only remove legacy file if no other sessions are using it
|
|
152
|
-
try {
|
|
153
|
-
if (fs.existsSync(legacyPortFilePath)) {
|
|
154
|
-
// Check if there are other session port files
|
|
155
|
-
const portFiles = fs.readdirSync(__dirname + '/..')
|
|
156
|
-
.filter(f => f.startsWith('.chromedebug-port-') && f !== `.chromedebug-port-${getSessionId()}`);
|
|
157
|
-
|
|
158
|
-
if (portFiles.length === 0) {
|
|
159
|
-
fs.unlinkSync(legacyPortFilePath);
|
|
160
|
-
console.log('Removed legacy port file (no other sessions)');
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
} catch (error) {
|
|
164
|
-
console.error('Failed to remove legacy port file:', error);
|
|
73
|
+
console.error('Failed to remove port file:', error);
|
|
165
74
|
}
|
|
166
75
|
}
|
|
167
76
|
|
|
168
77
|
/**
|
|
169
78
|
* Try to discover the Chrome Debug server by checking multiple ports
|
|
79
|
+
* @param {number[]} preferredPorts - Array of ports to check
|
|
80
|
+
* @param {Object} options - Discovery options
|
|
81
|
+
* @param {boolean} options.extendedRange - Whether to extend the port range
|
|
82
|
+
* @returns {Promise<number|null>} The discovered port, or null if not found
|
|
170
83
|
*/
|
|
171
84
|
export async function discoverServer(preferredPorts = [3000, 3001, 3002, 3028], options = {}) {
|
|
172
|
-
const {
|
|
85
|
+
const { extendedRange = true } = options;
|
|
173
86
|
|
|
174
87
|
// Extend port range to reduce conflicts in multi-session environments
|
|
175
88
|
if (extendedRange) {
|
|
@@ -183,13 +96,13 @@ export async function discoverServer(preferredPorts = [3000, 3001, 3002, 3028],
|
|
|
183
96
|
];
|
|
184
97
|
}
|
|
185
98
|
|
|
186
|
-
// First check
|
|
187
|
-
const filePort = readPortFile(
|
|
99
|
+
// First check port file
|
|
100
|
+
const filePort = readPortFile();
|
|
188
101
|
if (filePort) {
|
|
189
102
|
preferredPorts.unshift(filePort);
|
|
190
103
|
}
|
|
191
104
|
|
|
192
|
-
// Try each port
|
|
105
|
+
// Try each port
|
|
193
106
|
for (let i = 0; i < preferredPorts.length; i++) {
|
|
194
107
|
const port = preferredPorts[i];
|
|
195
108
|
try {
|
|
@@ -216,43 +129,3 @@ export async function discoverServer(preferredPorts = [3000, 3001, 3002, 3028],
|
|
|
216
129
|
|
|
217
130
|
return null;
|
|
218
131
|
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Get session information for port discovery
|
|
222
|
-
*/
|
|
223
|
-
export function getPortDiscoveryInfo() {
|
|
224
|
-
return {
|
|
225
|
-
sessionId: getSessionId(),
|
|
226
|
-
portFile: getPortFilePath(),
|
|
227
|
-
legacyPortFile: getLegacyPortFilePath(),
|
|
228
|
-
currentPort: readPortFile()
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Find all active ChromeDebug sessions by scanning port files
|
|
234
|
-
*/
|
|
235
|
-
export function findActiveSessions() {
|
|
236
|
-
const sessions = [];
|
|
237
|
-
|
|
238
|
-
try {
|
|
239
|
-
const files = fs.readdirSync(__dirname + '/..');
|
|
240
|
-
const portFiles = files.filter(f => f.startsWith('.chromedebug-port-'));
|
|
241
|
-
|
|
242
|
-
for (const file of portFiles) {
|
|
243
|
-
const sessionId = file.replace('.chromedebug-port-', '');
|
|
244
|
-
try {
|
|
245
|
-
const port = parseInt(fs.readFileSync(path.join(__dirname, '..', file), 'utf8').trim());
|
|
246
|
-
if (!isNaN(port)) {
|
|
247
|
-
sessions.push({ sessionId, port, portFile: file });
|
|
248
|
-
}
|
|
249
|
-
} catch (error) {
|
|
250
|
-
console.warn(`Could not read port file ${file}: ${error.message}`);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
} catch (error) {
|
|
254
|
-
console.error('Failed to scan for active sessions:', error);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
return sessions;
|
|
258
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# Chrome Debug Assistant Extension
|
|
2
|
-
|
|
3
|
-
This Chrome extension enables visual element selection for the Chrome Debug MCP server.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
1. Open Chrome and navigate to `chrome://extensions/`
|
|
8
|
-
2. Enable "Developer mode" (toggle in the top right)
|
|
9
|
-
3. Click "Load unpacked"
|
|
10
|
-
4. Select the `chrome-extension` directory
|
|
11
|
-
|
|
12
|
-
## Usage
|
|
13
|
-
|
|
14
|
-
1. Ensure the Chrome Debug MCP server is running (`npm start`)
|
|
15
|
-
2. Navigate to any webpage
|
|
16
|
-
3. Click on any element to select it
|
|
17
|
-
4. The element will be:
|
|
18
|
-
- Highlighted with a pink outline
|
|
19
|
-
- Sent to Chrome Debug server
|
|
20
|
-
- Available for modification via Claude MCP tools
|
|
21
|
-
|
|
22
|
-
## How it Works
|
|
23
|
-
|
|
24
|
-
1. Click any element on the page
|
|
25
|
-
2. The extension sends the element's selector to Chrome Debug
|
|
26
|
-
3. Use Claude in your terminal to modify the selected element:
|
|
27
|
-
```
|
|
28
|
-
"Show me what element is currently selected"
|
|
29
|
-
"Make the selected element blue"
|
|
30
|
-
"Apply CSS: padding: 20px !important; border-radius: 8px !important"
|
|
31
|
-
"Execute JavaScript to get the element's text content"
|
|
32
|
-
"Clear the selected element"
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## Features
|
|
36
|
-
|
|
37
|
-
- Visual element selection with hover highlighting
|
|
38
|
-
- Automatic server detection (scans ports 3000-3025)
|
|
39
|
-
- Pink outline shows currently selected element
|
|
40
|
-
- Server connection status indicator
|
|
41
|
-
- One-click element selection
|