browser-ipc-cdp 1.8.1 → 1.8.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/brave_mcp_launcher.js +101 -4
- package/lib/mcp.js +58 -2
- package/package.json +1 -1
package/brave_mcp_launcher.js
CHANGED
|
@@ -17,10 +17,11 @@
|
|
|
17
17
|
*
|
|
18
18
|
* Flujo:
|
|
19
19
|
* 1. Lee cdp_info.json -> puerto candidato
|
|
20
|
-
* 2. Verifica CDP (
|
|
20
|
+
* 2. Verifica CDP (/json/version) en hostIP:puerto
|
|
21
21
|
* 3. Si responde -> lanza chrome-devtools-mcp con ese URL
|
|
22
|
-
* 4. Si
|
|
23
|
-
* 5.
|
|
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)
|
|
24
25
|
*/
|
|
25
26
|
const { execSync, spawn, spawnSync } = require('child_process');
|
|
26
27
|
const fs = require('fs');
|
|
@@ -112,6 +113,90 @@ function testCdp(url, timeoutMs = 3000) {
|
|
|
112
113
|
});
|
|
113
114
|
}
|
|
114
115
|
|
|
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;
|
|
125
|
+
|
|
126
|
+
const images = ['brave.exe', 'chrome.exe', 'msedge.exe', 'chromium.exe'];
|
|
127
|
+
const pids = new Set();
|
|
128
|
+
|
|
129
|
+
for (const img of images) {
|
|
130
|
+
try {
|
|
131
|
+
const out = execSync(
|
|
132
|
+
`tasklist /FI "IMAGENAME eq ${img}" /FO CSV /NH`,
|
|
133
|
+
{ encoding: 'utf-8', timeout: 8000, maxBuffer: 16 * 1024 * 1024, stdio: ['ignore', 'pipe', 'ignore'] }
|
|
134
|
+
);
|
|
135
|
+
for (const line of out.split('\n')) {
|
|
136
|
+
const parts = line.trim().split('","');
|
|
137
|
+
if (parts.length >= 2) {
|
|
138
|
+
const pid = parseInt(parts[1].replace(/"/g, ''), 10);
|
|
139
|
+
if (Number.isFinite(pid) && pid > 0) pids.add(pid);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} catch (e) {}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (pids.size === 0) {
|
|
146
|
+
logErr('Auto-discovery: ningun proceso Chromium corriendo');
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let netstatOut = '';
|
|
151
|
+
try {
|
|
152
|
+
netstatOut = execSync('netstat -ano', {
|
|
153
|
+
encoding: 'utf-8', timeout: 20000, maxBuffer: 64 * 1024 * 1024, stdio: ['ignore', 'pipe', 'ignore'],
|
|
154
|
+
});
|
|
155
|
+
} catch (e) {
|
|
156
|
+
logErr(`Auto-discovery netstat fallo: ${e.message}`);
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const ports = new Set();
|
|
161
|
+
for (const line of netstatOut.split('\n')) {
|
|
162
|
+
const tokens = line.trim().split(/\s+/);
|
|
163
|
+
if (tokens.length < 5 || tokens[0] !== 'TCP' || !tokens[3].includes('LISTENING')) continue;
|
|
164
|
+
const pid = parseInt(tokens[4], 10);
|
|
165
|
+
if (!pids.has(pid)) continue;
|
|
166
|
+
const m = tokens[1].match(/:(\d+)$/);
|
|
167
|
+
if (!m) continue;
|
|
168
|
+
const port = parseInt(m[1], 10);
|
|
169
|
+
if (port > 1024 && port < 65536) ports.add(port);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
logErr(`Auto-discovery: ${ports.size} ports candidatos de ${pids.size} procesos`);
|
|
173
|
+
|
|
174
|
+
for (const port of ports) {
|
|
175
|
+
const version = await testCdp(`http://127.0.0.1:${port}`, 1500);
|
|
176
|
+
if (version) {
|
|
177
|
+
logErr(`Auto-discovery: CDP vivo en puerto ${port}`);
|
|
178
|
+
return { port, version };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function rewriteCdpInfo(port, version) {
|
|
186
|
+
const ws = (version && version.webSocketDebuggerUrl) || '';
|
|
187
|
+
const browser = (version && version.Browser) || 'Unknown';
|
|
188
|
+
const data = {
|
|
189
|
+
DEBUG_PORT: port,
|
|
190
|
+
DEBUG_WS: ws,
|
|
191
|
+
BROWSER: browser,
|
|
192
|
+
CDP_URL: `http://127.0.0.1:${port}`,
|
|
193
|
+
MODE: 'AUTO_DISCOVERED',
|
|
194
|
+
};
|
|
195
|
+
for (const p of [HOME_CDP_INFO, CDP_INFO]) {
|
|
196
|
+
try { fs.writeFileSync(p, JSON.stringify(data, null, 2), 'utf-8'); } catch (e) {}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
115
200
|
async function ensureCdpReady() {
|
|
116
201
|
const candidates = getHostCandidates();
|
|
117
202
|
logErr(`Candidatos host: ${candidates.join(', ')}`);
|
|
@@ -125,9 +210,21 @@ async function ensureCdpReady() {
|
|
|
125
210
|
logErr(`CDP activo en ${url}`);
|
|
126
211
|
return url;
|
|
127
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
|
+
}
|
|
128
225
|
}
|
|
129
226
|
|
|
130
|
-
// Intento
|
|
227
|
+
// Intento 3: auto-launch via brave_ipc.py
|
|
131
228
|
logErr('CDP no responde. Auto-lanzando Brave via brave_ipc.py...');
|
|
132
229
|
if (!fs.existsSync(BRAVE_IPC_PY)) {
|
|
133
230
|
logErr(`brave_ipc.py no encontrado en ${BRAVE_IPC_PY}`);
|
package/lib/mcp.js
CHANGED
|
@@ -46,6 +46,53 @@ function getWslHostIp() {
|
|
|
46
46
|
return '127.0.0.1';
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
function updateClaudeUserConfig(wrapperPath) {
|
|
50
|
+
// Registra el MCP "brave" en scope user (~/.claude.json top-level mcpServers)
|
|
51
|
+
// para que aparezca en /mcp desde cualquier directorio, sin depender del cwd.
|
|
52
|
+
const braveEntry = {
|
|
53
|
+
type: 'stdio',
|
|
54
|
+
command: 'node',
|
|
55
|
+
args: [wrapperPath],
|
|
56
|
+
env: {},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const candidates = new Set();
|
|
60
|
+
if (process.env.HOME) candidates.add(path.join(process.env.HOME, '.claude.json'));
|
|
61
|
+
if (process.env.USERPROFILE) candidates.add(path.join(process.env.USERPROFILE, '.claude.json'));
|
|
62
|
+
|
|
63
|
+
// Desde WSL: buscar tambien en /mnt/c/Users/*/.claude.json
|
|
64
|
+
try {
|
|
65
|
+
if (fs.existsSync('/mnt/c/Users')) {
|
|
66
|
+
for (const user of fs.readdirSync('/mnt/c/Users')) {
|
|
67
|
+
const candidate = `/mnt/c/Users/${user}/.claude.json`;
|
|
68
|
+
if (fs.existsSync(candidate)) candidates.add(candidate);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} catch (e) {}
|
|
72
|
+
|
|
73
|
+
let updated = 0;
|
|
74
|
+
for (const p of candidates) {
|
|
75
|
+
try {
|
|
76
|
+
// Solo modificar si Claude Code ya creo el archivo.
|
|
77
|
+
// No bootstrap-eamos .claude.json para no chocar con first-run setup.
|
|
78
|
+
if (!fs.existsSync(p)) continue;
|
|
79
|
+
const data = JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
80
|
+
if (!data.mcpServers) data.mcpServers = {};
|
|
81
|
+
data.mcpServers.brave = braveEntry;
|
|
82
|
+
|
|
83
|
+
// Write atomico: tmp + rename (evita corromper si Claude Code lee a medias)
|
|
84
|
+
const tmp = p + '.tmp';
|
|
85
|
+
fs.writeFileSync(tmp, JSON.stringify(data, null, 2));
|
|
86
|
+
fs.renameSync(tmp, p);
|
|
87
|
+
updated++;
|
|
88
|
+
log(` -> ${p} (user scope)`);
|
|
89
|
+
} catch (e) {
|
|
90
|
+
warn(`No se pudo actualizar ${p}: ${e.message}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return updated;
|
|
94
|
+
}
|
|
95
|
+
|
|
49
96
|
function updateMcpJson(port, wslIp) {
|
|
50
97
|
// Estrategia: apuntar al wrapper Node dinamico.
|
|
51
98
|
// El wrapper lee cdp_info.json en cada invocacion -> puerto siempre fresco.
|
|
@@ -115,7 +162,16 @@ function updateMcpJson(port, wslIp) {
|
|
|
115
162
|
}
|
|
116
163
|
|
|
117
164
|
success(`.mcp.json actualizado (${updated} archivos): brave -> ${wslIp}:${port}`);
|
|
118
|
-
|
|
165
|
+
|
|
166
|
+
// Tambien registrar en scope user (~/.claude.json) para que /mcp lo vea desde cualquier cwd
|
|
167
|
+
const userUpdated = updateClaudeUserConfig(wrapperPath);
|
|
168
|
+
if (userUpdated > 0) {
|
|
169
|
+
success(`.claude.json actualizado (${userUpdated} archivos, scope user): brave registrado`);
|
|
170
|
+
} else {
|
|
171
|
+
warn('No se encontro .claude.json para registrar en scope user. Reinicia Claude Code una vez para que se cree, luego re-ejecuta este comando.');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return (updated + userUpdated) > 0;
|
|
119
175
|
}
|
|
120
176
|
|
|
121
|
-
module.exports = { getWslHostIp, updateMcpJson };
|
|
177
|
+
module.exports = { getWslHostIp, updateMcpJson, updateClaudeUserConfig };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "browser-ipc-cdp",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.3",
|
|
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
6
|
"browser-ipc-cdp": "bin/cli.js"
|