blowsh-mcp

Kit de herramientas MCP con transporte Docker para búsqueda web, extracción, rastreo y extracción de enlaces con renderizado JS mediante Browsh. Sin claves API, protegido contra SSRF, MIT.

Documentación

blowsh-mcp

Servidor de Protocolo de Contexto Modelo (MCP) para Navegación de Terminal con Capacidad JavaScript usando Browsh


¿Qué es blowsh-mcp?

blowsh-mcp es un servidor de Protocolo de Contexto Modelo (MCP) que expone el poder de Browsh—un navegador de terminal con capacidad total de JavaScript—a cualquier Agente de IA, agente de IDE o cliente MCP. Este proyecto permite que tu IA obtenga y renderice cualquier página web moderna, incluidas aquellas que requieren JavaScript, y reciba el resultado como texto plano fácil de analizar, HTML o Markdown.

Mnemónico: "blowsh" = servidor MCP impulsado por Browsh.


Características Principales

  • Herramienta fetch_web: Herramienta unificada para extracción de texto plano legible, HTML o Markdown (después del renderizado completo de JS). Soporta extracción CSS selector, límites de salida max_chars, sondeo de estabilización JS wait_ms, además de extras de paridad DonSeTch: focus (relevancia BM25 — reduce tokens 50-80%), toc/section (esquema económico → sección específica), sonda must_contain (COINCIDENCIA/NO-COINCIDENCIA + extractos, ~60 tokens), archive (resurrección de Wayback auto/only) y stitch (seguir rel=next hasta 6 partes, mismo host).
  • Herramienta search_web: Descubre páginas mediante 4 motores renderizados (DuckDuckGo HTML, Bing, Brave, Mojeek) fusionados por consenso entre motores — además de verticales de intención (código→GitHub, paper→arXiv, noticias→HN, entidad→Wikipedia). Soporta query_variants (formulaciones alternativas en paralelo), intent (auto/web/código/paper/noticias/entidad), deadline_ms (presupuesto estricto), paginación y enrich (markdown de los 3 principales).
  • Herramienta crawl_web: Rastreo consciente de sitemaps — dos fases (mapa + contenido), frontera clasificada por enfoque (BM25-lite), ritmo del Gobernador, robots.txt, tokens de reanudación (30 min), delta since_last, globs (include/exclude), same_host, presupuestos (max_pages/max_total_chars/deadline_s).
  • Herramienta extract_links: Lista hipervínculos (texto + URL absoluta) de cualquier página renderizada con JS para seguir la navegación.
  • Herramienta fetch_web_batch: Obtiene hasta 10 URLs en una sola llamada con aislamiento de errores por URL.
  • Protección SSRF: Rechaza solicitudes a direcciones de bucle local, privadas, de enlace local o reservadas (resueltas por DNS), protegiendo el navegador del lado del servidor.
  • Documentación de herramientas optimizada para IA: Entradas, salidas y casos de uso ilustrados diseñados para automatización fluida de agentes. Las herramientas lanzan errores estructurados con códigos de estado HTTP (isError en respuestas MCP).
  • Gestión robusta de Browsh: Lanza Browsh una vez, lo mantiene en ejecución, reutiliza un singleton ligero en RAM/CPU, apagado elegante al salir.
  • Caché de renderizado en memoria con TTL: Las búsquedas repetidas se sirven al instante sin volver a renderizar.
  • Diseñado para PaaS, Nube, herramientas de IA locales y agentes de IDE.

Enlaces


Cómo Funciona

  1. La IA/Agente realiza una solicitud MCP: fetch_web (URL única, ahora con enfoque/toc/sección/debe_contener/archivo/unión), search_web (consulta + variantes/intención), crawl_web (semilla + presupuestos), extract_links (URL) o fetch_web_batch (hasta 10 URLs).
  2. blowsh-mcp lanza Browsh en modo servidor HTTP (en el primer uso) y lo reutiliza para todas las llamadas posteriores.
  3. blowsh-mcp solicita la salida cruda de Browsh, usando X-Browsh-Raw-Mode: PLAIN (para texto), DOM (para HTML), u obtiene HTML y luego lo convierte a Markdown; para crawl_web, recorre sitemaps + frontera mediante el mismo singleton de Browsh + XML de sitemap sobre axios.
  4. La página (después de la ejecución completa de JS) se devuelve como texto plano de terminal, DOM HTML enriquecido o Markdown limpio—la IA/agentes eligen el tipo de salida para coincidir con el procesamiento posterior; el rastreo devuelve {pages, map, queued, skipped, stop}.
  5. Los resultados se almacenan en caché en memoria (TTL) para que las búsquedas repetidas sean instantáneas; cada solicitud se verifica con SSRF antes de llegar al navegador.

Inicio Rápido (Docker — Imagen Preconstruida)

La imagen se publica en Registro de Contenedores de GitHub y se reconstruye automáticamente en cada push de main mediante GitHub Actions — sin necesidad de Firefox/Browsh/html2markdown en el host:

docker pull ghcr.io/mokhtarabadi/blowsh-mcp:latest
docker run --rm -i ghcr.io/mokhtarabadi/blowsh-mcp:latest

El indicador -i es obligatorio: el servidor MCP habla JSON-RPC sobre stdin/stdout. Mantenlo interactivo y canaliza solicitudes, o apunta tu cliente MCP hacia él (consulta Configuración de Cliente de IA a continuación).


Ejemplo de Uso

Desde Claude, Cursor o cualquier agente habilitado para MCP:

{
  "tool": "search_web",
  "params": { "query": "bitcoin price today", "max_results": 5 }
}
// → Ranked results with URLs + snippets → feed top URL to fetch_web

{
  "tool": "fetch_web",
  "params": { "url": "https://coindesk.com/price/bitcoin/", "type": "plain" }
}
// → Returns readable plain text (live price as text table, etc)

{
  "tool": "fetch_web",
  "params": { "url": "https://coindesk.com/price/bitcoin/", "type": "markdown", "selector": "main", "wait_ms": 3000 }
}
// → Markdown of <main> only, after JS settles ("# Bitcoin Price\n\n| Time | Price | ...")

{
  "tool": "extract_links",
  "params": { "url": "https://example.com", "limit": 20 }
}
// → [{"text": "Learn more", "url": "https://iana.org/domains/example"}, ...]

{
  "tool": "fetch_web_batch",
  "params": { "urls": ["https://a.com", "https://b.com"], "type": "markdown" }
}
// → Per-URL results; a failing page never fails the batch

{
  "tool": "fetch_web",
  "params": { "url": "https://example.com/long-docs", "type": "markdown", "focus": "authentication error handling", "toc": false }
}
// → Only BM25-relevant blocks (50-80% shorter)

{
  "tool": "fetch_web",
  "params": { "url": "https://example.com/article", "type": "markdown", "must_contain": "/CVE-2026-\\d+/" }
}
// → MATCH/NO-MATCH + 3 excerpts (~60 tokens) instead of full page

{
  "tool": "crawl_web",
  "params": { "url": "https://docs.example.com", "mode": "full", "focus": "authentication", "max_pages": 20 }
}
// → {pages:[{url, title, kind, markdown, chars, quality}], map, queued, stop, resume}

La IA recibe:

  • Con type: plain: texto plano legible puro (tablas, listas, contenido del cuerpo principal; ideal para NLP/resumen o ingesta de contexto de terminal).
  • Con type: html: el marcado HTML completo, después de todo el JavaScript. Úsalo para análisis de elementos, construcción de grafos de enlaces, raspados complejos, etc.
  • Con type: markdown: una versión Markdown limpia—mejor para fragmentos de contexto de LLM, pipelines semánticos y flujos de trabajo amigables con IA.
  • Con toc: true: solo esquema de encabezados (# Table of Contents); con section: "Heading" el markdown de esa sección.
  • Con must_contain: veredicto de sonda (MATCH/NO-MATCH) + ≤3 extractos.
  • Con archive: "auto": instantánea de Wayback etiquetada con fecha cuando falla la búsqueda en vivo.
  • Con stitch: true: artículo multipágina unido con marcadores *(part N)*.
  • Los errores son estructurados: las respuestas MCP establecen isError: true con un mensaje FetchError que incluye el estado HTTP cuando está disponible.

Estructura del Proyecto

  • src/server.ts — Servidor MCP que expone herramientas (5 herramientas en v2.3.0).
  • src/browshManager.ts — Lanzar, monitorear, apagar Browsh.
  • src/tools/fetchWeb.ts — fetchWeb (texto plano/html/markdown/pdf; selector/max_chars/wait_ms + enfoque/toc/sección/debe_contener/archivo/unión).
  • src/tools/searchWeb.ts — search_web (consenso DDG+Bing+Brave+Mojeek + verticales de intención + variantes_de_consulta + fecha_límite).
  • src/tools/crawlWeb.ts — crawl_web (descubrimiento de sitemap, frontera BM25-lite, ritmo del Gobernador, tokens de reanudación, desde_última).
  • src/tools/extractLinks.ts — extract_links (hipervínculos del DOM renderizado).
  • src/tools/fetchWebBatch.ts — fetch_web_batch (multi-URL, aislamiento de errores por URL).
  • src/tools/html2markdownManager.ts — Envoltorio para CLI html2markdown.
  • src/ssrf.ts — Protección SSRF (bloquea objetivos privados/bucle local/reservados).
  • src/cache.ts — Caché de renderizado TTL en memoria.
  • src/extract.ts — Extracción de contenido principal, ayudantes de selector, truncamiento, además de enfoque BM25, toc/sección, debe_contener, ayudantes de unión.
  • src/errors.tsFetchError + formato de mensajes.
  • README.md — Este archivo.
  • Dockerfile — Contenedor multietapa (compila TS, agrupa Firefox, Browsh, html2markdown).
  • .github/workflows/docker-publish.yml — CI/CD: compila y publica la imagen en ghcr.io en main/v*.
  • .env — Anulaciones de configuración. Consulta .env.example para todas las opciones.

Instalación

Requisitos:

  • Node.js >= 20.18
  • Firefox instalado y en PATH
  • CLI Browsh instalado y en PATH
  • CLI html2markdown instalado y en PATH
    • En Debian/Ubuntu, instala con:
      wget -O /tmp/html2markdown.deb "https://github.com/JohannesKaufmann/html-to-markdown/releases/download/v2.5.2/html2markdown_2.5.2_linux_amd64.deb"
      sudo apt-get install -y /tmp/html2markdown.deb
      rm /tmp/html2markdown.deb
      
    • O usa el binario preconstruido para tu sistema operativo desde la página de versiones.

¿Prefieres Docker? Omite las instalaciones del lado del host por completo—la imagen multietapa incluye Firefox, Browsh y html2markdown. El camino más rápido es la imagen publicada (ghcr.io/mokhtarabadi/blowsh-mcp:latest, consulta Inicio Rápido); para compilarla tú mismo:

docker build -t blowsh-mcp:latest .
docker run --rm -i blowsh-mcp:latest
git clone https://github.com/mokhtarabadi/blowsh-mcp.git
cd blowsh-mcp
npm install
npm run build

Ejecutar el servidor MCP

Después de compilar, inicia el servidor usando:

node dist/server.js

Reemplaza dist/server.js con la ruta correcta si tu salida de compilación difiere.

Crea un archivo .env según sea necesario para la configuración. Por ejemplo:

MCP_TRANSPORT=stdio
BROWSH_FIREFOX_PATH=/usr/bin/firefox-esr
HTML2MARKDOWN_PATH=html2markdown
CACHE_TTL_MS=300000
BROWSH_REQUEST_TIMEOUT_MS=30000
ALLOW_PRIVATE_URLS=false
NODE_ENV=production
  • BROWSH_FIREFOX_PATH te permite personalizar el ejecutable de Firefox usado por Browsh durante la operación headless/HTTP.
  • HTML2MARKDOWN_PATH te permite especificar una ruta personalizada al binario html2markdown (predeterminado: html2markdown en PATH).
  • CACHE_TTL_MS, BROWSH_REQUEST_TIMEOUT_MS y ALLOW_PRIVATE_URLS ajustan la caché de renderizado, el tiempo de espera por solicitud y la protección SSRF respectivamente.
  • El puerto/host HTTP de Browsh NO son configurables.

Documentación del Proyecto

ArchivoAudienciaPropósito
AGENTS.mdAgentesReglas operativas, salvaguardas, ciclo de vida de tareas
DESIGN.mdTodosLenguaje de diseño de respuestas/salidas MCP
docs/architecture.mdDesarrolladoresVisión general del sistema, cableado de componentes
docs/data_model.mdDesarrolladoresEsquemas de entrada/salida de herramientas y modelo de errores
docs/conventions.mdDesarrolladoresEstándar de fecha/hora, pautas SOLID
CHANGELOG.mdTodosHistorial de versiones (Mantener un Changelog)
tasks/EquipoArchivos de tareas Kanban (backlog → archivo)

Este README es el punto de entrada orientado al usuario; las reglas orientadas a agentes viven en AGENTS.md y son lectura obligatoria antes de cualquier implementación.


API de Herramientas

NombreParamsCaso de uso/Descripción para IA
fetch_web{ url, type: "plain"|"html"|"markdown"|"pdf", selector?, max_chars?, wait_ms?, focus?, toc?: boolean, section?, must_contain?, archive?: "auto"|"only"|"off", stitch?: boolean, deadline_ms?: number, tier?: "auto"|"1"|"2", links?: boolean, media?: boolean, since_last?: boolean, offset?: number }Obtiene una página tras el renderizado de JS como texto/HTML/Markdown. selector (CSS) extrae solo el elemento coincidente; max_chars limita la salida; wait_ms sondea hasta que JS se estabiliza. focus filtra bloques relevantes por BM25 (50-80% más corto); toc devuelve el esquema, section devuelve el contenido de un encabezado; must_contain la sonda devuelve MATCH/NO-MATCH + extractos; archive resucita enlaces muertos vía Wayback; stitch sigue rel=next hasta 6 partes; deadline_ms presupuesto duro; tier auto/1/2; links/media alternadores; since_last detección de contenido sin cambios; offset reanudar. type: pdf descarga el PDF directamente (máx. 20 MB) y extrae texto vía pdftotext — selector/wait_ms/max_chars no aplican.
search_web{ query: string, max_results?: number, page?: number, enrich?: boolean, query_variants?: string[] (max2), intent?: "auto"|"web"|"code"|"paper"|"news"|"entity", deadline_ms?: number }Busca en la web (DDG+Bing+Brave+Mojeek fusionados por consenso + verticales por intención: GitHub/Wikipedia/arXiv/HN) y devuelve [{title, url, snippet, fetched_at}]. query_variants busca formulaciones alternativas en paralelo (fusionadas); intent selecciona verticales; deadline_ms limita la llamada con error de plazo honesto (nunca se cuelga); page 1–10; enrich: true reemplaza los 3 primeros fragmentos con markdown obtenido (presupuesto de 45 s). fetched_at es época UTC en ms. Alimenta las URLs a fetch_web/extract_links.
crawl_web{ url: string, mode?: "full"|"map"|"content", focus?, max_pages?: number, max_depth?: number, max_total_chars?: number, per_page_max?: number, include_paths?: string[], exclude_paths?: string[], same_host?: boolean, respect_robots?: boolean, deadline_s?: number, resume?: string, since_last?: boolean }Rastrea un sitio desde la semilla: descubrimiento de sitemap en dos fases + frontera clasificada por enfoque (BM25-lite) + ritmo del Gobernador (varianza de permanencia + retroceso). mode full=mapa+contenido, map=solo inventario, content=BFS desde la semilla. Presupuestos: focus clasifica la frontera, max_pages/max_total_chars/deadline_s limitan la ejecución; resume token (30 min respaldado en disco) continúa; since_last omite páginas sin cambios (huella <24h). Devuelve {seed, pages:[{url,title,kind,chars,quality}], map, queued, skipped, stop, elapsed_s, resume, crawl_delay}.
extract_links{ url: string, limit?: number }Devuelve todos los hipervínculos ({text, url}, absolutos) presentes en una página renderizada con JS, para seguir la navegación sin volcados completos del DOM.
fetch_web_batch{ urls: string[], type: "plain"|"html"|"markdown", selector?, max_chars?, wait_ms? }Obtiene hasta 10 URLs en una sola llamada (consciente de caché). Devuelve {url, ok, content|error} por URL — un fallo nunca mata el lote.

Devoluciones

  • type: plain: Texto legible estilo terminal, ejecutado con JS (o cadena de error).
  • type: html: Cadena de marcado HTML posterior a JS (o cadena de error). Con selector, solo el HTML del elemento coincidente.
  • type: markdown: Conversión a Markdown del contenido principal o del elemento seleccionado (o cadena de error). Enlaces, encabezados, listas y estructura de página conservados para contexto amigable con IA.
  • type: pdf: texto plano extraído del documento PDF (vía pdftotext, límite de 20 MB).
  • Los errores son estructurados: una respuesta MCP con isError: true y un mensaje FetchError que incluye el estado HTTP cuando es conocible (nunca una cadena vacía silenciosa).

Variables de Entorno

Configúralas vía .env (cargado automáticamente) o el entorno:

VariablePredeterminadoDescripción
BROWSH_FIREFOX_PATHfirefoxBinario de Firefox usado por Browsh (p. ej. /usr/bin/firefox-esr).
HTML2MARKDOWN_PATHhtml2markdownRuta al binario html2markdown.
BROWSH_REQUEST_TIMEOUT_MS30000Tiempo de espera de solicitud por renderizado (ms).
PDF_MAX_BYTES20971520Tamaño máximo de archivo PDF en bytes para fetch_web type: pdf.
BROWSH_RECYCLE_REQUESTS100Número de solicitudes tras las cuales se recicla el proceso del navegador.
BROWSH_IDLE_TIMEOUT_MS600000Tiempo de inactividad en ms antes de que se elimine el proceso del navegador (10 min).
CACHE_TTL_MS300000TTL de caché de renderizado en memoria (ms).
ALLOW_PRIVATE_URLSfalseConfigura true para deshabilitar la protección SSRF para objetivos de bucle local/privados.
MCP_TRANSPORTstdioTipo de transporte (solo stdio implementado).
NODE_ENVproductionEntorno de Node.

Selección de Herramientas Guiada por IA

  • Comienza con search_web: Para descubrir páginas, ejecuta una consulta y elige las mejores URLs de resultados; luego obténlas. Usa intent cuando conozcas el dominio (código/artículo/noticia/entidad) y query_variants para recuerdos ambiguos; configura deadline_ms para acotar la latencia.
  • Usa fetch_web para páginas individuales: plain cuando necesites salida legible rápida para resumir/clasificar; html para analizar elementos, enlaces o tablas; markdown para fragmentos de contexto amigables con LLM. Añade selector/max_chars/wait_ms para ser eficiente en tokens y obtener contenido establecido y relevante. Nuevo: focus cuando conozcas el tema (reduce tokens 50-80%), toc→section para dos llamadas baratas en páginas largas, must_contain para preguntas de verificación (MATCH + extractos, ~60 tokens), archive=auto para enlaces muertos, stitch para paginación.
  • Usa crawl_web para sitios: Consciente de sitemap, clasificado por enfoque, con presupuestos. Comienza con mode: map para inventario barato; luego mode: full, focus: "topic" para páginas relevantes. Reanuda con el token resume si se detuvo temprano.
  • Usa extract_links antes de rastreos profundos: Sigue la navegación de forma económica en lugar de obtener DOMs completos.
  • Usa fetch_web_batch para múltiples fuentes: Una llamada en lugar de N idas y vueltas; los fallos se aíslan por URL.

Manejo de errores: Las herramientas lanzan FetchError y MCP devuelve isError: true con un mensaje accionable — protocolos inválidos, bloqueos SSRF, selectores sin coincidencia, códigos de estado HTTP y fallos de renderizado nunca son silenciosos.


Protocolo MCP: Configuración del Cliente de IA

Antes de configurar tu cliente de IA (Claude, Cursor, etc.), debes

  1. Instalar dependencias:    npm install
  2. Compilar el proyecto:    npm run build
  3. Lanzar el servidor MCP desde la salida compilada:    node dist/server.js

Ejemplo de configuración para Claude Desktop o Cursor:

{
  "mcpServers": {
    "blowsh": {
      "command": "node",
      "args": ["dist/server.js"],
      "env": {}
    }
  }
}

Ejemplo de configuración para opencode (proyecto opencode.json):

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "blowsh": {
      "type": "local",
      "command": ["docker", "run", "--rm", "-i", "ghcr.io/mokhtarabadi/blowsh-mcp:latest"],
      "enabled": true,
      "timeout": 120000
    }
  },
  "permission": { "blowsh_*": "allow" }
}

La forma Docker no necesita binarios del lado del host; la imagen incluye Firefox, Browsh y html2markdown. Reinicia opencode después de guardar (la configuración se carga una vez al inicio).


Apagado Elegante

blowsh-mcp captura SIGINT/SIGTERM y asegura que Browsh se termine limpiamente—sin navegadores huérfanos.


Seguridad y Consideraciones

  • El servidor ejecuta Browsh localmente y obtiene vía HTTP localhost.
  • Protección SSRF: Por defecto, fetch_web/search_web/extract_links/fetch_web_batch rechazan URLs que resuelven a rangos de IP de bucle local, privados, link-local o reservados (verificado vía DNS). Configura ALLOW_PRIVATE_URLS=true para deshabilitar — no recomendado.
  • Sin exposición pública a menos que se configure explícitamente MCP HTTP/streamable.
  • Nunca expongas puertos a la web abierta sin firewall.
  • Usa variables de entorno para secretos/configuración.

Extensión

Añade nuevas herramientas en src/tools/, expórtalas en src/server.ts y documenta.
Los clientes de IA descubrirán automáticamente los docstrings.


Solución de Problemas

  • Si fetchPlain devuelve 404 o falla al renderizar JS: verifica que Firefox y Browsh estén instalados y en PATH.
  • Si Firefox no se encuentra o falla al iniciar, configura BROWSH_FIREFOX_PATH en .env para especificar la ruta completa a tu instalación de Firefox.
  • El puerto/host de Browsh son fijos—no hay configuración de entorno o CLI para cambiarlos.
  • Para máxima seguridad, ejecuta en un contenedor.

Licencia

MIT


Autor: Mohammad Reza Mokhtarabadi mmokhtarabadi@gmail.com