Agent Evidence API
Evidencia web citada y verificación de afirmaciones para agentes de IA: envía una pregunta o afirmación más URLs públicas y obtén pasajes vinculados a fuentes con tiempos de recuperación y hashes de contenido. El descubrimiento es gratuito; cada llamada se paga en USDC en Base mediante x402.
Servidor MCP alojado
npx add-mcp 'https://agent-evidence-api.thx93.workers.dev/mcp'Se instala en Claude Code, Codex, Cursor y más
Documentación
Agent Evidence API
Evidencia web citada y fundamentada en fuentes, y verificación de afirmaciones para agentes de IA, tanto por HTTP como por MCP. Se paga por llamada en USDC sobre Base mediante x402: $0.003 por llamada, y nada en absoluto cuando no se recupera nada.
| Qué | Dónde |
|---|---|
| Endpoint de evidencia HTTP | POST https://agent-evidence-api.thx93.workers.dev/v1/evidence |
Servicio MCP (research_evidence) | POST https://agent-evidence-api.thx93.workers.dev/mcp |
Servicio MCP (get-alerts, get-forecast) | POST https://agent-evidence-api.thx93.workers.dev/weather/mcp |
| Muestra gratuita de salida real | GET https://agent-evidence-api.thx93.workers.dev/demo — una respuesta sin editar de una llamada de pago real, sin necesidad de cartera |
| CLI de compra sin instalación | curl -fsSL https://agent-evidence-api.thx93.workers.dev/buy.mjs -o buy.mjs |
| Manifiesto de capacidades x402 | https://agent-evidence-api.thx93.workers.dev/.well-known/x402 |
El descubrimiento es gratuito en cada ruta MCP (tools/list, initialize, ping y una herramienta health), de modo que un agente puede leer los esquemas y comprobar la disponibilidad antes de pagar por nada.
Opcional: clasificación semántica
La clasificación es por defecto coincidencia léxica determinista, y tiene un techo medido. Ante la pregunta "¿Cuál es la población de Tokio?" devuelve pasajes que mencionan "población" y "Tokio" — historia de 1603, descensos en tiempos de guerra, impuestos municipales — y omite la frase que responde a la pregunta, porque esa frase dice "la ciudad propiamente dicha" en lugar de repetir "Tokio". Se probaron dos heurísticas léxicas y se revirtieron; ningún truco de palabras clave lo resuelve.
Un sidecar opcional Laya lo resuelve. Laya es un modelo de decisión tipada de pesos abiertos (Apache-2.0): puntúa un pasaje con noul, P(este pasaje responde directamente a la pregunta). Sin proveedor, sin clave API, sin tarifa por llamada, y tu contenido nunca sale del host.
python services/laya/server.py # ~2 GB resident, ~35 s cold load
REASONING_PROVIDER=laya REASONING_URL=http://127.0.0.1:8077 ...
Medido en el caso anterior, fuente única:
| Fuentes | none | laya | ¿El top 3 contiene la respuesta? |
|---|---|---|---|
| 1 | ~1.4 s | ~11.4 s | ❌ → ✅ |
| 5 | ~1.6 s | ~1.8 s (recurre al léxico) | ❌ → ❌ |
El modelo se activa solo cuando el presupuesto puede permitirse el grupo completo. El coste es de ~800 ms por candidato (una llamada individual mide ~420 ms, pero el sidecar serializa, así que ~700 ms bajo concurrencia real). El presupuesto se divide entre las fuentes, de modo que 1–2 fuentes pueden permitirse un grupo de 12 y las solicitudes más grandes no. Se midió que un grupo más reducido no vale nada — solo puede reordenar pasajes que ya excluían la respuesta — así que en lugar de gastar ~20 s en eso, esas solicitudes recurren al léxico y siguen siendo rápidas. Reduce REASONING_POOL_SIZE para activarlo en solicitudes más grandes, a cambio de recuperación.
El presupuesto es un techo duro, no un objetivo: se reserva antes de cada llamada, de modo que las fuentes concurrentes no pueden superarlo colectivamente. (Una versión anterior comprobaba y luego esperaba, lo que permitía que cinco fuentes pasaran la comprobación antes de que ninguna lo decrementara — un presupuesto de 8 s produjo una solicitud de 50 s.)
El intercambio es latencia, no precisión: ~420 ms por candidato, así que un grupo de 12 candidatos cuesta ~5 s de inferencia por fuente. Por eso está desactivado por defecto. Activarlo cambia solo el ORDEN de la evidencia — cada extracto, fuente, URL y hash es idéntico — y si el sidecar falta, va lento o devuelve algo inesperado, el servicio conserva silenciosamente su resultado léxico. La probabilidad bruta del modelo nunca se expone (SPEC §21).
Ten en cuenta que el grupo importa: la primera versión de esto clasificaba solo los 5 candidatos que el léxico ya había elegido, y la respuesta había quedado en los puestos 8.º–20.º, así que nunca estaba disponible para promocionarla. El grupo más amplio es lo que lo hace funcionar.
¿Ejecutas el backend en Docker? El sidecar debe ser accesible desde el contenedor: --add-host=host.docker.internal:host-gateway y apunta REASONING_URL a http://host.docker.internal:8077, o pon ambos en una misma red Docker.
Solo se te cobra cuando el servicio entrega
El middleware x402 liquida un pago solo cuando el manejador devuelve un estado inferior a 400, así que el servicio está construido para que cada forma en que puede fallar sea un 4xx o 5xx:
- Si no se pudo recuperar ninguna fuente — todos 404, todos bloqueados por SSRF, todos tipos de contenido no admitidos — la solicitud falla con
NO_SOURCES_RETRIEVED(502) y no se toma ningún pago. Una versión anterior devolvía200 inconclusiveaquí, lo que habría facturado a un comprador $0.03 por un resultado vacío. - El éxito parcial sigue liquidándose. Si se recuperó una de cinco fuentes, se entregó evidencia real y las advertencias por fuente explican el resto.
- En el transporte MCP, una llamada a herramienta fallida se informa como
isError: truedentro de un HTTP 200 — comportamiento MCP correcto, pero invisible para el middleware de pago. El Worker lo reescribe a 502 para que no se tome el pago; el cuerpo JSON-RPC se pasa sin cambios.
Verificado contra el despliegue en vivo: una solicitud cuyas fuentes fallan todas devuelve 502 NO_SOURCES_RETRIEVED, y una solicitud con una fuente buena y una muerta devuelve 200.
Análisis de mercado
docs/market-analysis.md — lo que la economía x402 realmente paga, medido a partir del único catálogo que publica datos de uso reales (llamadas a 30 días y pagadores únicos por servicio). Es una lectura incómoda: la categoría en la que se sitúa este servicio es el 1% de la búsqueda por volumen, y su banda de precios es la más débil de las cinco.
Informe de ingeniería
docs/ENGINEERING-REPORT.md — el informe requerido por SPEC.md §44: qué se construyó, la arquitectura, los comandos de despliegue y prueba, el estado de x402 y MCP, la preparación para el registro, la configuración manual restante y las limitaciones conocidas. Declara claramente qué está demostrado y qué no — incluido que no se ha ejercitado ninguna liquidación y que un criterio de aceptación del §42 no se cumple. No afirma que el proyecto esté listo para producción.
Acciones que solo tú puedes tomar
docs/CHECKLIST.md — cada paso restante que necesita a un humano: el primer pago, confirmar el listado en el catálogo, publicar y las decisiones que deliberadamente te dejé a ti. Todo lo demás está hecho.
Cómo hacer la primera venta
Consulta docs/FIRST-SALE.md — el camino completo desde una cartera vacía hasta un pago liquidado y una entrada en el catálogo, en una sola página.
Cómo ver los ingresos
Cada solicitud que el backend atiende en una ruta de pago ya ha pasado por la propia pasarela de pago x402 del backend (el Worker solo hace de proxy). El registro es de solo añadidura en el volumen de datos, así que sobrevive a la recreación de contenedores y a los redespliegues — a diferencia de los registros de Docker.
Una línea significa que se verificó una prueba de pago en este proceso. No es prueba de liquidación. El orden x402 es verificar → manejador → liquidar, así que el backend escribe su línea antes de que el facilitador liquide, y una liquidación que luego falle aún aparece aquí. El campo se llama payment_provided por esa razón; antes se llamaba settled, lo cual era incorrecto.
docker exec aee-live cat /app/data/usage.jsonl # the raw record
# Buyer traffic: requests that carried an x402 payment proof, so operator and
# test calls are excluded. This is NOT a revenue figure - see below.
docker exec aee-live sh -c "grep -c '"payment_provided":true' /app/data/usage.jsonl"
Para el dinero, usa el registro en cadena. El cliente del comprador imprime la transacción de liquidación, y el libro mayor autoritativo es la dirección del destinatario:
https://basescan.org/address/0x9c0e2B44180439294Fa30Ae2B2a94f8655455FD0
Cada línea lleva la marca de tiempo, el id de solicitud, un hash con sal de la pregunta (nunca el texto), cuántas fuentes se recuperaron, cuántos elementos de evidencia volvieron, la evaluación y la duración del lado del servidor.
{"ts":"2026-09-21T07:53:04.341Z","request_id":"req_2fcc…","question_hash":"725f50fca9fbe049",
"question_chars":32,"sources_requested":1,"sources_retrieved":1,"evidence_items":5,
"assessment":"supported","processing_ms":6,"outcome":"ok"}
Para el lado en cadena, la respuesta de pago lleva un hash de transacción de liquidación, y el destinatario es 0x9c0e2B44180439294Fa30Ae2B2a94f8655455FD0 — compruébalo en basescan.
Despliegue en vivo
| Endpoint | https://agent-evidence-api.thx93.workers.dev |
| MCP | https://agent-evidence-api.thx93.workers.dev/mcp |
| Salud | /health · profunda: /health?deep=1 |
| Registro | io.github.Thx93/agent-evidence-api — publicado, estado activo |
| Pago | x402, exact, 0.03 USDC en Base mainnet (eip155:8453) |
| Cómpralo | sin instalación: curl -fsSL .../buy.mjs -o buy.mjs && node buy.mjs "question" https://source.example |
Compra algo sin cuenta npm y sin nada que instalar — el CLI de compra es un único archivo autocontenido servido por el propio servicio:
curl -fsSL https://agent-evidence-api.thx93.workers.dev/buy.mjs -o buy.mjs
X402_PRIVATE_KEY=0x... node buy.mjs "Is Rotamech a manufacturer of centrifugal pumps?" https://example.com
O simplemente mira primero el muro de pago — esto devuelve un 402 y no cobra nada:
curl -X POST https://agent-evidence-api.thx93.workers.dev/v1/evidence \
-H 'content-type: application/json' \
-d '{"question":"Is Rotamech a manufacturer of centrifugal pumps?","urls":["https://example.com"]}'
Infraestructura de evidencia para agentes de IA. Dale una pregunta o afirmación más las páginas web públicas que quieras comprobar, y devuelve evidencia estructurada y citada: URLs de fuente, marcas de tiempo de recuperación, extractos de evidencia breves, metadatos y una evaluación explícita de supported / contradicted / mixed / inconclusive.
Es accesible para agentes por HTTP y MCP, y se paga por solicitud con x402 + USDC en Base. No hay cuentas, ni contraseñas, ni claves API — el pago es la autenticación.
- Especificación del producto:
SPEC.md— la autoridad en requisitos. - Reglas de ingeniería:
AGENTS.md— el único libro de reglas del proyecto. - Arquitectura:
docs/architecture.md
Proporciona evidencia, no verdad. La evaluación se produce mediante coincidencia léxica determinista, y el servicio dice
inconclusiveen lugar de adivinar. No trates un estadosupportedcomo prueba.
Estado de implementación — lee esto primero. Cada paquete y ambas aplicaciones están implementados, y
pnpm typecheckpasa en todo el espacio de trabajo.pnpm testcubre ~147 casos en seis suites, incluida una prueba de flujo de extremo a extremo entre paquetes.Lo que no está hecho: no se ha demostrado ningún pago x402 en cadena, no hay prueba automatizada de pago x402,
ROBOTS_POLICYestá configurado pero inerte, y dos direcciones de cartera de prueba literales confirmadas enapps/worker/wrangler.jsoncdeben reemplazarse antes de cualquier despliegue. Consulta Estado de implementación.
1. Qué hace el servicio
Un agente envía:
- una pregunta o afirmación, y
- una o más URLs públicas para usar como fuentes.
El servicio entonces:
- valida la URL y rechaza cualquier cosa insegura (defensas SSRF),
- obtiene la página con tiempos de espera limitados, redirecciones y tamaño de respuesta,
- detecta el tipo de contenido y analiza el HTML,
- extrae metadatos (título, descripción, URL canónica, Open Graph, JSON-LD, idioma, fechas de publicación y modificación, encabezados, recuento de palabras),
- normaliza el contenido principal y produce un hash SHA-256 del contenido,
- extrae extractos de evidencia breves que coinciden con la pregunta, con su posición en el documento,
- compara las fuentes y llega a una evaluación,
- devuelve una respuesta estructurada con procedencia completa.
No es un raspador web, una herramienta SEO, un rastreador genérico, un proxy, un servicio de automatización de navegador ni un motor de búsqueda. Es infraestructura de evidencia: lo que un agente llama cuando necesita mostrar su trabajo.
Qué devuelve
{
"request_id": "req_3f9c1a7b2e4d4a6f8b0c1d2e",
"version": "1",
"question": "Is Rotamech Industries a manufacturer of centrifugal pumps?",
"assessment": {
"status": "supported",
"basis": "1 source(s) contained passages matching the question (example.invalid) with no contradicting passages found."
},
"sources": [
{
"requested_url": "https://example.invalid/products",
"final_url": "https://example.invalid/products",
"status": 200,
"content_type": "text/html; charset=utf-8",
"title": "Rotamech Industries — Products",
"canonical_url": "https://example.invalid/products",
"description": "Rotamech Industries manufactures centrifugal pumps for industrial use.",
"publisher": "Rotamech Industries",
"language": "en",
"published_at": "2024-03-01T09:00:00.000Z",
"modified_at": null,
"retrieved_at": "2026-09-20T21:16:07.412Z",
"word_count": 63,
"content_hash_sha256": "3a7bd3e2360a3d29eea436fcfb7e44c735d117c42d1c1835420b6b9942dd4f1b",
"evidence": [
{
"excerpt": "Rotamech Industries is a manufacturer of centrifugal pumps.",
"context": "h2:Products > p[1]",
"relevance": "direct"
}
],
"structured_data": { "json_ld": [], "open_graph": {} },
"warnings": [],
"from_cache": false,
"redirect_chain": []
}
],
"limitations": [
"Assessment is derived from deterministic lexical matching, not semantic reasoning. A configured reasoning provider may refine it, but never replaces source evidence."
],
"processing_ms": 412
}
Cada campo está documentado en docs/api.md.
2. Por qué lo usaría un agente
Porque un agente que cita fuentes es más útil que uno que las afirma.
- Fundamentación. En lugar de generar una frase plausible, el agente puede adjuntar una URL real, una marca de tiempo de recuperación, un hash de contenido y el pasaje en el que se apoya la afirmación.
- Verificación. Un agente puede comprobar qué dice realmente una página específica, en lugar de confiar en un resumen.
- Comparación. Se comparan múltiples fuentes, y el desacuerdo se informa como
mixeden lugar de promediarse. - Honestidad por construcción. Cuando nada coincide lo bastante, la respuesta es
inconclusive. El servicio nunca inventa un hecho, nunca finge que una fuente dice algo que no dice y nunca emite una puntuación de confianza. - Sin incorporación. Sin registro, sin clave, sin plan. Descubre la herramienta, llámala, paga por solicitud, obtén evidencia.
3. Caso de uso de ejemplo
A un agente se le ha preguntado: "¿Rotamech Industries es realmente un fabricante de bombas centrífugas, o solo un revendedor?"
Su propio conocimiento del modelo es obsoleto e inverificable, así que reúne URLs candidatas y luego llama a este servicio:
{
"question": "Is Rotamech Industries a manufacturer of centrifugal pumps?",
"urls": [
"https://example.invalid/products",
"https://example.invalid/about",
"https://example.invalid/registry-record"
],
"max_sources": 5,
"language": "auto",
"mode": "evidence"
}
El sitio de la empresa respalda la afirmación; la página del registro la niega. El servicio devuelve:
{
"assessment": {
"status": "mixed",
"basis": "Sources disagreed: 2 leaning to support (example.invalid) and 1 leaning against (example.invalid)."
}
}
con extractos citados breves de cada lado, sus marcas de tiempo de recuperación y sus hashes de contenido.
El agente ahora tiene algo que no tenía antes: una respuesta defendible que nombra a ambas partes y muestra de dónde proviene cada una, además de limitaciones honestas. Puede decirle al humano "la empresa afirma esto; un registro lo contradice; aquí están ambas fuentes", en lugar de elegir una y sonar seguro.
4. Uso de MCP
Transporte: HTTP transmisible. Endpoint: /mcp en el mismo host que la API.
${MCP_PUBLIC_URL} e.g. https://${PUBLIC_API_DOMAIN}/mcp
Herramientas
| Herramienta | Pago | Propósito |
|---|---|---|
research_evidence | de pago (x402) | Obtener fuentes públicas y devolver evidencia estructurada y citada. |
health | gratuito | Verificación de estado. |
Ejemplo de configuración de cliente
{
"mcpServers": {
"agent-evidence-api": {
"type": "http",
"url": "${MCP_PUBLIC_URL}",
"headers": {
"Accept": "application/json, text/event-stream"
}
}
}
}
No hay nada que poner en headers para la autenticación. No hay clave.
initialize y tools/list son gratuitos
Esto importa para agentes autónomos, y es deliberado:
| Método JSON-RPC | ¿De pago? |
|---|---|
initialize | gratuito |
tools/list | gratuito |
tools/call → health | gratuito |
tools/call → research_evidence | de pago |
| todo lo demás | gratuito |
Un agente debe poder descubrir las herramientas antes de decidir pagar por una. Solo una invocación real de la herramienta de pago requiere pago. Un lote JSON-RPC se cobra si cualquier elemento es una llamada de pago.
Ejemplo de llamada a herramienta
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "research_evidence",
"arguments": {
"question": "Is Rotamech Industries a manufacturer of centrifugal pumps?",
"urls": ["https://example.invalid/products"],
"max_sources": 5,
"language": "auto",
"mode": "evidence"
}
}
}
Esquema de entrada de la herramienta (idéntico al cuerpo de la solicitud HTTP):
| Campo | Tipo | Obligatorio | Notas |
|---|---|---|---|
question | string | sí | 1–2000 caracteres. |
urls | string[] | no | Máximo 25 en el esquema, 5 por defecto en tiempo de ejecución. |
max_sources | integer | no | Positivo, ≤ 25. |
language | string | no | "auto" o una etiqueta BCP-47. |
mode | "evidence" | no | El único valor en v0.1.0. |
Detalles completos, incluido cómo se ve una respuesta de pago requerido para un
cliente MCP: docs/mcp.md.
5. Uso de HTTP
GET /health free liveness
GET / free capabilities
POST /v1/evidence paid the evidence resource
GET /health
curl -s https://${PUBLIC_API_DOMAIN}/health
{ "status": "ok", "service": "agent-evidence-api", "version": "0.1.0" }
GET /
Una descripción de capacidad gratuita, útil para el descubrimiento antes de pagar.
curl -s https://${PUBLIC_API_DOMAIN}/
{
"service": "agent-evidence-api",
"version": "0.1.0",
"description": "Evidence infrastructure for AI agents. Returns structured, cited, freshly-retrieved evidence from public web pages so an agent can verify claims and ground answers in sources.",
"endpoints": {
"health": "GET /health (free)",
"evidence": "POST /v1/evidence (paid, x402)",
"mcp": "POST /mcp (MCP streamable HTTP; initialize and tools/list are free)"
},
"mcp": { "transport": "streamable-http", "tools": { "research_evidence": "paid", "health": "free" } },
"payment": {
"protocol": "x402",
"scheme": "exact",
"network": "eip155:84532",
"asset": "USDC",
"price_usd": "0.03",
"recipient": "0x…",
"facilitator": "https://x402.org/facilitator"
}
}
POST /v1/evidence
POST /v1/evidence HTTP/1.1
Host: ${PUBLIC_API_DOMAIN}
Content-Type: application/json
payment-signature: <x402 payment proof>
{
"question": "Is Rotamech Industries a manufacturer of centrifugal pumps?",
"urls": ["https://example.invalid/products", "https://example.invalid/about"],
"max_sources": 5,
"language": "auto",
"mode": "evidence"
}
Devuelve el documento EvidenceResponse que se muestra en §1.
Sin un pago válido, obtienes HTTP 402 con un sobre de error PAYMENT_REQUIRED
y los requisitos de pago x402:
{
"error": {
"code": "PAYMENT_REQUIRED",
"message": "Payment is required to access this resource.",
"request_id": "req_5c4b3a291807f6e5d4c3b2a1"
}
}
Errores
Cada fallo usa un solo sobre. Los códigos son estables: usa error.code para decidir.
{
"error": {
"code": "INVALID_URL",
"message": "The supplied URL is invalid or unsupported.",
"request_id": "req_3f9c1a7b2e4d4a6f8b0c1d2e"
}
}
| Código | HTTP |
|---|---|
INVALID_REQUEST | 400 |
INVALID_URL | 400 |
BLOCKED_URL | 400 |
SSRF_ATTEMPT | 400 |
PAYMENT_REQUIRED / PAYMENT_INVALID / PAYMENT_EXPIRED | 402 |
UNAUTHORIZED | 401 |
UNSUPPORTED_CONTENT | 415 |
RATE_LIMIT | 429 |
EXTRACTION_FAILURE / INTERNAL_ERROR | 500 |
TIMEOUT | 504 |
REDIRECT_LIMIT / RESPONSE_TOO_LARGE / UPSTREAM_HTTP_FAILURE / BACKEND_UNREACHABLE | 502 |
NOT_CONFIGURED | 503 |
Tabla completa con significados: docs/api.md §5.
Una fuente que falla no hace fallar la solicitud
Si una de cinco URLs es inalcanzable, aún obtienes las otras cuatro. La fuente
fallida aparece con status: null, evidence vacío y una advertencia con el
motivo:
{
"requested_url": "https://example.invalid/gone",
"status": null,
"evidence": [],
"warnings": [
{ "code": "UPSTREAM_HTTP_FAILURE", "message": "The source returned an error status." }
]
}
y limitations gana: "1 de 2 fuente(s) no se pudo recuperar; consulta las advertencias
por fuente."
6. El flujo de pago x402
Agent
│ 1. POST /v1/evidence (or tools/call research_evidence)
▼
Worker ──▶ 402 Payment Required + payment requirements
│
Agent
│ 2. pays USDC on Base / Base Sepolia to the recipient address
│ 3. retries the same request with payment-signature: <proof>
▼
Worker ──▶ 4. verifies via the x402 facilitator
│ invalid → 402 PAYMENT_INVALID
│ expired → 402 PAYMENT_EXPIRED
▼
Backend ──▶ 5. runs the evidence pipeline
▼
Worker ──▶ 6. 200 + EvidenceResponse
- Sin claves de API. Sin cuentas de usuario. Por diseño (SPEC §11, §38). Un agente nunca debería tener que registrarse, solicitar una clave o suscribirse.
- El destinatario solo necesita una dirección pública. Recibir USDC no requiere clave privada, porque el servicio nunca firma nada — el cliente que paga lo hace.
- Nunca comprometas una clave privada. No en
wrangler.jsonc, no en.env, no en ningún lugar.X402_TEST_PRIVATE_KEYes una clave desechable de Base Sepolia para pruebas locales únicamente, nunca debe tener fondos de mainnet y nunca debe comprometerse. - La omisión de desarrollo está doblemente protegida. Localmente,
DEV_BYPASS_PAYMENT=trueomite el control de acceso — pero solo cuando la red no es Base mainnet. Eneip155:8453el pago nunca se puede omitir, incluso si la bandera se filtra a la configuración de producción.
Detalle completo: docs/x402.md.
7. Base y Base Sepolia
| Entorno | Red | Identificador CAIP-2 |
|---|---|---|
| Pruebas | Base Sepolia | eip155:84532 |
| Producción | Base | eip155:8453 |
El token de liquidación es USDC en ambos casos.
La configuración de testnet y producción se mantiene en entornos de Worker separados y
nunca debe mezclarse. El valor predeterminado seguro en packages/core/src/config.ts es Base
Sepolia, por lo que una implementación no configurada está en testnet en lugar de
accidentalmente en mainnet.
apps/worker/wrangler.jsonc proporciona tres entornos:
| Entorno | Activación | Red | Pago |
|---|---|---|---|
| producción | wrangler deploy | eip155:8453 (Base) | requerido |
| dev | wrangler dev --env dev | eip155:84532 (Sepolia) | omitido |
| test | wrangler dev --env test | eip155:84532 (Sepolia) | requerido |
8. Configuración de precios
Los precios se basan en el entorno, nunca están codificados.
| Variable | Predeterminado | Significado |
|---|---|---|
X402_PRICE_USD | 0.003 | Precio por solicitud de evidencia, en USD. |
X402_NETWORK | eip155:84532 | Red de liquidación. |
X402_RECIPIENT | (vacío) | Dirección receptora pública. |
X402_FACILITATOR_URL | https://x402.org/facilitator | Facilitador utilizado para la verificación. |
El valor predeterminado del MVP es aproximadamente $0.03 por solicitud de evidencia. Ese es un punto de partida, no un precio comercial fijo.
La cadena de precio se normaliza antes de usarse: un $ inicial es opcional y el
valor se muestra con dos decimales. Ten en cuenta que un valor no analizable no
detiene al Worker — cae silenciosamente a $0.03. Verifica el valor después de cualquier
cambio.
9. Desarrollo local
Requiere Node.js ≥ 22.6 (la caché usa el módulo integrado node:sqlite, por lo que
no hay dependencia nativa que compilar) y pnpm.
pnpm install
cp .env.example .env # placeholders only; fill in locally, never commit
openssl rand -hex 32 # → BACKEND_AUTH_SECRET
pnpm typecheck
pnpm test
| Script | Propósito |
|---|---|
pnpm typecheck | Verificación de tipos de cada espacio de trabajo desde el código fuente. |
pnpm test | Todos los archivos *.test.ts. |
pnpm test:security | Suite de validación SSRF / URL. |
pnpm test:e2e | Flujo de aceptación de extremo a extremo. |
pnpm fixtures | Servidor de fixtures local determinista. |
pnpm dev:backend | Backend en :8080. |
pnpm dev:worker | Worker en :8787. |
pnpm validate | typecheck && test. |
El servidor de fixtures (tests/fixtures/server.ts) es offline y determinista — sin
dependencia de sitios web en vivo. Sirve páginas de contenido, cadenas de redirección,
trampas de redirección a privado y a metadatos, una ruta que nunca responde, una
ruta de transmisión de 8 MiB y respuestas 404/500, para que los controles SSRF y de límites
puedan ejercitarse contra un socket real.
pnpm fixtures
# [fixtures] listening on http://127.0.0.1:<port>
Ambos comandos de desarrollo funcionan, con una advertencia.
pnpm dev:backendinicia el backend Fastify en:8080;pnpm dev:workerinicia el Worker. Apuntar el Worker al backend y llamar a/v1/evidencetambién requiere que la etapa x402 se liquide, oDEV_BYPASS_PAYMENT=truecon una red que no sea mainnet. Consulta Estado de implementación.
10. Implementación
Cloudflare Worker (public edge) → protected origin → Docker container → Fastify backend → SQLite
-
Backend en el VPS. Un servicio Node.js + Fastify construido desde
docker/y ejecutado condocker compose, conrestart: unless-stoppedy un volumen persistente montado en/app/datapara la caché SQLite. -
Cloudflare Worker.
bash scripts/deploy-live.shenvía el entorno de producción y se niega a informar éxito a menos que la compilación termine limpiamente, el ID de versión realmente cambie y el servicio en vivo se verifique desde el exterior. (wrangler deploysolo canaliza mal:wrangler deploy | tailsale con 0 incluso cuando la compilación falla, lo que ocultó una implementación rota dos veces). (Base mainnet). -
El secreto compartido.
BACKEND_AUTH_SECRETestá deliberadamente ausente dewrangler.jsoncpara que nunca pueda comprometerse:wrangler secret put BACKEND_AUTH_SECRETDebe coincidir con el propio
BACKEND_AUTH_SECRETdel backend. Genéralo conopenssl rand -hex 32. -
Protección del origen — la opción preferida es un Cloudflare Tunnel, por lo que el backend no tiene listener público ni registro DNS público. La alternativa es un origen autenticado detrás de un firewall restringido a los rangos de salida de Cloudflare. En ambos casos, el secreto compartido se valida en cada solicitud interna, porque la restricción de red y la autenticación de solicitudes son capas independientes.
Usa marcadores de posición — PUBLIC_API_DOMAIN, BACKEND_ORIGIN_URL, MCP_PUBLIC_URL
— y nunca inventes un dominio real.
Instrucciones completas, tablas de variables de entorno, verificaciones de salud y la lista
de verificación manual de credenciales: docs/deployment.md.
Implementable en principio, aún no implementado ni ejercitado de extremo a extremo. Los artefactos Docker, el backend y el Worker existen. Nada se ha implementado desde este repositorio, y dos defectos de configuración deben corregirse primero (consulta Estado de implementación). Consulta
docs/deployment.md.
11. Seguridad
El buscador trata cada URL como entrada hostil. Defensas requeridas (SPEC §18), todas
aplicadas en el paquete @aee/fetcher:
- Lista de permitidos de esquemas — solo
httpyhttps;file://y todo lo demás rechazado. - Rechazo de direcciones — localhost y sus alias, loopback, IPv4 RFC1918/privada,
link-local, multicast, no especificada, loopback IPv6, link-local IPv6,
rangos IPv6 privados y endpoints de metadatos de nube (
169.254.169.254y similares). - Evasión codificada — las representaciones decimal, octal, hexadecimal, de forma corta y IPv4-mapeada-a-IPv6 se verifican por rango después del análisis, nunca se comparan como cadenas.
- DNS — resuelve explícitamente, valida cada dirección resuelta y revalida en el momento de la conexión para cerrar la ventana de rebinding de DNS.
- Redirecciones — valida cada salto, no solo la primera URL, y aplica
MAX_REDIRECTS. - Puertos — solo lista de permitidos.
- Límites — tiempo de espera de conexión, tiempo de espera total de solicitud, bytes máximos de respuesta aplicados durante la transmisión, defensa contra bombas de descompresión, límites de concurrencia.
- Sin ejecución de shell de URLs, nunca. Sin
child_processen la ruta de búsqueda. - Registros — JSON estructurado con redacción de claves secretas. Nunca registres secretos de pago o de origen, claves privadas o secretos de billetera.
Por separado, la protección del origen garantiza que un llamador no pueda omitir al Worker y
llamar al backend directamente para evitar pagar: un Cloudflare Tunnel (preferido) o un
origen con firewall, más autenticación servidor a servidor en cada solicitud
interna. BACKEND_AUTH_SECRET no tiene valor predeterminado precisamente para que un
backend mal configurado falle de forma cerrada en lugar de ejecutarse sin autenticación.
Y las reglas de acceso de SPEC §19: sin omitir controles de acceso, sin anular protecciones
contra bots, sin automatización de inicio de sesión, sin evasión de muros de pago, sin resolución
de CAPTCHA. Se usa un User-Agent claramente identificable, el comportamiento de robots.txt es
configurable mediante ROBOTS_POLICY, y solo los recursos de acceso público están en
alcance. Envía solo URLs que tengas permitido acceder.
Estos controles están implementados en
packages/fetchery probados unitariamente entests/security/, que pasa localmente (92 casos de seguridad). No se ha realizado una auditoría independiente.ROBOTS_POLICYse aplica. Consultadocs/security.mdpara la tabla de estado por control, qué se aplica dónde y la lista honesta de brechas.
12. Limitaciones
Dicho claramente, porque debes conocerlas antes de confiar en el resultado.
| Limitación | Detalle |
|---|---|
| Solo coincidencia léxica determinista | La evaluación proviene de la coincidencia de términos más una lista fija de señales de negación. No hay comprensión semántica. Una contradicción parafraseada que no comparte vocabulario no se detectará; un pasaje negado que comparte muchos términos sí se detectará. |
| Sin razonamiento semántico a menos que se configure un proveedor | El punto de extensión ReasoningProvider en SPEC §37 existe en el diseño y no está implementado. Incluso cuando lo esté, puede descomponer, clasificar, comparar y sintetizar, pero nunca debe reemplazar la evidencia fuente con prosa no respaldada. |
| Solo URLs proporcionadas por el agente en v0.1.0 | Debe proporcionar las URLs. No hay proveedor de búsqueda, por lo que el servicio no puede descubrir fuentes por usted. urls es necesario en la práctica. |
| Los extractos son cortos por diseño | El MAX_EXCERPT_CHARS predeterminado es 600 (límite del esquema 4000). Este es un servicio de evidencia citada, no un servicio de volcado de contenido, y no reproduce documentos completos. |
inconclusive es común y correcto | Si la pregunta no comparte términos significativos con el texto recuperado, la respuesta honesta es inconclusive. Eso es el servicio funcionando, no fallando. |
| Los tokens de menos de 3 caracteres se ignoran | Se eliminan de los términos de la pregunta, por lo que las preguntas muy cortas o solo con acrónimos pueden coincidir mal. |
| La derivación es rudimentaria | Se elimina un ing/ed/es/s final. Sin biblioteca de derivación, sin formas irregulares. |
| Las palabras vacías son una lista fija en inglés | Las preguntas no inglesas pierden poco (la lista es específica del inglés y mayormente inerte en otros lugares), pero la calidad de clasificación para preguntas no inglesas no está ajustada. |
| Sin evidencia en PDF, imagen o renderizada por JavaScript | DocumentProvider y BrowserProvider son puntos de extensión futuros y no están implementados. Solo HTML estático. |
| Sin detección de cambios de fuente ni puntuación de confianza | SourceTrustProvider no está implementado; el servicio informa lo que dice una página, no cuánto confiar en el editor. |
| Los aciertos de caché se marcan, no se ocultan | from_cache: true más una advertencia de SERVED_FROM_CACHE, y retrieved_at es siempre el tiempo real de recuperación. Se puede servir evidencia de hasta CACHE_TTL_SECONDS (predeterminado 24 h) de antigüedad. |
| Una fuente mala no hace fallar la solicitud | Los fallos se informan por fuente en warnings, y se agrega un recuento de fallos a limitations. Lea esas matrices. |
| La limitación de velocidad es por proceso de backend | Un cubo de tokens con clave en CF-Connecting-IP aplica RATE_LIMIT (429) con un mapa de cubos limitado. Está en proceso, por lo que múltiples réplicas de backend tienen cada una su propio presupuesto; la limitación a nivel de borde necesitaría Durable Objects. |
| El contenido no HTML no se procesa | Un tipo de contenido no compatible produce una fuente con un cuerpo null y una advertencia UNSUPPORTED_CONTENT_TYPE en lugar de un error. HTML, texto plano, XML y JSON son los tipos procesados. |
| La coincidencia de robots es de prefijo/comodín, no RFC 9309 completo | ROBOTS_POLICY=ignore|warn|enforce se aplica, con un caché limitado por origen. Las reglas ambiguas se resuelven a permitir y se advierten en lugar de rechazarse silenciosamente. Se envía un User-Agent claramente identificable en cada solicitud. |
| Sin pago en cadena demostrado | La puerta x402 está implementada; no se ha ejecutado un acuerdo financiado en Base Sepolia o mainnet contra un despliegue. |
| Sin revisión de seguridad independiente | Los controles SSRF están implementados y probados unitariamente en el código fuente; no han sido auditados de manera adversaria. |
Y, ahora mismo
Implementado y con verificación de tipos: los seis paquetes (schemas, core, fetcher,
extraction, cache, mcp), ambas aplicaciones (backend, worker), los artefactos Docker
y server.json. pnpm typecheck pasa en todo el espacio de trabajo, y
seis suites de pruebas cubren aproximadamente 147 casos, incluida una prueba de flujo de extremo a extremo que
impulsa el backend real sobre HTTP contra el servidor de accesorios.
Aún abierto:
| Brecha | Detalle |
|---|---|
| Direcciones de billetera de prueba literales | apps/worker/wrangler.jsonc tiene la misma dirección de destinatario literal tanto en env.dev como en env.test. Reemplácelas con marcadores de posición antes de implementar. |
| La coincidencia de robots está simplificada | ROBOTS_POLICY se aplica; la coincidencia es de prefijo/comodín en lugar de RFC 9309 completo, y la ambigüedad se resuelve a permitir. |
| Sin verificación de pago en cadena | La puerta x402 está implementada y revisada; no se ha ejecutado ningún acuerdo financiado en Base Sepolia o mainnet. |
| Sin prueba automatizada de pago x402 | SPEC §26 solicita una prueba 402 y una prueba de pago válida en testnet. Ninguna existe. |
| La limitación de velocidad está en proceso | Se aplica por réplica de backend, no globalmente en el borde. |
La URL remota de server.json es un marcador de posición | El espacio de nombres está confirmado (io.github.Thx93/), pero remotes[].url debe reemplazarse, y repository.url hace referencia a un repositorio que aún no existe. |
| Sin revisión de seguridad independiente | Los controles SSRF están implementados y probados unitariamente en el código fuente; nadie los ha auditado de manera adversaria. |
Verificado localmente: pnpm typecheck sale con 0, pnpm test informa 199 pruebas
que pasan, y tanto scripts/smoke.sh como scripts/worker-smoke.sh pasan contra
sockets en vivo. Vuelva a ejecutarlos e informe la salida real en lugar de asumir un
pase: estos resultados son de una sola máquina.
13. Publicación en el registro
SPEC §28 requiere que el proyecto esté listo para el registro, no auto-publicado. Eso significa:
- un archivo de metadatos
server.jsonque use el esquema oficial actual, - validación de ese archivo,
- pasos de publicación exactos documentados,
- una lista de verificación de propiedad del espacio de nombres y autenticación,
- publicación manual con el flujo de trabajo oficial de
mcp-publisher, y solo cuando las credenciales y la autorización estén explícitamente disponibles.
server.json ahora existe y declara esto como un servidor remoto
HTTP de flujo continuo (sin entrada packages, porque no hay paquete npm para
instalar). Quedan dos cosas antes de que sea publicable:
remotes[].urlsigue siendohttps://mcp.example.invalid/mcp; reemplácelo con suMCP_PUBLIC_URLreal.- los
nameyrepository.urlafirman un espacio de nombres de GitHub — confirme que son genuinamente suyos, porque la propiedad del espacio de nombres se valida y un identificador fabricado falla.
Nada se ha publicado. El procedimiento de publicación exacto, los pasos de validación y la
lista de verificación del espacio de nombres están en
docs/registry-publication.md.
La estrategia de descubrimiento — el Registro MCP, el repositorio de GitHub, metadatos npm
cuando corresponda, metadatos legibles por máquina, documentación y descripciones de herramientas
orientadas a agentes — está en docs/discovery.md. También
enumera las frases descriptivas veraces que vale la pena usar (evidencia web, verificación
de fuentes, verificación de afirmaciones, extracción de evidencia, investigación
basada en fuentes, comparar fuentes, evidencia citada, evidencia web fresca), y establece
claramente que las descripciones deben ser veraces y no deben estar rellenas de palabras clave.
No publique hasta que el servicio realmente funcione. Una entrada de registro que liste una
herramienta que no se puede llamar es peor que ninguna entrada. Quedan dos bloqueadores concretos:
el marcador de posición remotes[].url debe reemplazarse con un punto final en vivo, y el
espacio de nombres name/repository.url debe confirmarse como genuinamente suyo.
Diseño del proyecto
agent-evidence-api/
├── AGENTS.md # sole project rules file
├── SPEC.md # product specification (authority on requirements)
├── README.md
├── server.json # MCP Registry metadata (remote streamable-HTTP server)
├── .env.example # environment template, placeholders only
├── pnpm-workspace.yaml
├── package.json
├── docs/
│ ├── architecture.md # architecture, pipeline, layering rule
│ ├── api.md # HTTP endpoints, exact schemas, error codes, limits
│ ├── mcp.md # MCP transport, tools, free vs paid, client config
│ ├── x402.md # payment flow and configuration
│ ├── security.md # SSRF controls, origin protection, secrets
│ ├── deployment.md # local, Docker, Worker, origin protection
│ ├── discovery.md # agent discoverability strategy
│ └── registry-publication.md # exact MCP Registry publish steps
├── apps/
│ ├── backend/ # Fastify service — the VPS workload
│ │ └── src/ # app.ts, auth.ts, server.ts
│ └── worker/ # Cloudflare Worker — the public edge
│ ├── src/index.ts
│ ├── wrangler.jsonc
│ └── .dev.vars.example
├── packages/
│ ├── schemas/ # zod schemas + types (the shared wire contract)
│ ├── core/ # EvidenceService, config, logger, assessment
│ ├── fetcher/ # SSRF-hardened HTTP fetching (ip.ts, fetch.ts)
│ ├── extraction/ # HTML parsing, metadata, main content, lexical ranking
│ ├── cache/ # SQLite-backed bounded cache (node:sqlite)
│ └── mcp/ # MCP server + tool definitions (streamable HTTP)
├── tests/
│ ├── fixtures/ # deterministic local fixture server
│ ├── security/ # ssrf.test.ts, limits.test.ts
│ └── e2e/ # evidence-flow.test.ts (cross-package)
├── scripts/
│ ├── run-tests.mjs # test runner (node --test + tsx)
│ └── smoke.sh # live socket smoke test against the built backend
├── .dockerignore
└── docker/
├── Dockerfile
├── docker-compose.yml
├── env.production.example # Base mainnet
└── env.development.example # Base Sepolia
Tanto el adaptador HTTP como el adaptador MCP llaman al mismo EvidenceService —
no hay lógica de negocio duplicada entre ellos (SPEC §39):
HTTP adapter ─────┐
├──> EvidenceService ──> Fetch / Extract / Cache
MCP adapter ──────┘
x402 se encuentra en el límite de pago en el Worker, nunca dentro del motor central.
Estado de implementación
Instantánea tomada mientras se escribía la documentación.
Implementado
packages/schemas— el contrato de cable completo (solicitudes, respuestas, fuentes, elementos de evidencia, advertencias, errores, límites) con salida construida.packages/fetcher— recuperación endurecida contra SSRF: lista de permitidos de esquemas, clasificación de rangos IPv4/IPv6 (incluidas formas decimal/octal/hex/127.1e IPv4 mapeado a IPv6), reglas de nombres de host, lista de permitidos de puertos, validación de IP resuelta en todos los registros DNS, validación de redirecciones por salto, revalidación en tiempo de conexión contra el rebinding de DNS, tiempos de espera de conexión y totales, límite de bytes de transmisión y descompresión manual.packages/extraction— análisis de HTML, metadatos, extracción de contenido principal, normalización, hash de contenido, clasificación léxica determinista, señales de negación.packages/cache— normalización de claves,CacheProvider, adaptador SQLite limitado.packages/core— configuración, registrador estructurado con redacción de secretos, evaluación entre fuentes,EvidenceService,ServiceErrortipado.packages/mcp— el adaptador MCP remoto: HTTP de flujo continuo en modo sin estado (sessionIdGenerator: undefined,enableJsonResponse: true), un par nuevo deServer+ transporte por solicitud HTTP,tools/listytools/calldelegando al mismoEvidenceServiceque usa la capa HTTP. Los esquemas de entrada de herramientas se generan desde zod mediantez.toJSONSchema.apps/backend— servicio Fastify: inicio a prueba de fallos ante un secreto faltante, verificación de secreto compartido en tiempo constante en todo excepto/health,GET /health,POST /internal/v1/evidencey POST/GET/DELETE en/mcp.apps/worker— el borde completo: puerta x402,/healthy/gratuitos, reenvío dePOST /v1/evidence, inspección y reenvío del cuerpo MCP, bypass de desarrollo protegido, envoltorio de error canónico.docker/—Dockerfilede múltiples etapas,docker-compose.yml, plantillas de entorno separadas para mainnet/testnet, más un.dockerignoreraíz.server.json,apps/worker/.dev.vars.example,scripts/smoke.sh.
Verificado en este entorno
pnpm typecheck— pasa en todo el espacio de trabajo (tsc --noEmit, salida 0).- Existen seis suites de pruebas con un total de ~147 casos:
tests/security/ssrf.test.ts(30),tests/security/limits.test.ts(16),tests/e2e/evidence-flow.test.ts(18),packages/mcp/src/mcp.test.ts(15),packages/extraction/src/extraction.test.ts(46),packages/cache/src/cache.test.ts(22).
Verificado localmente, no reproducido de forma independiente
pnpm typecheck→ salida 0;pnpm test→ 199 que pasan;scripts/smoke.shyscripts/worker-smoke.sh→ todas las comprobaciones pasaron. Ningún tercero ha reproducido estos resultados.- No se ha demostrado ningún pago x402 en cadena (la puerta 402 está verificada; la ruta de liquidación necesita una billetera de testnet financiada).
Defectos abiertos
| Defecto | Detalle |
|---|---|
| Direcciones de billetera de prueba literales | apps/worker/wrangler.jsonc contiene la misma dirección de destinatario literal tanto en env.dev como en env.test. SPEC §33 y AGENTS.md §6 prohíben comprometer la configuración de la billetera. |
| La coincidencia de robots está simplificada | ROBOTS_POLICY se aplica en EvidenceService; la coincidencia es de prefijo/comodín, no RFC 9309 completo. |
| — (corregido) | UNSUPPORTED_CONTENT (415) ahora se emite por fuente. |
| Sin prueba de liquidación en cadena | La puerta 402 y la división MCP gratuita/de pago están cubiertas por scripts/worker-smoke.sh; la limitación de velocidad está probada unitaria y de integración. La liquidación real de USDC necesita una billetera financiada de Base Sepolia, que es una credencial externa. |
server.json aún no publicable | El espacio de nombres está confirmado, pero remotes[].url es un marcador de posición y el repositorio referenciado no existe. |
| La lista de permitidos de puertos incluye 8080/8443 | Conveniente para herramientas, pero también son puertos comunes de interfaces internas. Redúzcala si no es necesaria. |
Documentación
| Documento | Contenido |
|---|---|
docs/architecture.md | Mapa de componentes, pipeline de solicitudes, división edge/backend, límite de autenticación, regla de capas, propiedad de paquetes, puntos de extensión. |
docs/api.md | GET /health, GET /, POST /v1/evidence; esquemas exactos de solicitud y respuesta; estados de evaluación; ejemplos resueltos; cada código de error; límites de recursos y sus variables de entorno. |
docs/mcp.md | Transporte HTTP Streamable, /mcp, ambas herramientas, esquema de entrada exacto, descubrimiento gratuito vs. de pago, configuración del cliente, llamada de ejemplo, respuesta de pago requerido. |
docs/x402.md | La secuencia 402 → pagar → reintentar → verificar → ejecutar, variables de configuración, IDs de red CAIP-2, liquidación USDC, separación de prueba/producción, el bypass de desarrollo y las reglas de clave privada. |
docs/security.md | El conjunto de controles SSRF requerido por SPEC §18, protección de origen, el esquema de secreto compartido, manejo de secretos, comportamiento de robots/acceso y qué se aplica dónde. |
docs/deployment.md | Desarrollo local, Docker en el VPS, el Cloudflare Worker, wrangler secret put BACKEND_AUTH_SECRET, comprobaciones de salud, política de reinicio, el volumen SQLite y opciones de protección de origen. |
docs/discovery.md | Registro, repositorio, npm, metadatos legibles por máquina, documentación y descripciones de herramientas orientadas a agentes. |
docs/registry-publication.md | Los pasos exactos de publicación en el Registro MCP, validación y la lista de verificación de propiedad del espacio de nombres. |
Reglas de contribución
AGENTS.md es el único libro de reglas del proyecto. Define las restricciones
estrictas, las reglas de seguridad, los estándares de codificación, el nivel de pruebas y la
definición de "terminado". No crees CLAUDE.md, .cursorrules, GEMINI.md ni ningún
archivo de instrucciones competidor.
Los requisitos del producto viven en SPEC.md. Cuando este README y la
especificación difieran, la especificación es la autoridad — y cualquier contradicción entre el
código fuente y la especificación debe informarse en lugar de resolverse silenciosamente.
Licencia
MIT. Ver package.json.