chore: auto-commit (3 archivos)

- .mcp.json
- CAPABILITIES_TODO.md
- demo_e2e/

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-06 12:49:54 +02:00
parent 2527fd306a
commit 23f9aa90e8
14 changed files with 1858 additions and 0 deletions
+460
View File
@@ -0,0 +1,460 @@
---
title: Capacidades de navegador (CDP) + construcción del MCP full-CDP
artefacto: project · projects/web_scraping
created: 06/06/2026 00:00
updated: 06/06/2026 07:00
status: in_progress
related_issues: []
related_flows: []
---
## Objetivo
Dos objetivos encadenados:
1. **Inventario** — mapear todas las capacidades de control de navegador del proyecto `web_scraping`
contra el dominio `browser` del registry (funciones Go, Bash y pipelines Python) y la app
`script_navegador`. Marcar qué está cubierto, qué está a medias y qué falta.
2. **MCP full-CDP** — construir un servidor MCP (`browser_mcp`) que exponga TODAS estas capacidades como
tools, para que cualquier agente Claude controle el navegador de punta a punta. Los gaps que faltan
se construyen en paralelo con varios `fn-constructor`, y el MCP los envuelve a medida que aparecen.
Este documento es la lista de trabajo viva del proyecto: cada gap es una tarea candidata a delegar a
`fn-constructor`, y cada función del registry es una tool candidata del `browser_mcp`.
Convención de estado por capacidad:
- `[x]` Cubierto — hay función(es) del registry dedicadas y probadas.
- `[~]` Parcial — se puede hacer pero indirecto (vía `cdp_evaluate`) o incompleto (falta parte del CRUD).
- `[ ]` Falta — no existe ninguna función para esto.
---
## Resumen ejecutivo
| # | Capacidad pedida | Estado | Notas |
|---|---|---|---|
| 1 | CRUD de perfiles | `[x]` | Create + Read + Delete + Update(apariencia/clonar/reset). Completo. |
| 2 | CRUD de ventanas | `[ ]` | No hay nada. Falta crear/listar/mover/redimensionar/cerrar ventanas (Browser.*WindowBounds). |
| 3 | CRUD de pestañas | `[x]` | Create/List/Navigate + **close/activate** (cdp_close_tab/activate_tab) + **back/forward**. Completo. |
| 4 | Lanzador personalizado | `[x]` | Perfil, flags, extensiones, proxy, headless. Completo. |
| 5 | Configuración de detalles | `[~]` | Apariencia + flag CDP global + policy de extensiones. Falta config genérica de prefs. |
| 6 | Datos del navegador (cookies, historial, marcadores) | `[~]` | Cookies: **CRUD completo** (get/set/delete/clear). Historial perfil: nada. Marcadores: backup/restore. |
| 7 | Lectura de página (HTML, AX tree, texto) | `[~]` | HTML sí, AX tree sí. Texto plano solo vía `cdp_evaluate` (no dedicado). |
| 8 | Selección de elementos del DOM | `[x]` | find_by_text, wait_element, picker interactivo, querySelector vía evaluate. |
| 9 | CRUD de iframes | `[x]` | **list_frames + eval_in_frame + get_frame_html**. Manejo de frames completo. |
| 10 | Lanzamiento de JS en la página | `[x]` | `cdp_evaluate` + steps `js` de `cdp_extract_recipe`. Completo. |
Extras que ya tenemos y no estaban en la lista (capital acumulado): captura de tráfico (HAR +
mitmproxy), interacción humanizada (curva Bézier + jitter anti-bot), esperas inteligentes
(idle/load/element), screenshot.
---
## Catálogo completo — todas nuestras habilidades de un vistazo
39 funciones del dominio `browser` (23 Go + 12 Bash + 4 pipelines Python) + 1 pipeline Bash de reset.
Cada una es una tool candidata del futuro `browser_mcp`. Columna "MCP tool" = nombre propuesto de la tool.
### CDP core — Go (`functions/browser/`, importables directo por el MCP)
| Función (registry id) | Qué hace | MCP tool propuesta |
|---|---|---|
| `chrome_launch_go_browser` | Lanza Chrome/Chromium con remote debugging, mata árbol de proceso | `browser_launch` |
| `cdp_connect_go_browser` | Handshake WebSocket CDP sobre localhost:port → conexión lista | (interno, lo usa el MCP) |
| `cdp_close_go_browser` | Cierra conexión WS y/o mata proceso Chrome por PID | `browser_close` |
| `cdp_navigate_go_browser` | Navega la pestaña a una URL (`Page.navigate`) | `tab_navigate` |
| `cdp_new_tab_go_browser` | Abre pestaña nueva (`/json/new`) → CdpTab | `tab_new` |
| `cdp_list_tabs_go_browser` | Lista pestañas/targets (`GET /json`), sólo HTTP | `tab_list` |
| `cdp_get_html_go_browser` | HTML del DOM vivo post-JS (`outerHTML`) | `page_get_html` |
| `cdp_screenshot_go_browser` | Screenshot PNG/JPEG, viewport o página completa | `page_screenshot` |
| `cdp_evaluate_go_browser` | Ejecuta JS arbitrario (`Runtime.evaluate`, soporta await) | `page_eval_js` |
| `cdp_click_go_browser` | Click por selector CSS (scroll + mousedown/up) | `dom_click` |
| `cdp_click_human_go_browser` | Click humanizado (Bézier + jitter + micro-pausa) anti-bot | `dom_click_human` |
| `cdp_click_text_go_browser` | Click sobre el elemento cuyo innerText matchea | `dom_click_text` |
| `cdp_type_text_go_browser` | Escribe texto char a char en el elemento activo | `dom_type` |
| `cdp_move_mouse_human_go_browser` | Mueve ratón con curva Bézier humanizada | `mouse_move_human` |
| `cdp_find_by_text_go_browser` | Texto visible → selector CSS único | `dom_find_by_text` |
| `cdp_wait_element_go_browser` | Espera a que un selector exista en el DOM | `dom_wait_element` |
| `cdp_pick_element_js_go_browser` | Picker interactivo: hover overlay + click captura selector/XPath/bbox | `dom_pick_element` |
| `cdp_wait_load_go_browser` | Espera `document.readyState === complete` | `page_wait_load` |
| `cdp_wait_idle_go_browser` | Espera red en idle (inflight ≤ N durante quietMs) | `page_wait_idle` |
| `cdp_set_cookie_go_browser` | Set cookie (incl. HttpOnly) vía `Network.setCookie` | `cookie_set` |
| `cdp_har_record_go_browser` | Captura HAR 1.2 de todo el tráfico de una acción | `traffic_record_har` |
| `list_chrome_profiles_go_browser` | Lista perfiles de un user-data-dir | `profile_list` |
| `list_chrome_profile_extensions_go_browser` | Lista extensiones instaladas de un perfil | `profile_list_extensions` |
### Perfiles + sistema — Bash (`bash/functions/browser/`, el MCP las invoca vía `fn run`/shell)
| Función (registry id) | Qué hace | MCP tool propuesta |
|---|---|---|
| `create_chrome_profile_bash_browser` | Crea perfil nuevo (con/sin lanzar headless para policy) | `profile_create` |
| `delete_chrome_profile_bash_browser` | Borra perfil(es) + limpia Local State (backup automático) | `profile_delete` |
| `prepare_chrome_profile_bash_browser` | Clona user-data-dir limpio (whitelist de extensiones) | `profile_prepare` |
| `set_chrome_profile_appearance_bash_browser` | Avatar + color de tema de un perfil | `profile_set_appearance` |
| `backup_chrome_bookmarks_bash_browser` | Backup byte a byte de Bookmarks (preserva checksum) | `bookmark_backup` |
| `restore_chrome_bookmarks_bash_browser` | Restaura Bookmarks desde backup | `bookmark_restore` |
| `chrome_load_extensions_bash_browser` | Lanza Chrome con extensiones unpacked (`--load-extension`) | `ext_load_unpacked` |
| `clean_chrome_profile_extensions_bash_browser` | Purga extensiones fuera de la whitelist de un perfil | `ext_clean` |
| `apply_chromium_extension_policy_bash_browser` | Policy managed: forcelist + blocklist de extensiones | `ext_apply_policy` |
| `install_chromium_proxy_extension_bash_browser` | Instala extensión unpacked en todos los perfiles (persistente) | `ext_install_persistent` |
| `apply_chromium_cdp_flag_bash_browser` | Activa CDP global del sistema (`/etc/chromium.d/cdp`) | `system_cdp_flag` |
| `launch_chromium_proxy_bash_browser` | Lanza Chromium con perfil aislado apuntando a proxy mitm | `browser_launch_proxy` |
### Pipelines — Python + Bash (`*/functions/pipelines/`, el MCP los invoca vía `fn run`)
| Pipeline (registry id) | Qué hace | MCP tool propuesta |
|---|---|---|
| `cdp_get_ax_tree_py_pipelines` | Accessibility tree completo de un tab | `page_get_ax_tree` |
| `cdp_open_url_and_wait_py_pipelines` | Crea tab + navega + espera loadEventFired → tab_id | `tab_open_and_wait` |
| `cdp_extract_recipe_py_pipelines` | Ejecuta recipe YAML (wait_selector/js) contra Chrome | `run_recipe` |
| `extract_hls_from_cdp_tab_py_pipelines` | Extrae manifests HLS de tabs + iframes | `extract_hls` |
| `reset_chrome_profiles_bash_pipelines` | Reset destructivo de perfiles (preserva bookmarks) | `profile_reset` |
> El MCP tendrá ~28 tools al ensamblar lo existente, y crecerá hasta ~40 al cerrar los gaps de abajo.
---
## Benchmark vs estado del arte (Playwright MCP + Chrome DevTools MCP)
Comparación contra los dos servidores MCP de referencia de la comunidad para fijar qué nos falta
para tener paridad con un "lanzador típico":
- **Microsoft Playwright MCP** — ~60+ tools (incluye testing/assertions/video). Modo por defecto =
accessibility snapshot, no HTML crudo. Fuente: github.com/microsoft/playwright-mcp.
- **Google Chrome DevTools MCP** — 26 tools en 6 categorías (input, navegación, emulación,
performance, network, debugging), CDP crudo igual que nosotros. Fuente: github.com/ChromeDevTools/chrome-devtools-mcp.
### Tabla de paridad por categoría
| Categoría | Ellos tienen | Nosotros | Veredicto |
|---|---|---|---|
| Navegación URL | navigate, back/forward | navigate ✅, back/forward ❌ | falta back/forward |
| Pestañas | list/new/close/select | list/new ✅, close/activate ❌ (planeado) | en gaps tanda 1 |
| Lectura DOM | snapshot (AX) + HTML | AX tree ✅, HTML ✅ | paridad |
| Selección | locators, find by text/role | find_by_text ✅, picker ✅, wait_element ✅ | paridad |
| Click / type | click, type, **press_key**, **hover** | click(+human) ✅, type ✅, press_key ❌, hover ❌ | falta press_key, hover |
| Formularios | **fill_form**, **select_option** | ❌ (manual con click+type) | falta |
| Mouse | xy click/move/**wheel(scroll)**/drag | move_human ✅, click ✅, wheel(scroll) ❌, drag ❌ | falta scroll + drag |
| Diálogos | **handle_dialog** (alert/confirm) | ❌ | falta (bloquea flujos) |
| Subida archivos | **file_upload** | ❌ | falta |
| JS | evaluate | evaluate ✅ | paridad |
| Consola | **console_messages** | ❌ | falta (debug + detección) |
| Cookies | get/list/set/delete/clear | set ✅, resto ❌ (planeado) | en gaps tanda 1 |
| Storage local | **localStorage/sessionStorage** CRUD | ❌ | falta |
| Estado de sesión | **storage_state** save/restore | ❌ | falta (login persistente) |
| Network captura | requests list + inspect, **HAR** | HAR ✅, list/inspect en vivo ❌ | HAR cubre; falta inspección puntual |
| Network mock | **route/abort/fulfill** (intercept) | ❌ (sí mitmproxy externo) | falta intercept inline CDP |
| Network emulación | online/offline, throttle | ❌ | falta |
| Emulación device | **emulate** (device/CPU), **resize** viewport | ❌ | falta |
| Screenshot | screenshot, **PDF** | screenshot ✅, PDF ❌ | falta PDF |
| Performance | **trace + lighthouse** | ❌ | falta (nicho) |
| Anti-bot humanizado | — (ellos NO tienen) | click_human, move_human, jitter ✅ | **ventaja nuestra** |
| Captura tráfico proxy | — (vía HAR) | web_proxy mitmproxy ✅ | **ventaja nuestra** |
| Perfiles (CRUD disco) | — (ellos NO gestionan perfiles) | create/delete/prepare/appearance/reset ✅ | **ventaja nuestra** |
**Conclusión**: en lectura/selección/JS/click hay paridad. Nuestras ventajas: humanización anti-bot,
captura mitmproxy y gestión de perfiles en disco (Playwright/CDP-MCP no hacen nada de esto). Nos
faltan, además de los gaps de la lista 1 (tabs/iframes/cookies/ventanas/historial), las capacidades
de abajo (tanda 2) para alcanzar paridad de automatización.
---
## Pendiente (gaps a construir)
> ✅ **CERRADOS en la tanda mínima viable (06/06/2026)**: #2 (tabs close/activate), #3 (cookies get/delete/clear),
> #6 (iframes) + tanda 2 #10 (press_key), #11 (handle_dialog), #13 (storage_state), #14 (scroll), #15 (nav back/forward).
> Ver sección **Hecho**. Lo de abajo es lo que QUEDA.
- [ ] 1. **CRUD de ventanas** — funciones nuevas dominio `browser`:
- `cdp_list_windows``Browser.getWindowForTarget` por cada target → id de ventana + bounds.
- `cdp_set_window_bounds` — mover/redimensionar/maximizar/minimizar (`Browser.setWindowBounds`).
- `cdp_new_window` — abrir ventana nueva (Target con `newWindow:true`) vs pestaña.
- `cdp_close_window` — cerrar una ventana concreta sin matar todo el proceso.
- [ ] 2. **Cerrar/activar pestaña individual** — hoy `cdp_close` mata el proceso entero o cierra la conexión:
- `cdp_close_tab``Target.closeTarget(targetId)` (cierra UNA pestaña).
- `cdp_activate_tab``Target.activateTarget` / `/json/activate/<id>` (traer al frente).
- [ ] 3. **Cookies completas** — hoy solo `cdp_set_cookie`:
- `cdp_get_cookies``Network.getCookies` / `getAllCookies` (leer, filtrar por dominio).
- `cdp_delete_cookies``Network.deleteCookies`.
- `cdp_clear_cookies``Network.clearBrowserCookies` (wipe completo).
- [ ] 4. **Historial del navegador** — no existe nada:
- `cdp_get_history` — leer historial (vía `History` DB del perfil o `Page.getNavigationHistory` para la sesión actual).
- `cdp_clear_history` — limpiar historial del perfil (decidir: SQLite del perfil con Chromium cerrado, como bookmarks).
- [ ] 5. **Marcadores CRUD individual** — hoy solo backup/restore byte a byte:
- `cdp_add_bookmark` / `cdp_remove_bookmark` / `cdp_list_bookmarks` — editar el archivo `Bookmarks` (JSON)
preservando el checksum, o vía CDP si hay endpoint. Complementa, no sustituye, backup/restore.
- [ ] 6. **CRUD de iframes** — solo hay lectura indirecta en `extract_hls_from_cdp_tab`:
- `cdp_list_frames` — árbol de frames (`Page.getFrameTree`): id, url, parent.
- `cdp_eval_in_frame` — ejecutar JS en el **contexto de ejecución** de un iframe concreto
(`Runtime.evaluate` con el `executionContextId`/`uniqueContextId` del frame).
- `cdp_get_frame_html` — HTML de un iframe específico.
- (opcional) `cdp_navigate_frame` — navegar un iframe a otra URL.
- [ ] 7. **Texto plano de página dedicado** — hoy se saca con `cdp_evaluate("document.body.innerText")`:
- `cdp_get_text` — función dedicada que devuelve el texto visible limpio (útil para LLM/scraping rápido).
Decidir si vale la pena o si el patrón vía evaluate es suficiente (no inflar por inflar).
- [ ] 8. **Configuración de detalles genérica** — hoy solo apariencia + flag CDP + policy extensiones:
- Evaluar si hace falta `set_chrome_profile_pref` (editar `Preferences` del perfil: idioma, descargas,
permisos por defecto, etc.) o si se cubre caso por caso. NO construir hasta tener caso real.
- [ ] 9. **Playground del proyecto**`web_scraping` **no tiene** `playground/` (sí lo tienen `analysis/nats`
y `message_bus/unibus`). Candidato: un `playground/` con UI mínima (server single-file) que liste las
capacidades CDP y deje lanzarlas contra una pestaña viva para probarlas visualmente. Opcional, solo si
aporta para validar las funciones nuevas.
### Tanda 2 — gaps detectados en el benchmark (paridad con Playwright/CDP-MCP)
Prioridad ALTA (bloquean automatización real, los construiría antes que ventanas/historial):
- [x] 10. **`cdp_press_key`** ✅ HECHO — `Input.dispatchKeyEvent` con tabla de teclas especiales.
- [x] 11. **`cdp_handle_dialog`** ✅ HECHO — auto-handler `Page.javascriptDialogOpening` (con `go sendCDP` anti-deadlock).
- [ ] 12. **`cdp_get_console`** — capturar mensajes de consola y excepciones JS
(`Runtime.consoleAPICalled` + `Runtime.exceptionThrown`). Debug + detección de errores de la página.
**PENDIENTE** — único ALTA de tanda 2 sin construir. Sube a P1 (ver análisis LLM-readiness).
- [x] 13. **`cdp_save_storage_state` / `cdp_load_storage_state`** ✅ HECHO — cookies + localStorage a archivo.
⚠️ Falta `sessionStorage` y forzar navigate-first (ver deuda P2 del análisis).
- [x] 14. **`cdp_scroll`** ✅ HECHO — `Input.dispatchMouseEvent mouseWheel`. ⚠️ punto (100,100) hardcodeado (deuda P1).
- [x] 15. **`cdp_nav_back` / `cdp_nav_forward`** ✅ HECHO — `Page.getNavigationHistory` + `navigateToHistoryEntry`.
Prioridad MEDIA (formularios, storage fino, subida, intercept):
- [ ] 16. **`cdp_select_option`** — seleccionar valor en `<select>` (vía `cdp_evaluate` envuelto o CDP).
- [ ] 17. **`cdp_hover`** — hover sobre elemento (`Input.dispatchMouseEvent mouseMoved`) para menús
desplegables que aparecen al pasar el ratón.
- [ ] 18. **`cdp_file_upload`** — adjuntar archivos a un `<input type=file>` (`DOM.setFileInputFiles`).
- [ ] 19. **`cdp_storage_get` / `set` / `clear`** — CRUD de localStorage y sessionStorage (vía evaluate
o `DOMStorage` domain). Útil si no se quiere todo el storage_state.
- [ ] 20. **`cdp_intercept_requests`** — interceptar/abortar/modificar/mockear peticiones inline vía
`Fetch.enable` (bloquear ads/trackers, mockear respuestas, inyectar headers). Complementa, no
sustituye, al `web_proxy` mitmproxy (este es inline sin proxy externo).
- [ ] 21. **`cdp_emulate_network`** — online/offline + throttle (`Network.emulateNetworkConditions`).
- [ ] 22. **`cdp_save_pdf`** — guardar la página como PDF (`Page.printToPDF`).
Prioridad BAJA (formularios compuestos, emulación device, performance, drag):
- [ ] 23. **`cdp_fill_form`** — rellenar varios campos de una (composición de find+click+type, candidato
a **pipeline** no a función — encaja con la doctrina de promover composiciones).
- [ ] 24. **`cdp_emulate_device`** — viewport/userAgent/touch móvil (`Emulation.setDeviceMetricsOverride`).
- [ ] 25. **`cdp_drag_drop`** — drag and drop entre elementos.
- [ ] 26. **Performance/Lighthouse**`cdp_perf_trace` (Tracing domain) + audit Lighthouse. Nicho, solo
si aparece caso de análisis de rendimiento web.
## En curso
- [~] (ninguna ahora mismo — documento recién creado)
## Hecho (lo que YA tenemos)
- [x] **Tanda de deuda A+D+E+B — 4 fixes + 8/8 e2e** (06/06/2026)
- **A** aislamiento robusto: `chrome_launch` usa el binario real (salta el wrapper que pisaba flags).
- **D** `sessionStorage` añadido a `storage_state` (save+load). Validado por prueba e2e 6.
- **E** `cdp_find_by_text` devuelve error en no-encontrado (antes vacío silencioso). Validado por prueba 7.
- **B** fin del fire-and-forget: `cdp_click` verifica visibilidad, `cdp_type_text` verifica foco. Validado por prueba 8.
- La batería e2e pasó de 5 a 8 pruebas, todas verdes. Pendiente: C (Enter en widgets JS), `cdp_scroll`
con target (P1.5), puente percepción→acción por nodeId (P1.3).
- enlace: functions/browser/{chrome_launch,cdp_save_storage_state,cdp_load_storage_state,cdp_find_by_text,cdp_click,cdp_type_text}.go
- [x] **Fase C — validación e2e real: 5/5 PASS** (06/06/2026, headless + ventana visible)
- resultado: batería `projects/web_scraping/demo_e2e/` contra sitios sandbox (quotes/books.toscrape.com,
the-internet.herokuapp.com). 5 tareas simples→complejas: extracción estructurada, percepción AX,
teclado/form, **login persistente (storage_state)**, scraping paginado+dedup. Cliente MCP stdio
secuencial. Chrome aislado en 9333.
- **3 bugs reales encontrados y arreglados ejecutando** (lo que "compila" no detecta):
`page_perceive` (args posicionales a fn run), `cdp_save_storage_state` (filtrar cookies por dominio),
`cdp_load_storage_state` (añadir `url` por cookie para httpOnly). Login persistente ahora funciona.
- enlace: projects/web_scraping/demo_e2e/RESUMEN.md + results/
- [x] **`browser_mcp` v1 — servidor MCP de control de navegador** (Go, 06/06/2026)
- resultado: app en `projects/web_scraping/apps/browser_mcp/` (sub-repo Gitea, `git init` hecho).
**36 tools** (v0.2.0), pool de conexiones por puerto, stdio + `--http` + `--read-only`. **Build verde**
(smoke `tools/list`=36). Registrado en `projects/web_scraping/.mcp.json` como server `browser`.
- ✅ Fase B.5 (P0 LLM-readiness) cerrada: Chrome aislado 9333, `tab_select` determinista, `page_get_text`,
`page_perceive`. Pendiente Fase C (e2e real contra Chrome) + P1 (verificación post-acción).
- enlace: projects/web_scraping/apps/browser_mcp/ — patrón `apps/registry_mcp`.
- [x] **Fix bug `%v` en `cdp_evaluate` + `cdp_eval_in_frame`** (06/06/2026)
- resultado: objetos/arrays JS ahora se serializan con `json.Marshal` (antes repr de Go inservible).
Build+vet+test del paquete `browser` verdes. Reindexado.
- enlace: functions/browser/cdp_evaluate.go, cdp_eval_in_frame.go
- [x] **Tanda mínima viable del MCP — 15 funciones CDP nuevas** (Go, dominio `browser`, 06/06/2026)
- resultado: 5 `fn-constructor` en paralelo. Compila (`go build`/`vet`/`test` verdes), indexado (`fn index`),
15 entradas confirmadas. Tag de grupo `navegator` en todas.
- **Tabs/navegación**: `cdp_close_tab_go_browser`, `cdp_activate_tab_go_browser` (registradas — el código ya
vivía en `cdp_list_tabs.go`), `cdp_nav_back_go_browser`, `cdp_nav_forward_go_browser`.
- **Iframes**: `cdp_list_frames_go_browser`, `cdp_eval_in_frame_go_browser`, `cdp_get_frame_html_go_browser`.
- **Cookies**: `cdp_get_cookies_go_browser`, `cdp_delete_cookies_go_browser`, `cdp_clear_cookies_go_browser`.
- **Input/diálogos**: `cdp_press_key_go_browser`, `cdp_scroll_go_browser`, `cdp_handle_dialog_go_browser`.
- **Sesión**: `cdp_save_storage_state_go_browser`, `cdp_load_storage_state_go_browser` (login persistente).
- enlace: functions/browser/cdp_*.go — cierra gaps #2, #3, #6(cookies), #9 + tanda2 #10/#11/#13/#14/#15.
- [x] **Perfiles — CRUD completo** (Bash, dominio `browser`)
- resultado: Create `create_chrome_profile`, Read `list_chrome_profiles` (+ `list_chrome_profile_extensions`),
Delete `delete_chrome_profile`, Update `set_chrome_profile_appearance` (avatar + color de tema) /
`prepare_chrome_profile` (clonar limpio) / `reset_chrome_profiles` (pipeline reset destructivo con
preservación de bookmarks).
- enlace: bash/functions/browser/, bash/functions/pipelines/reset_chrome_profiles.md
- [x] **Lanzador personalizado** (Go + Bash)
- resultado: `chrome_launch` (remote debugging, multi-OS, mata árbol de proceso),
`launch_chromium_proxy` (perfil aislado apuntando a proxy mitm/Burp),
`chrome_load_extensions` (unpacked), `apply_chromium_cdp_flag` (CDP global del sistema),
`apply_chromium_extension_policy` + `install_chromium_proxy_extension` (distribuir extensiones),
y subcomando `launch` de `script_navegador`.
- enlace: functions/browser/chrome_launch.go, bash/functions/browser/
- [x] **Pestañas — crear / listar / navegar**
- resultado: `cdp_new_tab`, `cdp_open_url_and_wait` (crear+navegar+esperar load), `cdp_list_tabs`,
`cdp_navigate`. (Falta cerrar/activar una pestaña concreta → Pendiente #2.)
- enlace: functions/browser/cdp_new_tab.md, cdp_list_tabs.go, cdp_navigate.go
- [x] **Lectura de página — HTML + Accessibility tree**
- resultado: `cdp_get_html` (DOM vivo post-JS), `cdp_get_ax_tree` (pipeline Python, AX tree completo).
Texto plano hoy vía `cdp_evaluate` (ver Pendiente #7).
- enlace: functions/browser/cdp_get_html.go, python/functions/pipelines/cdp_get_ax_tree.md
- [x] **Selección de elementos del DOM**
- resultado: `cdp_find_by_text` (texto→selector CSS único), `cdp_wait_element` (polling existencia),
`cdp_pick_element_js` (picker interactivo: hover overlay + click captura selector/XPath/bbox),
querySelector arbitrario vía `cdp_evaluate`.
- enlace: functions/browser/cdp_find_by_text.go, cdp_pick_element_js.js, cdp_wait_element.go
- [x] **Lanzamiento de JS en la página**
- resultado: `cdp_evaluate` (expresión JS arbitraria, soporta await, reporta excepciones),
steps `js` de `cdp_extract_recipe` (recipe YAML).
- enlace: functions/browser/cdp_evaluate.go, python/functions/pipelines/cdp_extract_recipe.md
- [x] **Marcadores — backup / restore**
- resultado: `backup_chrome_bookmarks` + `restore_chrome_bookmarks` (copia byte a byte preservando
checksum, sin reserializar JSON). CRUD individual de bookmarks pendiente (#5).
- enlace: bash/functions/browser/backup_chrome_bookmarks.sh, restore_chrome_bookmarks.sh
- [x] **Cookies — set**
- resultado: `cdp_set_cookie` (incl. HttpOnly, para auth e2e). get/delete/clear pendientes (#3).
- enlace: functions/browser/cdp_set_cookie.go
- [x] **Extras (capital acumulado, fuera de la lista pedida)**
- resultado: captura de tráfico `cdp_har_record` (HAR 1.2) + app `web_proxy` (mitmproxy siempre activo);
interacción humanizada `cdp_click_human` / `cdp_move_mouse_human` / `cdp_type_text` (anti-detección);
esperas inteligentes `cdp_wait_idle` / `cdp_wait_load` / `cdp_wait_element`; `cdp_screenshot`;
pipeline `extract_hls_from_cdp_tab` (manifests HLS de tabs+iframes).
- enlace: functions/browser/, projects/web_scraping/apps/web_proxy
## MCP full-CDP (`browser_mcp`) — estado e iteración
**Meta**: un servidor MCP que da a cualquier agente Claude control total del navegador vía CDP.
Nombre **resuelto: `browser_mcp`** (genérico, para todo control de navegador, no solo CDP).
### Estado actual — v1 construido (06/06/2026)
- **App**: `projects/web_scraping/apps/browser_mcp/` (Go, sub-repo Gitea propio, `git init` hecho).
Patrón `registry_mcp`: `github.com/mark3labs/mcp-go` v0.52.0, archivos `tools_<grupo>.go`, registro en
`main.go`, stdio por defecto + `--http` opcional + flag `--read-only`. **Build verde, 33 tools.**
- **Pool de conexiones** (resuelto: pool por puerto, NO connect-per-tool). Ver explicación abajo.
- **Registro**: `projects/web_scraping/.mcp.json` con el server `browser`.
- **Omitido v1**: `cdp_har_record` (requiere callback), `cdp_get_ax_tree` (pipeline Python), perfiles Bash
(requieren Chrome cerrado → incompatible con un Chrome vivo).
### Pool de conexiones — por qué es requisito, no opción
`browser.CdpConnect(port)` hace un handshake WebSocket a una tab "page" de Chrome (~50-200 ms) y esa
conexión ES una sesión viva (Page.*/Runtime.*/Input.* + un `readLoop` en goroutine + event handlers).
Si reconectáramos en cada tool: (1) pagaríamos el handshake cada vez, (2) **perderíamos estado entre
tools** — los handlers de eventos (p.ej. el auto-handler de `handle_dialog`) mueren al cerrar la conexión.
Por eso `browser_mcp` mantiene `map[port]→*CDPConn`: la primera tool que toca el puerto abre la conexión,
las siguientes la reusan; se cierra al apagar el MCP o con `browser_disconnect`. **Sin pool, encadenar
navigate→wait→click→eval es imposible** (cada `fn run` suelto pierde la conexión al terminar el proceso).
### ⚠️ Deuda crítica (análisis LLM-readiness) — ver sección dedicada abajo
El v1 **envuelve las funciones tal cual**. Un LLM percibe-decide-actúa-verifica; las funciones están
hechas para un script que ya sabe qué hacer. Antes de declarar el MCP "usable por un agente" hay que
cerrar los P0 de la sección siguiente (percepción compacta + verificación + target determinista). El
valor del MCP está en lo que AÑADE encima, no en el wrapping.
### Fases
- **Fase 0** — inventario + catálogo + plan. ✅
- **Fase A** — 15 funciones gap de la tanda mínima viable. ✅ (5 fn-constructor paralelos)
- **Fase B** — ensamblar `browser_mcp` v1 (33 tools, pool, build verde). ✅
- **Fase B.5 — NUEVO, BLOQUEANTE** — cerrar los P0 del análisis (abajo) y elevar el MCP de "wrapper" a
"capa de agente": percepción compacta, verificación post-acción, target determinista.
- **Fase C** — registrar en Claude + e2e (un agente abre Chrome aislado, navega, lee, click, extrae) +
documentar en `LLM_BROWSER_GUIDE.md`.
### Capability group
Crear/actualizar `docs/capabilities/browser.md` con el cluster completo (39 funciones + 15 nuevas) +
ejemplo canónico end-to-end + las tools del MCP, para cargar el grupo en un solo read.
---
## Análisis LLM-readiness — deuda P0/P1/P2
Crítica recibida (06/06/2026): el núcleo CDP es sólido y la cobertura de verbos casi completa, pero el
sistema está hecho para un programa que ya sabe qué hacer, no para un LLM que percibe-decide-actúa-verifica.
Faltan las dos piezas que un agente necesita: **percibir sin colapsar el contexto** y **saber si la acción
funcionó**. Evaluado contra el bucle PERCIBIR → DECIDIR → ACTUAR → VERIFICAR (+ ESTADO transversal).
### Ya corregido este turno
- [x] **BUG GRAVE `cdp_evaluate` / `cdp_eval_in_frame`** — usaban `fmt.Sprintf("%v", value)`; objetos/arrays
JS llegaban como repr de Go (`map[a:1]`) en vez de JSON. Arreglado: `json.Marshal` para no-strings.
(Sin esto el scraping de datos estructurados era inservible.)
- [x] **Comentario mentiroso de `cdp_navigate`** — decía "espera Page.loadEventFired"; el código solo lanza
`Page.navigate`. Comentario corregido: NO espera carga, hay que encadenar `CdpWaitLoad`/`CdpWaitIdle`.
### P0 — CERRADOS (06/06/2026, Fase B.5) ✅
- [x] **P0.1 `render_ax_outline`** ✅ — `render_ax_outline_py_core` (puro: nodos AX → outline indentado con
`#ref`) + pipeline `cdp_perceive_outline_py_pipelines` (compone `cdp_get_ax_tree` + `trim_ax_tree` +
`render_ax_outline`). Expuesto como tool `page_perceive` del MCP (vía `fn run`). La pieza de PERCEPCIÓN.
- [x] **P0.2 Lecturas con límite** ✅ — `cdp_get_text_go_browser` (texto visible, selector opcional, `maxBytes`
con corte rune-safe). Tool `page_get_text` del MCP (default 20000 bytes). `get_html` se deja intacto
(sin romper firma); el truncado de HTML vive en la tool del MCP (200k). Decisión KISS documentada.
- [x] **P0.3 Target determinista + Chrome aislado** ✅ — `cdp_connect_target_go_browser` (elige target por id o
substring de URL) + refactor `cdp_connect.go` (helper `cdpConnectWS`). En el MCP: **default puerto 9333 =
Chrome aislado del MCP** (no el 9222 diario), `browser_launch` con `user_data_dir` dedicado, y tool
`tab_select` para fijar la pestaña determinísticamente. **Cierra el riesgo de operar sobre banca/correo.**
- [x] **Aislamiento robusto del binario** ✅ (06/06) — `chrome_launch` usa el binario real
`/usr/lib/chromium/chromium` (salta el wrapper `/etc/chromium.d/cdp` que inyectaba flags globales).
Antes el aislamiento dependía del orden de los flags.
### P1 — fiabilidad de acción
- [x] **P1.1 Verificación post-acción** ✅ (06/06) — `cdp_click` verifica visibilidad (oculto → error, no clic
en (0,0)); `cdp_type_text` verifica foco editable (sin foco → error). Validado por prueba e2e 8. PENDIENTE:
`cdp_scroll`/`press_key` con confirmación de efecto (menor).
- [x] **P1.2 `cdp_find_by_text` honesto** ✅ (06/06) — ahora devuelve error explícito en no-encontrado (antes
`("", nil)` silencioso). Validado por prueba e2e 7. (Aviso de múltiples matches: pendiente, menor.)
- [ ] **P1.3 Puente percepción→acción** — el LLM percibe en AX tree (role/name/nodeId) pero actúa por selector
CSS. Falta `click(nodeId)`/`act(#ref)` para actuar por ref del snapshot (como Playwright), sin adivinar CSS.
- [x] **P1.4 `cdp_type_text` verifica focus** ✅ (06/06) — error claro si no hay campo editable enfocado
(antes el texto iba a `document.body` en silencio). Validado por prueba e2e 8.
- [ ] **P1.5 `cdp_scroll` con target** — punto (100,100) hardcodeado; si ahí hay un navbar fixed no scrollea el
contenido. Permitir x,y o selector/elemento objetivo.
- [ ] **P1.6 `cdp_get_console`** (era tanda2 #12) — capturar consola + excepciones; el LLM detecta errores de página.
### P2 — paridad
- [ ] **P2.1** `cdp_select_option`, `cdp_file_upload`, `cdp_hover` (ver tanda 2 #16-18).
- [x] **P2.2 `storage_state`** ✅ (06/06) — cookies (filtradas por dominio) + localStorage + **sessionStorage**
+ login persistente, todo validado e2e (pruebas 4 y 6). `load` sigue exigiendo navigate-first al dominio (documentado).
> Implicación: el MCP NO es "envolver 39 funciones". Es la capa que arregla los 4 ejes (pool=estado,
> representaciones compactas=percepción, JSON real=ya hecho, verificación=acción fiable). Un MCP que solo
> expone las funciones hereda todos los defectos.
---
## Enlaces
- App orquestadora — projects/web_scraping/apps/script_navegador (CLI rápido: open/click/type/eval/html/shot/wait/tabs/launch/close/profiles + runner YAML)
- App captura — projects/web_scraping/apps/web_proxy (mitmproxy, service systemd-user puerto 8080)
- Guía LLM — projects/web_scraping/LLM_BROWSER_GUIDE.md
- Setup CDP global — projects/web_scraping/CHROMIUM_SYSTEM.md
- Dominio browser en registry — `mcp__registry__fn_search query="" domain="browser"`
## Issues / flows relacionados
- (ninguno aún — al priorizar los gaps, abrir issue por bloque o delegar a fn-constructor)
## Notas
- **Decisión de arquitectura**: el control de navegador es CDP crudo sobre Chrome/Chromium en Linux nativo,
no Playwright/Selenium. Toda capacidad reutilizable vive en `functions/browser/`; las apps solo orquestan.
- **Prioridad sugerida de gaps** (mayor impacto scraping/automatización primero):
1) Cerrar/activar pestaña individual (#2) y CRUD de iframes (#6) — bloquean automatización de SPAs reales.
2) Cookies completas (#3) — leer/borrar cookies es básico para sesiones y limpieza.
3) CRUD de ventanas (#1) — útil para multi-ventana y posicionamiento, menos urgente para scraping headless.
4) Historial (#4) y bookmarks CRUD (#5) — nicho, construir cuando haya caso concreto.
- **No inflar por inflar**: `cdp_get_text` (#7) y config genérica de prefs (#8) solo si aparece un caso real
repetido; el patrón vía `cdp_evaluate` puede ser suficiente (regla function_growth_and_self_docs).
- Cada función nueva del dominio `browser` debe declararse en `uses_functions` del `app.md` que la consuma
(el indexer no deduce deps en Go automáticamente para apps).