Three MCP tools to manage the user's Chromium instances by profile, distinct
from browser_launch's isolated automation Chrome:
- browser_list: enumerate running Chromium master processes by scanning
/proc/*/cmdline (has --user-data-dir, no --type=). Returns pid, profile,
user_data_dir, cdp_port, has_cdp as a JSON array.
- browser_launch_profile: launch a concrete profile using the REAL binary
/usr/lib/chromium/chromium (bypassing the /usr/bin/chromium wrapper). No CDP
by default so Google keeps the session for human profiles; cdp=true adds
--remote-debugging-port + --remote-allow-origins=*. Detects DISPLAY/XAUTHORITY
from the XFCE session and launches decoupled via setsid.
- browser_close: locate a master by profile/cdp_port/pid, SIGTERM with a 10s
wait, then SIGKILL as a last resort.
Per-profile instances are NOT registered in the connection pool: they are
user-facing and survive the MCP dying; cleanup is explicit via browser_close.
Unit tests for cmdline master detection, flag parsing, and close-target
matching. Bumps version 0.6.0 -> 0.7.0 (42 -> 45 tools).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Capacidades nuevas y cambios (40 -> 42 tools):
- page_perceive ahora se genera de forma NATIVA en Go sobre la conexion CDP
viva del pool (cdp_get_ax_outline_go_browser). Elimina el subprocess
`fn run cdp_perceive_outline` (Python), el venv y la dependencia del binario
`fn` en runtime (se borra resolveRoot/exec.Command). Respeta tab_select.
- page_perceive acepta frame_id para percibir DENTRO de un iframe. El campo
tab_id queda obsoleto (se ignora; usar tab_select) pero se conserva por
compatibilidad.
- frame_get_text (nueva, lectura): innerText de un iframe via
cdp_get_text_in_frame_go_browser. Activa tambien bajo --read-only.
- dom_click_xy (nueva, MUTA): click humanizado por coordenadas absolutas via
cdp_click_xy_human_go_browser, con mode human/fast/instant y auto-observe.
Fallback para actuar sobre lo que el LLM ve en page_screenshot.
- page_screenshot devuelve la imagen como image content
(cdp_screenshot_bytes_go_browser + mcp.NewToolResultImage) para que el LLM
vea los pixeles; path pasa a ser opcional (si se da, ademas guarda a disco).
- Auto-observe de las tools *_ref sube su truncado de 4000 a 8000 chars.
- Fix de seguridad documental: todas las descripciones del parametro port que
decian "Default 9222" (navegador diario del usuario) corregidas a
"Default 9333" (Chrome aislado del MCP). El codigo ya usaba 9333; la doc era
falsa y podia inducir al modelo a tocar pestanas de banca/correo.
uses_functions del app.md: +cdp_get_ax_outline, +cdp_get_text_in_frame,
+cdp_screenshot_bytes; -cdp_perceive_outline_py_pipelines.
Verificacion: go build OK, go test OK (4 unit pass, 3 e2e skip gated BMCP_E2E=1),
go vet OK, gofmt limpio, sin "Default 9222" en el codigo.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
El pool nunca guardaba el PID del Chrome lanzado por browser_launch, así que
closeAll() y drop() cerraban con CdpClose(c, 0): solo soltaban el WebSocket y
dejaban el proceso chromium vivo y huérfano (~789 MiB RSS cada uno). Llamadas
repetidas a browser_launch acumulaban instancias sin límite hasta saturar la RAM
(apagón del 06/06/2026, ~35 chromium huérfanos).
Cambios:
- pool.go: el pool registra el PID lanzado por puerto (mapa `pids`) con
setPID/getPID/clearPID/launchedCount. drop() y closeAll() matan el grupo de
proceso completo (CdpClose con pid real) SOLO si el PID está registrado, es
decir, si lo lanzó el MCP. Un Chrome externo sin PID registrado (el navegador
diario del usuario en 9222) nunca se mata: pid=0 solo cierra el WebSocket.
Nuevo releaseConn() suelta únicamente el WebSocket preservando el PID, para la
reconexión interna (no debe matar el navegador).
- tools_session.go: handleLaunch registra el PID devuelto por ChromeLaunch
(setPID); es idempotente por puerto (reusa el Chrome ya lanzado), pasa
ReuseExisting=true para no duplicar un Chrome ya vivo en el puerto, y aplica
un tope duro de 4 instancias (maxLaunchedChromes) devolviendo un error de tool
al superarlo. browser_disconnect ahora mata el Chrome propio.
- main.go: handler SIGTERM/SIGINT que llama closeAll antes de salir (los defers
no corren al recibir señal). El retry de withConn usa releaseConn en vez de
drop para no matar el Chrome al reconectar.
- pool_test.go: tests lógicos sin Chrome (cap, idempotencia, ciclo de PID, drop).
- pool_e2e_test.go: tests con Chrome real (gate BMCP_E2E=1) — golden (3 launch →
closeAll → 0 huérfanos), dedup mismo puerto, y salvaguarda propio-vs-externo.
- app.md: e2e_checks (build, unit, leak_no_orphans) + growth log + bump a 0.5.0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
El wrapper CdpDisconnect comparte entry de registry con CdpClose; el auditor
uses_functions no lo reconoce como mismo símbolo y marcaba cdp_close como
declared-but-unused. CdpClose(c,0) expresa lo mismo sin drift.
dom_find_ref_by_text usa la nueva CdpFindRefByText del registry: encuentra por
texto y devuelve el #ref (backendDOMNodeId) listo para dom_click_ref, sin
selector CSS frágil; reporta count para ambigüedad.
Incluye WIP pre-existente ya estable: dom_click_ref/dom_hover_ref exponen
'mode' (human/fast/instant) vía MouseProfileForMode. Compila + 9 e2e verdes.
CdpHandleDialog ahora devuelve (cancel, *DialogLog, error). El pool guarda el
DialogLog por puerto y browser_disconnect reporta cuántos diálogos se
auto-respondieron y el último (tipo + mensaje). drop/closeAll usan CdpDisconnect
(alias legible de CdpClose(c,0)).