--- name: browser_mcp lang: go domain: infra version: 0.3.0 description: "Servidor MCP que expone control total del navegador via CDP (39 tools: navegación, DOM, cookies, iframes, teclado/scroll, diálogos, estado de sesión, selección determinista de pestaña, lectura compacta texto/AX + bucle percibir→actuar por #ref con auto-observe) reusando funciones del dominio browser del registry con un pool de conexiones CDP vivas. Por defecto opera sobre un Chrome aislado (puerto 9333) separado del navegador diario." tags: [mcp, browser, cdp, automation, scraping] uses_functions: - chrome_launch_go_browser - cdp_connect_go_browser - cdp_close_go_browser - cdp_navigate_go_browser - cdp_list_tabs_go_browser - cdp_new_tab_go_browser - cdp_close_tab_go_browser - cdp_activate_tab_go_browser - cdp_nav_back_go_browser - cdp_nav_forward_go_browser - cdp_wait_load_go_browser - cdp_wait_idle_go_browser - cdp_get_html_go_browser - cdp_evaluate_go_browser - cdp_screenshot_go_browser - cdp_click_go_browser - cdp_click_human_go_browser - cdp_click_text_go_browser - cdp_type_text_go_browser - cdp_find_by_text_go_browser - cdp_wait_element_go_browser - cdp_press_key_go_browser - cdp_scroll_go_browser - cdp_handle_dialog_go_browser - cdp_set_cookie_go_browser - cdp_get_cookies_go_browser - cdp_delete_cookies_go_browser - cdp_clear_cookies_go_browser - cdp_list_frames_go_browser - cdp_eval_in_frame_go_browser - cdp_get_frame_html_go_browser - cdp_save_storage_state_go_browser - cdp_load_storage_state_go_browser - cdp_get_text_go_browser - cdp_connect_target_go_browser - cdp_perceive_outline_py_pipelines - cdp_click_ref_go_browser - cdp_type_ref_go_browser - cdp_hover_ref_go_browser - cdp_click_xy_human_go_browser uses_types: [] framework: "" entry_point: "main.go" dir_path: "projects/web_scraping/apps/browser_mcp" repo_url: "" --- # browser_mcp Servidor MCP (Model Context Protocol) en Go que expone el control de navegador via CDP del registry `fn_registry` como tools MCP. Cualquier cliente MCP (Claude Code, otros agentes) puede manejar un Chrome/Chromium vivo: navegar, leer el DOM, hacer clicks, gestionar cookies, evaluar JavaScript, operar iframes y persistir/restaurar sesiones. Clona el patrón de `apps/registry_mcp/` (librería `github.com/mark3labs/mcp-go` v0.52.0, `server.NewMCPServer` + `server.ServeStdio`, tools con `mcp.NewTool` + handlers tipados via `mcp.NewTypedToolHandler`, transporte stdio por defecto + HTTP opcional con `--http`, slog a stderr porque stdout pertenece al JSON-RPC). ## Arquitectura: pool de conexiones CDP A diferencia de `registry_mcp` (que abre la DB una vez), `browser_mcp` mantiene un **pool de conexiones CDP vivas** indexado por puerto (`pool.go`). Razón: `browser.CdpConnect(port)` hace un handshake WebSocket contra una tab "page" de Chrome (~50-200ms) y esa conexión ES una sesión viva (soporta `Page.*`, `Runtime.*`, `Input.*`). El agente llama muchas tools seguidas (navigate → wait → click → eval); reconectar en cada tool pagaría el handshake repetidamente y perdería estado entre tools (los event handlers persistentes, como el de `handle_dialog`, viven mientras la conexión esté viva). Por eso reusamos la conexión por puerto. - `connPool.get(port)` devuelve la conexión cacheada o abre una nueva. - `connPool.drop(port)` cancela el handler de diálogo (si lo hay) y cierra la conexión. - `connPool.connectTarget(port, match)` descarta la conexión actual y reconecta a un target determinista (por id o substring de URL). Es lo que usa `tab_select` para fijar la pestaña. - `connPool.setCancel(port, cancel)` registra el cancel del auto-handler de `handle_dialog`. - `connPool.closeAll()` se ejecuta con `defer` en `main()`. - `deps.withConn(port, fn)` ejecuta `fn` con la conexión del pool y, si el error indica conexión muerta (`isConnErr`: connection close, broken pipe, use of closed, ws read, EOF), descarta la conexión y reintenta UNA vez (Chrome pudo cerrar la tab entre tools). Toda tool con argumento `port` usa `portOr(a.Port)` (default 9333). Las tools de tabs (`tab_list`, `tab_new`, `tab_close`, `tab_activate`, `tab_select`) usan el endpoint HTTP `/json` de CDP directamente (host `localhost`), no el pool, porque no requieren una sesión WebSocket viva. ## Seguridad: Chrome aislado por defecto (puerto 9333) **El default del MCP es operar sobre su PROPIO Chrome aislado, no sobre el navegador diario.** En este ecosistema el chromium diario del usuario tiene CDP habilitado globalmente en el puerto **9222** (via `/etc/chromium.d/cdp`). Si el MCP usara 9222 por defecto, el agente podría manipular pestañas ajenas del usuario (banca, correo). Para evitarlo: - `portOr` devuelve **9333** por defecto (no 9222) — el Chrome dedicado del MCP. - `browser_launch` sin `user_data_dir` usa un perfil DEDICADO y aislado: `/browser_mcp_userdata` (se crea si hace falta) en el puerto 9333. - Para adjuntarte deliberadamente al navegador diario, pasa `port: 9222` explícito en cada tool. Hazlo solo con cuidado. ## Tools (39) ### Sesión (`tools_session.go`) - `browser_launch` (MUTA) — lanza Chrome con CDP. args: port, headless, user_data_dir, url. - `browser_connect` — abre/poolea la conexión CDP del puerto. args: port. - `browser_disconnect` — cierra y descarta la conexión del puerto (no mata Chrome). args: port. ### Navegación + tabs (`tools_nav.go`) - `tab_navigate` (MUTA) — `Page.navigate`. args: port, url. - `tab_list` — lista targets via `GET /json`. args: port. - `tab_new` (MUTA) — abre tab via `PUT /json/new`. args: port, url. - `tab_close` (MUTA) — cierra tab por ID. args: port, tab_id. - `tab_activate` — pone tab en foreground. args: port, tab_id. - `tab_select` — fija la pestaña sobre la que operan las siguientes tools, eligiéndola por id o por substring de su URL (determinista). Usar tras `tab_list` para no operar sobre la pestaña equivocada. args: port, match. - `nav_back` (MUTA) — atrás en el historial. args: port. - `nav_forward` (MUTA) — adelante en el historial. args: port. - `page_wait_load` — espera el evento load. args: port, timeout_ms (default 10000). - `page_wait_idle` — espera red idle. args: port, timeout_ms (default 15000). ### Lectura (`tools_read.go`) - `page_get_html` — HTML serializado (truncado a 200000 chars). args: port. - `page_get_text` — texto visible (innerText) de la página o de un elemento (selector CSS), truncado a `max_bytes`. Preferir sobre `page_get_html` cuando solo necesitas leer contenido — no revienta el contexto. args: port, selector (opcional), max_bytes (default 20000). - `page_perceive` — outline indentado y accionable del árbol de accesibilidad (roles, nombres, `#ref`): la forma compacta de que el agente "perciba" la página sin reventar el contexto. Implementado por subprocess (`fn run cdp_perceive_outline`). Si `tab_id` se omite, usa la primera pestaña page. args: port, tab_id (opcional), max_chars (default 20000). **Gotcha:** requiere el binario `fn` y el venv de Python del registry disponibles en runtime. - `page_eval_js` (MUTA) — `Runtime.evaluate`. args: port, expression. - `page_screenshot` — captura a archivo. args: port, path, full_page. ### DOM (`tools_dom.go`) - `dom_click` (MUTA) — click por selector. args: port, selector. - `dom_click_human` (MUTA) — click con movimiento humano. args: port, selector. - `dom_click_text` (MUTA) — click sobre el primer elemento con ese texto. args: port, text. - `dom_type` (MUTA) — escribe texto en el elemento enfocado. args: port, text. - `dom_find_by_text` — devuelve un selector CSS único para un texto visible. args: port, text. - `dom_wait_element` — espera a que aparezca un selector. args: port, selector, timeout_ms (default 10000). - `dom_click_ref` (MUTA) — click humanizado por `#ref` (backendDOMNodeId del outline de `page_perceive`) + auto-observe. args: port, ref. - `dom_type_ref` (MUTA) — enfoca el `#ref` y escribe texto + auto-observe. args: port, ref, text. - `dom_hover_ref` (MUTA) — hover humanizado por `#ref` + auto-observe. args: port, ref. #### Bucle percibir→actuar (por `#ref`) `page_perceive` devuelve un outline accionable donde cada elemento lleva un `#ref` estable (su `backendDOMNodeId`). Las tools `dom_click_ref` / `dom_type_ref` / `dom_hover_ref` actúan directamente sobre ese `#ref` — no necesitas resolver un selector CSS. Tras la acción esperan un settle breve (400ms) y **devuelven el outline actualizado** (auto-observe), cerrando el bucle percibir→actuar: ``` page_perceive → outline con #ref de cada elemento dom_click_ref → click humanizado + outline nuevo tras la acción dom_type_ref → escribe + outline nuevo ``` Las tools `*_ref` usan humanización por defecto (Bézier+jitter). Una política de sesión `fast`/`instant` para scraping masivo está pendiente (ver TODO en el código). ### Input (`tools_input.go`) — todas MUTA - `press_key` — presiona una tecla nombrada (Enter/Tab/Escape/ArrowDown/...). args: port, key. - `scroll` — scroll por (delta_x, delta_y). args: port, delta_x (default 0), delta_y (default 300). - `handle_dialog` — arma un auto-handler de diálogos JS (vive en la conexión del pool). args: port, accept (default true), prompt_text. ### Cookies (`tools_cookies.go`) - `cookie_get` — todas las cookies como JSON. args: port. - `cookie_set` (MUTA) — set cookie. args: port, name, value, domain, path, http_only. - `cookie_delete` (MUTA) — borra cookies por nombre. args: port, name, domain. - `cookie_clear` (MUTA) — borra todas las cookies. args: port. ### Iframes (`tools_frames.go`) - `frame_list` — lista frames con sus IDs. args: port. - `frame_eval` (MUTA) — evalúa JS dentro de un frame. args: port, frame_id, expression. - `frame_get_html` — HTML de un frame (truncado a 200000). args: port, frame_id. ### Estado de sesión (`tools_storage.go`) - `storage_save` — guarda cookies + localStorage a JSON. args: port, path. - `storage_load` (MUTA) — carga cookies + localStorage desde JSON. args: port, path. ## Cómo lanzarlo Transporte stdio (default, para clientes MCP): ```bash cd projects/web_scraping/apps/browser_mcp go build -o browser_mcp . ./browser_mcp ``` Transporte HTTP (Streamable HTTP): ```bash ./browser_mcp --http :7740 # bind 127.0.0.1:7740 ./browser_mcp --http :7740 --bind 0.0.0.0 # requiere REGISTRY_API_TOKEN (bearer auth) ``` ### Flag `--read-only` Con `--read-only`, el servidor NO registra las tools mutantes (marcadas MUTA arriba): solo expone las 17 tools de lectura/control (`browser_connect`, `browser_disconnect`, `tab_list`, `tab_activate`, `tab_select`, `page_wait_load`, `page_wait_idle`, `page_get_html`, `page_get_text`, `page_perceive`, `page_screenshot`, `dom_find_by_text`, `dom_wait_element`, `cookie_get`, `frame_list`, `frame_get_html`, `storage_save`). Útil para sesiones de inspección sin riesgo de modificar el estado del navegador. ## Omitido en v1 Funciones del dominio `browser` que NO se exponen como tools en esta versión, con su razón: - **`cdp_har_record_go_browser`** — graba el tráfico de red (HAR). Requiere un callback de larga duración (registrar handlers + un punto de "stop" que devuelve los datos acumulados); no encaja en el modelo request/response de una tool MCP simple. Pendiente de un diseño con tool de start + tool de stop. - **`cdp_get_ax_tree`** — ya expuesto desde v0.2.0 via la tool `page_perceive`, que invoca el pipeline `cdp_perceive_outline` por subprocess (`fn run`) en vez de duplicar la lógica aquí. - **Funciones de perfiles Chrome (Bash: create/delete/appearance/reset)** — requieren que Chrome esté CERRADO para modificar el `Local State` / `Preferences` del perfil; son incompatibles con un MCP cuyo propósito es controlar un Chrome vivo. Quedan disponibles como `fn run` aparte. ## Capability growth log - v0.3.0 (2026-06-06) — Cierre del bucle percibir→actuar. Nuevas tools `dom_click_ref`, `dom_type_ref`, `dom_hover_ref`: actúan sobre el `#ref` (backendDOMNodeId estable) del outline de `page_perceive` con humanización por defecto (Bézier+jitter) y auto-observe (devuelven el outline actualizado tras la acción). Refactor: la generación del outline se extrajo a `deps.perceiveOutline`/`perceiveOutlineTab`, reusado por `page_perceive` y por las tools `*_ref`. 36 → 39 tools. - v0.2.0 (2026-06-06) — P0 LLM-readiness. Seguridad: Chrome aislado por defecto (puerto 9333 + perfil dedicado `/browser_mcp_userdata`), separado del navegador diario en 9222. Nuevas tools: `tab_select` (selección determinista de pestaña por id/URL), `page_get_text` (lectura compacta de innerText), `page_perceive` (outline AX via `fn run cdp_perceive_outline`). 33 → 36 tools.