Files
web_scraping/CAPABILITIES_TODO.md
T
egutierrez 618e3b0295 chore: auto-commit (13 archivos)
- CAPABILITIES_TODO.md
- demo_e2e/RESUMEN.md
- demo_e2e/results/prueba_1_quotes.json
- demo_e2e/results/prueba_2_perceive.json
- demo_e2e/results/prueba_3_search.json
- demo_e2e/results/prueba_4_login_session.json
- demo_e2e/results/prueba_5_books.json
- demo_e2e/results/prueba_6_session_storage.json
- demo_e2e/results/prueba_7_find_honesto.json
- demo_e2e/results/prueba_8_verificacion.json
- ...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-06 13:20:36 +02:00

582 lines
42 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 09: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] **Gap #1 — bucle percibir→actuar por `#ref` + auto-observe** (06/06/2026, 9/9 e2e)
- `#ref` = `backendDOMNodeId` (estable, no el efímero del AX) → ref→acción + refs estables resueltos
juntos y **stateless** (sin mapa en el MCP). Validado con dump de AXNode real antes de codear.
- Funciones nuevas: `cdp_click_xy_human` (primitivo de click humanizado por coords, compartido por
selector/#ref/visión), `cdp_click_ref`, `cdp_type_ref`, `cdp_hover_ref`. `cdp_click_human`
refactorizado para usar el primitivo (un solo camino de click).
- MCP: tools `dom_click_ref`/`dom_type_ref`/`dom_hover_ref` con **auto-observe** (devuelven el outline
tras la acción). browser_mcp v0.3.0, **39 tools**.
- `render_ax_outline` mejorado (defectos F1): guard de ciclo + profundidad, sin `ljust`, renderiza `value`.
- Validado prueba e2e 9: login en the-internet **solo por `#ref`**, sin un selector CSS.
- Pendiente de la familia: política de humanización por sesión (`human`/`fast`/`instant`).
- enlace: functions/browser/cdp_{click_xy_human,click_ref,type_ref,hover_ref}.go + render_ax_outline.py
- [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.
---
## Roadmap MCP de clase — benchmark Playwright + Visión (apuntado 06/06/2026)
Objetivo: pasar de "wrapper con ~36 tools" a un MCP de clase. La métrica NO es el número de tools
(Playwright MCP ~60, Chrome DevTools MCP 26) — gran parte de los 60 de Playwright son inflado (storage
desglosado, video/tracing). Lo que sube el nivel real es **cerrar los 4 gaps de clase + dar al agente
tres sentidos de percepción→acción**.
### Los tres sentidos del agente (percepción → acción)
El agente tendrá tres vías para "ver" una página y actuar sobre ella. Cada una necesita su puente a la acción:
1. **DOM / AX tree** (hoy parcial) — `page_perceive` → outline con `#ref`. **Falta el puente `#ref` → acción.**
2. **Texto** (hoy) — `cdp_get_text` (compacto, selector opcional).
3. **Visión** (futuro, el usuario aportará los modelos) — screenshot → **OCR** (texto + bbox) + **YOLO**
(objetos + bbox) → el agente ve la página como imagen. **Puente a la acción = coordinate mouse**
(`click_xy` sobre el bounding box detectado). Por esto coordinate mouse NO es inflado: es el sentido
de la mano para el ojo visual.
### Transversal — humanización SIEMPRE en las acciones (anti-detección)
REGLA DE DISEÑO: todas las acciones de ratón/teclado del agente usan por defecto la variante humanizada
(curva de Bézier + jitter + micro-pausas variables), no el evento sintético directo. Ya existen
`cdp_click_human` y `cdp_move_mouse_human`; el `cdp_type_text` ya escribe char a char con pausa. Aplicar:
- El puente `#ref` → acción (A1) y `click_xy` (C1) deben ir **por defecto** por el camino humanizado
(mover el ratón con Bézier hasta el destino + click con micro-pausa press/release), no por
`Input.dispatchMouseEvent` seco. Exponer un flag `instant=true` solo para tests/velocidad.
- Donde falte versión humanizada (scroll, drag_xy), crear la variante con jitter/easing.
- Objetivo: que el tráfico de input sea indistinguible de un humano — es ventaja nuestra frente a
Playwright/CDP-MCP, que no humanizan. No perderla al añadir tools nuevas.
### A. Gaps de clase (los 4 que de verdad suben el nivel)
- [x] **A1 — Puente `#ref` → acción** ✅ (06/06). `cdp_click_ref`/`cdp_type_ref`/`cdp_hover_ref` + primitivo
`cdp_click_xy_human`. Tools MCP `dom_click_ref`/`type_ref`/`hover_ref`. Validado prueba e2e 9.
Hoy el outline da `#ref=44` pero el LLM no puede usarlo: tiene que volver a selector CSS. Cierra el loop
percibir↔actuar. **Gap #1 ahora.**
- [x] **A2 — Refs estables** ✅ (06/06). Resuelto stateless: el `#ref` ES el `backendDOMNodeId` (estable
mientras el nodo viva), no el `nodeId` efímero del AX. Sin mapa de estado en el MCP. `render_ax_outline` actualizado.
- [x] **A3 — Auto-observe tras acción** ✅ (06/06). Las tools `_ref` del MCP devuelven el outline AX
actualizado tras la acción (settle 400ms + perceiveOutline). Verificación implícita. Validado prueba 9.
- [ ] **A4 — Política de target segura en el MCP** (P0 seguridad). `cdp_connect_target` da la herramienta;
falta la regla: el MCP lanza Chrome propio (perfil + puerto dedicados, ya en 9333) o exige target
explícito; nunca engancha a la tab del 9222 por `match=""`.
- [ ] **A5 — Intercept / mock de red** (`Fetch.enable`) (P0 programático). Mockear APIs, bloquear recursos,
inyectar headers. La tool más valiosa de Playwright (`route`). Hoy 0.
### B. Tools valiosas del benchmark Playwright (delta real, ~12)
- [ ] **B1 verbos de formulario**: `hover`, `select_option` (`<select>`), `file_upload` (`DOM.setFileInputFiles`),
`drag`/`drop`.
- [ ] **B2 network observability**: `network_requests` (lista en vivo) + `network_request` (inspeccionar uno).
Complementa el HAR post-mortem.
- [ ] **B3 `console_messages`**: capturar consola + excepciones JS (`Runtime.consoleAPICalled`). Debug + detección.
- [ ] **B4 assertions (modo programático)**: `verify_element_visible` / `verify_text_visible` / `verify_value`.
Es el EJE VERIFICAR aplicado a recetas YAML (assert por paso).
### C. Coordinate mouse — IMPORTANTE (base del modo visión)
Reclasificado de "inflado" a importante por el usuario: es el puente entre visión (bbox de OCR/YOLO) y acción.
- [ ] **C1** `cdp_click_xy(x, y)` — click por coordenadas (sobre lo que la visión detecta).
- [ ] **C2** `cdp_move_xy` / `cdp_mouse_down` / `cdp_mouse_up` — control fino del ratón por píxeles.
- [ ] **C3** `cdp_drag_xy(x1,y1,x2,y2)` — drag por coordenadas.
- [ ] **C4** `cdp_scroll` ya existe; añadir target por coordenadas/elemento (P1.5).
### D. Misc — IMPORTANTE
- [ ] **D1** `cdp_save_pdf` (`Page.printToPDF`) — exportar página a PDF.
- [ ] **D2** `cdp_get_console` (= B3) — consola + excepciones.
- [ ] **D3** `cdp_resize` / `cdp_emulate_device` (`Emulation.setDeviceMetricsOverride`) — viewport conocido
(clave para visión: coordenadas estables) + mobile/touch.
- [ ] **D4** `cdp_nav_back`/`forward` ya existen; `wait_for` cubierto por wait_load/idle/element.
- [ ] Screenshot como **MCP image content** (hoy `page_screenshot` va a archivo → el LLM no lo ve). Prerequisito
del modo visión.
### E. VISIÓN (OCR + YOLO) — tarea futura, el usuario aporta los modelos
- [ ] **E1** Pipeline de percepción visual: `page_screenshot` (a buffer) → **OCR** (texto + bbox) + **YOLO**
(objetos UI + bbox) → estructura `{label, text, bbox}` que el agente consume como "outline visual".
- [ ] **E2** Función `ocr_page` y `detect_ui_elements` (los modelos los aportará el usuario). Dominio nuevo
(¿`vision`?) o `browser`. Probable Python (modelos ML).
- [ ] **E3** Puente visión → acción vía coordinate mouse (sección C): el agente clica el centro del bbox.
- [ ] **E4** Screenshot como image content en el MCP (D) para que el LLM también vea el píxel directamente.
- Nota: la visión es el tercer sentido — complementa, no sustituye, al AX outline (que es más barato en
tokens). Útil cuando el DOM miente (canvas, iframes cross-origin, apps que pintan en `<canvas>`).
### F. Defectos de calidad a corregir en lo ya construido (del coach)
- [x] **F1** ✅ (06/06) `render_ax_outline`: guard de ciclo (`visited`) + límite de profundidad (`_MAX_DEPTH=60`);
`ljust(60)` eliminado; renderiza el `value` de inputs (`= 'texto'`). Hecho en la misma edición del `#ref`.
- [ ] **F2** `cdp_get_html` sin límite: **documentar** que es "crudo a propósito" (la vía compacta es
get_text + outline).
- [ ] **F3** `cdp_connect_target` con `match=""`: documentar que es inseguro fuera del MCP; la política
segura vive en A4.
- [ ] **F4** Limpieza de recursos: isolated worlds que crea `cdp_eval_in_frame`, conexiones del pool del MCP.
### G. Inflado de Playwright — NO construir salvo necesidad concreta
Storage granular desglosado (cookie/localStorage/sessionStorage × get/set/delete/list/clear = 17 tools;
nosotros lo hacemos con 6), video/tracing/highlight (9 tools de debug visual de testing), `generate_locator`,
`run_code_unsafe` (= nuestro `page_eval_js`). Replicar esto sube el contador a ~45 sin añadir poder.
### Telemetría
`call_monitor` ya existe — cada tool del MCP encaja sin trabajo extra.
---
## 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).