Iris
oficialServidor de evaluación y observabilidad de agentes nativo de MCP con registro de trazas, evaluación de calidad de salida, seguimiento de costos, 12 reglas de evaluación integradas, panel en tiempo real y detección de PII
¿Qué puedes hacer con Iris MCP?
- Registra y evalúa ejecuciones del agente — Pídele a tu asistente que registre una tarea en Iris y obtén puntuaciones deterministas de calidad, seguridad y costo sobre la salida.
- Consulta el historial de trazas — Recupera ejecuciones de agentes almacenadas con filtros, paginación y soporte de rangos de tiempo para revisar el rendimiento pasado.
- Compara ejecuciones a lo largo del tiempo — Analiza dos ejecuciones sobre las mismas preguntas lado a lado para detectar regresiones o mejoras en el comportamiento del agente.
- Puntúa la calidad de la salida — Evalúa cualquier texto contra 25 reglas integradas que cubren completitud, relevancia, seguridad y costo, con detección de PII e inyección de prompts.
- Ejecuta el panel de demostración — Lanza una base de datos de demostración precargada con fallos y veredictos de muestra para explorar el motor de puntuación de Iris localmente.
Documentación
Iris — deja de lanzar agentes a ciegas
Iris puntúa cada ejecución de agente en calidad, seguridad y costo, en tu máquina, sin SDK y sin cuenta. La mayoría de los proyectos de agentes verifican calidad ejecutando algunos prompts recordados y revisando la salida a ojo. Iris reemplaza eso con números que puedes auditar: las ejecuciones de tu agente se guardan en una base de datos SQLite en tu disco, 25 reglas integradas las puntúan de forma determinista — PII, inyección de prompts, marcadores de alucinación, umbrales de costo y las propias llamadas a herramientas del agente — gratis, sin llamadas a LLM, y un juez LLM opcional con un límite de costo por evaluación maneja las preguntas semánticas. Cada regla es inspeccionable y editable, porque un juez que no puedes auditar es solo una corazonada con un número encima. Licencia MIT, sin telemetría. Nada sale de tu máquina a menos que actives una de estas opciones: un endpoint de OpenTelemetry (IRIS_OTEL_ENDPOINT), que exporta trazas al colector que nombres; el juez LLM con tu propia clave, que envía el texto que juzga a ese proveedor, y cuya verificación de citas obtiene las páginas que una salida cita; o un webhook, que publica ids, el veredicto y los nombres de reglas, nunca el texto, a la dirección que configures.
Requiere Node.js 22.13 o posterior. Verifica con node --version.

La base de datos de demostración, grabada por scripts/demo-media.mts; la fuente es demo.mp4. Una imagen fija: dashboard-overview.png.
Un fallo en pantalla en 60 segundos
Sin cableado de agente, sin configuración — un comando:
npx @iris-eval/mcp-server --demo
Esto siembra una base de datos de demostración — cinco agentes pequeños, dos semanas de ejecuciones, cada veredicto del propio motor — y sirve el panel contra ella en http://localhost:6920 (tu navegador se abre automáticamente en la primera ejecución). El panel aterriza en Fallos: qué falló, peor y más reciente primero, cada tarjeta nombra la regla y su evidencia. Vale la pena hacer clic — una fuga de PII detectada por las reglas de seguridad, una directiva oculta en una publicación de foro que el resumidor cumplió, un número que el documento fuente nunca dijo, dos ejecuciones sobre las mismas doce preguntas comparadas con un intervalo (Ejecuciones), una regla personalizada desplegada y una pausada con sus filas de auditoría, y una puntuación fallida del juez LLM con su justificación.
Los datos de demostración viven en su propia base de datos (demo.db en tu directorio de inicio de Iris — ~/.iris en macOS/Linux, %USERPROFILE%\.iris en Windows) y nunca se mezclan con tus trazas reales. Elimina todo con un comando:
npx @iris-eval/mcp-server --demo-clear
Conecta tu propio agente
Primero, comprueba que la instalación funciona en esta máquina — se ejecuta sin conexión y no abre nada tuyo:
npx @iris-eval/mcp-server --self-test # exit 0 = healthy
Luego añade Iris a tu cliente MCP. Un comando escribe el archivo de configuración del propio cliente, conserva todos los demás servidores y fija la versión que ejecutaste:
npx -y @iris-eval/mcp-server install claude-code
Los clientes: claude-code, claude-desktop, cursor, windsurf, continue, vscode, cline, zed, codex, gemini. install --list muestra los encontrados en esta máquina, el Iris que ejecuta cada uno y el archivo que lee; install <client> --uninstall saca a Iris de nuevo. Cada cliente comparte una base de datos, así que tras una actualización muévelos todos a la vez con install --upgrade (Actualización). Reinicia el cliente para cargarlo.
Claude Desktop: un clic. Cada versión desde la 0.20.0 adjunta iris-eval.mcpb, un MCP Bundle: descarga la más reciente, ábrela y Claude Desktop muestra un diálogo de instalación. Nada en él es obligatorio — una clave de Anthropic u OpenAI para el juez LLM es opcional, y el panel es un interruptor que comienza apagado. El bundle contiene el paquete npm y sus dependencias, así que no necesita instalar nada más: Claude Desktop lo ejecuta bajo el Node que incluye cuando ese Node es 22.13 o más reciente (Claude Desktop 1.1.6679 incluye 24.13), e Iris almacena trazas con el SQLite integrado de Node, en el mismo ~/.iris que usa cualquier otra instalación. Las notas de versión muestran cómo verificar su firma y atestación de compilación.
Se ejecuta en cualquier cliente MCP, y cada cliente que nombra tiene una fila con lo que realmente se verificó. Verificado en cada ejecución de CI: Claude Code, Gemini CLI — el cliente real inicia Iris desde la configuración que escribió el instalador e informa que se conectó (claude mcp list, gemini mcp list), en Linux, macOS y Windows; los hooks del plugin de captura de Claude Code también se manejan a través de los scripts reales. Afirmado desde la propia documentación MCP de cada cliente — el instalador escribe la forma de configuración que el cliente documenta, y ese escritor se prueba en esa forma; nadie del lado de Iris lo ha visto conectarse: Claude Desktop, Cursor, Devin Desktop (Windsurf), Continue, VS Code, Cline, Zed, OpenAI Codex CLI. Cada fila con su fuente y la fecha en que se leyó: https://iris-eval.com/clients. A mano en su lugar, un bloque, panel incluido:
{
"mcpServers": {
"iris-eval": {
"command": "npx",
"args": ["-y", "@iris-eval/mcp-server", "--dashboard"]
}
}
}
Tu cliente lista las doce herramientas de Iris al conectarse, y el panel sirve en http://localhost:6920. Ahora pega esto a tu agente:
Registra esa última tarea en Iris y evalúa la salida.
La traza aterriza en el panel con sus puntuaciones. ¿Prefieres el servidor MCP sin interfaz? Elimina --dashboard de los argumentos — puedes abrir el mismo panel en cualquier momento con npx @iris-eval/mcp-server --dashboard.
Una cosa que vale la pena saber de antemano: las herramientas MCP se llaman cuando el modelo decide llamarlas. Iris no intercepta tu agente, así que las trazas se registran cuando tu agente pide registrarlas — ya sea porque se lo dijiste, o porque tu código llama a las herramientas directamente. Pide a tu agente que "registre esto en Iris y lo evalúe" y lo hará. Si quieres una captura que no dependa de la elección del modelo, POST /api/v1/traces hace exactamente eso — tu código envía la traza por HTTP simple, sin modelo en el bucle (ver docs/http-ingest.md). La CLI y los hooks de host en el roadmap serán clientes ligeros sobre el mismo endpoint.
Captura por HTTP (sin modelo en el bucle)
El endpoint de ingesta vive en el puerto del panel — 6920 por defecto, no el puerto de transporte MCP — y existe solo mientras el panel está en ejecución. Pasa --dashboard (o establece IRIS_DASHBOARD=true); --transport http por sí solo no lo inicia, y una solicitud al puerto de transporte devuelve 404. Con el panel activo, cualquier cosa que pueda enviar una solicitud HTTP puede registrar una traza — y opcionalmente ejecutar las evaluaciones deterministas en la misma solicitud. GET /api/v1/capabilities en el mismo puerto dice qué puede juzgar este servidor, qué necesita cada regla, el estado del juez con los pasos que lo habilitan y los límites — el mismo objeto que sirve el recurso MCP iris://capabilities — para que un llamador HTTP tenga el marco que un cliente MCP obtiene al inicializar:
curl -s -X POST "http://127.0.0.1:6920/api/v1/traces" \
-H "Content-Type: application/json" \
-d '{
"agent_name": "support-bot",
"input": "What is the refund policy?",
"output": "Refunds are available within 30 days of purchase.",
"evaluate": true,
"eval_type": "safety"
}'
Devuelve 201 con el trace_id almacenado y el resultado de la evaluación (en modo --demo el endpoint rechaza escrituras con 403, para que los datos de demostración nunca se mezclen con los tuyos). El endpoint acepta el mismo cuerpo que la herramienta log_trace y está detrás de la misma pila de middleware que el resto del panel: enlace de loopback y la protección contra rebinding de DNS por defecto, más autenticación Bearer cuando estableces una. Dos hechos simples al respecto: acepta escrituras no autenticadas a menos que Iris se haya iniciado con --api-key (o IRIS_API_KEY) — el enlace de loopback es lo que lo mantiene en tu máquina por defecto, así que establece una clave antes de vincular más allá del loopback; y lo que almacena es textual — input y output llegan a iris.db exactamente como se enviaron, incluido cualquier texto que no_pii luego marque. Contrato completo, referencia de campos y semántica de errores: docs/http-ingest.md.
Captura cada turno de Claude Code (opcional)
/plugin marketplace add iris-eval/mcp-server
/plugin install iris-eval-capture@iris-eval
Un segundo plugin, instalado por separado: tres hooks registran el prompt de cada turno, las llamadas a herramientas y la respuesta final y los entregan a iris-eval ingest, de forma separada, con los tramos críticos redactados en el texto de evaluación almacenado — captura que no depende de que el modelo decida llamar a una herramienta. Nunca registra un turno que el modelo ya registró, nunca imprime, nunca bloquea, nunca envía nada a ningún lado. Instalar iris-eval solo no cambia nada en tu bucle de turnos. Límites y eliminación: claude-plugin-capture/README.md.
Python
pip install iris-eval
from iris_eval import IrisClient
iris = IrisClient() # IRIS_URL, or the running dashboard's runtime.json
iris.evaluate_output("…", input="…", agent_name="support-bot")["verdict"] # {"state": "pass", "basis": "clean", "by": []}
Un cliente ligero sobre la API HTTP del servidor 0.16.0 y posterior, versionado por separado — iris_eval.__version__ y la página de PyPI llevan su número, que no es el del servidor: log_trace(), evaluate_output(), get_traces(), get_trace(), health(), capabilities(), síncrono y asíncrono, respuestas tipadas, la propia frase del servidor ante un rechazo — y un plugin de pytest: un fixture iris y assert_iris(output, expect="pass") que afirma sobre el estado del veredicto. packages/python/README.md.
Registra cada llamada de OpenAI y Anthropic
from iris_eval import wrap_openai
client = wrap_openai(OpenAI(), agent_name="support-bot") # every call: a GenAI span to POST /v1/traces, scored
import { wrapOpenAI } from '@iris-eval/sdk';
const openai = wrapOpenAI(new OpenAI(), { agentName: 'support-bot' });
Envuelve el cliente del proveedor una vez y cada llamada de modelo se convierte en un span GenAI de OpenTelemetry enviado a la puerta OTLP, almacenado con su entrada, salida, uso de tokens y llamadas a herramientas, y puntuado: captura que no depende de que el modelo llame a una herramienta. wrap_openai / wrap_anthropic en el cliente Python; wrapOpenAI, wrapAnthropic y irisMiddleware para el Vercel AI SDK en @iris-eval/sdk. Ambos aún no están publicados (la próxima versión iris-eval en PyPI; @iris-eval/sdk se compila desde la fuente hasta su primer lanzamiento npm). Los streams, los helpers de stream de los SDK y las llamadas a herramientas están cubiertos, el cliente original no se modifica, y que Iris esté caído nunca rompe una llamada — packages/sdk/README.md, packages/python/README.md.
Puntúa cada ejecución de LangChain y LangGraph
from iris_eval.langchain import IrisCallbackHandler
graph.invoke(inputs, config={"callbacks": [IrisCallbackHandler(agent_name="support-bot")]})
Cada ejecución de nivel superior se convierte en una traza (la ejecución, sus llamadas de modelo, sus llamadas a herramientas y sus nodos de grafo como spans GenAI) con su entrada, salida, llamadas a herramientas, uso de tokens y un veredicto. Python en el cliente (próxima versión, aún no publicada en PyPI), JavaScript como @iris-eval/langchain (aún no publicado en npm). Ambos están probados en CI contra una aplicación LangGraph real con un modelo con guion; la exportación OpenTelemetry de LangSmith está probada de la misma manera — docs/otel-recipes.md.
Una puerta de CI, sin necesidad de servidor
npx -y @iris-eval/mcp-server ingest --file traces.ndjson --evaluate --fail-on detector_veto
O la GitHub Action (0.16.0), que falla el trabajo en los veredictos que nombres, escribe el recibo en el resumen del trabajo y lo publica como un comentario de pull request actualizado en su lugar: uses: iris-eval/mcp-server/.github/actions/gate@v0.19.0 con traces: traces.ndjson — docs/ci-gate.md.
Una cuarta puerta (0.15.0): POST /v1/traces en el puerto del panel toma el OTLP/HTTP JSON o protobuf que tu instrumentación de OpenTelemetry ya emite (el exportador del SDK de Python solo habla protobuf, así que esta también es la puerta de Python), y cada traza OTLP se convierte en una traza de Iris con sus spans — docs/otel-integration.md; una receta por framework (Pydantic AI, Google ADK, LangGraph vía LangSmith, CrewAI, el SDK de OpenAI Agents en Python y JavaScript, LlamaIndex, AutoGen, Microsoft Agent Framework, Semantic Kernel, el SDK de Vercel AI y Mastra), cada una probada por un fixture, en docs/otel-recipes.md. ingest lee una traza JSON (o NDJSON, una por línea) desde stdin o un archivo, la almacena, la evalúa bajo exactamente las reglas que ejecuta evaluate_output, imprime una línea JSON por traza con el veredicto y su base, y sale con 1 cuando un veredicto coincide con --fail-on. --dataset <id|label> restringe esa puerta a las claves de caso en un dataset (POST /api/v1/datasets promueve las claves de caso de una ejecución a uno), de modo que un trabajo falla solo en los casos que elegiste. La receta completa, los códigos de salida y las ocho bases están en docs/ci-gate.md.
Escribe una regla como código
eval.plugins en config.json carga reglas que escribiste — un módulo ES cuya exportación por defecto es { name, kind, mechanism, version, needs, evaluate(ctx) } — fijado por el sha256 del archivo, de modo que un archivo que cambió desde que lo fijaste rechaza el arranque en lugar de ejecutarse. Un plugin cargado se dispara como uno integrado y se muestra en list_rules bajo plugins. El contrato, la receta de hash y lo que un plugin puede devolver: docs/plugins.md.
Usa el motor en tu propio proceso
El motor de evaluación es importable — sin servidor, sin base de datos, sin modelo:
import { EvalEngine, defaultConfig } from '@iris-eval/mcp-server/engine';
const engine = new EvalEngine(defaultConfig.eval.defaultThreshold, defaultConfig.eval.ruleThresholds, defaultConfig.eval);
const result = await engine.evaluateAll({ output: answer, input: prompt, toolCalls, costUsd });
result.verdict.state; // 'pass' | 'fail' | 'unknown', with result.verdict.basis and result.interpretations
El mismo motor, las mismas reglas y el mismo compositor que ejecuta el servidor; builtInRules(), createCustomRule(), compose() y los lectores de precisión publicada se exportan junto a él.
Un cliente tipado para la ruta HTTP
import { createClient } from '@iris-eval/mcp-server/client';
const iris = createClient({ baseUrl: 'http://127.0.0.1:6920', apiKey: process.env.IRIS_API_KEY });
const { trace_id, evaluation } = await iris.logTrace({ agent_name: 'support-bot', input, output, tool_calls, evaluate: true });
evaluation?.verdict?.state; // the same object evaluate_output returns
Un solo cuerpo en cada puerta: es lo que aceptan log_trace y iris-eval ingest. Una negativa lanza IrisClientError con la propia frase y estado del servidor. Ambos subcaminos se verifican desde un tarball empaquetado en cada compilación.
Verifica tu instalación
npx @iris-eval/mcp-server --self-test # offline diagnostic; exit 0 = healthy, 1 = a check failed
npx @iris-eval/mcp-server --version # prints the bare version, e.g. 1.2.3
--self-test primero crea tu hogar de Iris si falta y verifica que sea escribible (salida 1, nombrando la ruta, si no lo es), informa dónde está el índice de búsqueda de tu base de datos (completo, cuántas trazas ha indexado una compilación en segundo plano hasta ahora, o sin FTS5 en este SQLite), lee el esquema de tu base de datos (salida 1, con la solución, cuando esta versión o un cliente MCP fijado a una versión anterior no puede abrirla), luego ejecuta sus verificaciones — ida y vuelta de almacenamiento, un SSN plantado y una inyección plantada detectados por las reglas de seguridad, arranque del panel, la protección contra rebinding de DNS — dentro de un hogar temporal aislado. Tu base de datos real solo se lee, nunca se cambia. Todo lo que Iris escribe vive bajo un directorio, tu hogar de Iris: ~/.iris por defecto (%USERPROFILE%\.iris en Windows), o donde apunte IRIS_HOME. Ahí es donde viven iris.db, config.json, custom-rules.json, audit.log, preferences.json y los archivos de demostración; apunta IRIS_HOME a un directorio de prueba para probar Iris sin tocar tus datos reales.
Configuración por herramienta
| Cliente | Estado | Qué significa | Lectura |
|---|---|---|---|
| Claude Code | verificado | una prueba impulsa el cliente real en cada ejecución de CI | 2026-09-25 |
| Claude Desktop | afirmado | el instalador escribe la forma que el cliente documenta, y ese escritor se prueba en la forma; nadie del lado de Iris lo ha visto conectarse | 2026-09-25 |
| Cursor | afirmado | el instalador escribe la forma que el cliente documenta, y ese escritor se prueba en la forma; nadie del lado de Iris lo ha visto conectarse | 2026-09-25 |
| Devin Desktop (Windsurf) | afirmado | el instalador escribe la forma que el cliente documenta, y ese escritor se prueba en la forma; nadie del lado de Iris lo ha visto conectarse | 2026-09-25 |
| Continue | afirmado | el instalador escribe la forma que el cliente documenta, y ese escritor se prueba en la forma; nadie del lado de Iris lo ha visto conectarse | 2026-09-25 |
| VS Code | afirmado | el instalador escribe la forma que el cliente documenta, y ese escritor se prueba en la forma; nadie del lado de Iris lo ha visto conectarse | 2026-09-25 |
| Cline | afirmado | el instalador escribe la forma que el cliente documenta, y ese escritor se prueba en la forma; nadie del lado de Iris lo ha visto conectarse | 2026-09-25 |
| Zed | afirmado | el instalador escribe la forma que el cliente documenta, y ese escritor se prueba en la forma; nadie del lado de Iris lo ha visto conectarse | 2026-09-25 |
| OpenAI Codex CLI | afirmado | el instalador escribe la forma que el cliente documenta, y ese escritor se prueba en la forma; nadie del lado de Iris lo ha visto conectarse | 2026-09-25 |
| Gemini CLI | verificado | una prueba impulsa el cliente real en cada ejecución de CI | 2026-09-25 |
Cada fila con lo que se verificó: iris-eval.com/clients. Ningún cliente se llama soportado sin una fila.
npx -y @iris-eval/mcp-server install <client> escribe cada uno de estos por ti. A mano, por cliente:
Claude Desktop
Edita tu archivo de configuración de MCP:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
Agrega la configuración JSON anterior, luego reinicia Claude Desktop.
Claude Code
claude mcp add --transport stdio iris-eval -- npx -y @iris-eval/mcp-server
Luego reinicia la sesión (/clear o relanza) para que se carguen las herramientas.
Nota de Windows: No uses el envoltorio
cmd /c— causa problemas de análisis de rutas. El comandonpxfunciona directamente.
Cursor
Agrega la configuración JSON anterior a ~/.cursor/mcp.json (cada proyecto) o .cursor/mcp.json en un espacio de trabajo, con "type": "stdio" en la entrada iris-eval — los documentos de Cursor lo marcan como requerido.
Devin Desktop (Windsurf)
Agrega la configuración JSON anterior a mcp_config.json: ~/.config/devin/mcp_config.json en macOS y Linux, %APPDATA%\devin\mcp_config.json en Windows.
Continue
Guarda la configuración JSON anterior como su propio archivo en la carpeta mcpServers de Continue: ~/.continue/mcpServers/iris-eval.json (cada espacio de trabajo) o .continue/mcpServers/iris-eval.json en uno.
VS Code (MCP nativo)
Agrega a .vscode/mcp.json en tu espacio de trabajo (nota: VS Code usa servers, no mcpServers):
{
"servers": {
"iris-eval": {
"command": "npx",
"args": ["-y", "@iris-eval/mcp-server"]
}
}
}
Cline
Abre el panel de Servidores MCP de Cline → Configurar Servidores MCP, y agrega la configuración JSON mcpServers anterior a cline_mcp_settings.json (~/.cline/data/settings/cline_mcp_settings.json, compartida por Cline en VS Code, JetBrains y la CLI).
Zed
Agrega a Zed settings.json:
{
"context_servers": {
"iris-eval": {
"command": "npx",
"args": ["-y", "@iris-eval/mcp-server"],
"env": {}
}
}
}
OpenAI Codex CLI
Agrega a ~/.codex/config.toml:
[mcp_servers.iris-eval]
command = "npx"
args = ["-y", "@iris-eval/mcp-server"]
Gemini CLI
Agrega la configuración JSON mcpServers anterior a ~/.gemini/settings.json. Gemini CLI se conecta a servidores MCP solo en carpetas que confía: si gemini mcp list muestra iris-eval como Deshabilitado, ejecuta /permissions en esa carpeta.
Cualquier otra cosa que hable MCP
Iris es un servidor MCP estándar de stdio — un comando npx @iris-eval/mcp-server, sin SDK, sin cambios de código. Si tu cliente soporta MCP, soporta Iris. Los formatos de configuración de clientes cambian; en caso de duda, consulta los documentos de MCP de tu cliente y apúntalo a ese comando.
Otros Métodos de Instalación
# Global install (recommended for persistent data and faster startup)
npm install -g @iris-eval/mcp-server
iris-eval --dashboard
# Docker — two servers, two ports: 3000 = MCP HTTP transport,
# 6920 = dashboard (which also serves the POST /api/v1/traces ingest endpoint).
# The image binds 0.0.0.0 inside the container, so a key is required (see Production).
# The volume is the Iris home: the database, deployed rules and audit log persist in it.
docker run -p 3000:3000 -p 6920:6920 -v iris-data:/data \
-e IRIS_API_KEY="$(openssl rand -hex 32)" ghcr.io/iris-eval/mcp-server
Consejo: La instalación global (
npm install -g) almacena trazas de forma persistente en~/.iris/iris.db. Connpx, las trazas persisten en la misma ubicación, pero el arranque es más lento debido a la resolución de paquetes.
Lo Que Obtienes
| Registro de Trazas | Árboles de spans jerárquicos con latencia por llamada de herramienta, uso de tokens y costo en USD. Almacenados en SQLite, consultables al instante. |
| Evaluación de Salida | 25 reglas integradas en 4 categorías: completitud, relevancia, seguridad, costo. Detección de PII (21 patrones: SSN, tarjeta de crédito, teléfono, correo electrónico, IBAN, fecha de nacimiento, MRN, IP, clave API, pasaporte, más tokens de AWS/Slack/SendGrid/GitHub/Google/npm/DigitalOcean, credenciales dentro de URLs, asignaciones con nombres secretos, bloques de clave privada PEM y frases semilla; fecha de nacimiento, número de registro médico, pasaporte y frase semilla se activan solo junto a su etiqueta, por diseño), detección de inyección de prompts (38 patrones, frase + estructural), detección de salida simulada, detección de alucinaciones (25 señales de fabricación/contradicción basadas en contexto — pasa input para fundamentarlas contra el material fuente del agente), y seis reglas de trayectoria que leen lo que el agente HIZO: una llamada de herramienta fallida no reconocida, una repetida (por llamada, por secuencia repetida, o por objetivo una vez que envías tools), una llamada cuyos argumentos el propio JSON Schema de la herramienta rechaza y el agente nunca reintentó, un archivo, directorio o URL que la respuesta cita y que no aparece en nada que el agente leyó, una instrucción que llegó dentro de un RESULTADO DE HERRAMIENTA y luego fue obedecida por una llamada posterior, y una tarea que tomó más llamadas de herramienta que tu presupuesto de pasos. Una trayectoria puede llegar como tool_calls o como spans de herramienta de OpenTelemetry. Agrega reglas personalizadas con esquemas Zod. |
| LLM-como-Juez | Puntuación semántica opcional vía Anthropic u OpenAI — trae tu propia clave API. Siete plantillas. Con IRIS_RELEVANCE_JUDGE_MODEL configurado, answers_the_ask pregunta al juez relevance y falla una respuesta fuera de tema; sin él, la regla lee la pregunta léxicamente y aconseja. Tope de costo duro por evaluación (IRIS_LLM_JUDGE_MAX_COST_USD_PER_EVAL, predeterminado $0.25), precio por evaluación divulgado en el resultado. |
| Visibilidad de Costos | Costo agregado en todos los agentes en cualquier ventana de tiempo. Establece umbrales de presupuesto. Recibe alertas cuando los agentes gastan de más. Una traza que envía conteos de tokens y un modelo pero sin costo (la mayoría de las trazas de OpenTelemetry y frameworks) se cotiza al precio de lista del modelo y se marca como estimado dondequiera que se muestre; pricing.models en config.json cotiza modelos que la tabla integrada no tiene — docs/cost.md. |
| Panel Web | Interfaz en tiempo real de modo oscuro que aterriza en los fallos, peores y más nuevos primero — visualización de trazas con búsqueda de texto completo sobre el texto de cada traza, resultados de evaluación, desgloses de costos y una paleta de comandos (⌘K) que busca tus propias reglas, trazas y evaluaciones. |
| Local-primero | Todo vive en SQLite en tu disco. Sin cuenta, sin registro, sin telemetría. El HTTP saliente ocurre solo donde optas: tu propia clave de juez LLM, obtención de citas, un exportador OTel que configures o un webhook que establezcas. |
Hacia dónde va esto a continuación: el mapa de capacidades — cada pregunta que se le puede hacer a Iris sobre cada tema, con lo que tiene y lo que le falta — y las tres pistas.
Medido, no afirmado
Toda regla integrada tiene una precisión, recuperación y F1 publicadas con intervalos de confianza del 95%, medidas sobre un corpus etiquetado que vive en este repositorio (proof/corpus/) y se regenera con un solo comando — npm run proof — sin conexión, sin clave y sin modelo en el bucle. Esos números son de dos tipos distintos, y la página nunca los suma: algunas reglas se miden contra etiquetas que un modelo dio al leer la falla en sí, lo que mide la detección; el resto se comprueban contra su propia definición documentada, aplicada de forma independiente, lo que muestra que el código implementa su fórmula y no dice nada sobre si la fórmula detecta la falla. proof/RESULTS.md y la página de pruebas marcan cada regla. CI vuelve a ejecutar la medición en cada pull request y falla si los números confirmados difieren de lo que produce el código, por lo que una regla no puede cambiar sin que sus números cambien con ella. Los números están en iris-eval.com/proof y en proof/RESULTS.md; cómo se creó el corpus, qué no es y cómo leer un intervalo están en docs/proof.md. El corpus es sintético y etiquetado por modelo — una etiqueta humana ciega está pendiente, y la página lo dice; node proof/blind-sample.mjs extrae la muestra reproducible que lo resolverá.
Herramientas MCP
Iris registra doce herramientas que cualquier agente compatible con MCP puede invocar — ciclo de vida de trazas y reglas, comparación entre ejecuciones, LLM como juez y verificación de citas semánticas:
log_trace— Registra una ejecución de agente con tramos, llamadas a herramientas, uso de tokens y costo; pasaevaluate: truepara puntuarla en la misma llamadaevaluate_output— Puntúa la calidad de la salida según reglas de completitud, relevancia, seguridad y costo (heurísticas, deterministas, gratuitas)get_traces— Consulta trazas almacenadas con filtrado, paginación y soporte de rango temporal, y encuentra la ejecución donde el agente dijo algo conq: búsqueda de texto completo sobre entrada, salida, valores de llamadas a herramientas y metadatos, clasificada, con las palabras coincidentes marcadaslist_rules— Enumera las reglas de evaluación personalizadas desplegadas (solo lectura)deploy_rule— Registra una nueva regla de evaluación personalizada para que se active en cadaevaluate_outputde esa categoríadelete_rule— Elimina una regla personalizada desplegada (destructiva, idempotente)delete_trace— Elimina una sola traza almacenada por ID (destructiva, limitada al inquilino)evaluate_with_llm_judge— Evaluación semántica mediante LLM (Anthropic u OpenAI). Siete plantillas: precisión, utilidad, seguridad, corrección, fidelidad, tarea_completada, relevancia. Con límite de costo, precio por evaluación divulgado. Trae tu propia clave API (IRIS_ANTHROPIC_API_KEYoIRIS_OPENAI_API_KEY) — Iris no hace proxy ni retransmite llamadas LLM.verify_citations— Extrae citas de la salida (numeradas, autor-año, URLs, DOIs), obtiene las fuentes detrás de un resolvedor protegido contra SSRF y con lista de dominios permitidos, y usa un juez LLM para comprobar si cada fuente respalda realmente la afirmación citada. HTTP saliente opcional. Mismo requisito de traer tu propia clave queevaluate_with_llm_judge.compare_runs— ¿Un cambio empeoró al agente? Compara dos ejecuciones de evaluaciones almacenadas: una prueba exacta pareada cuando las ejecuciones comparten claves de caso, un intervalo sobre la diferencia en caso contrario, un honesto "no se puede determinar" con el número de casos que haría falta, o "equivalente dentro de un margen". Cada regla lleva su propia prueba unilateral, corregida en conjunto (Benjamini–Hochberg) para que veinte reglas no puedan fabricar una regresióncompare_traces— ¿Con qué fiabilidad responde el agente la misma pregunta? Tasas de aprobación por caso con intervalos, casos inestables primero, y una tasa general que respeta las repeticionesevaluate_runs— Vuelve a puntuar cada traza de una ejecución bajo las reglas actuales en una nueva ejecución, para que un cambio de reglas nunca se lea como un cambio del agente
Habilita el juez LLM (opcional; las reglas deterministas nunca lo necesitan)
- Obtén una clave API de Anthropic u OpenAI.
- Ponla en el entorno del proceso que ejecuta Iris, no solo en tu shell. Claude Code, Claude Desktop, Cursor y la mayoría de clientes MCP: el bloque "env" de la entrada iris-eval en tu configuración MCP — "iris-eval": { "command": "npx", "args": ["-y", "@iris-eval/mcp-server"], "env": { "IRIS_ANTHROPIC_API_KEY": "sk-ant-..." } } (IRIS_OPENAI_API_KEY para una clave de OpenAI). Docker: -e IRIS_ANTHROPIC_API_KEY=... en el comando run. HTTP o CI: expórtala antes de iniciar iris-eval.
- Reinicia la sesión MCP. Un proceso en ejecución nunca ve una variable establecida después de que comenzó.
- Confirma desde tu cliente: lee iris://capabilities — judge.enabled debe ser true allí. Una clave exportada en tu shell no se pasa al proceso que tu cliente inicia a menos que su configuración la liste. En una máquina,
npx @iris-eval/mcp-server --self-testimprime la línea del juez para ese shell, y GET /api/v1/health informa judge.enabled en un panel en ejecución. - Protección de gasto: cada llamada está limitada por IRIS_LLM_JUDGE_MAX_COST_USD_PER_EVAL (predeterminado 0.25 USD) y se rechaza antes de cualquier gasto si el peor caso lo excediera. Iris llama al proveedor directamente con tu clave y nunca la hace proxy.
- Opcional: establece IRIS_RELEVANCE_JUDGE_MODEL a un ID de modelo con precio (claude-haiku-4-5, por ejemplo) para que answers_the_ask pida al juez si cada respuesta aborda su pregunta, y falle una fuera de tema. Eso es una llamada al juez por cada evaluación que lleva una entrada, con tu clave y bajo el límite anterior; la clave sola nunca lo activa. Cada llamada envía esa entrada y salida al proveedor del modelo, con los indicadores de datos personales y credenciales no_pii reemplazados primero (IRIS_RELEVANCE_JUDGE_REDACT=off los envía tal cual). Gasta como máximo IRIS_RELEVANCE_JUDGE_DAILY_BUDGET_USD por día UTC (predeterminado 1 USD) y hace como máximo IRIS_RELEVANCE_JUDGE_MAX_CALLS_PER_REQUEST llamadas por solicitud (predeterminado 20); superado cualquiera de los dos, answers_the_ask lee la pregunta léxicamente y dice por qué.
Cuando IRIS_OTEL_ENDPOINT está configurado, las llamadas a log_trace también emiten una exportación JSON OTLP/HTTP de mejor esfuerzo a cualquier colector OpenTelemetry (Jaeger, Grafana Tempo, Datadog OTLP, Honeycomb, etc.). Consulta docs/otel-integration.md.
Cómo se decide passed
evaluate_output devuelve tanto un score como un indicador passed — responden preguntas diferentes:
score(0..1) es el promedio ponderado entre las reglas que se ejecutaron — un gradiente de calidad.passedes el veredicto de lanzar/no lanzar, y la puntuación nunca se consulta para él. Un compositor lee cada regla según el tipo de afirmación que hace: una política que configuraste actúa como compuerta; un detector crítico veta; una comprobación crítica que se pidió y no pudo responder hace que el veredicto sea desconocido (passed: false) en lugar de limpio; cada detector restante se combina en una probabilidad de que la salida sea mala, ponderada contra la relación de pérdida que indicas eneval.falsePassCost(predeterminado 1, por lo que el corte es 0.5).verdict.basisnombra la capa que decidió yverdict.bylas reglas, yverdict.alsoenumera cada capa posterior que también lo habría decidido;interpretations[]dice por qué una regla que falló no decidió y qué ajuste lo cambiaría, y nombra cualquier pregunta que no se juzgó y la entrada que permitiría hacerlo.
Las violaciones de seguridad genuinas fallan de forma dura. Por defecto, no_pii, no_injection_patterns y no_blocklist_words son reglas críticas: si una falla, la evaluación informa passed: false sin importar cuán bien puntuaron las demás reglas, y la respuesta nombra a los culpables en critical_failures. Un SSN filtrado no se puede promediar. Qué reglas integradas son críticas es un ajuste de despliegue (eval.criticalRules / eval.nonCriticalRules); cada resultado de regla lleva el indicador critical efectivo y criticalSource, y list_rules informa el roster que aplica este servidor. Las reglas personalizadas desplegadas con severity: "high" o "critical" fallan de forma dura igual; las severidades low/medium solo afectan la puntuación. Un límite a conocer, expresado igual en todas las superficies: una regla crítica que se omitió (contexto faltante, una definición rota o un regex eliminado por el presupuesto del sandbox) no ha juzgado la salida y no veta — cada una de esas reglas se nombra en critical_skipped. Una compuerta que debe fallar cerrada trata un critical_skipped no vacío como desconocido, no limpio, y puede tratar cualquier omisión budgetExceeded en rule_results de la misma manera.
Para compuertas de CI: si omites eval_type, se ejecuta cada paquete — completitud, relevancia, seguridad, costo y cualquier regla personalizada — y la respuesta dice eval_type: "all" con un note de que se ejecutó el predeterminado, más un mapa categories por paquete. Un paquete sin nada que juzgar (costo sin cost_usd, relevancia sin input) informa passed: null allí — no evaluado, no fallando — y nunca cuenta para el veredicto. La respuesta siempre repite el eval_type que se ejecutó, para que tu compuerta pueda verificar la cobertura; clave en passed para el veredicto y nombra un paquete solo cuando quieras una ejecución más limitada.
Creación de una regla personalizada
Dos formas de añadir una regla. Las reglas en línea viajan en una sola llamada a evaluate_output (custom_rules, hasta 10 por llamada); se activan junto con el paquete eval_type que elijas, o solas con eval_type: "custom". Las reglas desplegadas se registran una vez con deploy_rule, persisten en custom-rules.json bajo tu hogar de Iris y se activan en cada futuro evaluate_output de su evalType. La definición tiene la misma forma en ambos casos:
| Campo | Obligatorio | Qué es |
|---|---|---|
name | sí | 1–80 caracteres; aparece como ruleName en los resultados |
type | sí | uno de regex_match · regex_no_match · min_length · max_length · contains_keywords · excludes_keywords · json_schema · cost_threshold |
config | sí | las claves para ese tipo: pattern (+ flags opcional) para los dos tipos de regex · min_length / max_length (un recuento de caracteres) · keywords (+ threshold opcional, 0–1, predeterminado 1 = todos deben aparecer) para los dos tipos de palabras clave · {} para json_schema · max_cost en USD para cost_threshold |
weight | no | peso en la puntuación; predeterminado 1 |
deploy_rule envuelve la definición con name, un description opcional, evalType (completeness · relevance · safety · cost · custom) y severity. La severidad dice qué significa un fallo: low/medium solo bajan la puntuación; high/critical fallan la evaluación de forma dura — passed: false, la regla nombrada en critical_failures — diga lo que diga la puntuación ponderada. Una regla que se omite (una regla cost_threshold sin cost_usd, o un regex eliminado por el presupuesto del sandbox de 100 ms) no ha juzgado la salida y se lista en critical_skipped en su lugar. Despliega una regla crítica que prohíba nombres de host internos en cualquier cosa que diga el agente:
{
"name": "no_internal_hostnames",
"description": "Output must not mention internal hostnames.",
"evalType": "safety",
"severity": "critical",
"definition": {
"name": "no_internal_hostnames",
"type": "regex_no_match",
"config": { "pattern": "\\b[a-z0-9-]+\\.internal\\.example\\b", "flags": "i" }
}
}
La respuesta es la regla persistida — guarda el id para delete_rule:
{ "rule": { "id": "rule-588823d0", "name": "no_internal_hostnames", "evalType": "safety", "severity": "critical", "enabled": true, "version": 1, "definition": { "…": "…" } } }
Desde el siguiente evaluate_output con eval_type: "safety", una salida que mencione db-primary.internal.example vuelve passed: false con critical_failures: ["no_internal_hostnames"] — incluso aunque las cinco reglas de seguridad integradas hayan pasado y la puntuación ponderada sea 0.895. Los patrones de regex deben pasar una comprobación de ReDoS en el despliegue y siempre se ejecutan en un trabajador sandbox bajo un plazo duro de 100 ms. list_rules muestra lo que está desplegado; el compositor de reglas del panel construye la misma forma desde un fallo en el que hiciste clic. Referencia completa, puntuación por tipo y ejemplos trabajados: docs/custom-rules.md.
Esquemas completos de herramientas y configuración: iris-eval.com
Funciones alojadas
Iris se ejecuta completamente en tu máquina hoy, y todo lo que hace es gratuito y con licencia MIT, sin límites y sin cuenta. Almacenamiento alojado, historial de equipo compartido y alertas están en consideración, no en construcción. No hay precios ni nada que comprar. Si el historial compartido te resultara útil, la lista de espera es cómo descubrimos si vale la pena construirlo; no te compromete a nada.
Dos compromisos se mantienen en cualquier caso: nada que hoy sea gratuito pasará detrás de un muro de pago, y no se reclamará ninguna certificación de cumplimiento antes de obtenerla.
Ejemplos
- Configuración de Claude Desktop — Configuración de MCP para modos stdio y HTTP
- TypeScript — Cliente del SDK de MCP — Conectar e invocar herramientas
- Transporte HTTP (TS + Python) — Código de cliente completo para integración estilo REST
- Un agente LangGraph, evaluado ejecución por ejecución (Python) —
IrisCallbackHandleren los callbacks del grafo; CI ejecuta el mismo grafo con un modelo con guion - Un equipo CrewAI sobre OpenTelemetry (Python) — El instrumentador OpenInference directo a la puerta OTLP de Iris, la receta CrewAI como script
- Un agente del SDK de OpenAI Agents sobre OpenTelemetry (Python) y (JavaScript), y un agente LlamaIndex — cada receta como el script que CI ejecuta contra un servidor Iris real
Comunidad
- Problemas de GitHub — Informes de errores y solicitudes de funciones
- Discusiones de GitHub — Preguntas e ideas
- Guía de contribución — Cómo contribuir
- Ingesta HTTP — Captura determinista de trazas mediante
POST /api/v1/traces - Mapa de capacidades — Cada pregunta que se le puede hacer a Iris y lo que le falta
- Política de versionado — Qué promete cada número de versión y qué debe ser cierto antes de 1.0
Configuración y Seguridad
Argumentos de CLI
| Indicador | Predeterminado | Descripción |
|---|---|---|
--transport | stdio | Tipo de transporte: stdio o http |
--port | 3000 | Puerto del transporte HTTP |
--db-path | ~/.iris/iris.db | Ruta de la base de datos SQLite |
--config | ~/.iris/config.json | Ruta del archivo de configuración |
--api-key | — | Clave API para autenticación HTTP (transporte y panel, incluido POST /api/v1/traces) |
--dashboard | false | Habilitar el panel web. También es la única forma de que el endpoint de ingesta POST /api/v1/traces se inicie; nunca se inicia implícitamente con --transport http |
--dashboard-port | 6920 | Puerto del panel |
--dashboard-host | 127.0.0.1 | Dirección de enlace del panel. Loopback por defecto: el panel no está autenticado a menos que se establezca --api-key, por lo que vincular más allá del loopback expone todo tu historial de trazas |
--demo | false | Sembrar una base de datos de demostración (separada de tus trazas reales) y servir el panel contra ella |
--demo-clear | false | Eliminar la base de datos de demostración y salir |
--self-test | false | Ejecutar el diagnóstico de instalación sin conexión en un home temporal aislado y luego salir (0 = saludable, 1 = falló una verificación). También lee la base de datos configurada, en modo solo lectura, y falla si esta versión o un cliente MCP fijado no puede abrirla |
--purge | false | Eliminar todas las trazas, tramos y evaluaciones almacenados de la base de datos configurada, compactar el archivo y truncar el registro de escritura anticipada para que el texto eliminado no permanezca en el disco, y luego salir. Las reglas implementadas, el registro de auditoría y las preferencias se conservan. No es reversible. Detén cualquier servidor Iris en ejecución primero: el archivo se compacta en su lugar. Se niega a combinarse con --demo, --demo-clear o --self-test |
--version | — | Imprimir la versión simple (p. ej., 1.2.3) en stdout y salir con 0. No lee nada bajo tu home de Iris |
Tres comandos toman sus propios argumentos y salen: iris-eval ingest carga trazas desde un archivo o stdin (Una puerta de CI, sin necesidad de servidor), iris-eval export traces|evaluations --format csv|jsonl escribe lo que está almacenado, filtrado como las listas del panel, en stdout o --out (docs/api-reference.md), y iris-eval install <client> escribe Iris en la configuración de un cliente MCP — --uninstall lo quita, --list muestra los clientes encontrados en esta máquina y el Iris que ejecuta cada uno, --upgrade mueve cada cliente que ejecuta Iris a esta versión (Conecta tu propio agente, Actualización). Ninguno inicia un servidor.
config.json se valida cuando Iris se inicia. Una clave que Iris no lee — un error tipográfico como eval.critcalRules, una clave de otra herramienta — o un valor del tipo incorrecto rechaza el inicio con una oración que nombra la clave completa, la clave que probablemente se quiso decir o el tipo que se esperaba. Nada en el archivo se ignora silenciosamente.
Variables de entorno
Cada variable que --help documenta. Los indicadores de CLI tienen prioridad sobre las variables de entorno cuando ambos están establecidos.
| Variable | Descripción |
|---|---|
IRIS_TRANSPORT | Tipo de transporte (stdio o http) |
IRIS_HOST | Dirección de enlace del transporte HTTP (predeterminado 127.0.0.1) |
IRIS_PORT | Puerto del transporte HTTP (1-65535, predeterminado 3000) |
IRIS_HOME | Directorio para todos los archivos por usuario: config.json, iris.db, custom-rules.json, audit.log, preferences.json (predeterminado ~/.iris) |
IRIS_DB_PATH | Ruta de la base de datos SQLite (anula IRIS_HOME solo para la base de datos) |
IRIS_SQLITE_DRIVER | Qué controlador SQLite mantiene la base de datos: native (better-sqlite3, el predeterminado) o node (el node:sqlite integrado de Node, Node 22.13+). Sin establecer: nativo, y cuando el módulo nativo no puede cargarse (o es una compilación que abortaría en este Node), Iris advierte una vez y recurre al integrado |
IRIS_SEARCH_BUDGET_MS | Cuánto tiempo una búsqueda de trazas (q) puede leer antes de responder con las coincidencias encontradas hasta ahora y search.complete: false, en milisegundos (50 a 60000, predeterminado 1000). Una búsqueda mantiene otras solicitudes mientras lee, por lo que también es el tiempo máximo que puede hacerlas esperar. También storage.searchBudgetMs en config.json |
IRIS_SEARCH_INDEX | on (el predeterminado) o off. off no mantiene un índice de texto completo de las trazas: una escritura almacena la traza y nada más, y una búsqueda de trazas (q) lee las trazas mismas dentro de IRIS_SEARCH_BUDGET_MS, de la más reciente a la más antigua, por lo que en un almacén grande puede responder con parte de las coincidencias (search.complete: false). Desactivarlo borra el índice que la base de datos mantenía; activarlo de nuevo construye uno nuevo en segundo plano. También storage.searchIndex en config.json |
IRIS_LOG_LEVEL | Nivel de registro: debug, info, warn, error |
IRIS_DASHBOARD | true/1/yes/on habilita el panel web; false/0/no/off lo deshabilita (también anula dashboard.enabled en config.json) |
IRIS_DASHBOARD_PORT | Puerto del panel (1-65535, predeterminado 6920) |
IRIS_WEBHOOK_URL | El receptor del webhook que se dispara en un momento — fusionado sobre notify.webhook en config.json (docs/webhooks.md) |
IRIS_WEBHOOK_SECRET | La clave de firma del webhook (cualquier cadena, o whsec_ + base64); el formato iris se niega a ejecutarse sin una |
IRIS_DASHBOARD_HOST | Dirección de enlace del panel (predeterminado 127.0.0.1) |
IRIS_API_KEY | Clave API para autenticación HTTP. Requerida para vincular el transporte HTTP o el panel más allá del loopback (0.0.0.0, una dirección LAN, un contenedor): sin ella, el servidor se niega a iniciar |
IRIS_API_KEY_FILE | Ruta a un archivo cuyo contenido recortado es la clave API — el patrón de archivo secreto que Docker y Kubernetes montan, para que la clave nunca esté en un bloque de entorno. Establece esto o IRIS_API_KEY, no ambos |
IRIS_ALLOW_UNAUTHENTICATED | Establecer a 1 para ejecutar un enlace no loopback con ninguna clave a propósito (levanta la negativa; la red es entonces tu límite) |
IRIS_ALLOWED_ORIGINS | Lista de orígenes permitidos separados por comas. Panel: encabezados CORS (admite globs, p. ej., http://localhost:*). Transporte HTTP: lista de permitidos Origin de coincidencia exacta para protección contra rebinding de DNS (los globs se ignoran; los orígenes loopback del propio servidor siempre están permitidos) |
IRIS_NO_AUTO_LAUNCH | Establecer a 1 para deshabilitar el auto-lanzamiento del panel en el primer uso |
IRIS_ANTHROPIC_API_KEY | Requerido por evaluate_with_llm_judge + verify_citations con provider=anthropic |
IRIS_OPENAI_API_KEY | Requerido por evaluate_with_llm_judge + verify_citations con provider=openai |
IRIS_LLM_JUDGE_MAX_COST_USD_PER_EVAL | Límite de costo duro por llamada de juez LLM (predeterminado 0.25) |
IRIS_RELEVANCE_JUDGE_MODEL | Un ID de modelo de juez con precio (p. ej., claude-haiku-4-5). Cuando se establece, con la clave de ese proveedor, answers_the_ask pregunta a este juez LLM en cada evaluación que lleva una entrada y condiciona su veredicto de relevancia — una llamada de juez por evaluación, bajo el límite de costo anterior y los dos límites siguientes. La entrada y salida de cada una de esas evaluaciones se envían al proveedor de ese modelo (Anthropic u OpenAI) con tu clave, con los datos personales y credenciales que no_pii marca reemplazados primero. Sin establecer (el predeterminado), answers_the_ask lee la pregunta léxicamente y aconseja, y no se envía nada (docs/llm-as-judge.md) |
IRIS_RELEVANCE_JUDGE_DAILY_BUDGET_USD | Lo que el juez de relevancia puede gastar por día UTC, por inquilino (predeterminado 1). Se mantiene en la base de datos, por lo que un reinicio no lo restablece. Una llamada se hace solo si su peor caso cabe en lo que queda; más allá de eso, answers_the_ask lee la pregunta léxicamente y judge.withheld es daily_budget. 0 detiene cada llamada |
IRIS_RELEVANCE_JUDGE_MAX_CALLS_PER_REQUEST | Llamadas de juez de relevancia que una solicitud puede hacer (predeterminado 20): un lote OTLP o una re-puntuación evaluate_runs juzga sus primeras 20 trazas y lee el resto léxicamente, con judge.withheld: "request_cap" |
IRIS_RELEVANCE_JUDGE_REDACT | on (predeterminado): cada tramo que no_pii marca (datos personales y credenciales) en la entrada y salida se reemplaza por un marcador [REDACTED:<kind>#<n>] antes de enviarse al juez de relevancia. off los envía tal como están |
IRIS_CITATION_ALLOW_FETCH | Establecer a 1 para permitir HTTP saliente en verify_citations (desactivado por defecto) |
IRIS_CITATION_DOMAINS | Lista de nombres de host permitidos separados por comas para verify_citations (coincidencia de sufijo) |
IRIS_OTEL_ENDPOINT | Habilitar exportación de trazas OTLP/HTTP JSON de mejor esfuerzo a esta URL de colector |
IRIS_OTEL_SERVICE_NAME | Atributo de recurso service.name para exportación OTel (predeterminado iris-eval) |
IRIS_OTEL_HEADERS | Encabezados k=v separados por comas para exportación OTel (p. ej., authorization=Bearer abc) |
IRIS_OTEL_TIMEOUT_MS | Tiempo de espera por exportación (predeterminado 15000) |
RATE_LIMIT_SALT | Solo API de lista de espera del sitio web — requerida cuando el sitio iris-eval.com está implementado; el servidor nunca la lee |
Seguridad
Al usar transporte HTTP, Iris incluye:
- Autenticación con clave API con comparación de tiempo constante (Bearer para clientes API; inicio de sesión del navegador al panel mediante
?key=) - CORS restringido a localhost por defecto
- Limitación de velocidad por dirección de cliente y minuto: 600 solicitudes a la API del panel (
security.rateLimit.api) y 20 al endpoint MCP (security.rateLimit.mcp), ambos establecidos enconfig.json; una solicitud MCP sobre el límite recibe un error JSON-RPC que nombra la clave - Encabezados de seguridad Helmet
- Validación de entrada Zod en todas las rutas
- Regex seguro contra ReDoS para reglas de evaluación personalizadas
- Un límite de tamaño de solicitud de 1MB en cada transporte (
security.requestSizeLimit): HTTP responde413, stdio responde un error JSON-RPC y mantiene la sesión abierta
# Production deployment
iris-eval --transport http --port 3000 --api-key "$(openssl rand -hex 32)" --dashboard
Con una clave configurada, los clientes de API — clientes MCP, SDKs de captura, POST /api/v1/traces — envían Authorization: Bearer <key>. Para abrir el panel en un navegador, añade la clave una vez a cualquier URL del panel, http://localhost:6920/?key=<api key>: Iris la intercambia por una cookie de sesión HttpOnly, SameSite=Lax y redirige a la misma página con la clave eliminada de la barra de direcciones. Una página abierta sin sesión muestra un formulario de inicio de sesión que realiza el mismo intercambio. La clave nunca se almacena en el navegador, y las sesiones viven solo en el proceso del servidor (como máximo 256 activas a la vez; un inicio de sesión que las encuentre todas activas es rechazado en lugar de expulsar una).
Producción
Varias claves, y rotación sin interrupción. security.apiKeys en config.json contiene cualquier número de claves adicionales, cada una con un id y exactamente uno de keyFile (un archivo cuyo contenido recortado es la clave) o keyHash (el sha256 hex de la clave, de modo que el archivo de configuración no contiene ningún secreto — printf %s "$KEY" | openssl dgst -sha256), y un expiresAt opcional (ISO 8601) después del cual deja de coincidir en ese instante. Para rotar: añade la nueva clave, mueve tus clientes, elimina la clave antigua. Las claves en config.json y en los archivos de clave surten efecto sin reiniciar (0.20.0): en cada solicitud, el servidor comprueba si config.json o un archivo de clave que nombra ha cambiado y, si es así, vuelve a leer las claves antes de responder. Eliminar una clave de security.apiKeys, o borrar su archivo de clave, la revoca en la siguiente solicitud: esa solicitud es rechazada, y cada sesión de navegador abierta con ella se cierra. Un config.json que no se puede leer (por ejemplo, a medio escribir) falla de forma segura, y hasta que se arregle solo se acepta una clave de IRIS_API_KEY o --api-key. La clave en IRIS_API_KEY o --api-key en sí misma, y si la autenticación está activada o no, solo cambian con un reinicio. Cada clave autentica hasta que se elimina o expira, tanto en la ruta Bearer como en el inicio de sesión del navegador; el registro de inicio nombra los ids. security.rateLimit.mcpKeyBy: "apiKey" cuenta el presupuesto por minuto del endpoint MCP por clave en lugar de por dirección de cliente, de modo que varios agentes detrás de una dirección obtienen cada uno su propio minuto.
Iris se niega a iniciar cuando el transporte HTTP o el panel están vinculados más allá de loopback — 0.0.0.0, una dirección LAN, un contenedor — sin clave de API, y lo dice en una frase nombrando IRIS_API_KEY. Eso incluye un docker run desnudo de la imagen, que vincula 0.0.0.0 dentro del contenedor porque loopback es inalcanzable a través de un puerto publicado. Loopback sin clave sigue funcionando (con una advertencia en el transporte HTTP): el límite de la máquina es el control de exposición allí.
# The image: pass a key
docker run -p 3000:3000 -p 6920:6920 -v iris-data:/data \
-e IRIS_API_KEY="$(openssl rand -hex 32)" ghcr.io/iris-eval/mcp-server
# Compose: the file requires the variable and refuses before the container starts
IRIS_API_KEY="$(openssl rand -hex 32)" docker compose up
# A network you have already fenced some other way: run open, on purpose
IRIS_ALLOW_UNAUTHENTICATED=1 iris-eval --transport http --dashboard
Abierto por diseño, en un servidor con clave: GET /health en el transporte y GET /api/v1/health en el panel responden sin clave y fuera de todo límite de tasa, en una sola forma: estado, versión, tiempo de actividad, el controlador SQLite, checks para almacenamiento, el archivo de reglas implementadas y las migraciones (aplicadas contra las conocidas), el estado del índice de búsqueda (search: listo, o cuánto ha avanzado una construcción como proporción de los rastros), y si hay una clave de juez presente — nunca la clave, nunca un rastro, nunca un recuento de ellos. status es ok solo cuando todas las comprobaciones lo son; de lo contrario es degraded con HTTP 503, que el propio HEALTHCHECK de la imagen Docker lee. Todo lo demás necesita Authorization: Bearer <key> o una sesión de navegador. La retención se ejecuta en cada servidor: los rastros y evaluaciones más antiguos que retention.days (por defecto 30) se eliminan al inicio, una vez que el servidor está respondiendo, y cada retention.sweepIntervalHours, en pasos cortos que nunca hacen esperar mucho a una solicitud; --self-test imprime la política de esta instalación, y iris://capabilities / GET /api/v1/capabilities la llevan como retention.
Un webhook se dispara en un momento (0.16.0): notify.webhook en config.json (o IRIS_WEBHOOK_URL y IRIS_WEBHOOK_SECRET) nombra un receptor, e Iris publica un mensaje firmado cuando un veredicto falla, una detección crítica veta, un costo es un valor atípico, la tasa de fallos de una regla cambia, o un caso se responde de ambas maneras por primera vez — ids, el veredicto, las reglas y los números, nunca el texto del agente. Firmado a la manera de Standard Webhooks y a la manera de GitHub a la vez, reintentado con retroceso, con enfriamiento por agente y regla, nunca en el camino de la evaluación; cuerpos de Slack y Discord integrados. docs/webhooks.md.
Tus datos en disco
Todo lo que Iris almacena vive bajo tu directorio de Iris (~/.iris, o IRIS_HOME). iris.db guarda el input y el output de cada rastro textualmente — incluyendo cualquier texto que no_pii llegue a marcar; la detección no redacta a menos que se lo pidas: storage.redact: "critical_spans" en config.json almacena la salida de cada evaluación con los spans que un detector crítico marcó reemplazados por [REDACTED:<pattern>] (desactivado por defecto; los offsets de evidencia aún indexan el texto que el llamador vio). storage.synchronous establece cuándo una escritura llega al disco: normal (el predeterminado) sincroniza el registro de escritura anticipada en cada punto de control, de modo que un bloqueo de Iris no pierde nada y el archivo no puede corromperse, pero un corte de energía o un bloqueo del sistema operativo puede deshacer las escrituras desde la última sincronización; full sincroniza cada confirmación y las mantiene a través de ambos, a aproximadamente 1,5 ms más por escritura. Al inicio, y cada retention.sweepIntervalHours (por defecto 24, 0 desactiva el temporizador) después, los rastros y evaluaciones más antiguos que retention.days (por defecto 30, 0 desactiva, establecido en config.json) se eliminan y el registro de escritura anticipada se pone en punto de control. Eliminar un rastro — por delete_trace o por el barrido — borra el texto de cada evaluación vinculada a él (la salida, el texto esperado y los mensajes de regla) y sella erased_at; el veredicto, las puntuaciones y los offsets de evidencia permanecen. Cada eliminación pone en punto de control el registro de escritura anticipada antes de regresar, de modo que el texto eliminado no queda legible en iris.db o iris.db-wal (si una búsqueda está leyendo el archivo en ese momento, u otro proceso lo está leyendo o escribiendo, la eliminación regresa sin esperar y el texto sale del archivo tan pronto como termina). Para eliminar todo ahora, detén el servidor y ejecuta --purge: elimina cada rastro, span y evaluación almacenados, compacta la base de datos y trunca el registro de escritura anticipada para que el texto desaparezca del disco, y conserva tus reglas implementadas, registro de auditoría y preferencias. Antes de que una versión aplique una migración a un iris.db existente, copia el archivo junto a él (iris.db.<from>-to-<to>.<time>.bak, solo propietario, los tres más recientes conservados; Downgrading): la copia contiene los rastros tal como estaban, de modo que el barrido de retención elimina uno más antiguo que retention.days y --purge los elimina todos. El servidor hace la copia y las migraciones después de haber respondido a su cliente, en un hilo propio: las llamadas a herramientas, lecturas de recursos y solicitudes HTTP que llegan mientras tanto esperan por ellas, como máximo 30 s cada una, y luego son rechazadas con una frase que dice lo que el servidor está haciendo (IRIS_STORAGE_ERROR, reintentable; HTTP 503 con Retry-After). Las respuestas de salud responden en todo momento y dicen lo que la actualización está haciendo. Desde 0.19.0 con 100 000 rastros que son cada uno un bucle de agente, la copia y las migraciones tomaron aproximadamente 6 s. iris-eval ingest, --purge y --self-test aún se actualizan antes de hacer cualquier otra cosa.
Iris no cifra sus datos en reposo. iris.db y sus archivos de registro de escritura anticipada se crean solo para el propietario (modo 600), y el directorio de Iris se crea en modo 700 (en Windows, las ACL de archivos gobiernan en su lugar). La base de datos no almacena claves de proveedores de LLM: IRIS_ANTHROPIC_API_KEY y IRIS_OPENAI_API_KEY se leen del entorno y nunca se escriben en disco. Sí almacena entradas y salidas de rastros textualmente, así que coloca el directorio de Iris en un disco o volumen cifrado (FileVault, BitLocker, LUKS, o un volumen en la nube cifrado para el montaje /data de la imagen Docker).
Una exportación — el botón Export en las páginas Traces y Evaluations del panel, GET /api/v1/traces/export y /api/v1/evaluations/export, o iris-eval export — lleva este texto almacenado tal como está, igual que el panel lo muestra: entrada y salida de rastro textualmente, salida de evaluación con storage.redact aplicado. Trata un archivo exportado como la base de datos de la que proviene.
Solución de problemas
Primer paso: ejecuta la autoprueba
npx @iris-eval/mcp-server --self-test
Comprueba el almacenamiento, las evaluaciones deterministas y el panel en un directorio temporal aislado e imprime un veredicto por paso — la salida de fallo nombra el paso roto. El código de salida 0 significa que la instalación está sana.
Iris no se inicia / ERR_MODULE_NOT_FOUND
Puede que tengas una versión anterior en caché. Limpia la caché de npx y reintenta:
npx --yes @iris-eval/mcp-server@latest
O instala globalmente para evitar problemas de caché por completo:
npm install -g @iris-eval/mcp-server@latest
npm install --ignore-scripts rompió el enlace SQLite
Iris almacena rastros con better-sqlite3, un módulo nativo que obtiene o compila su enlace en un script de instalación. Si ese script se omitió — --ignore-scripts en la línea de comandos, ignore-scripts=true en un .npmrc (común en máquinas corporativas), o un espejo de registro que elimina postinstall — el inicio falla con un volcado largo de "Could not locate the bindings file" que lista una docena de rutas que intentó. Reconstruye ese único módulo:
npm rebuild better-sqlite3
# for a global install:
npm rebuild -g better-sqlite3
Herramientas que no aparecen en Claude Code
Las herramientas MCP solo se cargan al inicio de la sesión. Después de añadir iris-eval, reinicia la sesión con /clear o relanza la terminal.
Verificación de versión
npx @iris-eval/mcp-server --version
La primera línea de registro de inicio también la lleva (Starting Iris MCP server vX.Y.Z), y --self-test la imprime en su resumen. Para una instalación global, npm ls -g @iris-eval/mcp-server muestra la versión instalada.
Actualización
Cada cliente MCP en una máquina comparte una base de datos, ~/.iris/iris.db, y install fija cada cliente a la versión que escribió su configuración. Cuando una versión cambia el esquema de la base de datos, el primer proceso de esa versión que abre el archivo lo actualiza, y a partir de entonces un cliente aún fijado a una versión anterior se niega a iniciar. Así que mueve cada cliente en un solo paso, antes o justo después de actualizar:
npx -y @iris-eval/mcp-server@latest install --upgrade
Encuentra cada configuración de cliente en esta máquina que ejecuta Iris, mueve cada fijación a esa versión (conservando cualquier cosa que hayas añadido a la entrada, como --dashboard o un bloque env), deja intacta una fijación a una versión más nueva y una entrada que ejecuta algo distinto del paquete npm, y lista lo que hizo. Reinicia los clientes que nombra. install --list muestra qué Iris ejecuta cada cliente.
Dos instalaciones viven fuera de esos archivos: la extensión de Claude Desktop (iris-eval.mcpb) se mueve cuando abres un paquete más nuevo, y los plugins de Claude Code con claude plugin marketplace update iris-eval y luego claude plugin update iris-eval@iris-eval (y claude plugin update iris-eval-capture@iris-eval para el plugin de captura).
Actualización de 0.19.x a 0.20.0. 0.20.0 añade el índice de búsqueda y otras adiciones a la base de datos (migraciones 015 y posteriores). Una vez que cualquier proceso de 0.20.0 ha abierto ~/.iris/iris.db (la extensión de Claude Desktop, npx iris-eval, o npx @iris-eval/mcp-server sin versión), un cliente fijado a 0.19.x se detiene con This database was migrated by a newer Iris (…) — migration(s) 015-trace-search, … are unknown to v0.19.0. Upgrade Iris, …. Ese mensaje viene de 0.19.x y no puede cambiar; la solución es el comando anterior. Antes de la actualización, 0.20.0 copia el archivo junto a él, de modo que volver atrás también es posible (abajo).
Un inicio que actualiza la base de datos imprime lo que hizo en stderr: la copia que tomó, qué versiones anteriores ya no pueden abrir el archivo, y cualquier cliente en esta máquina fijado a una de ellas, con el comando. --self-test lee la base de datos sin cambiarla y dice lo mismo antes de que inicies cualquier cosa.
Para una instalación global, npm update -g @iris-eval/mcp-server, luego iris-eval install --upgrade.
Degradación de versión
Una versión que actualizó la base de datos la copia primero, junto a ella: iris.db.<from>-to-<to>.<time>.bak en tu directorio de Iris (<from> es la versión que cambió por última vez el esquema del archivo, <to> la que lo actualizó; la línea de inicio imprimió la ruta exacta). Para volver atrás:
- Detén cada cliente MCP y cualquier otro proceso de Iris que use la base de datos.
- Conserva el archivo actualizado, por si vuelves: renombra
iris.dbairis.db.upgraded, y eliminairis.db-walyiris.db-shmsi están ahí. - Copia la copia de seguridad a
iris.db:cp ~/.iris/iris.db.0.19.0-to-0.20.0.<time>.bak ~/.iris/iris.db. - Fija cada cliente de nuevo a la versión anterior:
npx -y @iris-eval/mcp-server@0.19.0 install <client>para cada uno (install --upgradenunca mueve un cliente hacia atrás).
Los rastros almacenados después de la actualización están en iris.db.upgraded, no en la copia de seguridad. Si no se tomó ninguna copia (la línea de inicio dice por qué, por ejemplo, un disco lleno), la versión anterior no puede abrir el archivo actualizado, y el camino a seguir es install --upgrade.
El controlador de almacenamiento
En una plataforma sin better-sqlite3 precompilado, la instalación aún tiene éxito. better-sqlite3 es una dependencia opcional: cuando npm no puede descargar un binario precompilado para tu Node y plataforma ni compilar uno (compilar necesita Python y un conjunto de herramientas C++ — las herramientas de compilación C++ de Visual Studio en Windows), npm imprime el error de compilación, omite el módulo y termina la instalación. Iris entonces se ejecuta en el SQLite integrado de Node, y lo dice: el inicio imprime una línea en stderr indicando el motivo, y --self-test muestra driver node: better-sqlite3 is not installed …. Para recuperar el controlador nativo, instálalo donde exista un precompilado o un conjunto de herramientas (npm install better-sqlite3 en el proyecto; para una instalación global, instala Iris de nuevo con npm install -g @iris-eval/mcp-server una vez que haya un conjunto de herramientas disponible). CI instala el servidor empaquetado con la compilación nativa forzada a fallar en cada cambio, y requiere que la instalación termine y que la autoprueba almacene y lea un rastro en el integrado.
Iris mantiene todo en un solo archivo SQLite, abierto por better-sqlite3 — un complemento nativo que se descarga o compila para tu Node y plataforma. Cuando ese módulo no puede cargarse, Iris recurre al SQLite integrado de Node (node:sqlite, Node 22.13 o posterior) con una advertencia en stderr, por lo que un precompilado faltante es un inicio más lento en lugar de uno muerto. Hace lo mismo, antes de cargarlo, para un better-sqlite3 compilado en tu máquina contra los encabezados de Node 24.19 o posterior: en cada versión 24.x hasta ahora, tal binario aborta todo el proceso la primera vez que libera una declaración (Assertion failed: (env) != nullptr, nodejs/node#65446), y npm rebuild better-sqlite3 lo reemplaza con el binario precompilado, que es seguro. IRIS_SQLITE_DRIVER=node elige el integrado a propósito, native prohíbe la alternativa. El integrado se abre con la carga de extensiones desactivada y trusted_schema desactivado; Node imprime su propia línea de ExperimentalWarning: SQLite is an experimental feature en stderr cuando se carga, e Iris no la silencia. --self-test y GET /health nombran el controlador en uso; cada número en la página de prueba se midió en el controlador nativo, y el conjunto de pruebas se ejecuta en ambos en CI.
Versión de Node.js
Iris requiere Node.js 22.13 o posterior. Node 20 llegó al final de su vida útil el 2026-04-30 y no es compatible; Node 18 lo hizo en abril de 2025.
El mínimo es 22.13 en lugar de 22.0 porque 22.13.0 es la primera versión que incluye node:sqlite. Eso lo convierte en la primera versión en la que cada instalación compatible de Iris tiene un segundo controlador de almacenamiento: cuando el complemento nativo better-sqlite3 no se carga, Iris recurre al SQLite integrado de Node en lugar de fallar al iniciar. Por debajo de 22.13 — y en Node 20, durante toda su vida — solo había un controlador, y un precompilado faltante era un inicio muerto.
node --version # Must be v22.13.0 or newer
Windows: cmd /c no es necesario
El /doctor de Claude Code puede sugerir envolver npx con cmd /c. Esto no es necesario y causa problemas de análisis de rutas. Usa npx directamente:
# Correct
claude mcp add --transport stdio iris-eval -- npx -y @iris-eval/mcp-server
# Wrong (causes /c to be parsed as a path)
claude mcp add --transport stdio iris-eval -- cmd /c "npx -y @iris-eval/mcp-server"
Si Iris te resulta útil, considera darle una estrella al repositorio — ayuda a que otros lo encuentren.
Licencia MIT.