Skip to main content
Glama
TrueNix

agent-browser

by TrueNix

agent-browser

Un navegador local endurecido para agentes de IA. Cero dependencias. Conduce el Chrome que ya tienes a través del Protocolo DevTools, extrae Markdown eficiente en tokens y no se marca trivialmente como automatización.

Superficie

Instalación

Úsalo para

Servidor MCP

npx -y @truenix/agent-browser mcp

Claude Code, Cursor, Codex, cualquier cliente MCP

CLI

npx -y @truenix/agent-browser markdown <url>

shells, scripts, CI

Biblioteca

import { withBrowser } from '@truenix/agent-browser'

tu propio código Node

Plugin DSH / Cordis

fila de composición

herramientas nativas en un entorno DSH

npx -y @truenix/agent-browser markdown https://news.ycombinator.com

Todo se ejecuta localmente. Sin cuenta, sin clave API, sin servicio remoto, sin cuota.

Por qué

Alimentar a un agente con HTML crudo desperdicia la mayor parte de su contexto. Medido en páginas reales:

Y un navegador que se anuncia como automatización es bloqueado, degradado o recibe contenido diferente, lo que corrompe silenciosamente lo que el agente haya concluido.

Benchmark de sitios pesados (en vivo, curl vs markdown)

sitio

HTML crudo

markdown

--links text

ahorro

en.wikipedia.org/wiki/WebAssembly

927 kB

120 kB

55 kB

7.7× / 16.8×

react.dev

273 kB

11 kB

8.7 kB

23.8× / 31.4×

nextjs.org

323 kB

10 kB

7.3 kB

32.0× / 44.2×

github.com/trending

648 kB

69 kB

13 kB

9.3× / 48.9×

apple.com

360 kB

4.6 kB

3.5 kB

78.6× / 102.8×

news.ycombinator.com

35 kB

10 kB

3.2 kB

3.4× / 11.0×

Regenera ambos gráficos y las mediciones subyacentes de esta tabla con npm run charts — mide en vivo y luego dibuja los PNG usando el propio agent-browser, por lo que los números de las imágenes no pueden desviarse de lo que produce el código.

Gastar menos tokens en la misma página

Las URLs son un tercio de la salida en un artículo de Wikipedia y cuatro quintos en github.com/trending. Cuando el agente está leyendo en lugar de navegar, elimínalas:

agent-browser markdown https://github.com/trending --links text      # 69 kB -> 13 kB
agent-browser markdown https://en.wikipedia.org/wiki/Rust --links relative

modo

renderiza

úsalo cuando

inline (predeterminado)

[text](https://site/page)

el agente navegará a continuación

relative

[text](/page)

rastreo del mismo sitio; conserva los destinos, elimina el origen

text

text

lectura, resumen, respuesta a preguntas

Benchmark de sitios pesados

Costo de tokens: HTML crudo vs agent-browser Costo de tokens por chequeo YC (lectura de HN + YC Blog con --links text, 51.1k → 1.5k tokens, +500 de salida) — precios de lista de agosto de 2026: Anthropic Haiku 4.5 $1/$5, Sonnet 5 $3/$15, Opus 5 $5/$25 • OpenAI GPT-4o $2.50/$10, GPT-4o mini $0.15/$0.60

Páginas que se renderizan después de la carga

La navegación espera el evento load, impulsada por eventos del ciclo de vida de CDP en lugar de un bucle de sondeo y un retraso fijo. En páginas reales, eso es 2–3× más rápido para una salida idéntica byte a byte:

página

--wait load (predeterminado)

antiguo asentamiento fijo de 250 ms

example.com

7 ms

258 ms

github.com/trending

115 ms

340 ms

nextjs.org

160 ms

365 ms

news.ycombinator.com

218 ms

466 ms

Una aplicación renderizada en el cliente cuyo contenido llega después de la carga necesita una señal real, no una suposición mayor — el antiguo asentamiento de 250 ms también perdía ese contenido:

agent-browser markdown https://some-spa.example --wait idle

modo

espera

úsalo cuando

domcontentloaded

el DOM está analizado

solo necesitas el marcado que vino en el HTML

load (predeterminado)

el evento load

casi siempre

idle

la red se queda en silencio

el resultado parece un caparazón vacío

Related MCP server: Browser-MCP Navigator

Cualquier otro entorno

Si tu framework habla JSON Schema, puede controlar agent-browser sin que este proyecto sepa que el framework existe. agent-browser tools imprime el catálogo en la forma que necesites:

agent-browser tools                     # MCP:       {name, description, inputSchema}
agent-browser tools --format openai     # OpenAI:    {type:"function", function:{...}}
agent-browser tools --format anthropic  # Anthropic: {name, description, input_schema}

Cada herramienta se ejecuta de cualquier manera, según lo que tu entorno pueda hacer:

import { findTool, TOOLS } from '@truenix/agent-browser/tools';
import { withBrowser } from '@truenix/agent-browser';

// in-process: the harness can hold a CDP session
const tool = findTool('browser_markdown');
const text = await withBrowser({}, async (session) => {
  await session.navigate('https://example.com');
  return tool.run(session, { url: 'https://example.com', links: 'text' });
});

// out-of-process: the harness can only run a command (sandboxes, shells)
tool.cli({ url: 'https://example.com', links: 'text' });
// => ['markdown', 'https://example.com', '--links', 'text']

Instalación

Como servidor MCP

claude mcp add browser -- npx -y @truenix/agent-browser mcp
{
  "mcpServers": {
    "browser": {
      "command": "npx",
      "args": ["-y", "@truenix/agent-browser", "mcp"]
    }
  }
}

Herramientas: browser_markdown, browser_text, browser_html, browser_links, browser_screenshot, browser_evaluate, browser_accessibility_tree, browser_pdf, browser_probe.

Cada superficie expone las mismas nueve herramientas de un catálogo compartido, por lo que la CLI, el servidor MCP y el plugin DSH nunca pueden divergir.

Como biblioteca

npm install @truenix/agent-browser
import { withBrowser } from '@truenix/agent-browser';

const md = await withBrowser({}, async (session) => {
  await session.navigate('https://example.com');
  return session.markdown();
});

Como plugin DSH / Cordis

Una línea (recomendado — se conecta automáticamente cuando quieres decir DSH):

npx -y @truenix/agent-browser install          # adds the bundle to ~/.dsh/profiles/web/package.json, then pnpm install (the mount ships in the package's own cordis.patch.yml layer)
# npx -y @truenix/agent-browser install --profile web --dry-run  # preview
# npx -y @truenix/agent-browser uninstall      # remove again

Reinicia dsh — las nueve herramientas browser_* aparecen como herramientas nativas. Ningún manejador se ejecuta con un simple npm install; se requiere un install intencional.

Manual (si prefieres editar la composición tú mismo):

npm i -g @truenix/agent-browser
# or inside the harness checkout: pnpm add @truenix/agent-browser

Requiere Node ≥ 18 y una instalación de Chrome/Chromium. Luego añade a la composición del host (el registro de herramientas vive en el host, no por agente):

# ~/.dsh/profiles/web/cordis.patch.yml  — persists for every web session
- insert:
  - id: agent-browser
    name: '@truenix/agent-browser/cordis'
    config:
      timeoutMs: 180000   # per-tool call budget; default respects AGENT_BROWSER_BIN / ENDPOINT
      # cli: 'npx -y @truenix/agent-browser'  # override only if needed

Forma corta (cuando la composición ya envuelve insert):

- '@truenix/agent-browser/cordis':
    timeoutMs: 180000

Anulaciones de entorno: AGENT_BROWSER_BIN (binario de Chrome), AGENT_BROWSER_ENDPOINT (conectar a un navegador de larga duración mediante --endpoint), o config.cli.

CLI

agent-browser <command> [options]

  markdown <url>     Extract the whole page as Markdown (main content by default)
  text <url>         Visible text only
  html <url>         Full serialized DOM after JavaScript runs
  links <url>        Every anchor as JSON
  screenshot <url>   PNG/JPEG   (-o file, --full)
  pdf <url>          PDF        (-o file)
  a11y <url>         Filtered accessibility tree
  eval <url> <expr>  Evaluate JS, return only its value (cheapest)
  probe              Browser, GPU and capability report
  mcp                Run as an MCP server on stdio
  tools              Print tool schemas (--format mcp|openai|anthropic)

Opciones: --headful, --no-stealth, --block-images, --gpu/--no-gpu, --width, --height, --viewport WxH, --main, --raw, --links, --max-rows, --limit, --wait, --settle, --full, --endpoint <ws>, --timeout, --json, -o.

markdown para leer, eval para buscar algo

El error más caro que un agente comete con esta herramienta es renderizar una página entera para responder a una pregunta de una línea. Tres preguntas específicas en tres páginas pesadas cuestan 235 bytes mediante eval, frente a 77 kB de Markdown:

agent-browser eval https://github.com/trending \
  "JSON.stringify(Array.from(document.querySelectorAll('article h2 a')).slice(0,3).map(a=>a.innerText.trim()))"
# ["openai / codex","mattpocock / skills","affaan-m / ECC"]   -> 58 bytes

Usa innerText, no textContent. textContent devuelve el espacio en blanco crudo de la fuente ("openai /\n\n codex"); innerText da lo que realmente se renderiza ("openai / codex"). Un agente que se equivoque en eso paga un viaje de ida y vuelta de reintento completo, que cuesta mucho más que los bytes que ahorró.

Usa markdown cuando realmente necesites leer, resumir o buscar en la página.

Cada invocación lanza su propio navegador (~1.4 s). Para varias búsquedas seguidas, mantén un navegador vivo y apunta --endpoint a él — las mismas tres preguntas tardan 5.9 s en tres arranques en frío, 3.2 s contra uno en caliente.

--endpoint se conecta a un navegador ya en ejecución en lugar de lanzar uno — útil para reutilizar un único navegador de larga duración en muchas llamadas.

Memoria

El mínimo de Chrome es de unos 420 MB de PSS en 18 procesos antes de cargar nada, y ese mínimo es de Chrome, no de este paquete — el ajuste de banderas lo mueve ~5%, y la bandera que lo mueve más (--enable-low-end-device-mode) informa navigator.deviceMemory: 2 junto a 24 núcleos, una máquina imposible y exactamente el tipo de inconsistencia que hace que un navegador sea marcado. Así que la palanca es menos navegadores, no más pequeños.

El servidor MCP mantiene un navegador y da a cada llamada de herramienta su propio contexto aislado. Tres páginas pesadas obtenidas simultáneamente:

PSS máximo

procesos

tiempo real

un navegador por llamada

1289 MB

44

2114 ms

un navegador, 3 contextos

654 MB

20

2049 ms

El aislamiento no cambia — el contexto de navegador por llamada era siempre lo que lo proporcionaba. El navegador se apaga después de 30 s de inactividad (AGENT_BROWSER_IDLE_MS, 0 para cerrar inmediatamente), por lo que un servidor de larga duración no se queda con 420 MB entre conversaciones. Una llamada repetida mientras está caliente se salta el lanzamiento por completo y se ejecuta aproximadamente el doble de rápido.

La compensación: las tareas comparten un árbol de procesos, por lo que un fallo a nivel de navegador se lleva todo lo que está en vuelo en lugar de una sola llamada. Un navegador muerto se detecta y se relanza en la siguiente llamada. Si necesitas aislamiento de radio de explosión por tarea, usa withBrowser, que sigue dando a cada llamada su propio navegador.

import { withPooledSession, shutdownPool } from '@truenix/agent-browser/pool';

await withPooledSession({}, async (session) => {
  await session.navigate('https://example.com');
  return session.markdown();
});
await shutdownPool();   // or let it idle out

Para la CLI, cada invocación es su propio proceso, por lo que reutilizar significa apuntar --endpoint a un navegador que mantengas vivo tú mismo.

El daemon: un navegador para cada llamada de CLI

Cada invocación de CLI es su propio proceso, por lo que por defecto cada una arranca en frío su propio Chrome — diez llamadas concurrentes significan diez navegadores. --daemon comparte un navegador residente en su lugar, y cada llamada sigue teniendo su propio contexto aislado:

agent-browser markdown https://example.com --daemon
agent-browser daemon --status
agent-browser daemon --stop

tiempo real

procesos máximos de Chrome

3 búsquedas secuenciales, sin daemon

5330 ms

—

3 búsquedas secuenciales, --daemon

3254 ms

—

10 llamadas concurrentes, sin daemon

1443 ms

140

10 llamadas concurrentes, --daemon

1144 ms

32

Es opcional (--daemon, o AGENT_BROWSER_DAEMON=1) porque iniciar un proceso en segundo plano que sobrevive a tu comando es un efecto secundario que vale la pena pedir. Se inicia bajo demanda y sale después de cinco minutos de inactividad (AGENT_BROWSER_DAEMON_IDLE_MS). Establecer la variable de entorno también enruta el servidor MCP hacia él, lo que vale la pena hacer cuando varios clientes MCP comparten una máquina.

--headful, --width, --height, --block-images y --gpu se fijan cuando un navegador se lanza, por lo que uno compartido no puede respetarlos. Pasar cualquiera de ellos gana: esa llamada obtiene silenciosamente su propio navegador privado, y lo dice en stderr.

Cuatro modos de fallo en torno a los que está construido, cada uno verificado por pruebas:

  • Exactamente un daemon. Diez primeros llamadores simultáneos generan uno; nueve pierden la carrera para vincular el socket y salen antes de lanzar nada. Vincular el socket es el bloqueo, por lo que no hay archivo de bloqueo que pueda quedar obsoleto.

  • El socket abierto es el contador de referencias. Un cliente mantiene su conexión mientras trabaja, por lo que un cliente eliminado con SIGKILL aún libera — el kernel cierra el socket. Un mensaje de "por favor libera" habría filtrado una referencia para siempre.

  • Salida por inactividad, para que no se quede con ~420 MB entre conversaciones.

  • Un daemon eliminado con SIGKILL no deja nada varado: su marcador de perfil nombra su pid, por lo que el barrido de abajo recupera el navegador en el siguiente lanzamiento.

Limpieza después de sí mismo

Cada lanzamiento escribe un marcador de propietario en su perfil temporal y barre los perfiles cuyo propietario ha muerto, matando el navegador que aún está adjunto a ellos. Combinado con los manejadores de SIGINT/SIGTERM/SIGHUP, eso da:

cómo termina la llamada

procesos huérfanos

después del siguiente lanzamiento

normalmente

0

0

SIGTERM / SIGINT

0

0

SIGKILL (incapturable)

1 navegador

0

Por lo tanto, un cierre forzado cuesta como máximo un navegador abandonado, no uno por llamada interrumpida. Diez llamadas CLI concurrentes, repetidas, no dejan nada atrás. Un navegador vivo nunca se elimina — su proceso propietario sigue en ejecución, y un perfil no atribuible se deja solo hasta que nada lo tenga abierto y haya estado inactivo durante 60 s.

Detección de bots

Pruébalo tú mismo: npm run test:bot. Último resultado:

detector

resultado

bot.sannysoft.com

31 aprobados, 0 fallidos

bot-detector.rebrowser.net

6 verdes, 0 rojos, runtimeEnableLeak: clean

deviceandbrowserinfo.com

isBot: false, 0 de 22 comprobaciones marcadas

Chrome headless puro falla en cuatro filas de sannysoft (HEADCHR_UA, CHR_MEMORY, WebGL SwiftShader, UA antigua) y se informa como bot.

Un detector inalcanzable cuenta como SKIP, nunca como un aprobado, y la puerta requiere tres aprobados alcanzables — por lo que un día en que los sitios de prueba estén caídos no puede confundirse con éxito.

El entorno importa más que los parches

El mismo código, medido en dos lugares:

esta estación de trabajo

runner de GitHub Actions

IP

residencial

centro de datos

GPU

real (NVIDIA)

ninguna → SwiftShader

bot.sannysoft.com

31 aprobados, 0 fallidos

30 aprobados, 1 fallido (WebGL Renderer)

bot-detector.rebrowser.net

6 verdes, 0 rojos

6 verdes, 0 rojos

deviceandbrowserinfo.com

isBot: false

isBot: true (hasSuspiciousWeakSignals)

Cada señal a nivel de CDP permanece limpia en ambos — esa parte es código, y el código es correcto. Lo que cambia el veredicto es el entorno: un ASN de centro de datos más renderizado por software activa un compuesto de señales débiles que ninguna cantidad de parches de huella digital aborda.

Esta es la forma honesta del problema. Endurecer el navegador elimina las señales triviales. Dónde lo ejecutes decide el resto.

Qué hace el endurecimiento y por qué

Cada elemento provino de un detector que nos decía que estábamos equivocados:

  • Sin --enable-automation. Esa bandera — que Puppeteer y Playwright añaden — es la que establece navigator.webdriver = true. El CDP puro no la establece, por lo que permanece false sin nada parcheado.

  • Sin Runtime.enable. Es la señal CDP más ruidosa y alimenta el detector clásico de consola/Error.stack. Runtime.evaluate funciona bien sin ella.

  • La ventana y la pantalla se mueven juntas. --window-size sin --ozone-override-screen-size da outerWidth > screen.width, lo cual es físicamente imposible — una señal más fuerte que el headless simple.

  • Sin anulación de métricas de dispositivo por defecto. El habitual 1280×720 es el viewport por defecto de Playwright y los detectores lo marcan por nombre. Establece --viewport solo si lo necesitas.

  • GPU real cuando esté disponible, dando un renderizador genuino ANGLE (NVIDIA …) en lugar de SwiftShader.

  • UA establecida al inicio, no solo a través de CDP. Emulation.setUserAgentOverride no llega a los Web Workers, por lo que un worker sigue informando la UA headless mientras la página informa la limpia (hasInconsistentWorkerValues).

  • Sin anulación de acceptLanguage. CDP deriva navigator.languages dividiendo ese encabezado, por lo que "en-US,en;q=0.9" se convierte en ["en-US","en;q=0.9"] — un valor q donde ninguno puede existir legalmente, y otro desajuste página/worker. --lang lo hace correctamente.

  • Client Hints derivados de la propia versión del binario, por lo que Sec-CH-UA no puede discrepar con navigator.userAgent.

  • Contexto de navegador aislado por sesión, eliminado al cerrar — estado limpio por tarea sin un segundo proceso de navegador.

La lección recurrente: la consistencia supera a la cobertura. Cuatro de esos son casos donde la suplantación parcial hizo la detección más fácil, detectada solo al ejecutar detectores reales.

Por qué la suplantación de WebGL está desactivada por defecto

spoofWebgl existe y está implementado cuidadosamente — un Proxy alrededor de getParameter nativo, por lo que Function.prototype.toString aún informa [native code]. Está desactivado, porque la medición dice que sale mal. De test/webgl-spoof-experiment.mjs:

brazo

renderizador declarado

maxTexture

extensiones

sannysoft

veredicto

GPU real, sin suplantación

NVIDIA

32768

37

0 fallidos

isBot: false ✅

SwiftShader, honesto

SwiftShader

8192

35

1 fallido

isBot: false ✅

SwiftShader + suplantación

NVIDIA

8192

35

0 fallidos

isBot: true ❌

Reclamar hardware que no tienes arregla una fila cosmética y falla el detector compuesto: el script inyectado no llega a los Web Workers, por lo que el worker aún informa SwiftShader, y MAX_TEXTURE_SIZE permanece en el valor de software mientras la cadena del renderizador afirma una GPU discreta.

SwiftShader honesto pasa. Una mentira convincente no. Dale al navegador una GPU real en su lugar — es gratis.

Lo que esto NO hace

La detección a nivel de huella digital es todo el alcance. No derrota, y no intenta derrotar:

  • Huella digital TLS/JA3-JA4 y HTTP/2 — decidida antes de que se ejecute cualquier JavaScript

  • Reputación de IP — centro de datos vs ASN residencial, a menudo el verdadero bloqueador

  • Análisis de comportamiento — trayectorias del ratón, tiempos, permanencia

Los productos comerciales de desafío se apoyan en eso, por lo que "pasa la puerta" significa no marcado trivialmente como automatización, nunca indetectable. Destinado a tus propios sitios, pruebas, trabajo de accesibilidad y navegación ordinaria de agentes.

Memoria

Una pila de Chrome cuesta aproximadamente 450 MB. La palanca es la arquitectura, no las banderas: ejecuta un navegador y muchos contextos aislados en lugar de un navegador por tarea. Inicia un navegador una vez, luego apunta cada llamada a él con --endpoint / AGENT_BROWSER_ENDPOINT. --block-images ayuda para trabajo de texto.

Cero dependencias

dependencies está vacío, incluido el transporte WebSocket.

El WebSocket global de Node (WHATWG) no puede enviar encabezados de solicitud, que cualquier endpoint CDP autenticado o con proxy necesita, y undici no es importable de forma independiente. Así que src/ws.mjs implementa RFC 6455 directamente sobre node:http(s) — handshake, enmascaramiento, fragmentos de continuación, longitudes de 64 bits, ping/pong — que es todo lo que CDP requiere.

El convertidor de Markdown recorre el DOM con una pila explícita, manteniendo la profundidad de llamadas JS en O(1) independientemente del anidamiento, y usa innerText nativo para nodos en línea a nivel de hoja. Eso lo hace seguro contra recursión en documentos profundamente anidados y notablemente más rápido en páginas grandes.

Entorno

AGENT_BROWSER_BIN

ruta a un binario de Chrome/Chromium

AGENT_BROWSER_ENDPOINT

conectar a este endpoint CDP en lugar de lanzar

Requisitos

Node ≥ 18 y una instalación de Chrome/Chromium. Sin paso de compilación.

Créditos

El endurecimiento de este proyecto se deriva casi por completo de la investigación de detección publicada por otras personas — ver CREDITS.md. Agradecimientos especiales a rebrowser-bot-detector, bot.sannysoft.com, deviceandbrowserinfo.com y Camoufox por mostrar cómo se hace correctamente.

Licencia

MIT

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    B
    maintenance
    MCP server for browser automation with anti-detection. Scout pages, find elements, interact with websites, and monitor network traffic from any AI client that supports the Model Context Protocol.
    21
    1
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    A fault-tolerant, stealth-enabled Model Context Protocol (MCP) server for web searching and content fetching. Built for AI Agents (Cursor, Claude Code, OpenCode), it uses a stealth browser engine to fetch pages, dynamically handles SPAs/React, and converts bloat into token-optimized Markdown.
    2
    13 npm
    3
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    MCP server that drives your real Chrome/Edge/Opera browser through a Chrome extension and DevTools Protocol, preserving logins and session state, and can also perform OS-level mouse and keyboard input behind approval.
    51
    34 PyPI
    2
    MIT