browser-ipc-cdp 1.8.3 → 2.0.0

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/brave_ipc.py CHANGED
@@ -539,11 +539,18 @@ def launch_browser_ipc(
539
539
  print()
540
540
  return result
541
541
  else:
542
- # Navegador corriendo pero SIN CDP → hay que reiniciar
543
- print(f" CDP no detectado. Reiniciando {browser_name} con CDP...")
542
+ # Navegador corriendo pero SIN CDP
544
543
  if kill_existing:
544
+ print(f" CDP no detectado. Reiniciando {browser_name} con CDP...")
545
545
  kill_browser(browser_exe)
546
546
  time.sleep(2)
547
+ else:
548
+ # --no-kill activo. No podemos reusar user-data-dir real (lock).
549
+ # Forzar perfil separado para evitar conflicto.
550
+ print(f" CDP no detectado. --no-kill activo: lanzando 2da")
551
+ print(f" instancia en perfil separado (no toca tu sesion).")
552
+ clean = True
553
+ USER_DATA_DIR = CLEAN_USER_DATA
547
554
  elif kill_existing:
548
555
  print(f" {browser_name} no esta corriendo.")
549
556
 
@@ -1,111 +1,77 @@
1
1
  /**
2
- * Brave MCP Dynamic Launcher
2
+ * Brave MCP Launcher (refactored)
3
3
  *
4
- * Resuelve dinamicamente el puerto CDP (Brave/Chrome/Edge) y lanza
5
- * chrome-devtools-mcp apuntando al puerto correcto. Si CDP no responde,
6
- * lanza brave_ipc.py automaticamente para iniciar el navegador.
4
+ * Resuelve el puerto CDP de Brave/Chrome/Edge y arranca chrome-devtools-mcp.
7
5
  *
8
- * Uso en .mcp.json:
9
- * {
10
- * "mcpServers": {
11
- * "brave": {
12
- * "command": "node",
13
- * "args": ["C:\\Users\\NyGsoft\\Desktop\\ipc\\brave_mcp_launcher.js"]
14
- * }
15
- * }
16
- * }
6
+ * Estrategia (rápida → lenta):
7
+ * 1. Leer DevToolsActivePort de cada User Data dir conocido (instantáneo, autoritativo).
8
+ * 2. Auto-discovery via tasklist + netstat (fallback si no hay archivo o stale).
9
+ * 3. Auto-launch via brave_ipc.py --no-kill (último recurso).
17
10
  *
18
- * Flujo:
19
- * 1. Lee cdp_info.json -> puerto candidato
20
- * 2. Verifica CDP (/json/version) en hostIP:puerto
21
- * 3. Si responde -> lanza chrome-devtools-mcp con ese URL
22
- * 4. Si stale -> auto-discovery via tasklist+netstat de procesos Chromium
23
- * 5. Si encuentra port vivo -> reescribe cdp_info.json y conecta
24
- * 6. Ultimo recurso -> brave_ipc.py --no-kill (auto-launch)
11
+ * Usa binario global de chrome-devtools-mcp (sin npx cold cache).
25
12
  */
13
+
26
14
  const { execSync, spawn, spawnSync } = require('child_process');
27
15
  const fs = require('fs');
28
16
  const path = require('path');
29
17
  const http = require('http');
30
18
 
31
- const CDP_INFO = path.join(__dirname, 'cdp_info.json');
32
- const HOME_CDP_INFO = path.join(process.env.USERPROFILE || process.env.HOME || '.', 'cdp_info.json');
33
- const BRAVE_IPC_PY = path.join(__dirname, 'brave_ipc.py');
19
+ const HERE = __dirname;
20
+ const CDP_INFO = path.join(HERE, 'cdp_info.json');
21
+ const BRAVE_IPC_PY = path.join(HERE, 'brave_ipc.py');
34
22
 
35
23
  const IS_WIN = process.platform === 'win32';
36
24
  let IS_WSL = false;
37
25
  try {
38
26
  const v = fs.readFileSync('/proc/version', 'utf-8').toLowerCase();
39
27
  IS_WSL = v.includes('microsoft') || v.includes('wsl');
40
- } catch (e) {}
41
-
42
- function logErr(msg) {
43
- process.stderr.write(`[brave-mcp] ${msg}\n`);
44
- }
28
+ } catch {}
45
29
 
46
- function readCdpInfo() {
47
- for (const p of [CDP_INFO, HOME_CDP_INFO]) {
48
- if (fs.existsSync(p)) {
49
- try { return JSON.parse(fs.readFileSync(p, 'utf-8')); } catch (e) {}
50
- }
51
- }
52
- return null;
53
- }
30
+ const HOME = process.env.USERPROFILE || process.env.HOME || '';
31
+ const LOCALAPPDATA = process.env.LOCALAPPDATA || path.join(HOME, 'AppData', 'Local');
54
32
 
55
- function getHostCandidates() {
56
- if (IS_WIN) return ['127.0.0.1'];
33
+ const USER_DATA_DIRS = [
34
+ { name: 'brave', path: path.join(LOCALAPPDATA, 'BraveSoftware', 'Brave-Browser', 'User Data') },
35
+ { name: 'chrome', path: path.join(LOCALAPPDATA, 'Google', 'Chrome', 'User Data') },
36
+ { name: 'edge', path: path.join(LOCALAPPDATA, 'Microsoft', 'Edge', 'User Data') },
37
+ { name: 'chromium', path: path.join(LOCALAPPDATA, 'Chromium', 'User Data') },
38
+ ];
57
39
 
58
- const seen = new Set();
59
- const candidates = [];
60
- const add = (ip) => { if (ip && !seen.has(ip)) { seen.add(ip); candidates.push(ip); } };
61
-
62
- // 1. Gateway IP via /proc/net/route — IP real del host Windows en WSL2 NAT
40
+ function resolveChromeDevtoolsMcpBin() {
63
41
  try {
64
- const route = fs.readFileSync('/proc/net/route', 'utf-8');
65
- for (const line of route.split('\n').slice(1)) {
66
- const parts = line.split(/\s+/);
67
- if (parts[1] === '00000000' && parts[2] && parts[2] !== '00000000') {
68
- const hex = parts[2];
69
- add([
70
- parseInt(hex.slice(6, 8), 16),
71
- parseInt(hex.slice(4, 6), 16),
72
- parseInt(hex.slice(2, 4), 16),
73
- parseInt(hex.slice(0, 2), 16),
74
- ].join('.'));
75
- break;
76
- }
77
- }
78
- } catch (e) {}
79
-
80
- // 2. Localhost (WSL2 mirrored networking mode)
81
- add('127.0.0.1');
82
-
83
- // 3. Nameservers de /etc/resolv.conf (último recurso)
84
- try {
85
- const resolv = fs.readFileSync('/etc/resolv.conf', 'utf-8');
86
- const re = /nameserver\s+(\d+\.\d+\.\d+\.\d+)/g;
87
- let m;
88
- while ((m = re.exec(resolv))) add(m[1]);
89
- } catch (e) {}
42
+ return require.resolve('chrome-devtools-mcp/build/src/bin/chrome-devtools-mcp.js');
43
+ } catch {}
44
+ const candidates = [
45
+ path.join('C:', 'nvm4w', 'nodejs', 'node_modules', 'chrome-devtools-mcp', 'build', 'src', 'bin', 'chrome-devtools-mcp.js'),
46
+ path.join(process.env.APPDATA || '', 'npm', 'node_modules', 'chrome-devtools-mcp', 'build', 'src', 'bin', 'chrome-devtools-mcp.js'),
47
+ path.join('/usr', 'local', 'lib', 'node_modules', 'chrome-devtools-mcp', 'build', 'src', 'bin', 'chrome-devtools-mcp.js'),
48
+ ];
49
+ for (const p of candidates) { if (p && fs.existsSync(p)) return p; }
50
+ return null;
51
+ }
52
+ const CHROME_DEVTOOLS_MCP_BIN = resolveChromeDevtoolsMcpBin();
90
53
 
91
- return candidates;
54
+ function log(msg) {
55
+ process.stderr.write(`[brave-mcp] ${msg}\n`);
92
56
  }
93
57
 
94
- async function tryCandidates(port, candidates, timeoutMs) {
95
- for (const host of candidates) {
96
- const url = `http://${host}:${port}`;
97
- if (await testCdp(url, timeoutMs)) return url;
98
- }
99
- return null;
58
+ function readJsonSafe(p) {
59
+ try { return JSON.parse(fs.readFileSync(p, 'utf-8')); } catch { return null; }
100
60
  }
101
61
 
102
- function testCdp(url, timeoutMs = 3000) {
62
+ function testCdp(url, timeoutMs = 1500) {
103
63
  return new Promise((resolve) => {
104
64
  const req = http.get(`${url}/json/version`, { timeout: timeoutMs }, (res) => {
65
+ if (res.statusCode !== 200) { res.resume(); return resolve(null); }
105
66
  let data = '';
106
67
  res.on('data', c => data += c);
107
68
  res.on('end', () => {
108
- try { resolve(JSON.parse(data)); } catch (e) { resolve(null); }
69
+ try {
70
+ const json = JSON.parse(data);
71
+ if (json && typeof json.webSocketDebuggerUrl === 'string' && json.webSocketDebuggerUrl) {
72
+ resolve(json);
73
+ } else { resolve(null); }
74
+ } catch { resolve(null); }
109
75
  });
110
76
  });
111
77
  req.on('error', () => resolve(null));
@@ -113,16 +79,33 @@ function testCdp(url, timeoutMs = 3000) {
113
79
  });
114
80
  }
115
81
 
116
- /**
117
- * Descubre el puerto CDP real de cualquier proceso Chromium activo.
118
- * Usa tasklist (PIDs por imagen) + netstat (LISTENING por PID) y
119
- * prueba /json/version en cada candidato.
120
- *
121
- * Retorna { port, version } si encuentra uno vivo, o null.
122
- */
123
- async function discoverBrowserCdp() {
124
- if (!IS_WIN) return null;
82
+ function readDevToolsActivePort(userDataDir) {
83
+ const file = path.join(userDataDir, 'DevToolsActivePort');
84
+ if (!fs.existsSync(file)) return null;
85
+ try {
86
+ const txt = fs.readFileSync(file, 'utf-8');
87
+ const lines = txt.split(/\r?\n/);
88
+ const port = parseInt(lines[0], 10);
89
+ if (Number.isFinite(port) && port > 0) return port;
90
+ } catch {}
91
+ return null;
92
+ }
93
+
94
+ async function tryDevToolsActivePort() {
95
+ for (const ud of USER_DATA_DIRS) {
96
+ const port = readDevToolsActivePort(ud.path);
97
+ if (!port) continue;
98
+ const version = await testCdp(`http://127.0.0.1:${port}`, 1500);
99
+ if (version) {
100
+ log(`DevToolsActivePort hit: ${ud.name} -> :${port}`);
101
+ return { port, version };
102
+ }
103
+ }
104
+ return null;
105
+ }
125
106
 
107
+ async function discoverViaNetstat() {
108
+ if (!IS_WIN) return null;
126
109
  const images = ['brave.exe', 'chrome.exe', 'msedge.exe', 'chromium.exe'];
127
110
  const pids = new Set();
128
111
 
@@ -139,21 +122,21 @@ async function discoverBrowserCdp() {
139
122
  if (Number.isFinite(pid) && pid > 0) pids.add(pid);
140
123
  }
141
124
  }
142
- } catch (e) {}
125
+ } catch {}
143
126
  }
144
127
 
145
128
  if (pids.size === 0) {
146
- logErr('Auto-discovery: ningun proceso Chromium corriendo');
129
+ log('Discovery: no Chromium processes');
147
130
  return null;
148
131
  }
149
132
 
150
- let netstatOut = '';
133
+ let netstatOut;
151
134
  try {
152
135
  netstatOut = execSync('netstat -ano', {
153
136
  encoding: 'utf-8', timeout: 20000, maxBuffer: 64 * 1024 * 1024, stdio: ['ignore', 'pipe', 'ignore'],
154
137
  });
155
138
  } catch (e) {
156
- logErr(`Auto-discovery netstat fallo: ${e.message}`);
139
+ log(`Discovery netstat failed: ${e.message}`);
157
140
  return null;
158
141
  }
159
142
 
@@ -169,120 +152,96 @@ async function discoverBrowserCdp() {
169
152
  if (port > 1024 && port < 65536) ports.add(port);
170
153
  }
171
154
 
172
- logErr(`Auto-discovery: ${ports.size} ports candidatos de ${pids.size} procesos`);
155
+ log(`Discovery: ${ports.size} candidate ports across ${pids.size} processes`);
173
156
 
174
157
  for (const port of ports) {
175
158
  const version = await testCdp(`http://127.0.0.1:${port}`, 1500);
176
159
  if (version) {
177
- logErr(`Auto-discovery: CDP vivo en puerto ${port}`);
160
+ log(`Discovery hit: :${port}`);
178
161
  return { port, version };
179
162
  }
180
163
  }
181
-
182
164
  return null;
183
165
  }
184
166
 
185
- function rewriteCdpInfo(port, version) {
186
- const ws = (version && version.webSocketDebuggerUrl) || '';
187
- const browser = (version && version.Browser) || 'Unknown';
167
+ function writeCdpInfo(port, version, mode) {
188
168
  const data = {
189
169
  DEBUG_PORT: port,
190
- DEBUG_WS: ws,
191
- BROWSER: browser,
170
+ DEBUG_WS: version.webSocketDebuggerUrl || '',
171
+ BROWSER: version.Browser || 'Unknown',
192
172
  CDP_URL: `http://127.0.0.1:${port}`,
193
- MODE: 'AUTO_DISCOVERED',
173
+ MODE: mode,
174
+ UPDATED_AT: new Date().toISOString(),
194
175
  };
195
- for (const p of [HOME_CDP_INFO, CDP_INFO]) {
196
- try { fs.writeFileSync(p, JSON.stringify(data, null, 2), 'utf-8'); } catch (e) {}
176
+ try { fs.writeFileSync(CDP_INFO, JSON.stringify(data, null, 2), 'utf-8'); } catch (e) {
177
+ log(`Write cdp_info failed: ${e.message}`);
197
178
  }
198
179
  }
199
180
 
200
- async function ensureCdpReady() {
201
- const candidates = getHostCandidates();
202
- logErr(`Candidatos host: ${candidates.join(', ')}`);
203
- let info = readCdpInfo();
204
- let port = info && info.DEBUG_PORT;
205
-
206
- // Intento 1: probar candidatos con el puerto en cdp_info.json
207
- if (port) {
208
- const url = await tryCandidates(port, candidates, 3000);
209
- if (url) {
210
- logErr(`CDP activo en ${url}`);
211
- return url;
212
- }
213
- logErr(`Puerto ${port} de cdp_info.json stale. Auto-discovery...`);
214
- }
215
-
216
- // Intento 2: auto-discovery via tasklist+netstat (no reinicia browser)
217
- const discovered = await discoverBrowserCdp();
218
- if (discovered) {
219
- rewriteCdpInfo(discovered.port, discovered.version);
220
- const url = await tryCandidates(discovered.port, candidates, 3000);
221
- if (url) {
222
- logErr(`CDP descubierto y cdp_info.json actualizado: ${url}`);
223
- return url;
224
- }
225
- }
226
-
227
- // Intento 3: auto-launch via brave_ipc.py
228
- logErr('CDP no responde. Auto-lanzando Brave via brave_ipc.py...');
181
+ async function autoLaunch() {
229
182
  if (!fs.existsSync(BRAVE_IPC_PY)) {
230
- logErr(`brave_ipc.py no encontrado en ${BRAVE_IPC_PY}`);
183
+ log(`brave_ipc.py not found at ${BRAVE_IPC_PY}`);
231
184
  return null;
232
185
  }
233
-
234
- const pythonCmd = IS_WSL ? 'python3' : 'python';
235
- const launchCmd = IS_WSL
186
+ log('Auto-launch via brave_ipc.py --no-kill');
187
+ const cmd = IS_WSL
236
188
  ? `cmd.exe /c python "${BRAVE_IPC_PY.replace(/^\/mnt\/([a-z])\//, (_, d) => `${d.toUpperCase()}:\\`).replace(/\//g, '\\\\')}" --no-kill`
237
- : `${pythonCmd} "${BRAVE_IPC_PY}" --no-kill`;
238
-
189
+ : `python "${BRAVE_IPC_PY}" --no-kill`;
239
190
  try {
240
- spawnSync(launchCmd, { shell: true, timeout: 60000, stdio: 'pipe' });
191
+ spawnSync(cmd, { shell: true, timeout: 60000, stdio: 'pipe' });
241
192
  } catch (e) {
242
- logErr(`Auto-launch fallo: ${e.message}`);
193
+ log(`Auto-launch failed: ${e.message}`);
194
+ return null;
243
195
  }
196
+ return await tryDevToolsActivePort();
197
+ }
244
198
 
245
- // Releer y reverificar contra todos los candidatos
246
- info = readCdpInfo();
247
- port = info && info.DEBUG_PORT;
248
- if (port) {
249
- const url = await tryCandidates(port, candidates, 5000);
250
- if (url) {
251
- logErr(`CDP activo despues de auto-launch en ${url}`);
252
- return url;
253
- }
254
- }
199
+ async function ensureCdp() {
200
+ let result = await tryDevToolsActivePort();
201
+ if (result) return result;
255
202
 
256
- logErr('CDP sigue sin responder despues de auto-launch.');
257
- return null;
258
- }
203
+ log('DevToolsActivePort miss. Falling back to netstat discovery.');
204
+ result = await discoverViaNetstat();
205
+ if (result) return result;
259
206
 
260
- async function main() {
261
- const browserUrl = await ensureCdpReady();
207
+ log('No live CDP found. Auto-launching browser.');
208
+ result = await autoLaunch();
209
+ return result;
210
+ }
262
211
 
263
- if (!browserUrl) {
264
- logErr('No se pudo establecer CDP. El MCP intentara conectar de todas formas.');
212
+ function pickRunner() {
213
+ if (CHROME_DEVTOOLS_MCP_BIN && fs.existsSync(CHROME_DEVTOOLS_MCP_BIN)) {
214
+ return { cmd: process.execPath, args: [CHROME_DEVTOOLS_MCP_BIN] };
265
215
  }
216
+ log(`chrome-devtools-mcp bin not resolvable. Falling back to npx (slow first run).`);
217
+ return IS_WIN
218
+ ? { cmd: 'cmd.exe', args: ['/c', 'npx', '-y', 'chrome-devtools-mcp@latest'], shell: true }
219
+ : { cmd: 'npx', args: ['-y', 'chrome-devtools-mcp@latest'], shell: true };
220
+ }
266
221
 
267
- const finalUrl = browserUrl || `http://127.0.0.1:9222`;
268
- logErr(`Lanzando chrome-devtools-mcp -> ${finalUrl}`);
222
+ async function main() {
223
+ const cdp = await ensureCdp();
224
+
225
+ let browserUrl;
226
+ if (cdp) {
227
+ writeCdpInfo(cdp.port, cdp.version, 'RESOLVED');
228
+ browserUrl = `http://127.0.0.1:${cdp.port}`;
229
+ log(`CDP ready: ${browserUrl}`);
230
+ } else {
231
+ browserUrl = 'http://127.0.0.1:9222';
232
+ log(`CDP unresolved. MCP will retry against fallback ${browserUrl}.`);
233
+ }
269
234
 
270
- const child = spawn(
271
- IS_WIN ? 'cmd.exe' : 'npx',
272
- IS_WIN
273
- ? ['/c', 'npx', '-y', 'chrome-devtools-mcp@latest', '--browserUrl', finalUrl]
274
- : ['-y', 'chrome-devtools-mcp@latest', '--browserUrl', finalUrl],
275
- { stdio: ['inherit', 'inherit', 'inherit'], shell: !IS_WIN }
276
- );
235
+ const runner = pickRunner();
236
+ const args = [...runner.args, '--browserUrl', browserUrl];
237
+ log(`Spawn: ${runner.cmd} ${args.join(' ')}`);
277
238
 
278
- child.on('exit', (code) => process.exit(code || 0));
279
- child.on('error', (e) => {
280
- logErr(`spawn error: ${e.message}`);
281
- process.exit(1);
239
+ const child = spawn(runner.cmd, args, {
240
+ stdio: ['inherit', 'inherit', 'inherit'],
241
+ shell: !!runner.shell,
282
242
  });
243
+ child.on('exit', (code) => process.exit(code || 0));
244
+ child.on('error', (e) => { log(`spawn error: ${e.message}`); process.exit(1); });
283
245
  }
284
246
 
285
- main().catch(e => {
286
- logErr(`fatal: ${e.message}`);
287
- process.exit(1);
288
- });
247
+ main().catch(e => { log(`fatal: ${e.message}`); process.exit(1); });
package/package.json CHANGED
@@ -1,9 +1,13 @@
1
1
  {
2
2
  "name": "browser-ipc-cdp",
3
- "version": "1.8.3",
3
+ "version": "2.0.0",
4
4
  "description": "Control remoto de navegadores Chromium (Brave, Chrome, Edge) via IPC + CDP dinamico. Un comando para conectar Claude Code a tu navegador real.",
5
5
  "bin": {
6
- "browser-ipc-cdp": "bin/cli.js"
6
+ "browser-ipc-cdp": "bin/cli.js",
7
+ "browser-ipc-cdp-mcp": "brave_mcp_launcher.js"
8
+ },
9
+ "dependencies": {
10
+ "chrome-devtools-mcp": "^0.25.0"
7
11
  },
8
12
  "keywords": [
9
13
  "cdp",
@@ -29,7 +33,7 @@
29
33
  },
30
34
  "homepage": "https://github.com/alexis14kl/browser-ipc-cdp#readme",
31
35
  "engines": {
32
- "node": ">=14.0.0"
36
+ "node": ">=18.0.0"
33
37
  },
34
38
  "files": [
35
39
  "bin/",