La respuesta corta
dribba.com saca 100/100 en Is Agentic —la herramienta de agent readiness que Vercel ha lanzado con Ora— y A+ en el ranker completo de Ora, dentro del top 10 de 56.002 dominios analizados. Por delante de vercel.com (88) y de ora.ai (94), las dos compañías que construyeron la metodología.
Este artículo no es la nota. Es lo que hay debajo: qué se construyó, en qué orden, qué se rompió por el camino, qué costó puntos hacer de más, y las cuatro cosas que seguimos sin tener bien. Si buscas la versión de producto —qué es una web agéntica y qué cambia para tu negocio— está en /web-agentica. Aquí va la ingeniería.
Aviso de honestidad, porque es el tipo de artículo donde toca: la nota se mueve. En diecinueve minutos pasamos de 97 y puesto 8 a 96 y puesto 9, sin tocar una línea. El ranking se recalcula en cada escaneo y el universo crece cada día. Por eso el titular dice "A+ · top 10" y el badge de arriba, que se actualiza solo, dice la cifra de hoy.
Qué es "agent readiness" y por qué no es SEO
Durante veinte años se optimizaron webs para que un buscador las encontrara, las entendiera y las clasificara. Eso tenía nombre y disciplina: SEO.
Lo que ha cambiado es quién llega. Cada vez más, el primer visitante de una web no es una persona con un navegador: es un agente. ChatGPT resolviendo una pregunta de compra, Claude leyendo tu documentación para escribir una integración, Perplexity citando tu página, un asistente corporativo comparando tres proveedores, un script que va a pedir presupuesto en veinte sitios.
Ese visitante tiene propiedades incómodas:
- No ve. Tu diseño, tu jerarquía visual y tu animación de scroll no existen para él. Lo que existe es el HTML crudo, y muchas veces ni eso: lo que existe es el markdown que tú le des.
- No tiene paciencia ni presupuesto. Cada petición cuesta tokens y latencia. Si necesita cinco navegaciones para averiguar tu precio, es probable que se rinda antes y responda con lo que encontró en otro sitio.
- No adivina. Un humano ante un 404 vuelve atrás. Un agente ante un 404 sin cuerpo se queda sin saber si la ruta está mal o el servidor está roto, y esas dos cosas se resuelven de forma muy distinta.
- No es uno. Un escáner de agent readiness dispara del orden de 150 sondas en 30 segundos. Tu web tiene que aguantar eso sin degradarse, porque es exactamente el patrón de tráfico que produce un agente trabajando.
Optimizar para eso no es SEO con otro nombre. Comparten algunas piezas —sitemap, datos estructurados, velocidad— pero la mitad del trabajo es infraestructura que en SEO no existe: una API pública, un servidor MCP, contenido negociable por Accept, errores legibles por máquina, límites de cuota declarados en cabeceras. Lo hemos escrito más largo en Agentic Browsing: el audit de Lighthouse para agentes IA.
Las dos escalas: Is Agentic y el ranker de Ora
Conviene separarlas porque miden cosas distintas y se citan como si fueran la misma.
Is Agentic (is-agentic.com) es la puerta de entrada, lanzada por Vercel junto a Ora en agosto de 2026. Más de cien checks agrupados en esenciales, recomendados y bonus, pensados para webs de producto y de contenido. Además del análisis estático, ejecuta un recorrido real de un agente por tu sitio y te enseña dónde se atasca.
El ranker de Ora (ora.ai/score) es la escala completa, con leaderboard público y cuatro capas:
- Discovery — ¿puede encontrarte y confiar en ti? robots, sitemap,
llms.txt, skills, registros públicos, verificación de identidad. - Access — ¿le dejas entrar y le das el contenido sin pelear? crawling, política de bots, contenido sin JavaScript, fallos que no dejan al agente colgado.
- Usability — ¿entiende quién eres, puede integrarse y puede operar tu web? HTML semántico, JSON-LD, documentación, API, SDK, MCP, WebMCP, accesibilidad.
- Payments — ¿puede transaccionar? precios legibles por máquina, x402, ACP, AP2.
Las dos escalas comparten motor: Ora es quien ejecuta cada escaneo. La diferencia es el techo. Es perfectamente posible sacar 100 en Is Agentic y tener deberes evidentes en el ranker completo — de hecho es exactamente nuestro caso, y lo desarrollamos más abajo.
El resultado, bloque a bloque
Is Agentic: 100 / 100. Etiqueta: "Strong technical baseline". Instantánea del 23 de agosto de 2026.
| Bloque | Checks | Puntos |
|---|---|---|
| Esenciales | 11 de 12 | 77,8 / 80 |
| Recomendados | 21 de 23 | 19,1 / 20 |
| Bonus | 58 señales positivas | +5 |
Ora: A+, nivel "Leading", verificado.
| Capa | Puntuación |
|---|---|
| Discovery | 18 / 20 |
| Access | 30 / 30 |
| Usability | 40 / 40 |
| Payments | N/A |
Por superficie evaluada:
| Superficie | Checks | % |
|---|---|---|
| API | 12 de 12 | 100 % |
| Autenticación | 3 de 3 | 100 % |
| MCP | 3 de 3 | 100 % |
| Web pública | 14 de 17 | 92 % |
Y el bloque que Ora llama acceso crítico, que es el que de verdad determina si un agente puede trabajar contigo:
| Comprobación | Resultado |
|---|---|
| Los agentes pueden alcanzar el sitio | 2 / 2 |
| El contenido principal está disponible | 1 / 2 |
| La navegación falla de forma segura | 2 / 2 |
| Los controles son comprensibles | 4 / 4 |
Ese 1 de 2 es el único check esencial que fallamos en toda la auditoría, y lo tratamos en la sección de deberes pendientes. No lo escondemos: es la parte del sitio que depende de render en cliente.
Los nueve objetivos del ranker de Ora, para quien quiera el detalle fino: descubrir y confiar 90, dar la bienvenida a agentes 100, entender quién eres 97, poder integrarse 97, integración bien construida 100, fiable en producción 100, poder autenticarse 100, poder transaccionar 100, poder operar la web 100.
Cómo se hizo cada pieza
Ninguna de estas cifras viene de un plugin. Vienen de tratar al agente como un tipo de usuario más, con su propia interfaz. Lo que sigue es, pieza a pieza: qué es, cómo está construido, y el detalle que costó descubrir. Ordenado por la capa que puntúa.
Discovery — que te encuentre y sepa qué eres
llms.txt y llms-full.txt. El índice y el corpus. El formato recomienda que el índice quede por debajo de los 30.000 caracteres; el nuestro estaba en 53 KB.
Cómo se redujo a 25 KB sin perder un solo enlace: se recortaron las descripciones de las viñetas a su primera frase, y dieciocho secciones que en realidad son enumeraciones —cobertura por ciudad, verticales, glosario, especialidades— se movieron verbatim a llms-full.txt, dejando en el índice un titular, una línea y el enlace. La verificación no fue opcional: el guion compara el conjunto de enlaces antes y después y se niega a escribir el fichero si falta uno. Salió con 108 enlaces antes y 108 después.
El detalle que costó: había un \n literal dentro del fichero, dos viñetas en la misma línea física. Un consumidor de markdown veía una viñeta con basura dentro, y al recortar por la primera frase se perdía el enlace de la segunda.
agents.txt. Política de uso explícita para agentes, en formato Clave: valor. Es lo contrario de un robots.txt que dice que no a todo por defecto: dice qué se puede hacer, con qué límites, dónde está el registro del MCP, dónde el repositorio público y a dónde escribir.
Bundle OKF en /okf. El sitio entero publicado en Open Knowledge Format —la especificación abierta de Google Cloud, Apache 2.0, para servir conocimiento como un árbol de markdown con front matter YAML. Unos 420 documentos.
Cómo está construido: es un route handler, /okf/[[...path]], no ficheros estáticos, porque el contenido sale de Strapi y del censo del sitio y un fichero se quedaría viejo. Y no es una metadata route de Next porque Next no comprime esas. Nada está escrito a mano: el catálogo se construye desde las mismas fuentes que renderizan el sitio —los datos de empresa y servicios, los mismos fetchAll* que usa el sitemap para casos, artículos y vacantes, y el generador de entradas del sitemap como censo del resto de páginas—. Los cuerpos son el HTML vivo convertido por el mismo conversor que sirve la negociación markdown. Editar contenido ahí es un bug: se edita la fuente.
Decisión que se tomó al revés de lo esperado: /.well-known/okf es solo un alias que devuelve un manifiesto JSON con bundle_root, y /.well-known/okf/<ruta> hace 308 al árbol real. No se movió el bundle ahí porque .well-known (RFC 8615) es para metadatos del sitio, no para 420 documentos, y los enlaces internos del bundle son absolutos a su raíz: dos raíces servirían el mismo documento con bases distintas.
Registros públicos. El servidor MCP publicado en el MCP Registry oficial como com.dribba/dribba, las skills indexadas en skills.sh, el paquete en npm, el repositorio público con AGENTS.md, plugin.json y mcp.json.
Cómo se verificó el namespace: el registry ofrece varias formas de probar que com.dribba/* es tuyo. La de DNS pide un TXT en el ápice —hay que entrar al registrador—. Elegimos la de HTTPS, que pide una URL que ya controlamos:
GET https://dribba.com/.well-known/mcp-registry-auth
v=MCPv1; k=ed25519; p=<clave pública en base64>
Con la clave publicada, se firma un timestamp RFC3339 con la privada, se canjea por un JWT corto y se publica. Todo el flujo vive en un script del repo con tres modos: --keygen (escribe la privada con permisos 0600 y solo imprime la pública), --check y --publish.
Tres cosas que el validador rechaza y no se ven mirando el JSON: la description se corta en 100 caracteres (la nuestra medía 162, habría fallado al publicar); el cuerpo va sin envolver —con { "server": … } responde 422—; y un repository con URL privada es error, no aviso. Se descubren gratis contra POST /v0/validate, que no pide auth.
Y una trampa de Next: el endpoint de la prueba de dominio empezó con revalidate = 3600 y devolvía 404 aunque la variable de entorno estuviera puesta — Next lo prerenderizaba en el build, donde la variable todavía no existe porque Cloud Run la inyecta al desplegar. Regla: si el valor lo pone el runtime, se lee en la petición. Es force-dynamic.
Índices por área. /api/llms.txt, /docs/llms.txt, /developers/llms.txt. Un agente que solo va a integrar la API no tiene por qué cargarse el mapa entero del sitio.
Descubribilidad del MCP: cuatro URLs, una fuente. /mcp/server-card (relativa al transporte, la que fija el spec), /.well-known/mcp/server-card.json (compatibilidad), /server.json (registry) y /.well-known/ai-catalog.json (dominio). Las cuatro salen de la misma función. El ecosistema no ha convergido en una y un cliente que solo conozca una tiene que poder encontrar el endpoint.
Access — que entre y se lleve el contenido sin pelear
Negociación de contenido por Accept. Cualquier página responde en text/markdown si el cliente lo pide.
Cómo: el proxy (src/proxy.ts, la convención de Next 16 antes llamada middleware) implementa negociación RFC 9110 §12.5.1 de verdad, no un includes("text/markdown"). Parsea el Accept completo, honra los q, desempata por especificidad de la media-range (tipo exacto > comodín de subtipo > comodín total) y, cuando los q empatan, por el orden del cliente — que es exactamente donde falla la lógica ingenua. Si nada matchea con q > 0, cae a HTML normal.
El guardarraíl que costó un 502: la conversión hace un fetch de loopback al propio servicio para pedirse la página como HTML. Ese fetch llega con el mismo Accept y el mismo User-Agent, así que sin una cabecera de bypass (x-md-bypass) el proxy se reescribe a sí mismo en bucle.
Gemelos .md. Además de la negociación, cualquier página responde añadiendo .md: /index.md, /servicios.md, /blog/<slug>.md.
Cómo: hace falta una segunda entrada en el matcher del proxy. La principal excluye a propósito toda ruta con punto —imágenes, ficheros estáticos—, así que sin la segunda estas URLs nunca pasarían por el proxy y caerían en el 404 de Next. Y hay una lista explícita de exclusiones (MD_DOCUMENTS): los .md que son documentos reales —/okf/**, los SKILL.md de .well-known, /skill.md, /pricing.md, /auth.md— salen con un next() explícito, porque dejarlos caer en next-intl los convertiría en /<locale>/pricing.md, que no existe.
Un detalle de forma que importa más de lo que parece: el cuerpo de un gemelo empieza siempre por un encabezado. Las páginas que llevan un rótulo encima del titular salían con una línea suelta delante, y un consumidor que decide "esto es markdown" mirando el primer carácter la descartaba. Cuando falta, el conversor antepone el <title> del documento — sacado del HTML crudo, no del <h1>, porque muchos <h1> llevan un <br> en medio y dan títulos truncados.
Recursos que no son HTML. /.well-known/api-catalog.md y compañía no se pasan por el conversor de HTML: se envuelven en un bloque de código con su encabezado y un enlace al recurso canónico. Pasarlos por el pipeline de HTML devolvía el JSON aplanado y sin cabecera, que es peor que no responder.
Endpoints /llms/es/* y /llms/en/*. El alias público de la representación markdown, y la URL que anuncia la cabecera Link: …rel="alternate"; type="text/markdown" de cada página. Se reescribe antes de que corra next-intl, porque el alias vive fuera del árbol de idiomas y next-intl lo mandaría a /<locale>/llms.
404 recuperable. Un 404 no es una pared, es una bifurcación: devuelve un mapa de recuperación —sitemap, llms.txt, bundle OKF, API—.
El aviso que conviene no olvidar: Next 16 sirve el subárbol de not-found como payload RSC, no como HTML ya pintado. El bloque de recuperación está en el cuerpo de la respuesta, pero dentro de ese payload. La versión limpia para un agente es la negociada: curl -H 'Accept: text/markdown' https://dribba.com/no-existe devuelve 404 con text/markdown y el mapa legible.
Normalización de puntuación pegada a la URL. /llms-full.txt), /precios, o /mcp%3E redirigen con un 308 a la ruta limpia.
Cómo: una función que recorta ), >, ,, ; y sus formas percent-encoded hasta agotar —/mcp); y /mcp%29) también son sondeos reales— y que nunca devuelve la cadena vacía. Ninguno de esos caracteres aparece en una ruta real del sitio, así que no puede capturar una URL legítima.
Lo que costó tiempo: hace falta una entrada de matcher por carácter. La entrada principal excluye toda ruta con punto y estas llegan con él (/llms-full.txt)), y el path-to-regexp que trae Next rompe con un Unterminated group si el grupo lleva una clase [...]. Comprobado.
Aguantar la ráfaga. Lo que resultó ser la mitad del problema, y ninguna línea de código: está desarrollado en el fallo número 9.
Usability — que se integre y opere
Es la capa más grande y donde está casi todo el trabajo.
API pública v1, sin clave, sin OAuth, sin registro:
GET /api/v1/services /cases /pricing
/comparisons /company /articles /jobs
POST /api/v1/estimate → horquilla de presupuesto
POST /api/v1/batch → hasta 20 lecturas en una petición
POST /api/v1/exports → 202 + Location, la única operación asíncrona
GET /api/v1/sandbox/* → fixtures congeladas, X-Sandbox: true
Cómo está montado: un solo route handler (/api/v1/[[...resource]]) para los seis recursos, no seis ficheros. Comparten el mismo borde —cuota, cabeceras, errores, negociación— y partirlo garantizaba desincronizarlo. El censo de endpoints vive en un único módulo que leen tres consumidores: el propio handler (que lo devuelve como índice en /api/v1), la página de documentación (que dibuja la tabla) y el script de verificación (que los sondea). No hay una segunda lista que se pueda quedar vieja.
Errores RFC 9457. Todo 4xx y 5xx sale como application/problem+json con cuatro campos: code estable y enumerado —lo que un agente ramifica—, detail legible, resolution (qué hacer a continuación) e instance (la URL que falló). Son ocho códigos: ruta desconocida, recurso no encontrado, método no permitido, petición inválida, media type no soportado, cuota excedida, origen no permitido y error interno.
Cómo se garantiza que no hay huecos: un catch-all en /api/[...unmatched] convierte cualquier /api/* inexistente en problem+json. Antes devolvía 175 KB del cromo del sitio con status 404, y un agente no podía distinguir "ruta mal" de "servidor roto". /api a secas necesita su propio fichero, porque un catch-all obligatorio no matchea la lista vacía. Y el enum de códigos del OpenAPI se deriva del catálogo de problemas, así que un código nuevo sin documentar no puede pasar desapercibido.
Cuota declarada en cabeceras. 120 peticiones por 60 segundos y por IP, en las cabeceras IETF RateLimit más las de compatibilidad, con Retry-After en el 429.
La letra pequeña, escrita a propósito: el contador es en memoria y por instancia de Cloud Run, así que la cuota real es un múltiplo de la anunciada y las cabeceras describen lo que vio la instancia que respondió. Está dicho así en la documentación en vez de dejar que el integrador lo descubra. Sirve para que el agente se auto-frene, no como control de abuso duro: para eso está el mismo-origen de los endpoints de escritura.
Paginación por cursor. ?limit= (1–100) y ?cursor=; la respuesta trae count, total, next_cursor e items.
La decisión, que va contra el manual: sin limit viene la colección entera y next_cursor es null. Son decenas de elementos; obligar a un agente a paginar tres veces para leer nueve servicios sería peor servicio, no mejor. Lo que aporta la convención es una forma documentada para cuando el cliente sí quiera trocear: quien pide ?limit=5 no tiene que adivinar si el siguiente parámetro se llama offset, page, after o startAt. El cursor es opaco (base64url de la posición) — si mañana los datos salen del CMS con paginación real, cambia su contenido y no se rompe nadie, porque nadie debía interpretarlo.
Idempotency-Key en POST. Misma clave y mismo cuerpo replican la respuesta y añaden Idempotency-Replayed: true; misma clave y otro cuerpo es un 400, porque se guarda una huella del cuerpo junto a la respuesta.
El alcance honesto, escrito en el propio código: hoy el único POST público es el estimador, que es una función pura, así que esto no evita un cobro doble — no hay nada que cobrar. Deja la convención montada y probada. Y una advertencia que va en el fichero para que no se olvide: la caché es en memoria y por instancia, así que cuando exista una escritura real, esto tiene que pasar a almacenamiento compartido antes, no después.
Lote de lecturas. POST /api/v1/batch, 20 operaciones máximo, resueltas en proceso llamando al resolutor interno — sin loopback, así que no multiplica la carga ni sirve para amplificar tráfico contra nadie. Solo GET y solo rutas de /api/v1: un lote que pudiera escribir sería una forma elegante de saltarse el mismo-origen de los formularios.
Sandbox de fixtures. /api/v1/sandbox/*, con datos congelados y las mismas formas. No aísla escrituras —no hay ninguna—: aísla los tests del integrador de nuestros cambios. Un test que afirme "hay 9 servicios" se rompe el día que publiquemos el décimo. Toda respuesta lleva X-Sandbox: true y los datos son ficticios a propósito (acme-*): nadie debería creerse que Acme Corp es cliente.
Y tiene prefijo propio porque el contrato lo declara: servers[1] del OpenAPI es https://dribba.com/sandbox, así que un cliente generado con openapi-generator construye /sandbox/api/v1/services. Esa ruta la reescribe el proxy, no los rewrites() de la configuración de Next, porque el middleware corre antes que esos y next-intl convertiría la ruta en /<locale>/sandbox/.... Las rutas de detalle también resuelven, buscando dentro de los items del fixture: si solo funcionaran las colecciones, la mitad de un cliente generado se estrellaría contra el sandbox.
Exportación asíncrona. POST /api/v1/exports devuelve 202 con Location y se resuelve por sondeo. Es la única operación asíncrona del sitio porque es la única que no cabe en una respuesta: exportar N páginas son N loopbacks y N conversiones. Da a cambio que un agente que quiere el sitio entero no tenga que recorrer 420 documentos del bundle de uno en uno.
Los límites van escritos en la propia respuesta (instance_scoped: true): el trabajo vive en la memoria de la instancia que lo aceptó y caduca a los 15 minutos, así que un GET del estado puede caer en otra instancia y responder 404. Eso es lo que significa, no "se perdió". Máximo 50 páginas por trabajo, para que ningún export dure más que su TTL.
Trabajos asíncronos más allá de ese: no hay, y no se finge. El OpenAPI lo dice en una extensión x-api-conventions en vez de callarlo, que es lo que obliga a un agente a probar y fallar para averiguarlo.
Versionado. En la ruta. Dentro de v1 solo se añaden campos; un cambio que rompa estrena /api/v2 y v1 se marca con Deprecation/Sunset (RFC 9745) con 90 días de aviso. Publicado en la documentación y en una extensión x-api-lifecycle del contrato.
La cabecera Link vive en la configuración, no en el handler. Cuatro enlaces de servicio: service-desc, api-catalog, service-doc, deprecation.
Por qué ahí: headers() de Next se aplica después de la respuesta de un route handler y sobrescribe su Link. Así que el valor se declara en la regla /api/:path* de la configuración, y que coincida con el que emite el módulo de errores lo comprueba el script de verificación. Relacionado y peor: varias entradas Link con la misma clave en headers() no se acumulan — Next se queda con la última. De los tres anuncios del sitio, solo llegaba el último. Ahora es un único valor con las tres URLs separadas por coma, como manda RFC 8288.
Dos servidores MCP, motor compartido:
https://dribba.com/mcp— producto: catálogo, casos, presupuesto, datos de empresa y una acción con confirmación. Lista 5 tools.https://dribba.com/docs/mcp— documentación: búsqueda de pasajes, ficha de servicio, ficha de caso, comparativas. Lista 4.
Cómo está parametrizado, y esta es la parte importante: lo que cambia es lo que cada superficie lista, no lo que acepta. tools/call resuelve las nueve en las dos, así que un cliente ya conectado a la superficie de producto que llamaba a search sigue funcionando. Romper clientes vivos para salir mejor en una auditoría sería el intercambio equivocado, y el script de verificación comprueba esa llamada cruzada precisamente para que no se pierda en un refactor.
Por qué separarlas: un servidor con nueve tools en la misma lista obliga al agente a elegir entre nueve descripciones antes de cada llamada, y las auditorías lo penalizan con el mismo argumento. La referencia que usamos —un sitio que puntúa 97— tiene 4 tools en producto y 2 en documentación.
Detalles del transporte que rompen clientes:
GET /mcpconAccept: text/event-streamdevuelve 405. El transporte Streamable HTTP usa GET para abrir el canal SSE del servidor; nosotros no tenemos nada que empujar, y el spec dice que en ese caso hay que responder 405. Antes devolvíamos 200 y JSON, y un cliente que abría ese canal tras elinitializerecibíaapplication/jsondonde esperabatext/event-stream: handshake roto sin motivo. Se distingue porAccept.- El POST se envuelve según el
Accept. El spec permite responder a un POST conapplication/jsono con untext/event-streamde un solo evento. Respondíamos siempre lo primero; un cliente que mandaAccept: text/event-streampuede rechazarlo por tipo de contenido antes de leer el cuerpo. El script comprueba las dos formas, y su parser entiende los dos envoltorios, porque un cliente de verdad también tiene que entenderlos. - Las tools llevan
annotationsdel spec (readOnlyHint,destructiveHint,idempotentHint,openWorldHint), que es lo que permite a un agente saber si una tool escribe sin deducirlo del nombre. - Un puente stdio,
npx dribba-mcp, para los clientes que solo saben lanzar un proceso y hablar por su entrada y salida estándar. No reimplementa nada: reenvía cada mensaje JSON-RPC al servidor remoto. Escrito sin dependencias y con dos invariantes en el propio fichero: por stdout viaja solo protocolo, un JSON por línea (unconsole.logde depuración corrompe el flujo y el cliente cierra), y toda petición recibe respuesta — un fallo de red devuelve un error JSON-RPC con el mismoid, porque un cliente que no oye nada se queda colgado para siempre en vez de fallar.
Cuándo tiene sentido montar un MCP y cuándo no, en MCP en producción y en A2A vs MCP.
WebMCP. Herramientas registradas en document.modelContext para que un agente que ya está navegando la página pueda operarla sin salir de ella, más /.well-known/webmcp.json, /.well-known/agent-card.json (A2A) y /.well-known/ai-catalog.json. El caso técnico completo está en WebMCP en producción y la explicación del estándar en Qué es WebMCP.
Sobre el catálogo de IA, una decisión de omitir: /.well-known/ai-catalog.json sigue el formato AI Catalog 1.0 con did:web:dribba.com y un trustManifest por entrada. Va sin signature a propósito: un JWS separado sobre el manifiesto canonicalizado exige clave y ceremonia de rotación, y poner el campo con algo que no verifica es peor que no ponerlo, porque un cliente que sí valida lo lee como manipulado.
NLWeb en POST /ask. Preguntas en lenguaje natural respondidas con pasajes literales del corpus y su URL, en SSE si se pide streaming. No hay un LLM detrás generando texto, y está declarado en la respuesta (response_type: passages). Meter un modelo ahí podría inventarse una cifra de cliente, y una cifra inventada con nuestro dominio delante es peor que no responder. El alias limpio /ask se reescribe en el proxy por lo mismo que /mcp: no lleva punto, así que next-intl lo mandaría a /<locale>/ask.
SDK y CLI. npm i dribba, npx dribba services, cero dependencias, MIT.
Qué publica el paquete y qué no: el files del package.json deja fuera las skills, el AGENTS.md y los manifiestos — son contenido del repositorio, no del paquete. Quien instala la librería quiere el cliente. Son 8,9 kB y seis ficheros.
Y el repositorio público es un subdirectorio, no el sitio: tres checks exigen un repositorio público donde un escáner encuentre AGENTS.md, las SKILL.md y plugin.json. No hizo falta abrir la web: se publica el paquete entero como repo. Las skills de ahí son generadas desde lo que sirve el sitio, que es de donde las lee un agente de verdad, y un script falla si las dos copias divergen — así el drift se ve en CI y no en una auditoría.
?mode=agent. Cualquier URL devuelve un briefing operativo: endpoints, límites, protocolos, a dónde ir para cada trabajo. No es el gemelo .md (que es la página sin HTML); es el mapa de qué puedes hacer desde aquí. Se resuelve también en el proxy, reescribiendo a un handler dedicado.
Autenticación — describir que no hay puerta
Esta es la parte contraintuitiva, y donde más fácil es perder puntos haciendo de más.
dribba.com no tiene cuentas. No hay nada que registrar ni credencial que emitir. El protocolo auth.md resuelve que un agente dé de alta a un usuario en tu producto y reciba un token con alcance; aquí no aplica.
Cómo se resolvió: publicamos /auth.md respetando las siete secciones del spec —Discover, Pick a method, Register, Claim, Use the credential, Errors, Revocation— porque son las que un agente busca, y cada una dice la verdad sobre este dominio. Y publicamos /.well-known/oauth-protected-resource (RFC 9728) con authorization_servers: [] y bearer_methods_supported: [] — listas vacías a propósito, que es la forma estándar de decir "esto se lee sin credencial" en una sola petición, sin provocar un 401 que aquí no llegaría nunca.
Lo que no publicamos, aunque cada uno valga puntos en alguna auditoría:
/.well-known/oauth-authorization-server. Seríamos un servidor de autorización que no autoriza nada.register_uri,claim_uri,revocation_uri. Un URI en un bloque de descubrimiento es la promesa de que ahí pasa algo. No vamos a montar un endpoint que conteste "no hace falta" para que un escáner lo sondee.- Un 401 con
WWW-Authenticateen la API. Anunciar un reto de autenticación que no existe rompe a cualquier cliente que lo obedezca.
El criterio, que atraviesa toda la disciplina: un campo relleno con algo que no funciona es peor que un campo ausente, porque el cliente que sí valida lo lee como manipulado.
Payments — N/A, y por qué está bien
No vendemos nada online. No hay carrito ni checkout ni suscripción. Los protocolos de pago agéntico —ACP, AP2, x402, UCP, MPP— no aplican, y el ranker lo marca N/A en vez de penalizarlo.
Lo que sí publicamos es /pricing.md: suelo de proyecto, modelos de contratación y producto, en un fichero de convención legible por máquina y en inglés, porque es un fichero para máquinas. Un agente que compara proveedores necesita el número, no el formulario. Y ninguna cifra de ese fichero es nueva: sale de la página de precios y de llms.txt, y si cambia, cambia en los tres sitios.
Para quien sí vende: cómo pagan los agentes está en AP2, el Agent Payments Protocol.
Y lo que atraviesa todas las capas: servir markdown a los bots correctos
Los crawlers de IA mandan Accept: text/html como un navegador y se llevan 400 KB de markup para extraer 18 KB de texto. A los que están en la lista, el proxy les sirve directamente la representación markdown de la misma URL, declarando Vary: Accept, User-Agent.
Googlebot, bingbot, Applebot y los crawlers de previsualización social NO están en esa lista. Esos alimentan índices de búsqueda y tarjetas sociales: necesitan el HTML con su JSON-LD y su Open Graph, y darles otra cosa sería encubrimiento. Google-Extended y Applebot-Extended sí están, porque son los tokens de uso en IA, no los crawlers de búsqueda.
(La versión general de esto —servir markdown a todo bot de IA aunque pida HTML— se probó, midió y revirtió. Está en el fallo número 8.)
El recorrido del agente: 17 pasos, cero clics
La parte de la auditoría que más nos ha servido no es el número. Es el recorrido grabado.
Is Agentic lanzó un agente —Claude Code Haiku 4.5— con una tarea deliberadamente vaga:
"¿Qué hace dribba.com y para quién es? Explícamelo."
Lo resolvió en 17 pasos y 8 de razonamiento, por este camino:
home → /en → /llms/en → /en/pricing → /llms/en/pricing
→ /en/services → /llms/en/services → llms.txt → /en/about
Dos cosas que leer ahí:
Primera: no tocó una sola interfaz visual. Entró por la home, encontró la ruta en inglés, y a partir de ahí vivió en las versiones markdown. Toda la inversión en diseño no participó en esa conversación. Toda la inversión en /llms/*, sí.
Segunda, y más valiosa: dijo qué no encontró. El evaluador anotó que entendió el modelo de negocio, la estructura de precios, el mercado objetivo y la diferenciación — y que marcó como huecos no documentados el tamaño de equipo, los SLA y las condiciones contractuales, sin inventárselos. Eso es un backlog de contenido escrito por el usuario más exigente que vas a tener, gratis.
En el apartado de feedback de agentes: 1 reseña, 100 % de éxito, 100 % de recomendación, 3,2 sobre 5. Una sola muestra no es una tendencia, pero la distancia entre "tarea completada" y "3,2" es justo el hueco anterior.
Diez cosas que se rompieron, y ninguna se veía navegando
Esta es la sección que habríamos querido leer hace seis meses. Todos estos fallos convivían con una web que funcionaba perfectamente en un navegador.
1. La página de 404 llevaba meses sin cuerpo
El fichero de 404 era un export { default } from "./not-found-content". Next no reconoce esa forma como frontera de 404: todos los 404 caían en su fallback interno, que responde el status correcto con el cuerpo vacío. El navegador enseñaba el cromo del sitio y nadie lo notó nunca.
Para un agente, un 404 sin cuerpo es un callejón sin salida: no sabe si la ruta está mal o si el servidor se ha caído. Se arregló declarando el componente en el fichero y poniendo una frontera equivalente en la rama de idioma.
2. El sitemap real no era el que creíamos
Teníamos cinco páginas creadas expresamente para ser el destino de búsquedas de desarrollador. El generador de entradas las listaba. Un check de descubribilidad daba 0.
Causa: el route handler que sirve el sitemap nunca se había commiteado. El /sitemap.xml de producción lo seguía generando un fichero viejo que no las conocía. La documentación interna afirmaba que estaban dentro y era falso.
Dos lecciones. La primera: si la documentación afirma un hecho verificable, el hecho se comprueba en CI, no se escribe en un documento. La segunda, colateral: el sitemap viajaba sin comprimir. Con el handler correcto son 136 KB de XML que salen en 4,9 KB con gzip.
3. Un paréntesis dentro de un fichero text/plain
Informe de auditoría: "1 de 5 enlaces sondeados de llms.txt no resuelve: https://dribba.com/llms-full.txt".
Esa URL responde 200 por todas las vías: GET, HEAD, con y sin compresión, con siete User-Agents distintos, bajo una ráfaga de 120 peticiones concurrentes contra producción. Lo comprobamos todo. Lo que no resolvía era la URL que el escáner sondeó de verdad:
https://dribba.com/llms-full.txt) → 404
llms.txt se sirve como text/plain. Un extractor de URLs de texto plano no tiene sintaxis markdown que quitar: de [texto](https://dribba.com/llms-full.txt) se lleva el paréntesis dentro del URL. Los enlaces a mitad de línea sobreviven porque el token acaba en otro carácter. Los que cierran la línea, no.
Arreglo doble, porque el texto plano no es el único sitio donde alguien copia un URL nuestro seguido de puntuación: el proxy normaliza con un 308 la puntuación pegada al final de la ruta, y ninguna línea de llms.txt acaba ya en un URL.
4. Dos veces el mismo salto de línea comido
Antes del paréntesis, el mismo parser se había comido dos veces el final de línea de un markdown nuestro:
- Remote MCP server: https://dribba.com/mcp → sondeó /mcp//n-
- <https://dribba.com/mcp> → sondeó /mcp/u003e//n-
El segundo es el arreglo ingenuo del primero: el autolink se comió el < y dejó el >. Lo que funciona es el enlace markdown con texto: [Remote MCP server](url).
Regla, y va en serio para cualquiera que escriba markdown para máquinas: el URL va dentro de [...](...), nunca suelto al final de línea. En front matter YAML sí va desnudo, que ahí es lo correcto.
5. Faltaba /.well-known/mcp, y eran tres puntos
Cinco escaneos seguidos reportaron "MCP protocol handshake failed". Probamos tres arreglos: la versión de protocolo, el envoltorio SSE, la capacidad resources. Ninguno era. Mientras tanto, el SDK oficial conectaba, listaba y llamaba sin un solo error.
La causa estaba escrita en el propio informe desde el principio: "Checked /.well-known/mcp". Servíamos /.well-known/mcp/server-card.json pero no /.well-known/mcp. El primer sondeo daba 404 y el mensaje que salía por el otro extremo hablaba de un handshake que nunca llegó a intentarse.
Publicado el manifiesto de dominio ahí, el check pasó de 3/6 a 6/6 en el escaneo siguiente.
Lección, y es la más transferible de todo el artículo: cuando el mensaje de un check no cuadra con lo que mides, lee la lista de lo que dice haber sondeado.
6. Una versión de protocolo copiada de la tarjeta de otro
La primera versión de protocolo que ofrecía nuestro MCP estaba copiada de la server card de otro servidor, sin comprobarla. No existía. Es la versión que se ofrece cuando el cliente no pide ninguna, así que cualquier cliente que no mandara protocolVersion recibía una que no conoce y abortaba.
Regla nueva: antes de añadir una versión de protocolo a esa lista, se prueba con un cliente real. El guion de prueba cabe en veinte líneas y encuentra en un segundo lo que una auditoría reporta como "handshake failed" sin más detalle.
7. Separar por lo que se lista, nunca por lo que se declara
Al partir el MCP en dos superficies, movimos los recursos (llms.txt, llms-full.txt) a la de documentación con un razonamiento aparentemente impecable: "son documentación, van en la superficie de docs".
Efecto: el servidor de producto dejó de declarar la capacidad resources en el handshake, y las dos comprobaciones que la miran pasaron de pleno a no aplicable. Seis puntos por ordenar de más.
Una capacidad omitida no es una capacidad ordenada: es una capacidad ausente. Ahora las dos superficies declaran resources y sirven los mismos ficheros; lo que cambia es lo que cada una lista, no lo que acepta. Un cliente ya conectado a una superficie que llamaba a una tool de la otra sigue funcionando, y eso también se comprueba en el script.
8. Servir markdown por User-Agent costó cuatro puntos reales
Hipótesis razonable: si el User-Agent es un bot de IA, servirle markdown aunque pida HTML. Es lo que hace un sitio de referencia que puntúa 97, y hay un check que lo premia.
Medido, con el cambio desplegado: dos checks que estaban en pleno se fueron a cero, con los cinco bots en "unknown". Y el check que lo premia es de bonus, o sea que vale cero en la fórmula. Cuatro puntos a cambio de nada.
La causa es nuestra arquitectura, no la idea: nuestra conversión a markdown hace un fetch de loopback al propio servicio, así que cada petición de un bot cuesta dos. Bajo una ráfaga de 150 sondas eso se convierte en timeouts. Un sitio que sirve markdown estático por UA no paga ese precio.
Revertido. Los agentes siguen teniendo markdown por Accept, por el sufijo .md, por /llms/<ruta>, por el bundle OKF y por ?mode=agent.
Un cambio que copia a un sitio mejor puntuado sin medirlo en el tuyo es una apuesta, no una mejora.
9. "Could not probe": era la concurrencia de Cloud Run, no el código
Dos checks fallaron con el mismo verbo y ninguno era un error de contenido: "could not probe a nonexistent path — both fetches failed" y "could not probe both Accept: text/markdown and Accept: text/html". Ninguno se reproducía nunca a mano.
Medido en producción con 20 peticiones concurrentes:
| Ruta | Media | Pico |
|---|---|---|
| Una página normal | 1,52 s | 2,52 s |
| Un 404 | 4,79 s | 7,01 s |
La home con Accept: text/markdown | 2,70 s | 3,98 s |
El mismo build en local, sin competencia por CPU, respondía a todo en 0,27 s.
El reparto por defecto de Cloud Run son 80 peticiones simultáneas por instancia con 1 vCPU, y este sitio renderiza en el servidor. Un escáner que dispara 150 sondas en 30 segundos apila decenas de renders en un núcleo. La corrección fue bajar la concurrencia a 20 y subir a 2 vCPU: contraintuitivo, porque bajar la concurrencia hace que Cloud Run escale antes en vez de apilar, y a diferencia de mantener instancias calientes eso no cuesta nada en reposo.
Además, la capa de markdown ahora cachea por ruta y colapsa las peticiones en vuelo: diez sondas simultáneas de la misma ruta lanzaban diez loopbacks —el caso exacto que rompía el check— y ahora lanzan uno. De 88 ms en frío a 3-5 ms en caliente.
Corolario incómodo: un porcentaje de los fallos que te reporta una auditoría de agent readiness son de capacidad, no de contenido. Se arreglan en la configuración del servicio.
10. Un YAML inválido dentro de una skill, invisible durante meses
Publicamos siete skills. Un indexador externo listó seis. La que faltaba tenía esto en el front matter:
description: Dribba serves a markdown representation of any page when the client sends Accept: text/markdown. HTML remains the default.
Dos puntos seguidos de espacio dentro de un valor sin comillas: "mapping values are not allowed here". El fichero se servía con 200 y se leía perfectamente a ojo. Llevaba meses roto para cualquier parser estricto, y hizo falta que un tercero intentara indexarlo para que se viera.
Los siete se validan ahora con un parser YAML de verdad, no con un grep.
Bonus: lo que probamos y salió al revés
Quitarle el JavaScript a la página de 404 parecía obvio: 89 KB de scripts en un 404 tenían que ser el problema. Convertimos el componente a servidor, sin estado ni navegación, con las animaciones en CSS puro. Mismo build, misma máquina:
| Versión | Peso | Ráfaga de 20 |
|---|---|---|
| Cliente (la que había) | 93 KB | 0,273 s |
| Servidor sin JS | 124 KB (+33 %) | 0,328 s (+20 %) |
Un componente cliente viaja en el payload RSC como una referencia de módulo; uno de servidor serializa toda su salida dentro. Quitar el JavaScript salía más caro que dejarlo. Revertido.
Tres cosas que aprendimos sobre las auditorías, no sobre la web
Los checks de bonus valen cero. La fórmula es puntos no-bonus / máximo no-bonus. Verificado con dos escaneos de dos dominios distintos. Media docena de cosas que construimos persiguiendo puntos —trust manifest, server cards, ?mode=agent, gemelos .md, sandbox, anotaciones de tools— son bonus y no movieron la nota ni un punto. Se quedan porque son útiles de verdad, pero antes de implementar algo que pide una auditoría, mira si el check es de bonus.
Cuando una auditoría dice que algo falla, compruébalo contra una referencia. Tres hallazgos que parecían nuestros no lo eran: un "handshake failed" que el SDK oficial desmentía; un supuesto problema de accesibilidad para crawlers que un sitio con 97 puntos tenía idéntico y aprobaba; y una guía de "cuándo usar" que sí existía, pero el escáner la buscaba en otro fichero y con otra grafía. Se acabó sirviendo con las dos grafías. El texto se escribe una vez y se emite dos.
Hay puntos que no se ganan con código. De las pérdidas que quedan, la mayoría son de otra naturaleza: presencia en directorios que exigen envío manual, notabilidad para una enciclopedia, indexación de marca. Y dos checks de estructura de contenido los pierde igual el sitio que puntúa 97, con el mismo mensaje: no es tu portada, es el umbral. Dejar de perseguirlos es una decisión legítima y ahorra semanas.
Lo que todavía no está bien
Un 100 en Is Agentic no significa que no queden deberes. Significa que el techo de esa escala se alcanza antes que la perfección. Estos son los cuatro huecos, en el orden en que los vamos a atacar, y ninguno se toca en este artículo:
1. Contenido sin JavaScript. El único check esencial fallado, y el 1 de 2 de la tabla de acceso crítico. Parte de la home depende de render en cliente. La corrección es servir un H1 y el cuerpo principal —500 caracteres largos— en el HTML crudo. Es la prioridad, porque es la única que le impide a un agente sin motor de JavaScript entender la portada.
2. Verificación bidireccional en el registro MCP. Es la observación que Ora destaca y explica los dos puntos que faltan en Discovery. La entrada existe y está activa; falta cerrar la verificación cruzada registro ↔ dominio y reescanear.
3. Discoverability del nombre de marca. Una búsqueda limpia por "dribba" no devuelve el dominio propio con suficiente claridad. Esto es trabajo de marca: NAP consistente en directorios, menciones de prensa al dominio canónico, y ninguna cadena de redirección que enmascare el apex. No hay commit que lo arregle.
4. Discoverability de recursos de desarrollador. Docs, OpenAPI, auth, webhooks y MCP existen, pero no se encuentran por nombre desde fuera. URLs predecibles, listados en llms.txt y el nombre de producto en títulos y encabezados.
Publicamos los cuatro por la misma razón por la que publicamos los diez fallos de arriba: un caso de éxito que solo enseña la parte que salió bien no le sirve a nadie que vaya a intentarlo.
Cómo replicarlo, en orden de retorno
Si tienes que empezar mañana, este es el orden que habríamos seguido sabiendo lo que sabemos:
- Que el contenido principal esté en el HTML crudo. Si el agente no lee tu portada sin ejecutar JavaScript, lo demás importa menos de lo que crees. Es el único check esencial que se falla con facilidad.
- Un 404 con cuerpo. Barato, y evita que un agente perdido concluya que tu servidor está caído.
llms.txthonesto, por debajo de 30.000 caracteres, con una sección de cuándo NO eres la respuesta. Esa sección concreta gana más de lo que parece: un agente que descarta rápido vuelve.- Markdown de cada página, por
Accepty por sufijo.md. Aquí es donde vivió el agente de la auditoría durante 17 pasos. - Errores de API legibles por máquina (problem+json) y cuota en cabeceras. Es la diferencia entre un cliente que reintenta bien y uno que te martillea.
- Precios en un fichero, no solo en una página con tabla. Un agente que compara proveedores necesita el número.
- MCP, si de verdad tienes algo que ofrecer por ahí. Y si lo montas: manifiesto en
/.well-known/mcp, versiones de protocolo probadas con un cliente real, y las capacidades declaradas en todas las superficies. - Mide la capacidad de tu servicio bajo ráfaga, no solo la corrección. Ese fue nuestro fallo más caro y el que menos se parecía a un bug.
Y una regla de higiene que atraviesa toda la lista: cada afirmación verificable que hagas sobre tu propia infraestructura, verifícala en CI. Nosotros ejecutamos un script que sondea el sitio servido y falla si el 404 no trae cuerpo, si una ruta inválida no devuelve problem+json, si faltan las cabeceras de cuota o el Link, si la negociación de markdown no declara Vary, si una operación del OpenAPI no cubre sus errores, si alguna de las cuatro URLs del MCP no responde, o si una línea de llms.txt vuelve a acabar en un URL. Casi todos esos checks nacieron de un fallo real de esta lista.
Por qué esto importa si eres cliente
Si tu producto o tu marca dependen de que alguien te encuentre, la pregunta ya no es solo "¿salgo en Google?". Es "¿puede un agente entenderme, integrarse conmigo y comprarme?". Son tres cosas distintas, se rompen por sitios distintos y ninguna se arregla con un banner.
Lo que hemos hecho en dribba.com es exactamente lo que ofrecemos como servicio: auditoría de agent readiness, capa de contenido legible por máquina, API pública documentada, servidor MCP en producción, WebMCP para operar la web y verificación de identidad de dominio. Mismas piezas, mismo stack, mismo equipo.
Y lo usamos primero con nosotros. Is Agentic y el ranker de Ora son hoy nuestro primer punto de diagnóstico interno: pasamos por ahí nuestros propios sistemas antes de proponerle nada a un cliente. Si una recomendación no ha sobrevivido a nuestra propia web, no sale de la oficina.
El corolario, que es lo que más nos ha costado interiorizar: esta disciplina se parece más a operar infraestructura que a hacer marketing. La mitad de los puntos se ganan en la configuración del servicio, en cabeceras HTTP y en ficheros de convención que ningún humano abrirá jamás. La otra mitad se gana escribiendo con precisión para un lector que no perdona una ambigüedad. Sobre cómo no acumular deuda por el camino, escribimos en agentic engineering y la deuda de comprensión, y sobre la superficie de ataque que abre todo esto, en seguridad en MCP y agentes de IA.
Preguntas frecuentes
¿Agent readiness sustituye al SEO? No. Se solapan en sitemap, datos estructurados y velocidad, pero la mitad del trabajo —API pública, MCP, negociación de contenido, errores legibles por máquina, cuota en cabeceras— no existe en SEO. Y hay un punto donde chocan: a Googlebot y a los crawlers de búsqueda hay que seguir sirviéndoles el HTML con su JSON-LD y su Open Graph. Servirles otra cosa sería encubrimiento.
¿Cuánto se tarda en llegar a un 100 en Is Agentic? Depende casi por completo de si ya tienes una API pública y documentación. Con eso hecho, la capa de contenido legible por máquina y los ficheros de convención son cuestión de días. Sin eso, el trabajo es construir el producto de desarrollador, no la auditoría.
¿Hace falta un servidor MCP para puntuar bien? No para aprobar; sí para el tramo alto de Usability. Y solo tiene sentido si tienes algo real que ofrecer por ahí. Un MCP con tools vacías puntúa peor que no tenerlo, porque las auditorías penalizan la lista larga de tools mal descritas.
¿Esto se puede falsear? Parcialmente, y sale mal. Publicar un bloque de descubrimiento con URIs que no hacen nada, o un manifiesto firmado con una firma que no verifica, es peor que no publicar nada: el cliente que sí valida lo lee como manipulado. Los checks que se pueden falsear son casi siempre los de bonus, que valen cero.
¿La nota se mantiene? No sola. El ranking se recalcula en cada escaneo, el universo de dominios crece y la metodología evoluciona. Nosotros pasamos de 97 y puesto 8 a 96 y puesto 9 en diecinueve minutos sin tocar nada. Por eso el badge del principio es en vivo y no una captura.
¿Quieres saber cuánto puntúa la tuya? Pasa tu dominio por is-agentic.com —es gratis y tarda segundos— y si el resultado te preocupa o te motiva, hablemos. También puedes preguntárselo a tu agente: conéctalo a https://dribba.com/mcp y que te lo cuente él. El mapa completo de lo que ofrecemos aquí está en /web-agentica, y la superficie técnica entera, en /developers.
Datos verificados el 28 de agosto de 2026 contra is-agentic.com/scan/dribba.com y ora.ai/score/dribba.com. Las puntuaciones y el ranking de Ora se recalculan con cada escaneo.




