search_web indica su backend y serp_rank señala resultados señuelo
6.21.0 acompaña el paso de CrawlForge de Google Custom Search, que Google retira el 2027-01-01, a Brave Search API. search_web ahora indica qué backend respondió, y serp_rank señala una página de resultados que parece un señuelo: Google ha empezado a servir a algunas consultas automatizadas de posicionamiento resultados de aspecto real sin relación con la búsqueda. Nota de actualización: los resultados de search_web vienen del índice independiente de Brave, total_results ya no se informa y related_searches está vacío. La 6.21.1 corrige la paginación de search_web en Brave: un offset que no era múltiplo de limit devolvía resultados equivocados, y offset 90 con un limit pequeño volvía vacío.
serp_rank devuelve suspect: true y una advertencia cuando menos de 1 de cada 5 de los primeros resultados menciona la palabra clave. El 2026-10-19, una consulta de "crawlforge" no lo nombraba en ninguno de los 10 primeros, mientras Search Console tenía el sitio en el puesto 1. Los mismos 5 credits
search_web informa provider.backend como Brave Search API en lugar de un Google Search fijo, y el Creator Mode puede llamar directamente a Brave Search API
El paquete .mcpb lee PDF en macOS y Windows: desde 6.0.0 cada paquete llevaba solo binarios nativos de Linux, así que process_document fallaba con "DOMMatrix is not defined". El manifiesto ahora enumera las 31 herramientas
v6.20.0
Corrección
Las acciones del navegador respetan el Crawl-delay y las rutas de archivos locales no salen de tu equipo
6.20.0 hace que las herramientas de navegador sean tan respetuosas como las de descarga y cierra dos huecos silenciosos. Un clic, una pulsación de tecla, un select o una casilla en scrape_with_actions y browser_session esperan ahora el Crawl-delay del robots.txt de la página en la que están antes de ejecutarse, como ya hacía un navigate, y las solicitudes que llegan a la vez a un mismo host se ponen en cola con un retardo completo entre ellas. Nota de actualización: scrape acepta ahora como máximo un formato highlights y uno question por llamada. Un segundo se rechaza sin coste, cuando antes sustituía al primero.
scrape_with_actions y browser_session: un click, press, select o check espera el Crawl-delay del host de la página antes de ejecutarse, porque el navegador envía cualquier solicitud que provoque antes de que nada pueda preguntar. Un navigate y la primera carga ya estaban espaciados
Las solicitudes que llegan juntas a un host con Crawl-delay se ponen ahora en cola con un retardo completo entre ellas, tanto en el MCP server como en la API REST. Antes, todas las que llegaban durante una misma espera salían en el mismo instante, y map_site y generate_llms_txt ahora también espacian sus descargas de sitemaps
process_document lee archivos locales, y su informe de uso enviaba la ruta completa a crawlforge.dev. Ahora solo informa del tipo de archivo, como [local file].pdf, el sitio web enmascara las rutas que llegan de servidores anteriores y las filas guardadas se han limpiado
scrape: un segundo formato highlights o question en una misma llamada es un error de validación sin coste, en lugar de sobrescribir en silencio al primero mientras se seguía cobrando el crédito adicional
v6.19.2
Corrección
Todos los defectos de la prueba en vivo de octubre, corregidos o decididos
6.19.0 completa la prueba en vivo de las 31 herramientas realizada el 2026-10-03. Los parámetros que no hacían nada ahora funcionan o se han eliminado, las herramientas de texto ya no pegan los párrafos entre sí, un error indica el siguiente paso según lo que realmente falló y ocho herramientas más aceptan max_inline_chars. Nota de actualización: se eliminan enable y disable de stealth_mode y captureScreenshots de scrape_with_actions, read de browser_session devuelve la página completa, y se recortaron algunos campos demasiado grandes de extract_content, search_web, map_site y scrape_with_actions.
stealth_mode en Chromium impedía que cargara el propio desafío de Cloudflare: tres cabeceras forzadas hacían que cada petición entre orígenes enviara una comprobación previa CORS que challenges.cloudflare.com rechaza. Ahora el desafío carga y la página se informa como bloqueada por cloudflare. 6.19.1 elimina el mismo tipo de cabecera forzada de los contextos de navegador localizados
Las tablas de extract_text son una cuadrícula uniforme con URLs absolutas de imágenes y enlaces tanto en el MCP server como en la API REST, compartidas a través de crawlforge-extractors 1.17.0, y el markdown de scrape en REST conserva sus tablas
comment_count de reddit_search cuenta todos los comentarios devueltos y comments_collapsed los que quedan fuera; serp_rank documenta que un target coincide por host y que cada consulta es una muestra, y la API REST devuelve se_results_count y check_url. 6.19.2 fija su depth predeterminada en 30 en ambos
Los errores incluyen un siguiente paso según el fallo real (robots, SSRF, 404, tiempo de espera o un parámetro incorrecto), y analyze_content tiene palabras vacías para once idiomas más
v6.18.1
Función
extract_text y extract_links llegan a las páginas bloqueadas y coinciden con la API REST
Los dos lectores de páginas más baratos reciben la escalera de descarga de scrape. Con escalate: true, una página bloqueada por un muro (un proveedor con nombre, 403, 429 o 444) se vuelve a leer por la misma etapa stealth que usa scrape, con una proyección de 6 credits y un cobro de 1 cuando la descarga simple consiguió la página; un 404 o un 5xx nunca escala. Sin él, un fallo ahora indica qué respondió el destino y qué proveedor lo bloqueó. Un PDF u otro binario falla con una indicación hacia process_document en lugar de devolver basura, un cuerpo JSON vuelve con una nota, el tiempo de espera de la descarga es de 15 s con un reintento tras un Retry-After corto, y una página vacía renderizada en el cliente se marca con rendered: false. Ambas herramientas ahora devuelven lo mismo que la API REST: extract_text lee una línea por elemento de bloque y acepta selector y max_length, y extract_links devuelve la misma forma de registro en ambas superficies. Nota de actualización: los registros de extract_links ya no llevan is_external; lee type en su lugar. La 6.18.1 corrige lo que encontró una prueba en vivo de las 31 herramientas: crawl_deep ya no falla con una URL inicial rechazada y se detiene al alcanzar max_pages, map_site informa como error una URL inicial que no pudo leer, batch_scrape rechaza un PDF en lugar de devolver sus bytes como texto, y scrape identifica como muro la página de desafío de cliente de Fastly. Nota de actualización: pages_crawled de crawl_deep ahora cuenta solo las páginas descargadas, y monitoringOptions.enabled: false de track_changes ya no inicia un monitor.
La misma URL a través del MCP server y de la API REST devuelve los mismos enlaces: 327, 196 y 123 enlaces, con los 20 primeros idénticos, en capturas de Wikipedia, Hacker News y BBC News
Los registros de enlace son { href, text, type, domain, rel, original_href }: los enlaces mailto:, tel: y javascript: son de type "other", se respeta un <base href> y los duplicados se comparan sin fragmento ni barra final
browserOptions.timeout de scrape_with_actions ahora limita la carga de la página, y un resultado de scrape_with_actions o browser_session que supera el límite conserva en línea su informe de muro
6.18.1: crawl_deep con max_pages: 3 en un sitio con muchos enlaces tardaba 232 s y procesaba 2.290 enlaces en cola; ahora se detiene al alcanzar el límite. formAutoFill marca botones de opción y casillas y selecciona opciones, act de browser_session devuelve una captura de pantalla como URI de recurso en lugar de base64 en línea, y agent no ejecuta ninguna búsqueda web para una pregunta limitada a sus URL iniciales
v6.17.1
Función
Cada muro que encuentra una cadena tiene nombre, y tus propios proxies en las cadenas de acciones
scrape_with_actions ahora comprueba cada navegación que hace, no solo la página final: la carga inicial y cada acción navigate aparecen en navigations con su estado HTTP y, ante un muro, el proveedor. La página intermedia de desafío de AWS WAF, la página 202 con la que Amazon responde a un navegador headless, ahora se identifica como aws-waf en todas las superficies. Una cadena stealth puede salir por tus propios proxies residenciales con browserOptions.proxyRotation (solo Chromium; CrawlForge no proporciona proxies), con las credenciales eliminadas de los resultados y de los informes de uso. Un proxy por llamada se rechaza en Camoufox, que comparte un mismo navegador entre llamadas y por eso habría llevado el tráfico de otros usuarios. La 6.17.1 también hace que la espera stealth de Cloudflare reconozca la página actual «Performing security verification» de Cloudflare y limita esa espera a 10 s, como se pretendía: una cadena bloqueada en doordash.com pasó de 39,5 s a 19,5 s. Nada de esto supera un desafío.
navigations: [{ url, finalUrl, httpStatus, blocked? }] en cada cadena, y httpStatus más blocked en cada resultado de navigate; success y blocked de nivel superior siguen decidiéndose por la página final
Un proxy tuyo cuyo host sea loopback, link-local o una dirección de metadatos de la nube se rechaza antes de lanzar el navegador
La regla aws-waf está en crawlforge-extractors 1.12.0, así que el MCP server y la API REST lo nombran igual
v6.16.0
Función
extract_embedded_state decodifica Nuxt 3, SvelteKit, YouTube y Shopify, y las redirecciones pasan por control
extract_embedded_state ahora decodifica lo que la descarga simple ya trajo, sin navegador: __NUXT_DATA__ de Nuxt 3 como objetos en lugar de un array de índices, los datos de página de SvelteKit, ytInitialData y ytInitialPlayerResponse (una página de reproducción de YouTube devuelve su videoDetails), Remix, el data-page de Inertia, ShopifyAnalytics.meta y el JSON de producto de Shopify, y los atributos data-* grandes con JSON. Una variable global a la que se asigna un literal de JavaScript en lugar de JSON se lee con un evaluador estático, así que no se ejecuta código de la página. En las filas Flight de Next.js se resuelven sus referencias a otras filas, un índice data_rows apunta a las filas que llevan datos y no marcado, y find devuelve cada ruta cuya clave coincide, lista para pasarla de vuelta como path. La misma versión controla cada redirección: cada salto se contrasta con la lista de bloqueo y con robots.txt antes de seguirlo, respeta el Crawl-delay del host y se firma de nuevo para Web Bot Auth. El precio sigue siendo 2.
find devuelve hasta 50 coincidencias como { path, preview } con los primeros 200 caracteres del valor; raw: true además conserva el array de Nuxt sin decodificar. Ambos funcionan también en la API REST
Una redirección a una ruta que robots.txt prohíbe, o a un host bloqueado, se rechaza antes de solicitarse, y una llamada que fracasa por ello no se cobra. Las herramientas de navegador comprueban cada salto y la URL en la que termina la página, y una página que scrape_with_actions o browser_session mantiene se vuelve a comprobar tras cada acción
Las filas del registro de auditoría ya no guardan credenciales de una URL: se descarta el userinfo y se ocultan los parámetros de consulta con nombre de clave o token
v6.15.0
Función
extract_embedded_state: vistas previas para payloads grandes y una relectura stealth
Un resultado que supera max_inline_chars ahora vuelve como una vista previa y un result_handle en lugar del payload completo: producthunt.com y zappos.com habían devuelto 1-2 MB en línea. La parte en línea conserva la lista found, el tamaño en bytes, las claves de primer nivel de data y una vista previa formateada cortada en un límite de línea, nunca dentro de un valor, y read_result lee cualquier parte del payload completo por json_path. keys_only: true devuelve dos niveles de claves con cada valor sustituido por su tipo, para explorar a bajo coste. Con escalate: true, una página bloqueada por un muro (403, 429, una página de desafío o una página vacía) se vuelve a leer por la misma etapa stealth que usa scrape y se analiza de nuevo, y en el navegador las variables globales del framework se leen de window después de que se ejecute JavaScript, bajo window_state. Sin escalate, un muro ahora es un error que nombra al proveedor en lugar de un éxito sin nada encontrado. El precio sigue siendo 2; escalate proyecta 7 y cobra 2 cuando la descarga simple consigue la página.
Verificado con max_inline_chars: 3000: producthunt.com 2979 y zappos.com 2970 caracteres en línea, y el resto a una llamada de read_result
doordash.com (Cloudflare) devolvió entre 103 y 106 filas Flight de Next.js mediante escalate con impit, Camoufox y Chromium
window_state lee __NEXT_DATA__, __NUXT__, __remixContext, ytInitialData, ytInitialPlayerResponse, los stores de Redux y Apollo, __TGT_DATA__ y __PWS_DATA__; en Camoufox la lectura pasa por el mundo aislado de Firefox, así que ningún script se ejecuta en el de la propia página
v6.14.0
Función
Snapshots dentro de Shadow DOM e iframes, y banners de cookies respondidos
Los snapshots ahora usan el snapshot de accesibilidad nativo de Playwright, así que los elementos dentro de shadow roots abiertos y de iframes, incluidos los de origen cruzado, aparecen en el árbol con refs @e normales sobre los que act puede hacer clic. El formato del árbol no cambia, el antiguo recorrido de la página se mantiene como respaldo y cada snapshot indica cuál se ejecutó. scrape_with_actions también acepta browserOptions.consent: "reject" ejecuta el opt-out de DuckDuckGo autoconsent una vez tras la primera navegación y tras cada acción navigate, "accept" su opt-in, y el valor por defecto "off" deja los banners en paz, así que las cadenas que ya hacen clic en ellos por su cuenta siguen funcionando. Los precios no cambian.
source: "aria" | "walk" en cada snapshot; los snapshots de browser_session reciben el mismo árbol, y los controles aria-hidden visibles en pantalla ahora aparecen en la lista
consent: { cmp, action, ms } en el resultado, con un tope de 2 s por navegación; una página sin un banner reconocido informa action: "none" y nunca hace fallar la cadena. Verificado con el diálogo de Sourcepoint de The Guardian y con Ecosia en Camoufox
Las funciones auxiliares stealth de desplazamiento y de tiempo de lectura aceptan selectores de Playwright, así que los refs dentro de shadow roots y frames también funcionan en la ruta stealth
v6.13.0
Mejora
Snapshots que esperan a la página y una cola en lugar de un rechazo
Segunda ronda de correcciones de scrape_with_actions. Un snapshot ahora espera a que la página se renderice antes de leerla: el evento load y después una ventana de calma sin cambios en el DOM durante 300 ms y sin peticiones en curso durante 500 ms, con un tope y nunca más allá del tiempo de espera de la acción. El snapshot de la página de inicio de Amazon pasó de 0 nodos a 236, cuadro de búsqueda incluido. Los clics, las pulsaciones de teclas y las selecciones esperan del mismo modo después, así que se detecta el cambio de ruta de una aplicación de una sola página. Una ráfaga de llamadas por encima del límite de navegadores ahora espera en cola hasta 60 s en lugar de fallar con «Maximum concurrent sessions», y browser_session comparte las mismas plazas solo mientras abre una página. Los clics stealth en Camoufox ya no se atascan: había dos humanizadores del cursor apilados, y el clic de consentimiento de Ecosia pasó de 12-20 s a 1,4 s. Los precios no cambian.
Los resultados de snapshot informan waited_ms y settled_by (quiet, cap o closed); los snapshots de browser_session reciben la misma espera
CRAWLFORGE_MAX_ACTION_SESSIONS fija las plazas de navegador (3 por defecto) y CRAWLFORGE_ACTION_QUEUE_TIMEOUT_MS el tiempo en cola (60 s por defecto); los resultados incluyen queued_ms, y executionTime ya no cuenta la espera
En Camoufox el simulador de clics hace un solo movimiento y deja que Camoufox trace la trayectoria, con un tope de 0,5 s por movimiento; simulateClick ahora respeta el tiempo de espera de la acción en lugar de los 30 s por defecto de Playwright
v6.12.1
Corrección
Páginas de error de la app detectadas y fallos de scrape_with_actions más baratos
Continuación del paso impit de la 6.11.0. Una página corta cuyo texto empieza con un error de la aplicación — «Something went wrong», «Oops!», «An error occurred» o la excepción del lado del cliente de Next.js — ahora se trata como un bloqueo blando aunque tenga un título normal, así que pasa al navegador stealth en lugar de devolverse como la página. El «Something went wrong. Wait a moment and try again.» de quora.com pasaba por todas las rutas, no solo por impit. La regla viene de crawlforge-extractors 1.10.0, que el servidor ahora exige. Un nuevo ajuste, CRAWLFORGE_IMPIT=off, desactiva el paso impit para todo un despliegue: medido desde la IP de centro de datos de la instancia alojada, no superó ninguno de los muros del benchmark, así que el servicio alojado lo tiene desactivado y las instalaciones de npm lo mantienen activado. Los precios no cambian. La 6.12.1 es la primera ronda de correcciones de scrape_with_actions surgidas de una revisión en vivo: maxRetries ahora vale 0 por defecto, así que una cadena que falla ya no se ejecuta dos veces (un clic en un selector inexistente pasó de 20,8 s a unos 4 s), y cada ejecución que sí pidas se informa en attempts[]. browserOptions.timeout ahora llega a cada acción, screenshotOnError: false se respeta y los bytes de las capturas ya no se filtran en línea junto a su URI de recurso.
Un documento de menos de 200 caracteres cuyo texto empieza con una frase de error de la aplicación es un bloqueo blando sea cual sea su título, así que una página de error de respaldo se reintenta en el navegador en lugar de devolverse como contenido
CRAWLFORGE_IMPIT=off omite el paso impit de la etapa de escalado en un despliegue cuya IP de salida no puede ayudar, y se lee en cada llamada. El servicio alojado lo desactiva; las instalaciones de npm lo mantienen, y desde una IP residencial superó indeed.com
El escalado de scrape en la API REST sigue siendo solo de navegador por decisión, y se descartó reutilizar una cookie de autorización de Cloudflare a través de impit: la cookie está ligada al User-Agent con el que se obtuvo, así que reutilizarla obligaría a enviar un User-Agent de navegador en lugar del de CrawlForge
scrape_with_actions: maxRetries vale 0 por defecto y cada ejecución se informa en attempts[], browserOptions.timeout es el plazo de cada acción, la recuperación de errores se limita a 3 s por acción y se omite en una acción de espera o en un selector que no coincide con nada, y una cadena con una captura devuelve menos de 5 KB en lugar de unos 118 KB
v6.11.0
Función
Un intento con TLS de Chrome antes del navegador
Cuando scrape se ejecuta con escalate: true, o el agent reintenta una página bloqueada, y el motor es "auto" (el predeterminado), ahora se prueba un handshake TLS de Chrome antes de lanzar ningún navegador. Lleva el User-Agent honesto de CrawlForge. Si consigue la página, no arranca ningún navegador: indeed.com respondió en un segundo aproximadamente desde una IP residencial donde Cloudflare bloquea la descarga simple. Si no, el navegador se ejecuta como antes, y el precio es 2 + 5 en ambos casos. El paso usa impit, una nueva dependencia opcional con binarios precompilados; sin ella, el escalado se comporta exactamente igual que en la 6.10.0. En Chromium, un renderizado stealth que sigue detenido por un muro de Cloudflare con un marco de Turnstile ahora hace clic una vez en la casilla, una página 200 que solo incrusta un widget de Turnstile ya no se considera un bloqueo, y cada escalado escribe una fila en el registro de auditoría de cumplimiento.
impit solo se ejecuta dentro de la etapa de escalado: nunca en la descarga simple ni cuando indicas playwright o camoufox. Una página que consigue informa stealth.engine: "impit" con un aviso que lo nombra, cada salto de redirección pasa la comprobación SSRF con resolución DNS, y una página con menos de 200 caracteres de texto visible pasa al navegador
Clic en la casilla de Turnstile en Chromium con el ratón de Playwright sin más, solo cuando el muro sigue ahí tras la espera habitual y la página tiene un marco de challenges.cloudflare.com. Verificado con la sitekey de prueba interactiva forzada de Cloudflare, lo que solo demuestra el mecanismo: que un sitio real acepte el clic sigue dependiendo de la IP y la huella que puntúa
nowsecure.nl responde 200 con un widget de Turnstile en la sitekey de prueba de Cloudflare, y cada «Blocked» registrado para él venía de nuestra propia capa de veredicto. Un documento 200 con título, texto y solo marcadores del widget ahora pasa; los muros reales (403) y las páginas vacías siguen bloqueados
Una fila stealth_escalation va a logs/compliance-audit.log para scrape con escalate, el reintento stealth del agent y stealth_mode, escrita después de la puerta de cumplimiento y antes de que el navegador navegue, con los identificadores de clave y propietario en hash en lugar de sus valores. Las filas robots_override ya no muestran apiKeyId: "anonymous" en un servidor en producción
v6.10.0
Función
Clearance jar: un desafío resuelto sigue resuelto
Cuando un renderizado stealth supera un muro de Cloudflare o DataDome, las cookies de autorización del proveedor — exactamente cf_clearance, __cf_bm y datadome — se guardan y se reutilizan en el siguiente contexto stealth con el mismo motor, User-Agent y proxy. Eso cubre la etapa de escalado de scrape, stealth_mode, el reintento stealth del agent, browser_session y scrape_with_actions. En stackoverflow.com desde una IP residencial, la primera llamada stealth pasó por la página intermedia de Cloudflare en 4,1 segundos; la segunda llegó directamente a 200 sin desafío en 1,6 segundos. Nunca se guarda ninguna otra cookie, así que el inicio de sesión de un usuario no puede llegar al contexto de otro.
Solo se guardan tres cookies con nombre, indexadas por motor, User-Agent y proxy. Un renderizado que vuelve a encontrar el muro descarta las autorizaciones de ese host, la caducidad es la de la propia cookie con un máximo de 24 horas, y el almacén guarda como mucho 32 identidades con 200 cookies cada una
Se guarda en ~/.crawlforge/stealth-clearance.json con modo 0600 y claves en hash, así que un segundo proceso reutiliza una autorización desde el disco: indeed.com cargó sin ninguna petición a la plataforma de desafíos. CRAWLFORGE_CLEARANCE_JAR=off lo desactiva
No se creó a propósito un grupo de perfiles persistentes de Chromium: un perfil compartido conserva inicios de sesión y almacenamiento de los sitios, y en la instancia alojada eso se filtraría entre clientes
v6.9.0
Función
El agent reintenta las páginas bloqueadas en el navegador stealth
Antes de la 6.9.0, la etapa de acción del agent solo hacía la descarga simple. Una página semilla con desafío se descartaba y la respuesta se construía con fragmentos de búsqueda, lo que en la prueba de Indeed de la revisión dio «3.3 stars, based on 3.3 reviews». Ahora una página que se encuentra un desafío, un 403 o 429, una página vacía o un tiempo de espera agotado se reintenta por la misma etapa stealth que usa escalate: true de scrape, con la misma puerta de cumplimiento, el mismo selector de motor y los mismos proxies. Con el motor Chromium, la misma prueba leyó la propia página semilla y respondió 3,3 estrellas sobre 58.942 reseñas. El precio sigue a lo que se consiguió: 8 credits, más 5 por cada reintento stealth que obtiene la página.
Las URL que indicaste se reintentan primero, y una URL descubierta solo cuando no tiene un fragmento de búsqueda relevante. Los rechazos, los 404 y los errores 5xx nunca se reintentan, una ejecución hace como mucho 2 reintentos, y ninguno empieza con menos de 20 segundos de margen
Un reintento que vuelve a encontrar el muro, o que falla, es gratis. El resultado informa stealth_retries (intentos) y stealth_retries_charged (cobrados), las pruebas de un reintento se marcan via: "stealth", y el máximo publicado es de 18 credits, 8 cuando no hay reintento. La ruta REST cobra lo mismo: 8 + 5 por reintento cobrado
Tal como se midió: con el motor Camoufox desde la misma IP, Cloudflare bloqueó el reintento, y el escalado de scrape, sin cambios, falla igual allí
v6.8.0
Función
Un benchmark stealth, y las fugas que midió, cerradas
El benchmark de la revisión stealth de 2026-09 se ejecutaba a mano; la 6.8.0 lo convierte en un comando, npm run bench:stealth, que pone el navegador stealth y la descarga simple frente a doce muros antibot y cinco páginas de detección y encabeza su matriz con el sistema operativo del host, el tipo de IP de salida, el motor y las versiones del navegador. Medidas con él, las fugas se cerraron: navigator.webdriver devuelve false en lugar de borrarse y nada es propiedad propia de navigator, userAgentData pierde su marca HeadlessChrome, la versión principal de Chrome sale del binario instalado y un Web Worker responde lo mismo que el documento. Las autopruebas de detección pasaron a 19 correctas, 0 fallos y 1 omitida en ambos motores. Camoufox, el motor que supera más muros, es ahora el predeterminado mediante el motor "auto", con un paso a Chromium que se informa en lugar de hacerse en silencio.
npm run bench:stealth:ci ejecuta diez autopruebas dentro de la página que no necesitan ningún sitio de terceros y solo falla ante una regresión que no esté en el archivo de referencia, con un control negativo --self-check que fuerza navigator.webdriver a true y debe detectarse
Una página que solo incrusta un widget de Turnstile ya no se considera un bloqueo de Cloudflare (quora.com ahora pasa en ambos motores), y una página intermedia con título propio recibe su tiempo de espera. Las páginas stealth ya no interceptan peticiones ni descartan al azar imágenes, fuentes y hojas de estilo, y --disable-web-security desaparece del navegador stealth
escalate_engine de scrape y engine de stealth_mode pasan a "auto" por defecto: primero Camoufox, y Chromium con un aviso si falta su binario. browser_session y scrape_with_actions ganan un parámetro engine, CRAWLFORGE_STEALTH_PROXIES da a cada ruta stealth una lista de proxies a nivel de servidor, y se corrige un User-Agent de Camoufox cuyos dos números de versión no coincidían en cerca de la mitad de los lanzamientos
v6.7.0
Corrección
Rotación de proxies que enruta, y un Camoufox que sigue siendo Firefox
Una prueba de detección de bots contra la 6.6.2 recomendó pasar por proxies residenciales para superar la comprobación de reputación de IP de Cloudflare. Esa configuración no hacía nada, por tres motivos distintos: el proxy iba a una opción de Chromium que no puede llevar el user:pass con el que se entrega cualquier proxy residencial, el lanzamiento de Camoufox funcionaba sin proxy se pidiera lo que se pidiera, y el proxy se leía una sola vez al arrancar, así que la rotación nunca podía producirse. Los tres están corregidos, y stealthConfig.proxyRotation ahora acepta URL de proxy normales con las credenciales aplicadas por contexto de navegador. Al investigarlo apareció más de la ruta stealth trabajando en su contra: Camoufox, elegido por no tener nada que detectar, recibía un User-Agent de Chrome y las client hints de Chromium.
proxyRotation acepta URL http, https, socks4 y socks5, se autentica en ambos motores, trata una entrada mal formada como un error en lugar de una petición sin proxy, empieza por la primera entrada y ya no devuelve la contraseña del proxy en get_stats. CrawlForge no proporciona proxies; esto enruta por los tuyos
Camoufox presenta su propia identidad de Firefox, sin cabeceras sec-ch-ua ni scripts pensados para Chromium, y funciona con sus propias funciones geoip, block_webrtc y humanize activadas. Detrás de un proxy toma el idioma, la zona horaria y la ubicación de la IP de salida
El scrape de stealth_mode espera al evento load de la página y a que el DOM deje de cambiar antes de leer, e informa de waited_for_render_ms; además, un error de JavaScript no capturado de una página de Firefox ya no termina el proceso
v6.6.2
Función
browser_session, la herramienta número 31
Una página de navegador que sobrevive a la llamada que la abrió. browser_session es una sola herramienta con un parámetro operation: open abre una sesión en una URL y devuelve un session id, snapshot devuelve el árbol de accesibilidad de la página con un ref estable en cada elemento interactivo (@e1 [textbox] "Email", @e3 [button] "Sign in"), act ejecuta de 1 a 20 acciones de navegador cuyo selector puede ser uno de esos refs, read extrae markdown, HTML, texto o metadatos del DOM vivo tal como queda después de todo lo que la sesión ha pulsado, screenshot captura la página o un solo elemento, close termina la sesión y list indica cuál está reteniendo una API key. El bucle es mirar y luego actuar: el agent lee los refs que le entregó un snapshot en lugar de adivinar selectores CSS, y el inicio de sesión se paga una vez y no una vez por llamada; scrape_with_actions no puede hacerlo, porque su navegador se cierra cuando la llamada termina. Cada operation se cobra por separado desde 1 credit: open 3, read 2, y snapshot, act, screenshot, close y list 1 cada una, con los 3 de open como tarifa publicada. Las sesiones son efímeras por diseño: ttl (600 s por defecto, rango 30-3600) es el reloj absoluto y activity_ttl (300 s por defecto, rango 10-3600) el de inactividad, y el que se agote primero cierra la página. El mismo "mirar primero" llega también a las cadenas de un solo disparo: scrape_with_actions acepta una acción snapshot, así que una cadena puede ver qué ofrece la página antes de nombrar un selector. La 6.6.1 llega tras la primera ronda de pruebas en vivo contra la propia herramienta: una página bloqueada ahora se identifica como tal en lugar de devolverse como contenido, executeJavaScript entrega su valor y read se limita como cualquier otra herramienta de contenido. La 6.6.2 cierra una segunda ronda en vivo: una captura de página completa por encima del límite de mensaje de stdio cerraba toda la conexión MCP, y ahora se rechaza indicando su tamaño y qué capturar en su lugar.
Nueva browser_session (1-3 credits por operation) mantiene viva una página de navegador entre llamadas a la API: open, snapshot, act, read, screenshot, close y list, con el session id de open en cada llamada posterior. Cada operation se cobra a su propio precio (open 3, read 2, el resto 1 cada una), así que una mirada barata nunca paga un open caro, y un flujo de inicio de sesión y lectura (open, snapshot, act, act, read, close) suma 9 credits
snapshot devuelve la página como un árbol de accesibilidad con un ref estable en cada elemento interactivo, y act apunta selector a @e1 en lugar de a un selector CSS adivinado. Un ref pertenece al documento del que se tomó su snapshot: cualquier navegación invalida todos los refs, y actuar sobre uno caducado falla con un error que te pide un nuevo snapshot en vez de pulsar lo que no era. scrape_with_actions gana la misma acción snapshot, así que las cadenas de un solo disparo también pueden mirar antes de actuar
Disponible en la API REST en POST /api/v1/tools/browser_session con el mismo envoltorio que las demás herramientas. Una API key REST puede mantener una sesión a la vez, y un segundo open mientras una sigue viva se rechaza con un error con nombre en lugar de ponerse en cola, así que cierra la sesión cuando termines en vez de dejar que se agote un TTL
La 6.6.1 corrige el fallo que más importaba: un muro antibot o una página de error HTTP ya no se leen como una sesión correcta. open y read informan de httpStatus y nombran al proveedor que los bloqueó — antes un 403 de DataDome volvía como success con la sola palabra «g2.com» como página —, executeJavaScript devuelve su valor sin que haya que pedírselo, y read se limita a max_inline_chars con un result_handle en lugar de devolver medio millón de caracteres en línea
La 6.6.2 limita los recursos de captura por debajo del tamaño máximo de mensaje de stdio — un PNG de página completa de 18,5 MB cerraba toda la sesión MCP, y con ella todas las herramientas de la conexión — y respect_robots: false ahora devuelve su aviso desde open y act, auditado como browser_session y no como scrape_with_actions
v6.5.0
Corrección
Ronda multisectorial: soporte DOCX y siete correcciones
Seis defectos y cuatro carencias de una ronda en vivo de las 30 herramientas sobre unas 600 URL de trece sectores: inmobiliario, salud, finanzas, educación, deportes, alimentación, entretenimiento, derecho y gobierno, suministro industrial, telecomunicaciones y precios SaaS, organizaciones sin ánimo de lucro, aire libre y ciencia. process_document ahora lee documentos de Word: un .docx descargado como URL volvía como un megabyte de bytes ZIP presentados como texto de página; ahora el cuerpo descargado decide cómo se lee, un PDF servido bajo una URL normal llega al parser de PDF, y un archivo comprimido o una imagen se rechazan por su nombre. scrape devolvía la página de tramos fiscales de irs.gov reducida a su cabecera porque el paso de contenido oculto leía una hoja de estilos de impresión como CSS de pantalla, convertía los estilos en línea en una regla universal y partía una lista de selectores :has() en cada coma; los tres fallos están corregidos y la página devuelve sus cuatro tablas de tipos. track_changes puntuaba como sin cambios un feed que había ganado un registro completo, extract_structured fallaba por completo una extracción de tabla cuando el modelo excedía su presupuesto de salida, map_site ignoraba la ruta de la URL inicial y su búsqueda, y un scrape truncado perdía sus respuestas de highlights y question. Tal como se publicó, 6.5.0 también rechaza cualquier URL de reddit.com antes de descargarla, porque reddit.com bloquea a todos los clientes que no son navegadores, e indica la llamada de reddit_search que lee los mismos posts; reddit_search ahora prueba primero Arctic Shift y después PullPush.
process_document lee DOCX con mammoth y envía un PDF servido bajo sourceType url al parser de PDF; un cuerpo que no puede leer se rechaza por su nombre en lugar de devolverse como bytes
El paso de contenido oculto de scrape omite las hojas de estilo de impresión, ya no lee los estilos en línea como reglas universales y parte las listas de selectores solo en las comas de nivel superior, así que las tablas fiscales de irs.gov vuelven; un resultado truncado conserva en línea sus respuestas de highlights, question y json
track_changes registra un documento de solo texto que creció en un registro; extract_structured conserva las filas completas de una respuesta cortada como partial: true y nombra el motivo real del fallback; map_site se limita a la ruta de la URL inicial y ordena por los términos de búsqueda de cada URL; un Crawl-delay largo de robots.txt se nombra en la respuesta
Las URL de reddit.com se rechazan antes de cualquier descarga con USE_REDDIT_SEARCH y se remiten a la llamada exacta de reddit_search para esa URL, y reddit_search prueba primero Arctic Shift y después PullPush
v6.4.0
Corrección
Correcciones de la ronda de retail, viajes y aviación
Cinco defectos y cinco carencias de una ronda en vivo de las 30 herramientas sobre 415 URL de retail, viajes y aviación. Como en la ronda anterior, cada defecto había devuelto éxito con algo ausente en lugar de un error. map_site leía 75 de las 1.878 URL del sitemap de boeing.com porque el sitemap usa rutas loc relativas, con las que el parser fallaba; ahora un loc se resuelve contra la URL del propio sitemap, una entrada inválida se omite por sí sola y un parseo vacío ni se guarda en caché ni se sirve desde ella. scrape con su onlyMainContent por defecto descartaba la tabla de tarifas de equipaje facturado de WestJet, de 6 filas, demasiado pequeña para la prueba de tamaño de tabla de datos y dentro de un componente que Readability puntúa negativamente; ahora una tabla descartada con celdas de cabecera th se recupera sea cual sea su tamaño, y una tabla que empieza con una celda de esquina vacía se renderiza como tabla de barras en lugar de texto aplanado. scrape_with_actions en un centro de ayuda en JavaScript devolvía el marcador Content not available in markdown format como éxito; markdown y html salen ahora del DOM posterior a las acciones cuando el extractor no tiene nada. agent, al preguntarle qué cobra Southwest por la primera maleta facturada, planificó solo la consulta de la entidad y respondió que la tarifa no consta; un plan que se detiene en una sola consulta recibe ahora las palabras de la tarea como segunda consulta, solo la consulta de la entidad vota por la raíz en vivo, y la respuesta es $45 citando southwest.com. Las vistas previas de crawl_deep eran el megamenú del sitio en cada página; las páginas pasan por el mismo paso de contenido principal que usa scrape. Ninguna herramienta cambia de precio.
map_site lee sitemaps escritos con rutas loc relativas. El sitemap de 1.878 URL de boeing.com se parseaba como vacío porque normalizeUrl fallaba con /, así que la herramienta recurría a rastrear los enlaces de la portada y devolvía 75. Un loc se resuelve contra la URL del sitemap, una entrada inválida ya no descarta el resto, y un parseo vacío nunca se escribe en la caché de sitemaps ni se sirve desde ella, que había seguido respondiendo 75 durante una hora después de corregir el parser
Tablas y páginas JavaScript: scrape recupera una tabla descartada cuyo autor marcó celdas de cabecera th sea cual sea su tamaño, de modo que la tabla de tarifas de WestJet sobrevive al onlyMainContent por defecto, y una celda de esquina vacía en una primera fila que por lo demás es toda th se promociona para que la tabla se renderice con sus columnas. scrape_with_actions sirve markdown y html desde el DOM posterior a las acciones, con markdownSource: body, en lugar de un marcador cuando Readability no encuentra artículo. El contenido de crawl_deep es el contenido principal de la página y no su menú
agent añade las palabras de la tarea como segunda consulta de búsqueda cuando un plan de estado actual se detiene en el nombre de la entidad, mantiene la consulta de la entidad como único voto para la raíz en vivo y puede pasar de la página en vivo cuando esta no responde. get_batch_results acepta max_inline_chars y devuelve una vista previa más un result_handle por encima del límite; extract_links ya no cuenta un pseudoenlace javascript:; reddit_search indica la ventana elegida y cómo estrecharla cuando Arctic Shift agota el tiempo dentro de ella
v6.3.1
Corrección
Validación anidada, autoridad de versiones e inicio de sesión en Linux
Cuatro defectos de la ronda en vivo R19, cada uno una respuesta incorrecta pero segura en lugar de un error, más un fallo solo en Linux. extract_structured validaba solo un nivel, así que un array de texto suelto de la página pasaba un esquema que pedía un array de objetos; el validador vive ahora en un único módulo, todos los consumidores lo usan, incluido el fallback de selectores CSS, y success es false cuando un campo obligatorio está presente pero con la forma equivocada. La misma herramienta recurría a los selectores CSS en una tabla grande sin explicación porque el presupuesto de salida contaba un array como un solo campo; el presupuesto escala con los arrays del esquema, una respuesta truncada se reintenta una vez con el doble de presupuesto, y el fallo nombra el límite de tokens: el rango útil pasa de unas 43 filas a unas 156. agent respondía a una pregunta de versión desde una publicación de foro que superaba la procedencia porque la cadena estaba en una página obtenida; ante una pregunta de versión, la versión debe aparecer ahora en una fuente que no sea una página de discusión, y lo que falla se marca en provenance.unsupported_versions. analyze_content inventaba entidades y una puntuación de legibilidad para texto no latino; ambos devuelven notApplicable para cirílico, griego, árabe y devanagari, y las palabras vacías rusas se sumaron a la lista. 6.3.1: crawlforge login fallaba en Linux con ERR_MODULE_NOT_FOUND porque una importación usaba mayúsculas incorrectas, algo que los sistemas de archivos sin distinción de mayúsculas ocultaban; una nueva prueba compara cada importación relativa con git ls-files.
extract_structured valida formas anidadas: countries como array de cadenas frente a un esquema de objetos ya no se informa como válido. Un único validador en src/utils/schemaValidate.js sirve a todos los consumidores, los errores nombran la ruta (Field countries.0.capital: expected string, got number) y se limitan a diez más un recuento, y success es false cuando un campo obligatorio está presente pero con la forma equivocada, un cambio visible para quien bifurca sobre él
El presupuesto de tokens de salida de extract_structured escala con los arrays del esquema en lugar de contar un array como un solo campo, una respuesta cortada a mitad de objeto se reintenta una vez con el doble de presupuesto, y el fallo dice model response was cut off at the N-token output limit en lugar de informar un desplazamiento JSON; el rango útil de tablas pasa de unas 43 filas a unas 156. agent ya no responde a una pregunta de versión desde una página de discusión: la versión debe aparecer en una fuente que no sea un foro, se ejecuta una reescritura correctiva, y lo que sobrevive se marca en provenance.unsupported_versions; provenance.checked informa ahora de si la comprobación se ejecutó
analyze_content devuelve notApplicable para entidades y legibilidad en texto no latino en lugar de inventarlas (rain and gusty había salido como organización, y un parte meteorológico ruso puntuó Very Easy), extendiendo la protección CJK al cirílico, griego, árabe y devanagari. 6.3.1 corrige crawlforge login en Linux: una importación de AuthManager.js usaba mayúsculas incorrectas, que macOS y Windows resuelven y Linux no, así que el comando moría al importar para todo usuario de Linux desde 6.1.0; tests/unit/importCaseExactness.test.js comprueba ahora cada importación relativa contra git ls-files
v6.2.0
Función
Monitores alojados desde el MCP server
Los monitores programados del MCP server ahora son de dos tipos. Un monitor local, con la operación create_scheduled_monitor como hasta ahora, se guarda en ~/.crawlforge/monitors, se dispara dentro del proceso solo mientras el proceso del MCP server está vivo, recupera al reiniciar las ejecuciones perdidas y notifica por webhook o Slack desde su máquina; crawlforge monitor:run-due desde el cron del sistema sigue siendo la vía local garantizada, y un monitor local nunca envía correo. Pase scheduledMonitorOptions con hosted: true y el servidor, en su lugar, registra el monitor en el servicio alojado en /api/v1/monitors con la clave API configurada y devuelve el id alojado y una URL del panel. A partir de ahí es el mismo monitor alojado que gestionan la API REST y el panel: el propio programador de CrawlForge obtiene, compara y registra cada comprobación y notifica por correo y mediante webhook firmado sin que nada se ejecute de su lado, el intervalo se convierte en un cron con un mínimo de 5 minutos y la zona horaria es UTC. Ninguna herramienta cambia de precio: crear o detener un monitor alojado no cuesta nada, crear uno local sigue costando 3 credits, y cada comprobación alojada cobra el precio de track_changes por destino comparado.
hosted: true en los monitores programados. create_scheduled_monitor registra el monitor en el servicio alojado a través de /api/v1/monitors con la clave API configurada y devuelve el id alojado y una URL del panel; el correo va a notificationOptions.email.recipients y los webhooks firmados a notificationOptions.webhook. list_scheduled_monitors lista juntos los monitores locales y alojados, cada uno con un indicador hosted, y stop_scheduled_monitor elimina cualquiera de los dos. Crear o detener un monitor alojado desde el MCP server no cuesta nada, igual que la API REST de monitores; crear uno local sigue costando 3 credits
Los monitores locales dejan de fingir que envían correo. Un monitor local con ajustes de notificación por correo ahora informa de un error que señala a hosted: true en lugar de un éxito falso; la vía local de correo siempre fue un marcador de posición. Un monitor local notifica por webhook o Slack desde su máquina, se dispara solo mientras el proceso del MCP server está en ejecución, recupera al reiniciar las ejecuciones perdidas, y crawlforge monitor:run-due desde el cron del sistema es la vía local garantizada
El esquema de entrada de track_changes se declara una sola vez y lo comparten el registro del servidor y la herramienta, sin cambios de comportamiento. AlertNotificationSystem.js, 601 líneas que nada importaba, queda eliminado: su equivalente vivo es el módulo notificador, y el servicio alojado es quien envía el correo y los webhooks firmados
v6.1.0
Corrección
Confirmaciones en ambas eras del protocolo
Las cinco herramientas que preguntan antes de una ejecución cara —crawl_deep por encima de 500 páginas, batch_scrape en modo síncrono por encima de 25 URLs, deep_research por encima de 50 URLs, agent con el modelo pro y extract_structured sin LLM configurado y con más de tres campos obligatorios— más el aviso de pocos credits, estaban construidas sobre la petición en línea elicitation/create de la era 2025, y la revisión 2026-07-28 no tiene ningún canal de petición del servidor al cliente: en esa era el aviso no se disparaba, en silencio, y la operación seguía adelante sin preguntar. Ahora son respuestas input_required de varias idas y vueltas, que el SDK sirve en ambas eras, así que la pregunta te llega de una manera o de otra. Una llamada que vuelve pidiendo confirmación no ha hecho ningún trabajo, así que no cuesta credits ni deja registro de uso; el cargo se toma una sola vez, cuando la llamada se completa de verdad, y rechazarla no cuesta nada. La misma versión corrige la elicitación sobre HTTP, que nunca había funcionado: las dos patas HTTP se sirven desde un clon de la instancia del servidor y solo un clon llega a estar conectado, así que el ayudante leía capacidades de cliente indefinidas en cada petición HTTP y todas las sesiones seguían adelante sin preguntar —roto desde la v3.2.0 y fallando del lado seguro todo ese tiempo—. Ahora el transporte marca en el contexto de la petición el clon que la sirve, y stdio nunca se vio afectado.
Las confirmaciones llegan ya a los clientes de la era 2026-07-28: crawl_deep por encima de 500 páginas, batch_scrape en modo síncrono por encima de 25 URLs, deep_research por encima de 50 URLs, agent con el modelo pro y extract_structured sin LLM configurado y con más de tres campos obligatorios, además del aviso de pocos credits, preguntan antes de ejecutarse. Estaban construidas sobre la petición en línea elicitation/create de la era 2025, y la revisión 2026-07-28 no tiene ningún canal de petición del servidor al cliente, así que en esa era el aviso no se disparaba, en silencio, y la operación seguía sin preguntar. Cada una es ahora una respuesta input_required de varias idas y vueltas, que el SDK sirve en ambas eras: la pregunta llega igual a un cliente de la era 2025 que a uno de la era 2026
Una ida y vuelta de confirmación nunca se cobra. Una llamada que vuelve pidiendo confirmación no ha hecho ningún trabajo, así que cuesta 0 credits y no produce ningún registro de uso; el cargo se toma una sola vez, cuando la llamada se completa de verdad. Rechazar una confirmación no cuesta nada, y ninguna herramienta cambia de precio en esta versión
La elicitación sobre HTTP queda corregida, y nunca había funcionado: las dos patas HTTP se sirven desde un clon de la instancia del servidor y solo un clon llega a estar conectado, así que el ayudante leía capacidades de cliente indefinidas en cada petición HTTP y todas las sesiones seguían adelante sin preguntar —roto desde la v3.2.0 y fallando del lado seguro todo ese tiempo, sin preguntar nunca en lugar de preguntar mal—. Ahora el transporte marca en el contexto de la petición el clon que la sirve. Esto es solo el endpoint HTTP alojado: una conexión stdio, que es como se conectan npx crawlforge-mcp-server y todos los clientes de escritorio, nunca se vio afectada. Corregido aparte: la prueba de humo del paquete .mcpb en CI ya no fija una clave de API de relleno
Nuevo comando `crawlforge login`: una entrega de claves API a través del navegador. La CLI imprime una URL de aprobación, el usuario con sesión iniciada aprueba en el sitio web y la clave se entrega a la CLI una sola vez y se guarda en ~/.crawlforge/config.json. Nunca edita la configuración de un cliente; un agente de código puede ejecutarlo y transmitir la URL para que la clave nunca se pegue en una terminal
v6.0.0
Función
La migración al protocolo 2026-07-28
Una versión mayor, porque se eliminan o cambian tres cosas que quien llama puede ver. El MCP server habla ahora la revisión de protocolo 2026-07-28 de forma sin estado en el mismo endpoint /mcp que ya servía la era 2025, sin handshake, sin identificador de sesión y con server/discover en lugar de initialize. A qué era pertenece una petición lo decide el propio clasificador del MCP SDK y no una regla nuestra, así que el endpoint nunca puede discrepar del SDK sobre una petición fronteriza, y ambas eras se sirven desde una única definición de herramientas, de modo que no pueden divergir. Esto es solo el endpoint HTTP: una conexión stdio, que es como se conectan npx crawlforge-mcp-server y todos los clientes de escritorio, sigue negociando la era 2025 y nada cambia en ella. Se elimina: el parámetro task y el modo de tareas asíncronas en crawl_deep, batch_scrape, deep_research y agent, porque la especificación borró la superficie del SDK sobre la que estaban construidos en vez de moverla; y el flag --legacy-http, cuatro versiones menores después de que su propio aviso dijera que desaparecería. También se corrige: el endpoint HTTP alojado nunca había aplicado la pasada de higiene de protocolo que stdio siempre ha tenido, así que devolvía las herramientas en orden de registro, sin iconos y sin dialecto de esquema.
La revisión 2026-07-28 en el endpoint HTTP /mcp, servida sin estado junto a la era 2025 con sesión en la misma ruta. Una petición moderna lleva el sobre _meta por petición (versión de protocolo, clientInfo, clientCapabilities) más la cabecera Mcp-Method, y Mcp-Name en una llamada a herramienta; recibe su propia instancia de servidor y ningún identificador de sesión. El enrutado, las comprobaciones de discrepancia entre cabecera y cuerpo y el 415 ante un content type que no es JSON son todos código del propio SDK y no reimplementaciones, y todos ellos rechazan la petición antes de que la herramienta se ejecute, así que una llamada rechazada nunca se cobra. La autenticación no cambia y es idéntica en ambas eras —Bearer o X-API-Key en cada petición— y se ejecuta antes del enrutado por era, así que no hay ningún hueco con forma de era. GET /health lista ahora todas las revisiones que sirve el endpoint
Cambio incompatible: params.task y el modo de tareas asíncronas desaparecen de crawl_deep, batch_scrape, deep_research y agent. Estaban construidos sobre la API experimental de tareas del MCP SDK, y SEP-2663 eliminó esa API por completo en lugar de moverla a una extensión: en una conexión de la era 2026 un tasks/get entrante responde -32601 aunque haya un handler registrado, así que no quedaba nada a lo que migrar. Las cuatro herramientas ya no anuncian execution.taskSupport y ahora se ejecutan de forma síncrona, que es exactamente lo que ya obtenía todo el que nunca pasó task. Para trabajos largos que no deberían mantener abierta una conexión, usa el modo de webhook asíncrono de batch_scrape o el patrón result_handle y read_result. Otro cambio incompatible: CRAWLFORGE_LEGACY_HTTP y --legacy-http se eliminan junto con el modo sin estado v3.1 que activaban; los clientes de la era 2025 no se ven afectados, porque los sirve el transporte con sesión que es el predeterminado desde v3.2.0
Corregido: el endpoint HTTP alojado devuelve ahora el mismo tools/list que un cliente stdio ha tenido siempre. Los envoltorios de higiene de protocolo —el orden alfabético, los iconos, el dialecto JSON Schema 2020-12 y los marcadores de cacheabilidad en las diez herramientas de solo lectura— viven en la instancia de protocolo del servidor plantilla, no en las tablas de registro que copia el clon de cada sesión HTTP, así que a una sesión HTTP se le servían las herramientas en orden de registro y sin ninguno de los cuatro, mientras que stdio los tenía todos. Esto cambia lo que devuelve el endpoint alojado en la era 2025: la salida se reordena y gana iconos y un dialecto de esquema en cada herramienta. Es metadato aditivo más una reordenación —no se quita nada—, pero un cliente que fijara el orden anterior lo verá moverse. Corregido aparte: las peticiones de elicitación llegan a los clientes que habían dejado de recibirlas en silencio desde la actualización del SDK, porque el método de conveniencia del SDK rechazaba una forma de capacidad que su propia regla cuenta como válida
02v5.x
Versión 5
11
v5.10.0
Función
Búsqueda por lotes y redacción de datos personales
Dos parámetros, tanto en el MCP server como en la API REST alojada. search_web acepta ahora queries: entre una y diez búsquedas en una sola llamada, cada una recorriendo la misma canalización completa que una búsqueda simple, y con las cargas útiles devueltas en results_by_query, una lista en el orden en que se enviaron, con una entrada por consulta, de modo que una consulta repetida aparece dos veces y una consulta que falló lleva error en lugar de sus resultados. La llamada cuesta 5 credits por cada consulta que devolvió resultados: la proyección del lote, 5 x las consultas enviadas, es un techo, y un lote en el que no tuvo éxito ninguna sigue devolviendo 200 y no cuesta nada. Una llamada con una sola query es idéntica byte a byte a como era antes de esta versión: en ella no aparece ni queries, ni count, ni results_by_query. redact_pii elimina los datos personales del texto que devuelven nueve herramientas, antes de almacenar el resultado y no después, así que un resultado grande que se lea más tarde con read_result ya viene redactado. Su modo fast por defecto es una pasada de expresiones regulares sobre texto que la llamada ya descargó y no añade nada al precio.
queries (de 1 a 10 cadenas) en search_web como alternativa a query: hace falta exactamente uno de los dos, y enviar ambos o ninguno es un error de validación. Cada consulta recorre la canalización existente completa —expansión, caché, deduplicación, ranking— y la respuesta lleva queries, count y results_by_query, cuyas entradas van en el orden de entrada y llevan cada una su propio query más los campos de esa búsqueda, o query y error cuando esa búsqueda concreta falló. Una llamada a search_web cuesta 5 credits por cada consulta que devolvió resultados: una query simple cuesta 5, y un lote queries se proyecta en 5 x el número de consultas enviadas, que es un techo: cada consulta que falló no se cobra, así que un lote en el que no tuvo éxito ninguna no cuesta nada. Las dos superficies cobran esto igual, y un lote en el que no tuvo éxito ninguna es un 200 con el sobre habitual —count tal como se envió, una entrada { query, error } por consulta y credits_used: 0—, no un error. La ruta de query simple no cambia: una búsqueda que falla da error como siempre
redact_pii (booleano, u { entities, replace_style, mode }) en scrape, extract_content, extract_text, batch_scrape, crawl_deep, stealth_mode (operación "scrape"), scrape_with_actions, process_document y search_web. Cuatro clases de expresiones regulares: EMAIL, PHONE, FINANCIAL (números de tarjeta validados con Luhn, IBAN con mod-97) y SECRET (claves de API, tokens bearer, contraseñas etiquetadas); PERSON y LOCATION solo existen con modelo. replace_style es tag (<EMAIL>), mask ([REDACTED]) o remove, y SECRET conserva su etiqueta y sustituye solo el valor en todos los estilos, incluido remove. Los nombres de entidad no distinguen mayúsculas, y un nombre que la superficie no puede redactar se rechaza con un 400 que nombra las clases aceptadas en lugar de descartarse en silencio: un descarte silencioso devolvería una página todavía llena de lo que se pidió eliminar
El resultado lleva redaction: { entities, count, mode } en result.redaction en el MCP server y dentro de data en la API REST, deliberadamente la misma ruta de acceso. Está presente siempre que se pidió redacción, incluido entities: {} con count: 0 cuando la página no contenía nada, de modo que «no se encontró nada» se distingue de «se ignoró el parámetro»; una clase sin coincidencias se omite en lugar de informarse como 0. El modo "fast" añade 0 credits. En el MCP server, el modo "model" añade 3 credits una vez por llamada y ejecuta una pasada NER de Ollama solo para PERSON y LOCATION, informa de model_ran y no cobra el suplemento cuando ningún modelo respondió; en la API REST es un 400 sin cobro que nombra el MCP server, el mismo trato que ya recibe el modo "model" de los formatos highlights y question. Dos límites conocidos, dichos en vez de escondidos: los contadores derivados del texto (content_length, word_count, character_count) lo describen antes de la redacción, y los campos de tipo dirección (url, link, href, canonical_url) no se redactan nunca, a propósito
v5.9.0
Función
Escalada opcional en scrape
Una página que responde con un muro antibots en lugar de con la página ya no obliga a hacer una segunda llamada. Con escalate: true, la descarga simple sigue ejecutándose primero; solo cuando el documento llega como un desafío de Cloudflare, Amazon, DataDome, PerimeterX, Akamai o Vercel, como un armazón vacío o como un marcador de posición corto con título de error, la misma llamada lo renderiza una vez en el navegador stealth y deriva de ese renderizado todos los formatos solicitados. La proyección es el techo, 7 credits, y una llamada que resolvió la descarga simple paga los 2 credits base. El robots.txt se respeta también en la ruta escalada, comprobado contra el mismo token de producto CrawlForge, y ninguna defensa antibots se trata de forma distinta a como ya la trata stealth_mode. La escalada ahorra una llamada; no garantiza la página: un muro en el que también se rechaza el renderizado stealth vuelve como un bloqueo honesto con escalated: true, sin cobro alguno, y el caso que resuelve con más fiabilidad es una página que simplemente necesita JavaScript para renderizarse.
escalate (booleano, por defecto false) y escalate_engine (playwright o camoufox, por defecto playwright) en scrape, tanto en el MCP server como en la API REST alojada: la descarga simple siempre se ejecuta primero y solo un veredicto de bloqueo escala — una página de desafío con nombre, un armazón vacío o un marcador de posición corto con título de error. markdown, html, rawHtml, text, links, metadata y los formatos highlights y question acotados a una consulta se derivan todos de la página renderizada, así que una respuesta escalada tiene la forma de una normal y no necesita una llamada aparte a stealth_mode
Lo que cuesta: los 2 credits base, +1 cuando hay un formato highlights o question acotado a una consulta, y +5 solo cuando la etapa stealth llegó a ejecutarse. Una llamada con escalate: true proyecta 7 y se cobra 2 cuando la descarga simple sirvió la página, así que la proyección es un techo y nunca un suelo; una ruta que robots.txt no permite se rechaza antes de descargar nada y no se cobra, y en la API REST alojada la etapa devuelve 503 y no cobra nada donde el backend de ejecución de CrawlForge no está configurado; un muro en el que también se rechaza el renderizado stealth se devuelve como un bloqueo con escalated: true y no se cobra
data lleva escalated siempre que se envió escalate: true, más stealth: { engine, vendor_detected } cuando el renderizado se ejecutó — vendor_detected nombra al proveedor de defensa antibots con el que topó la descarga simple (cloudflare, amazon, datadome, perimeterx, akamai, vercel) o null para un armazón vacío o un marcador de posición de error. Solo en el MCP server, un host que haya bloqueado una petición se recuerda durante 24 horas, de modo que la siguiente llamada con escalate: true a ese host se salta la descarga simple condenada al fracaso y lo indica en un aviso; la escalada reutiliza la ruta stealth existente, la misma comprobación de cumplimiento y la misma identidad CrawlForge, sin añadir ninguna evasión nueva
v5.8.0
Función
Handles de resultado y read_result
Un resultado que inundaría el contexto ahora llega como una vista previa y un handle. Diez herramientas de salida grande aceptan max_inline_chars (por defecto 40.000 caracteres de JSON); por encima, la respuesta lleva preview, result_handle, total_chars, truncated: true y expires_at, y la nueva herramienta read_result —la número 30, 1 credit— recorta, busca, pagina por líneas o lee una ruta JSON del resultado almacenado. No se vuelve a descargar nada y, en el MCP server, nada sale de tu máquina: el almacén es local, con un TTL de 1 hora.
max_inline_chars en scrape, fetch_url, extract_content, crawl_deep, batch_scrape, stealth_mode scrape, scrape_with_actions, process_document, deep_research y extract_embedded_state (1.000–10.000.000, por defecto 40.000): por encima, data conserva sus campos escalares de primer nivel más preview (los primeros max_inline_chars caracteres de la vista de texto: el markdown en scrape, el cuerpo en fetch_url, el JSON formateado en crawl_deep, batch_scrape y deep_research), result_handle (res_ + UUID), total_chars, view, view_path, truncated: true, expires_at y un aviso que nombra read_result; extract_embedded_state nunca se trunca pero devuelve igualmente un handle con truncated: false; las respuestas de error nunca se almacenan
read_result (1 credit, básica): { handle, operation: slice | search | lines | json_path }: slice devuelve offset, length, text y has_more (10.000 caracteres por defecto); search es una coincidencia literal de subcadena sin distinguir mayúsculas, nunca una expresión regular, y devuelve hasta max_matches (1–100, por defecto 20) coincidencias con offset, length y 200 caracteres de contexto a cada lado, más total_matches y truncated; lines devuelve first_line, line_count, total_lines, char_offset, lines y has_more (200 líneas por defecto, máximo 5.000); json_path devuelve path, value y value_chars, o value: null con un preview y un aviso para acotar la ruta cuando el valor supera max_inline_chars; un handle desconocido o caducado es 404 RESULT_NOT_FOUND y no se cobra
Dónde viven los resultados: en el MCP server, un almacén local en ~/.crawlforge/results/ con TTL de 1 hora y LRU de 200 MB, sin subir nada; en la API REST, un valor por cuenta con TTL de 1 hora que solo puede leer la cuenta que lo creó; los resultados almacenados de batch_scrape comparten el mismo almacén y get_batch_results no cambia; el recuento de herramientas es 30 en ambas superficies
v5.7.0
Función
Scrapes acotados a una consulta: highlights y question
scrape gana dos formatos que llevan una consulta: highlights devuelve las frases, filas de tabla y bloques de código que coinciden con ella, y question devuelve una respuesta construida a partir de ellos. Ambos son extractivos por defecto —texto literal de la página con un desplazamiento en el markdown de la misma llamada, sin ningún modelo por medio— por 1 credit además del scrape, a diferencia de las herramientas de descarga que resumen, parafrasean la página e inventan lo que no encuentran. El modo con modelo es opcional, está etiquetado y se cobra aparte, con una comprobación de fundamentación sobre cada cifra y nombre propio.
highlights: añade { "type": "highlights", "query": "…" } a formats y obtén hasta max_highlights (1–50, por defecto 10) unidades clasificadas por BM25 con refuerzos por frase y por encabezado, cada una con text, kind (sentence, table_row o code_block), offset, length y score; offset y length apuntan al formato markdown de la misma llamada, así que markdown.slice(offset, offset + length) === text
question: añade { "type": "question", "question": "…" } y obtén answer.text compuesto por la mejor evidencia con grounded: true y hasta 5 unidades de evidence; mode: "model" es opcional y deja que un modelo sintetice la respuesta (primero Ollama, luego una clave de OpenAI o Anthropic en el servidor, luego sampling de MCP) o elija entre los candidatos de highlights, y una comprobación de fundamentación marca la respuesta como grounded: false con un aviso si alguna cifra o nombre propio no aparece en la evidencia
Precio y paridad: scrape sigue costando 2 credits; un formato highlights o question añade 1 credit una sola vez por llamada, mode: "model" añade 3 más, y si no existe ninguna ruta a un modelo el servidor vuelve al modo extractivo, avisa y cobra solo el precio extractivo; la API REST admite ambos formatos en modo extractivo y rechaza mode: "model" con 400 UNSUPPORTED_FORMAT sin cobrar nada; un scrape cuyo markdown supera los 40.000 caracteres ahora sugiere highlights
v5.6.11
Mejora
Seis rondas de regresión en vivo, selección de herramientas más inteligente y resultados sigilosos honestos
La línea 5.6: todo lo que seis pasadas completas por las 29 herramientas y 18 plantillas contra sitios reales sacaron a la luz, un servidor que guía al modelo a la herramienta correcta a la primera, un navegador sigiloso que informa de una página de error como error y, en 5.6.11, un scrape normal que informa de una página de desafío, una cáscara vacía o una página de error de relleno como fallo nombrando al proveedor —el mismo veredicto que devuelve la API REST—, con informes de uso que llevan la versión real del cliente y el texto tecleado en formularios enmascarado antes de salir de tu máquina.
Las tablas de datos sobreviven y los fallos silenciosos desaparecen: extract_content, process_document y scrape_with_actions vuelven a adjuntar las tablas que Readability descarta; scrape conserva los enlaces en línea en CSS con anidamiento nativo; la extracción con LLM responde null en vez de inventar un valor; las descargas de páginas no envían Accept-Language; cada ruta de descarga decodifica la página según su juego de caracteres declarado, así que kakaku.com ya no llega como mojibake; el agente contrasta cada versión, fecha y cifra de su respuesta con las fuentes y señala las que no puede respaldar; analyze_content segmenta hindi, finés y japonés en palabras
Resultados honestos en todas las rutas: stealth_mode informa de los desafíos de Cloudflare, DataDome, PerimeterX y Vercel como bloqueos y de una página de error HTTP o un bloqueo blando con título como fallo con su código de estado, espera hasta 8 s a que un documento en blanco se pinte, mantiene vivo un contexto de Chromium durante una descarga con camoufox y simula el hardware también dentro de los Web Workers; scrape_with_actions juzga el documento en el que terminó su cadena; y desde 5.6.11 el scrape normal también: un muro de Cloudflare, Amazon, DataDome, PerimeterX, Akamai o Vercel, sea cual sea su código HTTP, una cáscara JavaScript vacía o una página de error corta es success: false con blocked.vendor y un siguiente paso que nombra stealth_mode, tanto en el servidor MCP como en la API REST
crawl_deep descarga las URL con su barra final (globalpetrolprices.com respondía 404 a la forma recortada); deep_research busca una forma de 12 palabras de un tema largo (12 fuentes donde el párrafo encontraba una); localization fija Accept-Language según el país configurado; la plantilla shopify-product lee el JSON-LD de la página del producto cuando la tienda rechaza su endpoint JSON; reddit_search explica un resultado vacío de Arctic Shift; los informes de uso llevan la versión real del cliente, y el texto que scrape_with_actions teclea en un formulario se enmascara antes de que el informe de uso salga de tu máquina
Selección de herramientas: las instrucciones del servidor son una escalera de decisión con costes en créditos y una regla de no volver a descargar, cada descripción de herramienta dice cuándo no usarla y cuánto cuesta, scrape, search_web y deep_research se cargan al inicio de la sesión, y cada resultado de error termina con un siguiente paso que nombra la herramienta a probar en lugar de un reintento
v5.5.9
Mejora
Precisión de extracción y refuerzo de seguridad
La línea 5.5: búsqueda de comentarios en todo Reddit, localization que ahora ejecuta la búsqueda que antes solo describía, y una larga serie de correcciones de precisión, incluidos los ejemplos de código que desaparecían en silencio de las páginas de documentación.
reddit_search busca comentarios en todo Reddit, y localize_search ya ejecuta la búsqueda
Cada número que devuelve un LLM se verifica contra el código fuente de la página, o vuelve como null y con un motivo
Protección SSRF en todas las rutas de navegador y webhook, y el texto de la página aislado antes de llegar a un modelo
v5.4.3
Función
extract_embedded_state, la herramienta número 29
Extracción de nivel 2: leer el estado JavaScript que la página ya trae en lugar de su HTML renderizado. Una sola petición, valores exactos, sin LLM de por medio. La misma versión corrigió nueve defectos encontrados al probar las 29 herramientas en sitios reales.
Nueva extract_embedded_state (2 credits): lee __NEXT_DATA__, payloads de React Server Components, Nuxt y Apollo
extract_metadata suma un filtro json_ld_types que también reconoce los subtipos
Cinco operaciones de track_changes que solo devolvían un error ahora funcionan
v5.3.1
Mejora
Una sola identidad de rastreo y robots.txt en cada ruta
El trabajo de cumplimiento e identidad: todas las herramientas que hacen peticiones consultan robots.txt, una petición rechazada no cuesta nada, y las peticiones salientes van firmadas para que un sitio pueda verificar que el rastreo vino de CrawlForge.
Todas las herramientas que hacen peticiones consultan robots.txt, incluidas las dos rutas de stealth que lo esquivaban
Peticiones salientes firmadas según RFC 9421 (Web Bot Auth), con un directorio de claves público
Un rechazo por cumplimiento es gratis: ya no consume credits
v5.2.9
Función
Plantilla de Shopify y un paquete de extractores compartido
Una plantilla shopify-product que lee el JSON de la propia tienda en vez de analizar la página renderizada, soporte para Ollama remoto, y los extractores de plantillas separados en su propio paquete npm para que ambas superficies de CrawlForge usen una sola implementación.
Plantilla shopify-product: precio exacto, precio comparativo y stock por variante, sin analizar HTML
track_changes califica un cambio de precio real, no cuánto de la página ocupa
Ollama registrado como proveedor real, eligiendo el mejor modelo instalado
v5.1.0
Función
reddit_search, la herramienta número 28
Búsqueda real en Reddit, aunque reddit.com bloquea todas las vías directas que CrawlForge puede tomar. La herramienta nunca toca reddit.com: consulta los archivos mantenidos por la comunidad. Es de uso gratuito y no necesita credenciales.
reddit_search (2 credits): búsqueda por palabras clave en publicaciones y comentarios, más hilos completos
Respaldada por los archivos Arctic Shift y PullPush, con un reintento acotado cuando uno se satura
Resultados normalizados a permalinks reales y fechas ISO, con avisos sobre la frescura del archivo
v5.0.5
Función
v5.0: adopción de la especificación MCP y revisión de seguridad
La versión mayor: refuerzo de SSRF, OAuth, secretos y facturación, 52 correcciones incluida una reescritura de crawl_deep, un transporte streamable-HTTP reconstruido, y las funciones de la especificación MCP que los clientes ya esperaban.
Salida estructurada (outputSchema) y tareas asíncronas en las herramientas de larga duración
CRAWLFORGE_TOOLS y CRAWLFORGE_TOOL_GROUPS exponen solo un subconjunto para reducir el contexto
Cero vulnerabilidades en npm audit y el mínimo de Node elevado a 20.16
03v4.x
Versión 4
03
v4.10.0
Mejora
Los clientes ahora prefieren CrawlForge para tareas web
El servidor envía instructions de MCP que le indican a cualquier cliente preferir las herramientas de CrawlForge antes que su búsqueda web integrada. Es una recomendación, no una imposición: un MCP server no puede desactivar las herramientas propias del cliente.
Las instructions de MCP a nivel de servidor llegan a cada cliente en el siguiente arranque, sin reinstalar
Cuatro descripciones de herramientas reforzadas donde el modelo las lee, en tools/list
serp_rank devuelve el top 10 orgánico completo junto con las posiciones del dominio
v4.9.0
Función
serp_rank: posición orgánica real en Google
La herramienta número 27. Informa en qué posición aparece realmente un dominio en los resultados orgánicos de Google para una palabra clave — la que una API de búsqueda no puede dar — con DataForSEO y cargado a tu propia cuenta.
serp_rank (5 credits) devuelve la mejor posición, la URL que rankea y todas las posiciones que ocupa
Nunca inventa una posición: informa configured:false cuando DataForSEO no está configurado
Facturación reforzada: una llamada sin costo no emite evento de uso y un error cobra la mitad
v4.8.1
Función
Agent Skills, SSRF real y monitoreo programado
Claude Agent Skills que se activan solas, dos controles de seguridad anunciados que en realidad no funcionaban ahora hechos realidad, y monitoreo de cambios que sí se programa.
Siete Claude Agent Skills con descripciones que las activan automáticamente
SSRF aplicado en la ruta real de scraping, validando la petición inicial y cada redirección
Los monitores programados de track_changes persisten y recuperan su línea base al reiniciar