Experimento de usuario: MCP Screaming Frog + API Python en Claude Code

Publicado en nicolasbillia.com — Mayo 2026

Read this post in English →

Rana de Screaming Frog entre dos paneles luminosos: API (con código Python a la izquierda) y MCP (interfaz tipo chat a la derecha), fondo navy con grilla

Disclaimer

Este contenido fue generado con ayuda de IA a partir de la ingesta de contexto: media docena de proyectos trabajados en Claude Code usando la API de Antonio Maculus (link en el segundo párrafo) + registro de logs de la sesión de iteración con el MCP. Todo el desglose técnico sale de los logs que me devolvió Claude Code; las ideas de análisis tienen una base propia + brainstorming en el momento de armar el post.

Hoy se anunció el MCP oficial de Screaming Frog. Lo conecté a Claude Code apenas salió y armé un smoke test sobre nicolasbillia.com (40 URLs) para medir, endpoint por endpoint, qué hace bien y cuánto “pesa” el output cuando viaja al contexto del LLM.

En paralelo, la librería Python de Antonio Atilio Maculus (repo en GitHub) la venimos usando desde principios de año en media docena de auditorías reales — e-commerce, retail premium, edu, moda, medios. Eso es la base de comparación: el MCP recién salido contra una herramienta probada en producción.

La pregunta natural es “cuál es mejor”, pero después de correr ambas en paralelo la respuesta es otra: no son lo mismo, no compiten, y combinándolas se desbloquean flujos que con cualquiera sola no salen.

Este post es una prueba de usuario, no autoridad técnica. Antonio sabe diez veces más de su librería que yo, y del MCP oficial recién llevo unas horas. Lo que aporto es la mirada del SEO que las usa para auditar sitios reales: la metodología del experimento de hoy, catálogo de capacidades con números absolutos, scoring por categoría de análisis, 20 ideas de uso combinado, setup paso a paso, queries SQL listas, y cómo cruzar el crawl con GSC y GA4.

Contenido del post

  1. 0. Cómo medí — metodología del smoke test
  2. 1. Catálogo de capacidades — qué saca cada uno
  3. 2. Scoring 1-3 por categoría de análisis (semáforo)
  4. 3. Decision tree — qué herramienta uso
  5. 4. Setup paso a paso
  6. 5. 20 ideas de uso combinado MCP + API
  7. 6. Workflow combinado clásico (3 pasos)
  8. 7. Pipeline 3-en-1: SF + GSC + GA4 con código real
  9. 8. Glosario de tablas Derby APP.* (~25)
  10. 9. Recetario — 10 queries SQL listas para copiar
  11. 10. Tokens consumidos en Claude Code por endpoint MCP
  12. 11. Gotchas reales que me trabaron
  13. 12. FAQ
  14. Cierre

0. Cómo medí — metodología del smoke test

Quería un sitio chico para no quemar crédito ni tiempo, y conocido para validar los resultados. Elegí nicolasbillia.com (mi sitio personal, ~40 URLs HTML, WordPress block theme, hreflang ES/EN).

Setup del experimento:

  1. Screaming Frog con MCP server activado en http://localhost:11435/mcp.
  2. Claude Code conectado al MCP vía claude mcp add seospider --transport http http://localhost:11435/mcp.
  3. API Python de Antonio cargando el mismo crawl con Crawl.load(crawl_id, db_id_backend="derby", csv_fallback=False).

Endpoints MCP medidos (6 calls representativas): sf_crawl, sf_crawl_progress, sf_generate_report (Redirects:All), sf_export_seo_element_urls (Canonicals:Missing), sf_bulk_export_page_content (visible_text), sf_export_embeddings.

Qué medí en cada llamada:

  • Tamaño del archivo en disco (KB) — output completo guardado a archivo.
  • Tamaño del output que vuelve al contexto LLM (chars / KB) — lo que Claude efectivamente “ve” después de la tool call.
  • Tokens estimados consumidos en el contexto (chars / 4, criterio conservador).
  • Errores y dependencias: si el endpoint falla, qué config previa exige (PageSpeed API key, hreflang crawl flag, Database storage, etc.).

Lo que el smoke test NO mide:

  • No es un benchmark formal — N=1 sitio, 40 URLs, una sola corrida.
  • Los tokens NO escalan linealmente: cuando hay file_path, el sample devuelto es ~1 URL y no crece con el sitio. Sin file_path, sí escala con el sitio entero.
  • No medí latencia del crawl en sí (SF tarda igual con o sin MCP). Lo que medí es el costo en contexto LLM y la “ergonomía” del endpoint.

De dónde sale la conclusión “uno escala a 100K+ URLs y el otro no”

Aclaración importante: no probé el MCP sobre un sitio de 100K URLs todavía. El MCP lleva pocos días desde su lanzamiento — no hubo tiempo material de correrlo en una auditoría real de ese tamaño, y el smoke test de hoy fue justamente sobre un sitio chico para mapear la mecánica básica de cada endpoint. La librería de Antonio Maculus, en cambio, sí venía con contexto de uso previo: la corrimos en auditorías reales de decenas de miles de URLs durante los últimos meses. La conclusión sobre escalabilidad es, entonces, inferencia desde la arquitectura de cada solución, validada parcialmente con esos casos reales en la librería de Antonio y no validada todavía con el MCP en sitios grandes.

Por qué la librería de Antonio escala mejor en volumen (razonamiento arquitectónico):

  • Lee directo de Apache Derby, la base de datos interna de SF. No hay parseo intermedio de NDJSON ni CSV.
  • Las queries usan SQL nativo: planner, índices, JOINs eficientes. La complejidad del query no infla el output que viaja al LLM — solo viaja el resultado del SELECT, no el dataset completo.
  • Tablas pre-computadas como APP.INLINK_COUNTS evitan agregar sitewide en runtime.
  • Lazy evaluation + pandas: el resultado se materializa solo cuando lo pedís.
  • Validación parcial en producción: corrimos auditorías sobre sitios de decenas de miles de URLs sin que la librería se rompa, aplicando los workarounds documentados (Derby forzado, APP.INLINK_COUNTS en vez de links("in").collect()).

Por qué el MCP oficial tiene un techo más bajo en volumen (razonamiento arquitectónico + dato del smoke test):

  • Los outputs viajan como NDJSON o CSV. No hay SQL ni indexación: es un stream de filas.
  • Si pasás file_path, el archivo se guarda a disco y al LLM le vuelve un sample chico (esto lo medimos hoy). Si NO pasás file_path, el archivo entero viaja al contexto.
  • En el smoke test sobre 40 URLs, sf_export_embeddings generó 539 KB. Extrapolación lineal a 100K URLs: del orden de 1,3 GB. El archivo se genera, pero el LLM no puede leerlo directo: hay que parsearlo afuera con un script.
  • El MCP no resuelve cross-table queries (no hay SQL). Para cruzar dos bulk exports en un sitio grande, terminás escribiendo Python igual.

Conclusión honesta: el techo del MCP en volumen es el context window del LLM, no SF. SF crawlea millones de URLs sin problema; el MCP hace de puente, y ese puente tiene ancho de banda limitado. La API de Antonio salta el puente y va directo al fondo de la pileta (la DB Derby), por eso aguanta mejor.

1. Catálogo de capacidades — qué saca cada uno

1.1 MCP oficial de Screaming Frog

  • 61 reports nativos en 13 grupos:
    • Crawl Overview / Issues / Segments (3)
    • Redirects (4): All, Chains, Redirect & Canonical Chains, Redirects to Error
    • Canonicals (2): Chains, Non-Indexable
    • Pagination (2): Non-200, Unlinked
    • Hreflang (7): All, Non-200, Unlinked, Missing Return, Inconsistent Language, Non-Canonical Return, Noindex Return
    • Insecure Content / SERP Summary / Orphan Pages (3)
    • Structured Data (5): Validation Errors Summary, Validation Errors, Parse Errors, Rich Results Summary, Rich Results
    • JavaScript Console Log Summary (1)
    • PageSpeed (28): Opportunities Summary, CSS/JS Coverage, Minify, Reduce Unused, Render Blocking, LCP, CLS, Fonts, DOM Size, etc.
    • Mobile (4): Viewport, Target Size, Content Sized, Illegible Font
    • Accessibility Violations Summary (1)
    • Cookies Summary (1)
  • 130+ bulk exports en 16 grupos: Queued, Links (12), Web Headers / Cookies (3), Path Type (4), Security (6), Response Codes (31), Content (6), Images (8), Canonicals (12), Directives (17), JavaScript (3), AMP (7), Structured Data (6), Sitemaps (4), Custom Search / Extraction (5), URL Inspection (3), Accessibility (12).
  • 5 utilidades extra: screenshots de URL, embeddings export (vectores), bulk page content (raw HTML o visible text), Node.js runner para post-procesos custom, browser opener.

Lo que no pude usar como usuario (limitaciones que detecté):

  • PageSpeed reports — requieren tener PageSpeed Insights API key configurada en SF antes del crawl. Sin esa key, los 28 reports vuelven vacíos.
  • Accessibility WCAG — requiere activar el módulo Axe en SF Config > Spider > Crawl > Accessibility antes del crawl.
  • URL Inspection (Rich Results, Referring Pages, Sitemaps) — requiere conexión a GSC vía OAuth dentro de SF.
  • Custom Search / Custom Extraction — dependen de patrones (XPath, regex) definidos en SF antes del crawl.
  • Change Detection — requiere 2 crawls del mismo sitio comparables.
  • Hreflang reports — el sitio crawleado debe haberse crawleado con “Crawl Hreflang” activado (mi caso del benchmark estaba apagado).

1.2 API Python de Antonio Maculus

  • 159 filtros GUI distribuidos en 12 módulos de SEO element:
    • response_codes (32 filtros)
    • directives (18)
    • hreflang (15)
    • canonicals (12)
    • headings H1/H2 (12)
    • internal content-types (12)
    • structured_data (12)
    • pagination (11)
    • page_titles (10)
    • meta_description (9)
    • images (8)
    • meta_keywords (4)
  • 9 reports de auditoría pre-armados: broken_links_report, broken_inlinks_report, nofollow_inlinks_report, title_meta_audit, indexability_audit, orphan_pages_report, security_issues_report, canonical_issues_report, hreflang_issues_report.
  • ~25 tablas Derby APP.* accesibles por SQL directo (ver glosario en sección 8).
  • 6 backends: Derby (fast, requiere Java), DuckDB (cache analítico), CSV (sin deps externas), SQLite, CLI, Hybrid (Derby + CSV fallback).
  • Outputs: pandas / polars / dicts / iteradores lazy / método .to_sql() para inspeccionar la query generada.
  • Cobertura: 601/628 tabs mapeados (95,7%), 15.490/15.589 campos (99,4%).

Lo que no pude usar o se cuelga:

  • crawl.summary() se cuelga en crawls grandes (100K+ URLs). Workaround: SQL directo sobre tablas APP.*
  • canonical_issues_report() e indexability_audit() con backend default. Forzar db_id_backend="derby" + csv_fallback=False.
  • links("in").collect() sitewide en sitios grandes. Usar APP.INLINK_COUNTS pre-computado.
  • Filtros documentados como TODO en el código fuente: pixel-width titles/metas, “Is Relative” canonicals, “Incorrect Language Codes” hreflang, “Background Images”, varios Pagination y Structured Data sub-filtros.

2. Scoring 1-3 por categoría de análisis (semáforo)

Escala: 3 super útil — 2 sirve con caveats — 1 no recomendado.

Criterio por columna y origen de cada score:

  • Velocidad: tiempo desde la query hasta tener el dato listo para analizar (de la llamada a pandas o al archivo). Score basado en el smoke test (MCP) + auditorías reales acumuladas (API).
  • Facilidad: curva de aprendizaje y fricción típica de cada endpoint. Score basado en el smoke test (MCP) + experiencia de uso en producción (API).
  • Volumen: capacidad inferida de procesar 100K+ URLs sin romper. Este score es inferencia arquitectónica (ver subsección anterior sobre infraestructura), validado parcialmente con la API en auditorías reales de decenas de miles de URLs, pero no probado todavía con el MCP en sitios de ese tamaño.

Importante: lo que sigue es una matriz de inferencias razonadas, no conclusiones cerradas. Cada celda la vamos a ir validando en sitios reales y este post se va a actualizar a medida que tengamos data dura. Si tenés un caso de uso o un sitio donde una celda no se sostiene, escribime y lo incorporo.

Categoría de análisis MCP Vel MCP Fac MCP Vol API Vel API Fac API Vol
Response codes
Redirects (chains, loops)
Canonicals
Hreflang
Directivas robots
Page titles / Meta description
Headings H1/H2
Inlinks / Outlinks (granular)
Content duplicates (exact / near / similar)
Imágenes (alt, peso, dimensiones)
Structured data / Rich Results
PageSpeed / Core Web Vitals
Mobile usability
Accessibility (WCAG)
Screenshots / Embeddings / Node.js

Lectura rápida: para inlinks granulares y queries cross-tabla, API gana. Para PageSpeed, accessibility, screenshots y embeddings, el MCP es exclusivo o claramente mejor. Para el grueso de análisis SEO estándar (canonicals, hreflang, directivas, titles), ambas funcionan bien — la elección depende más del workflow que de la herramienta.

3. Decision tree — qué herramienta uso

¿Querés lanzar el crawl desde el LLM?
├── Sí ────────────────────────────→ MCP (la API solo lee)
└── No (crawl ya existe)
    │
    └── ¿Qué tipo de análisis?
        │
        ├── PageSpeed / WCAG / Screenshots / Embeddings → MCP exclusivo
        │
        ├── Cross-tabla (canonical × hreflang × inlinks) → API (SQL directo)
        │
        ├── Volumen alto (1M+ inlinks / 100K+ URLs)
        │   ├── Solo extracción → API (pandas, sin parsear NDJSON)
        │   └── Reports nativos formato cliente → MCP con file_path
        │
        ├── Pipeline reutilizable (varios clientes)
        │   └── API (pandas + SQL más limpios de mantener)
        │
        └── Reports SF de catálogo
            ├── Para entregable cliente → MCP (formato CSV nativo)
            └── Para análisis intermedio → cualquiera

4. Setup paso a paso

4.1 Claude Desktop

Referencia oficial: Screaming Frog SEO Spider — Configuration / MCP Server. Al momento de escritura, la sección pública específica del MCP server todavía no estaba detallada en esa URL — el setup que documentamos abajo es el que funcionó en nuestra instalación.

  1. Abrir SF y activar el MCP server desde Configuration > API Access > MCP Server (la opción aparece en versiones recientes).
  2. Confirmar que el servidor está escuchando en http://localhost:11435/mcp (puerto default).
  3. Editar ~/Library/Application Support/Claude/claude_desktop_config.json y agregar:
{
  "mcpServers": {
    "seospider": {
      "type": "http",
      "url": "http://localhost:11435/mcp"
    }
  }
}
  1. Restart Claude Desktop (cerrar y abrir).
  2. En el chat, escribí “list available tools” — deberían aparecer los sf_*.

4.2 Claude Code (el flujo que usamos)

SF tiene que estar abierto y con el MCP server activado (mismos pasos 1 y 2 de arriba).

# Agregar el server al config de Claude Code:
claude mcp add seospider --transport http http://localhost:11435/mcp

# Verificar que quedó registrado:
claude mcp list

Alternativa equivalente — editar manualmente ~/.claude.json:

{
  "mcpServers": {
    "seospider": {
      "type": "http",
      "url": "http://localhost:11435/mcp"
    }
  }
}

Una vez registrado, las herramientas aparecen como mcp__seospider__sf_* dentro de Claude Code. Un crawl básico:

sf_crawl(crawl_url="https://example.com", crawl_name="Auditoría inicial")
sf_crawl_progress()
sf_generate_report(
  category="Hreflang:Missing Return Links",
  export_type="CSV",
  file_path="hreflang.csv"
)

Atajo lúdico pero real: si esto te parece denso, copiá la URL de este post y pasásela a Claude Code con /url <link>, después pedile “implementame el setup del MCP de Screaming Frog según las instrucciones del post”. Es legítimo. Es exactamente el caso de uso para el que se diseñó MCP.

5. 20 ideas de uso combinado MCP + API

# Idea Combinación Output
1 Auditoría técnica integral one-shot MCP lanza crawl + API genera DataFrame con issues priorizados CSV ordenado por impacto + crawl_id reutilizable
2 Canibalización semántica con valor comercial MCP exporta embeddings + API cruza con GA4 conversiones Lista de URLs a redirigir/mergear con justificación revenue
3 Diagnóstico hreflang post-deploy MCP lanza crawl + API SQL sobre MULTIMAP_HREF_LANG_* Tabla de fixes específicos por variante regional
4 Orphan pages con tráfico API extrae orphans + MCP descarga screenshots Lista visual + recomendación de internal linking
5 Audit canonicals cross-path post-migración API SQL custom + MCP confirma con bulk export Lista validada de redirects 301 a implementar
6 Detección cloaking por User-Agent MCP script Node.js (curl + UA spoof) + API compara HTML hash Reporte de URLs con respuesta divergente browser vs Googlebot
7 Auditoría WCAG agrupada por template MCP genera Accessibility:All Violations + API agrupa por path Lista priorizada por template, no por URL
8 Zombie pages con impressions GSC MCP exporta Directives:Noindex Inlinks + API cruza con GSC URLs noindex que aún reciben tráfico, candidatas a remoción real
9 Internal linking gaps en clusters semánticos MCP embeddings + API APP.INLINK_COUNTS + cosine similarity Pares de URLs altamente similares sin enlace mutuo
10 Structured data errors por template MCP Structured Data:Validation Errors + API agrupa por URL pattern Errores comunes al template, no a la URL individual
11 Chains de redirects internos MCP bulk Response Codes:3xx Inlinks + API SQL traversal Mapa de chains internos con número de hops
12 Imágenes sin alt priorizadas por tráfico MCP Images:Missing Alt + API cruza con APP.INLINK_COUNTS y GA4 Lista priorizada de imágenes a corregir según impacto
13 PageSpeed comparativo template vs template MCP PageSpeed reports + API agrupa por path pattern Templates más lentos identificados, no URLs sueltas
14 JS rendering issues clasificados MCP JavaScript Console Log + API filtra por severity Lista de páginas con JS roto agrupada por tipo de error
15 Duplicados near vs exact por sección API SQL sobre APP.NEAR_DUPLICATE / DUPLICATES_TITLE Decisión consolidación con criterio de proximidad
16 Canonicals contradictorios con hreflang API SQL multi-tabla (URLS + MULTIMAP_HREF_LANG_*) Conflictos en política de consolidación
17 Rich Results elegibilidad MCP Rich Results Features + API estima impacto SERP URLs elegibles para snippet enriquecido priorizadas
18 Diff pre/post deploy API CrawlDiff entre 2 crawls + MCP genera reports diff Validación QA post-deploy automatizada
19 Mapeo cookies por path (GDPR) MCP All Cookies bulk + API agrupa por path Audit GDPR / consent con coverage
20 Mixed content por sección MCP Security:Mixed Content + API agrupa por path Priorización por crawl depth y tráfico

6. Workflow combinado clásico (3 pasos)

  1. MCP lanza el crawl: sf_crawl(crawl_url=...) + sf_crawl_progress() hasta 100%.
  2. API lee el resultado con SQL: Crawl.load(crawl_id, db_id_backend="derby").sql(...). Cruces multi-tabla, agregaciones complejas.
  3. MCP exporta los reports finales al entregable cliente con sf_generate_report(category=..., file_path=...).

El LLM en Claude orquesta los tres pasos en una sola conversación.

7. Pipeline 3-en-1: SF + GSC + GA4 con código real

Un crawl solo cuenta la mitad de la historia. La otra mitad la tienen GSC (qué Google ve) y GA4 (qué hace el usuario). Una vez que el dato del crawl está en pandas, cruzarlo con GSC y GA4 es un join SQL.

import pandas as pd
from screamingfrog import Crawl
from search_console_connect import authenticate as gsc_auth, get_client as gsc_client
from extract_ga4 import get_client as ga4_client, extract_landing_pages

# 1) Crawl SF -> DataFrame de URLs
crawl = Crawl.load("CRAWL_ID", db_id_backend="derby", csv_fallback=False)
df_sf = crawl.sql("""
  SELECT u.URL_PATH AS url,
         u.STATUS_CODE,
         u.INDEXABILITY,
         u.CANONICAL_LINK_ELEMENT,
         ic.INLINKS_COUNT
  FROM APP.URLS u
  LEFT JOIN APP.INLINK_COUNTS ic ON ic.URL_ID = u.URL_ID
  WHERE u.CONTENT_TYPE = 'text/html'
""").to_pandas()

# 2) GSC 90 dias -> clicks / impressions / position por URL
gsc = gsc_client(gsc_auth())
df_gsc = (
  pd.DataFrame(gsc.searchanalytics().query(
    siteUrl="sc-domain:ejemplo.com",
    body={"startDate":"2026-02-19","endDate":"2026-05-19","dimensions":["page"]}
  ).execute()["rows"])
  .rename(columns={"keys":"url"})
)
df_gsc["url"] = df_gsc["url"].str[0]

# 3) GA4 30 dias -> sessions / conversions por landing
df_ga4 = extract_landing_pages(ga4_client(), property_id="123456789",
                                start_date="30daysAgo", end_date="today")

# 4) Merge sobre URL normalizada
df = (df_sf.merge(df_gsc, on="url", how="left")
              .merge(df_ga4, on="url", how="left"))

print(df.head())

Output: una tabla con url | status_code | indexability | canonical | inlinks_count | gsc_clicks_90d | gsc_impressions_90d | ga4_sessions_30d | ga4_conversions_30d. Sobre eso se filtra cualquier caso del top 20 (orphans con tráfico, canibalización con conversiones, zombies con impressions, etc.).

8. Glosario de tablas Derby APP.* (~25)

Tabla Qué contiene
APP.URLS URLs crawleadas con metadata completa (status, indexability, canonical, hreflang flags, content type)
APP.LINKS Grafo de links raw (SRC_ID, DST_ID, LINK_TYPE). LINK_TYPE=13 = hreflang HTML
APP.UNIQUE_URLS Mapping URL_ID → URL string normalizada
APP.INLINK_COUNTS Conteos pre-computados de inlinks por URL (rápido, no requiere agregación)
APP.DUPLICATES_TITLE URLs con título duplicado entre sí
APP.DUPLICATES_META_DESCRIPTION URLs con meta description duplicada
APP.DUPLICATES_H1 URLs con H1 duplicado
APP.DUPLICATES_H2 URLs con H2 duplicado
APP.MULTIMAP_CANONICALS_PENDING_LINK Canonicals sin link confirmado en HTML
APP.MULTIMAP_HREF_LANG_NON_200_LINK Hreflang apuntando a URLs non-200
APP.MULTIMAP_HREF_LANG_MISSING_CONFIRMATION Hreflang sin return links recíprocos
APP.MULTIMAP_HREF_LANG_INCONSISTENT_LANGUAGE_CONFIRMATION Return links con código de idioma inconsistente
APP.MULTIMAP_HREF_LANG_CANONICAL_CONFIRMATION Hreflang sin canonical en el return
APP.MULTIMAP_HREF_LANG_NO_INDEX_CONFIRMATION Hreflang apuntando a URLs noindex
APP.MULTIMAP_PAGINATION_PENDING_LINK Pagination sin link en anchor
APP.MULTIMAP_PAGINATION_SEQUENCE_ERROR Errores de secuencia rel=prev/next
APP.MISSING_ALT_TEXT_TRACKER Imágenes sin alt text
APP.MISSING_ALT_ATTRIBUTE_TRACKER Imágenes sin atributo alt
APP.ALT_TEXT_OVER_X_CHARACTERS_TRACKER Alt text excedido en longitud
APP.MISSING_SIZE_ATTRIBUTES Imágenes sin width/height (CLS)
APP.HTML_VALIDATION_DATA Validación HTML, ubicación de tags (in/outside head)
APP.URL_INSPECTION Datos de GSC URL Inspection (si la integración está activada)
APP.PAGE_SPEED_API PageSpeed Insights scores/metrics (si la API key está configurada)
APP.AXE_CORE_RESULTS Resultados auditoría accesibilidad Axe (si activado)
APP.COSINE_SIMILARITY Similaridad de contenido entre URLs (requiere activar Content similarity)
APP.NEAR_DUPLICATE Near duplicates con threshold configurable
APP.LOW_RELEVANCE URLs con contenido de baja relevancia / valor bajo

9. Recetario — 10 queries SQL listas para copiar

Todas asumen crawl = Crawl.load(crawl_id, db_id_backend="derby", csv_fallback=False) previo.

1. Orphan pages indexables

crawl.sql("""
SELECT u.URL_PATH FROM APP.URLS u
LEFT JOIN APP.INLINK_COUNTS ic ON ic.URL_ID = u.URL_ID
WHERE u.INDEXABILITY = 'Indexable'
  AND (ic.INLINKS_COUNT IS NULL OR ic.INLINKS_COUNT = 0)
""")

2. Páginas con near-duplicates > 0.9

crawl.sql("""
SELECT u1.URL_PATH AS url_a, u2.URL_PATH AS url_b, nd.SIMILARITY
FROM APP.NEAR_DUPLICATE nd
JOIN APP.UNIQUE_URLS u1 ON u1.URL_ID = nd.URL_ID_A
JOIN APP.UNIQUE_URLS u2 ON u2.URL_ID = nd.URL_ID_B
WHERE nd.SIMILARITY > 0.9
ORDER BY nd.SIMILARITY DESC
""")

3. Hreflang missing return links

crawl.sql("SELECT * FROM APP.MULTIMAP_HREF_LANG_MISSING_CONFIRMATION")

4. Distribución de inlinks (P50/P90/P99)

crawl.sql("""
SELECT
  PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY INLINKS_COUNT) AS p50,
  PERCENTILE_CONT(0.90) WITHIN GROUP (ORDER BY INLINKS_COUNT) AS p90,
  PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY INLINKS_COUNT) AS p99
FROM APP.INLINK_COUNTS
""")

5. Canonical chains de 2+ hops

crawl.sql("""
SELECT u.URL_PATH, u.CANONICAL_LINK_ELEMENT, c.URL_PATH AS canonical_path
FROM APP.URLS u
JOIN APP.URLS c ON c.URL_PATH = u.CANONICAL_LINK_ELEMENT
WHERE c.CANONICAL_LINK_ELEMENT IS NOT NULL
  AND c.CANONICAL_LINK_ELEMENT <> c.URL_PATH
""")

6. URLs noindex con inlinks (zombi en construcción)

crawl.sql("""
SELECT u.URL_PATH, ic.INLINKS_COUNT
FROM APP.URLS u
JOIN APP.INLINK_COUNTS ic ON ic.URL_ID = u.URL_ID
WHERE u.META_ROBOTS LIKE '%noindex%' AND ic.INLINKS_COUNT > 0
ORDER BY ic.INLINKS_COUNT DESC
""")

7. Títulos duplicados con word_count bajo

crawl.sql("""
SELECT u.URL_PATH, u.WORD_COUNT, u.TITLE
FROM APP.URLS u
JOIN APP.DUPLICATES_TITLE dt ON dt.URL_ID = u.URL_ID
WHERE u.WORD_COUNT < 300
ORDER BY u.WORD_COUNT
""")

8. Top 100 URLs por inlinks

crawl.sql("""
SELECT u.URL_PATH, ic.INLINKS_COUNT
FROM APP.URLS u
JOIN APP.INLINK_COUNTS ic ON ic.URL_ID = u.URL_ID
ORDER BY ic.INLINKS_COUNT DESC
FETCH FIRST 100 ROWS ONLY
""")

9. Internal redirects 3xx con anchor text descriptivo

crawl.sql("""
SELECT src.URL_PATH AS from_url, dst.URL_PATH AS to_url, l.ANCHOR_TEXT
FROM APP.LINKS l
JOIN APP.URLS src ON src.URL_ID = l.SRC_ID
JOIN APP.URLS dst ON dst.URL_ID = l.DST_ID
WHERE dst.STATUS_CODE BETWEEN 300 AND 399
  AND LENGTH(l.ANCHOR_TEXT) > 10
""")

10. URLs por tipo de Schema

crawl.sql("""
SELECT SCHEMA_TYPE, COUNT(*) AS urls
FROM APP.URLS
WHERE SCHEMA_TYPE IS NOT NULL
GROUP BY SCHEMA_TYPE
ORDER BY urls DESC
""")

10. Tokens consumidos en Claude Code por endpoint MCP

Benchmark real sobre nicolasbillia.com (sitio chico, 40 URLs crawleadas). Todos los endpoints corridos con file_path seteado (modo "guardar en disco"). Los números no escalan linealmente con sitios más grandes — el sample que devuelven es siempre 1 URL aprox.

Endpoint Archivo en disco Output al contexto LLM Tokens aprox
sf_crawl ~37 chars ~10
sf_crawl_progress ~60 chars ~15
sf_generate_report (Redirects:All) 3,3 KB / 4 filas ~1,2 KB (header + path) ~310
sf_export_seo_element_urls (Canonicals:Missing) 1,4 KB / 4 URLs ~600 chars (sample 1 URL) ~150
sf_bulk_export_page_content (visible_text) 128 KB / 18 URLs ~5,7 KB (sample 1 URL completa) ~1.425
sf_export_embeddings 539 KB / 40 URLs × 1.536 dims ~200 chars (status + path) ~50

Aviso operativo crítico: si NO pasás file_path a los bulk exports, el archivo entero viaja al contexto del LLM:

  • sf_bulk_export_page_content sin file_path en este crawl: ~128 KB ≈ 32.000 tokens
  • sf_export_embeddings sin file_path: ~539 KB ≈ 135.000 tokens (rompe la context window de Sonnet)

Regla práctica: para exports de más de ~5 KB, siempre file_path. Después leés el archivo con un script o un grep dirigido.

11. Gotchas reales que me trabaron

API Python de Antonio:

  • Crawl.load(...) sin db_id_backend="derby" activa el cache DuckDB y se cuelga en crawls grandes. Forzar siempre Derby + csv_fallback=False.
  • De los 9 reports pre-armados, crawl.summary(), canonical_issues_report() e indexability_audit() se cuelgan en algunos crawls. Workaround: SQL directo sobre APP.URLS.
  • links("in").collect() sitewide en sitios 100K+ URLs se cuelga. Usar APP.INLINK_COUNTS que es pre-computado.
  • Filtros con TODO en el código fuente (Antonio lo documenta): pixel-width titles/metas, "Is Relative" canonicals, "Background Images", varios.
  • Requiere Java 21 (bundled en SF). En macOS: export JAVA_HOME="/Applications/Screaming Frog SEO Spider.app/Contents/jre".

MCP oficial:

  • Base directory restringido. En mi instalación es /Users//seo_spider_mcp_server/. Para escribir output en otra carpeta hay que copiar después.
  • 5 reports de PageSpeed devuelven vacío si no hay API key de PageSpeed Insights configurada en SF.
  • Hreflang reports devuelven error si el crawl no se hizo con "Crawl Hreflang" activado en SF Config > Spider.
  • El crawl debe haberse hecho con Storage = Database (no Memory). Sino algunos reports no tienen los datos.
  • SF debe estar abierto siempre. Si SF se cierra a mitad del crawl, se pierde.

12. FAQ

¿Puedo usar la API de Antonio sin tener SF abierto?
Para crawls activos (en ~/.ScreamingFrogSEOSpider/ProjectInstanceData/), SF debe estar abierto porque Derby tiene lock exclusivo. Para archivos .seospider o .dbseospider exportados, no hace falta SF abierto.

¿Sirve en Linux / Windows?
API: sí (Python + Java cross-platform). MCP: sí, SF corre en Win/Mac/Linux, el endpoint HTTP es el mismo.

¿Cuánto pesa un crawl en disco?
En este benchmark, 40 URLs ocuparon ~6 MB en formato Derby. En sitios grandes (100K URLs) escala a 2-4 GB típicamente.

¿Antonio acepta PRs en el repo?
El repo es alpha pública y activo. Mejor canal: abrir issue primero, después PR. Link al repo.

¿El MCP funciona sin licencia paga de SF?
SF Free permite crawls de hasta 500 URLs. El MCP debería funcionar dentro de ese límite, pero los reports requieren modo Database (no Memory), que es licencia paga.

¿Puedo automatizar todo en un cron?
Sí, vía la CLI de SF (sin GUI). El MCP requiere SF abierto; para cron headless conviene la CLI de SF + la API de Antonio leyendo el output.

¿Qué versión mínima de SF necesito?
Para MCP server: las versiones más recientes (revisar el changelog oficial). Para la API de Antonio: cualquier versión que escriba crawls a Derby/CSV — la cobertura de tabs depende de la versión.

Cierre

El comparativo no es "cuál gana". Es "qué hace cada uno bien", para saber cuándo elegir cuál o cuándo combinarlos. En auditorías reales (e-commerce 100K+ URLs, medios con templates fragmentados, multi-país con hreflang complejo) terminé usando los dos.

Crédito explícito a Antonio Atilio Maculus por construir la librería Python y mantenerla open source. Si trabajás con SF programáticamente, su repo es lectura obligada.

Si probaste alguna de las dos (o las dos), me interesa saber qué workflows armaron. Dejá un comentario o escribime.

Compartir in X Link
Nicolas Billia

Nicolas Billia

SEO Strategist con foco en Producto y Datos. +10 anios, +50 proyectos en medios, e-commerce, B2B SaaS y fintech.

Nicolas Billia — 2026