Iris
официальныйMCP-нативный сервер для оценки агентов и наблюдаемости с трассировкой логов, оценкой качества вывода, отслеживанием затрат, 12 встроенными правилами оценки, панелью мониторинга в реальном времени и обнаружением PII
Что можно делать с Iris MCP?
- Логирование и оценка запусков агента — Попросите вашего ассистента залогировать задачу в Iris и получите детерминированные оценки качества, безопасности и стоимости на выходе.
- Запрос истории трассировок — Получайте сохранённые выполнения агентов с фильтрацией, пагинацией и поддержкой временных диапазонов для анализа прошлой производительности.
- Сравнение запусков во времени — Анализируйте два запуска на одних и тех же вопросах бок о бок, чтобы выявлять регрессии или улучшения в поведении агента.
- Оценка качества вывода — Оценивайте любой текст по 25 встроенным правилам, охватывающим полноту, релевантность, безопасность и стоимость, с обнаружением PII и инъекций в промпты.
- Запуск демонстрационной панели — Запустите предзаполненную демонстрационную базу данных с примерами сбоев и вердиктов, чтобы локально изучить механизм оценки Iris.
Документация
Iris — хватит выпускать агентов на удачу
Iris оценивает каждый запуск агента по качеству, безопасности и стоимости — на вашей машине, без SDK и без аккаунта. Большинство проектов с агентами проверяют качество, прогоняя несколько запомненных промптов и оценивая результат на глаз. Iris заменяет это числами, которые можно проверить: запуски вашего агента попадают в базу данных SQLite на вашем диске, 25 встроенных правил оценивают их детерминированно — PII, prompt injection, маркеры галлюцинаций, пороги стоимости и собственные вызовы инструментов агента — бесплатно, без обращений к LLM, а опциональный LLM-судья с жёстким лимитом стоимости на одну оценку разбирает семантические вопросы. Каждое правило можно посмотреть и отредактировать, потому что судья, которого нельзя проверить, — это просто удача с числом сверху. Лицензия MIT, без телеметрии. Ничего не покидает вашу машину, если вы не включите одно из следующего: конечная точка OpenTelemetry (IRIS_OTEL_ENDPOINT), которая экспортирует трейсы в указанный вами коллектор; LLM-судья с вашим собственным ключом, который отправляет оцениваемый текст этому провайдеру, а его проверка цитирования загружает страницы, на которые ссылается вывод; или вебхук, который отправляет идентификаторы, вердикт и названия правил, но никогда текст, на указанный вами адрес.
Требуется Node.js 22.13 или новее. Проверьте с помощью node --version.

Демонстрационная база данных, записанная с помощью scripts/demo-media.mts; исходник — demo.mp4. Кадр: dashboard-overview.png.
Сбой на экране за 60 секунд
Никакой обвязки агента, никакой конфигурации — одна команда:
npx @iris-eval/mcp-server --demo
Это создаёт демонстрационную базу данных — пять небольших агентов, две недели запусков, каждый вердикт от собственного движка — и запускает дашборд для неё на http://localhost:6920 (браузер открывается автоматически при первом запуске). Дашборд открывается на вкладке Failures: что упало, сначала худшее и самое свежее, каждая карточка называет правило и его доказательства. Стоит заглянуть внутрь — утечка PII, пойманная правилами безопасности, скрытая директива в сообщении на форуме, которой последовал суммаризатор, число, которого не было в исходном документе, два запуска на одних и тех же двенадцати вопросах, сравнённые с интервалом (Runs), развёрнутое кастомное правило и приостановленное с их строками аудита, а также неудачная оценка LLM-судьи с её обоснованием.
Демо-данные живут в отдельной базе (demo.db в вашем домашнем каталоге Iris — ~/.iris на macOS/Linux, %USERPROFILE%\.iris на Windows) и никогда не смешиваются с вашими реальными трейсами. Удалить всё одной командой:
npx @iris-eval/mcp-server --demo-clear
Подключите своего агента
Сначала убедитесь, что установка работает на этой машине — она работает офлайн и не открывает ничего вашего:
npx @iris-eval/mcp-server --self-test # exit 0 = healthy
Затем добавьте Iris в ваш MCP-клиент. Одна команда записывает собственный конфигурационный файл клиента, сохраняет все остальные серверы в нём и закрепляет версию, которую вы запустили:
npx -y @iris-eval/mcp-server install claude-code
Клиенты: claude-code, claude-desktop, cursor, windsurf, continue, vscode, cline, zed, codex, gemini. install --list показывает найденные на этой машине, какой Iris запускает каждый и какой файл он читает; install <client> --uninstall снова удаляет Iris. Все клиенты используют одну базу данных, поэтому после обновления перенесите их все сразу с помощью install --upgrade (Обновление). Перезапустите клиент, чтобы загрузить его.
Claude Desktop: один клик. Каждый релиз с 0.20.0 прикрепляет iris-eval.mcpb, MCP Bundle: скачайте последний, откройте его, и Claude Desktop покажет диалог установки. Ничего из этого не обязательно — ключ Anthropic или OpenAI для LLM-судьи опционален, а дашборд — это переключатель, который по умолчанию выключен. В бандле лежат npm-пакет и его зависимости, так что больше ничего устанавливать не нужно: Claude Desktop запускает его под своим Node, если этот Node версии 22.13 или новее (Claude Desktop 1.1.6679 поставляет 24.13), а Iris хранит трейсы во встроенном SQLite Node, в том же ~/.iris, который использует любая другая установка. В примечаниях к релизу показано, как проверить его подпись и аттестацию сборки.
Он работает в любом MCP-клиенте, и у каждого клиента, который он называет, есть строка с тем, что было проверено на самом деле. Проверено на каждом прогоне CI: Claude Code, Gemini CLI — реальный клиент запускает Iris из конфига, написанного установщиком, и сообщает о подключении (claude mcp list, gemini mcp list), на Linux, macOS и Windows; хуки плагина захвата Claude Code также управляются через реальные скрипты. Заявлено из собственной MCP-документации каждого клиента — установщик пишет форму конфигурации, которую документирует клиент, и этот писатель протестирован на этой форме; никто со стороны Iris не видел, как он подключается: Claude Desktop, Cursor, Devin Desktop (Windsurf), Continue, VS Code, Cline, Zed, OpenAI Codex CLI. Каждая строка с её источником и датой чтения: https://iris-eval.com/clients. Вручную, одним блоком, включая дашборд:
{
"mcpServers": {
"iris-eval": {
"command": "npx",
"args": ["-y", "@iris-eval/mcp-server", "--dashboard"]
}
}
}
Ваш клиент перечисляет двенадцать инструментов Iris при подключении, а дашборд работает на http://localhost:6920. Теперь вставьте это своему агенту:
Запиши последнюю задачу в Iris и оцени вывод.
Трейс попадает на дашборд со своими оценками. Предпочитаете MCP-сервер без головы? Уберите --dashboard из аргументов — вы можете открыть тот же дашборд в любой момент с помощью npx @iris-eval/mcp-server --dashboard.
Одна вещь, которую стоит знать заранее: MCP-инструменты вызываются, когда модель решает их вызвать. Iris не перехватывает вашего агента, поэтому трейсы записываются, когда ваш агент просит их записать — либо потому, что вы ему сказали, либо потому, что ваш код вызывает инструменты напрямую. Попросите агента «запиши это в Iris и оцени» — и он сделает. Если вам нужен захват, который не зависит от выбора модели, POST /api/v1/traces делает именно это — ваш код отправляет трейс по обычному HTTP, без модели в цикле (см. docs/http-ingest.md). CLI и хуки хоста из дорожной карты будут тонкими клиентами над той же конечной точкой.
Захват по HTTP (без модели в цикле)
Конечная точка приёма живёт на порту дашборда — 6920 по умолчанию, не на порту MCP-транспорта — и существует только пока запущен дашборд. Передайте --dashboard (или установите IRIS_DASHBOARD=true); --transport http сам по себе не запускает её, а запрос на порт транспорта возвращает 404. При поднятом дашборде всё, что может отправить HTTP-запрос, может записать трейс — и опционально запустить детерминированные оценки в том же запросе. GET /api/v1/capabilities на том же порту сообщает, что этот сервер может оценивать, что нужно каждому правилу, состояние судьи с шагами, которые его включают, и лимиты — тот же объект, который отдаёт MCP-ресурс iris://capabilities — так что HTTP-вызывающий получает рамку, которую MCP-клиент получает при инициализации:
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"
}'
Возвращает 201 с сохранённым trace_id и результатом оценки (в режиме --demo конечная точка отказывает в записи с 403, так что демо-данные никогда не смешиваются с вашими). Конечная точка принимает то же тело, что и инструмент log_trace, и стоит за тем же стеком промежуточного ПО, что и остальной дашборд: привязка к loopback и защита от DNS-rebinding по умолчанию, плюс Bearer-аутентификация, если вы её задали. Два простых факта о ней: она принимает неаутентифицированные записи, если Iris не был запущен с --api-key (или IRIS_API_KEY) — привязка к loopback и держит её на вашей машине по умолчанию, поэтому задайте ключ перед привязкой за пределами loopback; и то, что она хранит, сохраняется дословно — input и output попадают в iris.db ровно как отправлены, включая любой текст, который no_pii затем пометит. Полный контракт, справочник полей и семантика ошибок: docs/http-ingest.md.
Захват каждого хода Claude Code (опционально)
/plugin marketplace add iris-eval/mcp-server
/plugin install iris-eval-capture@iris-eval
Второй, отдельно устанавливаемый плагин: три хука записывают промпт, вызовы инструментов и финальный ответ каждого хода и передают их в iris-eval ingest, отсоединённо, с редактированием критических спанов в сохранённом тексте оценки — захват, который не зависит от решения модели вызвать инструмент. Он никогда не логирует ход, который модель уже записала, никогда не печатает, никогда не блокирует и никогда ничего никуда не отправляет. Установка одного iris-eval ничего не меняет в вашем цикле ходов. Лимиты и удаление: 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": []}
Тонкий клиент поверх HTTP API сервера 0.16.0 и новее, версионируется отдельно — iris_eval.__version__ и страница PyPI несут его номер, который не совпадает с серверным: log_trace(), evaluate_output(), get_traces(), get_trace(), health(), capabilities(), синхронный и асинхронный, типизированные ответы, собственное предложение сервера при отказе — и pytest-плагин: фикстура iris и assert_iris(output, expect="pass"), которая проверяет состояние вердикта. packages/python/README.md.
Запись каждого вызова OpenAI и 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' });
Оберните клиент провайдера один раз, и каждый вызов модели станет одним спаном OpenTelemetry GenAI, отправленным в дверь OTLP, сохранённым с его входом, выходом, использованием токенов и вызовами инструментов, и оценённым: захват, который не зависит от вызова инструмента моделью. wrap_openai / wrap_anthropic в Python-клиенте; wrapOpenAI, wrapAnthropic и irisMiddleware для Vercel AI SDK в @iris-eval/sdk. Оба ещё не опубликованы (следующий релиз iris-eval на PyPI; @iris-eval/sdk собирается из исходников до первого npm-релиза). Потоки, хелперы потоков SDK и вызовы инструментов покрыты, исходный клиент не изменяется, а падение Iris никогда не ломает вызов — packages/sdk/README.md, packages/python/README.md.
Оценка каждого запуска LangChain и LangGraph
from iris_eval.langchain import IrisCallbackHandler
graph.invoke(inputs, config={"callbacks": [IrisCallbackHandler(agent_name="support-bot")]})
Каждый запуск верхнего уровня становится одним трейсом (сам запуск, его вызовы моделей, его вызовы инструментов и его узлы графа как спаны GenAI) с его входом, выходом, вызовами инструментов, использованием токенов и вердиктом. Python в клиенте (следующий релиз, ещё не опубликован на PyPI), JavaScript как @iris-eval/langchain (ещё не опубликован на npm). Оба проверены в CI против реального приложения LangGraph со скриптованной моделью; собственный экспорт OpenTelemetry LangSmith проверен так же — docs/otel-recipes.md.
CI-шлюз, без сервера
npx -y @iris-eval/mcp-server ingest --file traces.ndjson --evaluate --fail-on detector_veto
Или GitHub Action (0.16.0), который проваливает задание на названных вами вердиктах, записывает квитанцию в сводку задания и публикует её как один комментарий к pull request, обновляемый на месте: uses: iris-eval/mcp-server/.github/actions/gate@v0.19.0 с traces: traces.ndjson — docs/ci-gate.md.
Четвёртая дверь (0.15.0): POST /v1/traces на порту дашборда принимает OTLP/HTTP JSON или protobuf, которые уже генерирует ваша инструментовка OpenTelemetry (экспортёр Python SDK говорит только на protobuf, так что это также дверь для Python), и каждый OTLP-трейс становится трейсом Iris со своими спанами — docs/otel-integration.md; по одному рецепту на фреймворк (Pydantic AI, Google ADK, LangGraph через LangSmith, CrewAI, OpenAI Agents SDK на Python и JavaScript, LlamaIndex, AutoGen, Microsoft Agent Framework, Semantic Kernel, Vercel AI SDK и Mastra), каждый подтверждён фикстурой, в docs/otel-recipes.md. ingest читает один JSON-трейс (или NDJSON, по одному на строку) из stdin или файла, сохраняет его, оценивает по точно тем же правилам, что и evaluate_output, выводит по одной JSON-строке на трейс с вердиктом и его основанием, и завершается с кодом 1, когда вердикт совпадает с --fail-on. --dataset <id|label> ограничивает этот шлюз ключами кейсов в наборе данных (POST /api/v1/datasets продвигает ключи кейсов запуска в такой набор), так что задание падает только на выбранных вами кейсах. Полный рецепт, коды выхода и восемь оснований — в docs/ci-gate.md.
Напишите правило как код
eval.plugins в config.json загружает написанные вами правила — ES-модуль, чей экспорт по умолчанию — это { name, kind, mechanism, version, needs, evaluate(ctx) } — закреплённый по sha256 файла, так что файл, изменившийся после закрепления, откажется запускаться, а не выполнится. Загруженный плагин срабатывает как встроенный и отображается на list_rules под plugins. Контракт, рецепт хеширования и что может вернуть плагин: docs/plugins.md.
Используйте движок в своём процессе
Движок оценки импортируем — без сервера, без базы данных, без модели:
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
Тот же движок, те же правила и тот же компоновщик, что запускает сервер; builtInRules(), createCustomRule(), compose() и считыватели опубликованной точности экспортируются рядом с ним.
Типизированный клиент для 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
Одно тело на каждой двери: это то, что принимают log_trace и iris-eval ingest. Отказ выбрасывает IrisClientError с собственным сообщением сервера и статусом. Оба подпути проверяются из упакованного tarball при каждой сборке.
Проверьте свою установку
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 сначала создаёт ваш домашний каталог Iris, если он отсутствует, и проверяет, что он доступен для записи (код 1 с указанием пути, если нет), сообщает, где находится поисковый индекс вашей базы данных (целиком, сколько трейсов фоновая сборка проиндексировала на данный момент, или нет FTS5 в этом SQLite), читает схему вашей базы данных (код 1 с исправлением, если эта версия или MCP-клиент, закреплённый за старой версией, не может её открыть), затем запускает свои проверки — цикл записи в хранилище, посаженный SSN и посаженную инъекцию, пойманные правилами безопасности, загрузку дашборда, защиту от DNS-rebinding — в изолированном временном домашнем каталоге. Ваша реальная база данных только читается, никогда не изменяется. Всё, что пишет Iris, живёт в одном каталоге — вашем домашнем каталоге Iris: ~/.iris по умолчанию (%USERPROFILE%\.iris на Windows) или там, куда указывает IRIS_HOME. Именно там живут iris.db, config.json, custom-rules.json, audit.log, preferences.json и демо-файлы; укажите IRIS_HOME на временный каталог, чтобы попробовать Iris, не трогая реальные данные.
Настройка по инструменту
| Клиент | Статус | Что это значит | Читать |
|---|---|---|---|
| Claude Code | проверено | тест управляет реальным клиентом на каждом CI-запуске | 2026-09-25 |
| Claude Desktop | заявлено | установщик пишет форму, которую документирует клиент, и этот писатель протестирован на форме; никто на стороне Iris не видел, как он подключается | 2026-09-25 |
| Cursor | заявлено | установщик пишет форму, которую документирует клиент, и этот писатель протестирован на форме; никто на стороне Iris не видел, как он подключается | 2026-09-25 |
| Devin Desktop (Windsurf) | заявлено | установщик пишет форму, которую документирует клиент, и этот писатель протестирован на форме; никто на стороне Iris не видел, как он подключается | 2026-09-25 |
| Continue | заявлено | установщик пишет форму, которую документирует клиент, и этот писатель протестирован на форме; никто на стороне Iris не видел, как он подключается | 2026-09-25 |
| VS Code | заявлено | установщик пишет форму, которую документирует клиент, и этот писатель протестирован на форме; никто на стороне Iris не видел, как он подключается | 2026-09-25 |
| Cline | заявлено | установщик пишет форму, которую документирует клиент, и этот писатель протестирован на форме; никто на стороне Iris не видел, как он подключается | 2026-09-25 |
| Zed | заявлено | установщик пишет форму, которую документирует клиент, и этот писатель протестирован на форме; никто на стороне Iris не видел, как он подключается | 2026-09-25 |
| OpenAI Codex CLI | заявлено | установщик пишет форму, которую документирует клиент, и этот писатель протестирован на форме; никто на стороне Iris не видел, как он подключается | 2026-09-25 |
| Gemini CLI | проверено | тест управляет реальным клиентом на каждом CI-запуске | 2026-09-25 |
Каждая строка с тем, что было проверено: iris-eval.com/clients. Ни один клиент не называется поддерживаемым без строки.
npx -y @iris-eval/mcp-server install <client> пишет каждый из них за вас. Вручную, по клиенту:
Claude Desktop
Отредактируйте ваш MCP-конфиг-файл:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
Добавьте JSON-конфиг выше, затем перезапустите Claude Desktop.
Claude Code
claude mcp add --transport stdio iris-eval -- npx -y @iris-eval/mcp-server
Затем перезапустите сессию (/clear или перезапустите) для загрузки инструментов.
Примечание для Windows: Не используйте обёртку
cmd /c— она вызывает проблемы с разбором путей. Командаnpxработает напрямую.
Cursor
Добавьте JSON-конфиг выше в ~/.cursor/mcp.json (каждый проект) или .cursor/mcp.json в рабочем пространстве, с "type": "stdio" в записи iris-eval — документация Cursor помечает его как обязательный.
Devin Desktop (Windsurf)
Добавьте JSON-конфиг выше в mcp_config.json: ~/.config/devin/mcp_config.json на macOS и Linux, %APPDATA%\devin\mcp_config.json на Windows.
Continue
Сохраните JSON-конфиг выше как отдельный файл в папке mcpServers Continue: ~/.continue/mcpServers/iris-eval.json (каждое рабочее пространство) или .continue/mcpServers/iris-eval.json в одном.
VS Code (нативный MCP)
Добавьте в .vscode/mcp.json в вашем рабочем пространстве (обратите внимание: VS Code использует servers, а не mcpServers):
{
"servers": {
"iris-eval": {
"command": "npx",
"args": ["-y", "@iris-eval/mcp-server"]
}
}
}
Cline
Откройте панель MCP-серверов Cline → Configure MCP Servers и добавьте JSON-конфиг mcpServers выше в cline_mcp_settings.json (~/.cline/data/settings/cline_mcp_settings.json, общий для Cline в VS Code, JetBrains и CLI).
Zed
Добавьте в Zed settings.json:
{
"context_servers": {
"iris-eval": {
"command": "npx",
"args": ["-y", "@iris-eval/mcp-server"],
"env": {}
}
}
}
OpenAI Codex CLI
Добавьте в ~/.codex/config.toml:
[mcp_servers.iris-eval]
command = "npx"
args = ["-y", "@iris-eval/mcp-server"]
Gemini CLI
Добавьте JSON-конфиг mcpServers выше в ~/.gemini/settings.json. Gemini CLI подключается к MCP-серверам только в доверенных папках: если gemini mcp list показывает iris-eval как Disabled, запустите /permissions в этой папке.
Всё остальное, что говорит на MCP
Iris — стандартный stdio MCP-сервер — одна команда npx @iris-eval/mcp-server, без SDK, без изменений кода. Если ваш клиент поддерживает MCP, он поддерживает Iris. Форматы конфигов клиентов меняются; если сомневаетесь, проверьте документацию MCP вашего клиента и укажите ему эту команду.
Другие способы установки
# 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
Совет: Глобальная установка (
npm install -g) хранит трейсы постоянно в~/.iris/iris.db. Сnpxтрейсы сохраняются в том же месте, но запуск медленнее из-за разрешения пакетов.
Что вы получаете
| Журналирование трейсов | Иерархические деревья спанов с задержкой на каждый вызов инструмента, использованием токенов и стоимостью в USD. Хранится в SQLite, мгновенно запрашивается. |
| Оценка вывода | 25 встроенных правил в 4 категориях: полнота, релевантность, безопасность, стоимость. Обнаружение PII (21 паттерн: SSN, кредитная карта, телефон, email, IBAN, DOB, MRN, IP, API-ключ, паспорт, плюс токены AWS/Slack/SendGrid/GitHub/Google/npm/DigitalOcean, учётные данные в URL, присваивания с секретными именами, блоки приватных ключей PEM и сид-фразы; дата рождения, номер медицинской карты, паспорт и сид-фраза срабатывают только рядом со своей меткой, по дизайну), обнаружение промпт-инъекций (38 паттернов, фразовых и структурных), обнаружение заглушек-выводов, обнаружение галлюцинаций (25 сигналов фабрикации/противоречия, основанных на контексте — передайте input, чтобы привязать их к исходному материалу агента), и шесть правил траектории, которые читают, что агент СДЕЛАЛ: непризнанный неудачный вызов инструмента, повторный вызов (по вызову, по повторяющейся последовательности или по цели, если вы отправите tools), вызов, чьи аргументы отклоняет собственная JSON Schema инструмента и агент не повторил попытку, файл, каталог или URL, на который ссылается ответ, но который не появляется ни в чём, что агент читал, инструкция, прибывшая внутри TOOL RESULT и затем выполненная более поздним вызовом, и задача, занявшая больше вызовов инструментов, чем ваш бюджет шагов. Траектория может прибыть как tool_calls или как OpenTelemetry TOOL-спаны. Добавляйте пользовательские правила со схемами Zod. |
| LLM-как-судья | Опциональное семантическое оценивание через Anthropic или OpenAI — принесите свой API-ключ. Семь шаблонов. С установленным IRIS_RELEVANCE_JUDGE_MODEL, answers_the_ask спрашивает судью relevance и проваливает ответ не по теме; без него правило читает запрос лексически и советует. Жёсткий предел стоимости на оценку (IRIS_LLM_JUDGE_MAX_COST_USD_PER_EVAL, по умолчанию $0.25), цена за оценку раскрывается в результате. |
| Видимость стоимости | Совокупная стоимость по всем агентам за любой период времени. Устанавливайте пороги бюджета. Получайте флаги, когда агенты перерасходуют. Трейс, который отправляет количество токенов и модель, но не стоимость (большинство OpenTelemetry и фреймворковых трейсов), оценивается по прейскуранту модели и помечается как оценённый везде, где отображается; pricing.models в config.json оценивает модели, которых нет во встроенной таблице — docs/cost.md. |
| Веб-дашборд | Тёмный интерфейс в реальном времени, который приземляется на сбои, сначала худшие и новые — визуализация трейсов с полнотекстовым поиском по тексту каждого трейса, результаты оценок, разбивка стоимости и палитра команд (⌘K), которая ищет ваши собственные правила, трейсы и оценки. |
| Локально-первый | Всё живёт в SQLite на вашем диске. Без аккаунта, без регистрации, без телеметрии. Исходящий HTTP происходит только там, где вы соглашаетесь: ваш собственный ключ LLM-судьи, получение цитат, экспортёр OTel, который вы настраиваете, или вебхук, который вы устанавливаете. |
Куда это движется дальше: карта возможностей — каждый вопрос, который можно задать Iris по каждой теме, с тем, что есть и чего не хватает — и три трека.
Измерено, не заявлено
Каждая встроенная метрика имеет опубликованные precision, recall и F1 с 95% доверительными интервалами, измеренные на размеченном корпусе, который хранится в этом репозитории (proof/corpus/) и пересоздаётся одной командой — npm run proof — офлайн, без ключа и без модели в цикле. Эти числа бывают двух разных видов, и страница никогда не складывает их вместе: некоторые метрики измеряются по меткам, которые модель дала, прочитав сам сбой, что измеряет детектирование; остальные проверяются по их собственному документированному определению, применённому независимо, что показывает, что код реализует свою формулу, и ничего не говорит о том, ловит ли формула сбой. proof/RESULTS.md и страница доказательств отмечают каждую метрику. CI перезапускает измерение на каждом pull request и завершается ошибкой, если зафиксированные числа отличаются от того, что выдаёт код, поэтому метрика не может измениться без изменения её чисел. Числа находятся на iris-eval.com/proof и в proof/RESULTS.md; как был создан корпус, чем он не является и как читать интервал — в docs/proof.md. Корпус синтетический и размечен моделью — человеческая слепая разметка ожидается, и страница об этом говорит; node proof/blind-sample.mjs формирует воспроизводимую выборку, которая это урегулирует.
MCP-инструменты
Iris регистрирует двенадцать инструментов, которые может вызывать любой MCP-совместимый агент — жизненный цикл трасс и метрик, сравнение между запусками, LLM-как-судья и проверка семантического цитирования:
log_trace— Запись выполнения агента с спанами, вызовами инструментов, использованием токенов и стоимостью; передайтеevaluate: true, чтобы оценить его в том же вызовеevaluate_output— Оценка качества вывода по метрикам полноты, релевантности, безопасности и стоимости (эвристические, детерминированные, бесплатные)get_traces— Запрос сохранённых трасс с фильтрацией, пагинацией и поддержкой временных диапазонов, а также поиск запуска, где агент сказал что-то с помощьюq: полнотекстовый поиск по вводу, выводу, значениям вызовов инструментов и метаданным, ранжированный, с отмеченными совпавшими словамиlist_rules— Перечисление развёрнутых пользовательских метрик оценки (только чтение)deploy_rule— Регистрация новой пользовательской метрики оценки, чтобы она срабатывала на каждомevaluate_outputэтой категорииdelete_rule— Удаление развёрнутой пользовательской метрики (разрушительно, идемпотентно)delete_trace— Удаление одной сохранённой трассы по ID (разрушительно, ограничено тенантом)evaluate_with_llm_judge— Семантическая оценка через LLM (Anthropic или OpenAI). Семь шаблонов: accuracy, helpfulness, safety, correctness, faithfulness, task_completed, relevance. С ограничением стоимости, цены за оценку раскрыты. Принесите свой собственный API-ключ (IRIS_ANTHROPIC_API_KEYилиIRIS_OPENAI_API_KEY) — Iris не проксирует и не ретранслирует LLM-вызовы.verify_citations— Извлечение цитат из вывода (нумерованные, автор-год, URL, DOI), получение источников через резолвер с защитой от SSRF и белым списком доменов, и использование LLM-судьи для проверки, действительно ли каждый источник поддерживает цитируемое утверждение. Опциональный исходящий HTTP. То же требование BYOK, что и уevaluate_with_llm_judge.compare_runs— Сделало ли изменение агента хуже? Сравнивает два запуска сохранённых оценок: парный точный тест, когда запуски имеют общие ключи случаев, интервал на разницу в противном случае, честное «нельзя сказать» с количеством случаев, которое потребуется, или «эквивалентно в пределах допуска». Каждая метрика несёт свой собственный односторонний тест, скорректированный вместе (Бенджамини–Хохберг), чтобы двадцать метрик не могли сфабриковать регрессиюcompare_traces— Насколько надёжно агент отвечает на один и тот же вопрос? Показатели прохождения по случаям с интервалами, сначала нестабильные случаи, и общий показатель, учитывающий повторыevaluate_runs— Переоценка каждой трассы в запуске по сегодняшним метрикам в новый запуск, чтобы изменение метрик никогда не читалось как изменение агента
Включение LLM-судьи (опционально; детерминированные метрики никогда в нём не нуждаются)
- Получите API-ключ от Anthropic или OpenAI.
- Поместите его в окружение процесса, который запускает Iris, а не только в вашу оболочку. Claude Code, Claude Desktop, Cursor и большинство MCP-клиентов: блок «env» записи iris-eval в вашем MCP-конфиге — «iris-eval»: { «command»: «npx», «args»: [«-y», «@iris-eval/mcp-server»], «env»: { «IRIS_ANTHROPIC_API_KEY»: «sk-ant-...» } } (IRIS_OPENAI_API_KEY для ключа OpenAI). Docker: -e IRIS_ANTHROPIC_API_KEY=... в команде запуска. HTTP или CI: экспортируйте его перед запуском iris-eval.
- Перезапустите MCP-сессию. Запущенный процесс никогда не видит переменную, установленную после его старта.
- Подтвердите из вашего клиента: прочитайте iris://capabilities — judge.enabled должно быть true там. Ключ, экспортированный в вашей оболочке, не передаётся процессу, который запускает ваш клиент, если его конфиг не перечисляет его. На машине
npx @iris-eval/mcp-server --self-testпечатает строку судьи для этой оболочки, а GET /api/v1/health сообщает judge.enabled на запущенной панели. - Защита расходов: каждый вызов ограничен IRIS_LLM_JUDGE_MAX_COST_USD_PER_EVAL (по умолчанию 0,25 USD) и отклоняется до любых расходов, если худший случай превысит его. Iris вызывает провайдера напрямую с вашим ключом и никогда не проксирует его.
- Опционально: установите IRIS_RELEVANCE_JUDGE_MODEL на идентификатор тарифицируемой модели (например, claude-haiku-4-5), чтобы answers_the_ask спрашивал судью, отвечает ли каждый ответ на свой вопрос, и проваливал не по теме. Это один вызов судьи на каждую оценку, которая несёт ввод, на вашем ключе и под указанным выше лимитом; сам ключ никогда не включает это. Каждый вызов отправляет этот ввод и вывод провайдеру модели, с предварительно заменёнными флагами no_pii для персональных данных и учётных данных (IRIS_RELEVANCE_JUDGE_REDACT=off отправляет их как есть). Это тратит не более IRIS_RELEVANCE_JUDGE_DAILY_BUDGET_USD за UTC-день (по умолчанию 1 USD) и делает не более IRIS_RELEVANCE_JUDGE_MAX_CALLS_PER_REQUEST вызовов за запрос (по умолчанию 20); после любого из них answers_the_ask читает вопрос лексически и объясняет почему.
Когда IRIS_OTEL_ENDPOINT настроен, вызовы log_trace также отправляют экспорт OTLP/HTTP JSON с максимальными усилиями в любой коллектор OpenTelemetry (Jaeger, Grafana Tempo, Datadog OTLP, Honeycomb и т. д.). См. docs/otel-integration.md.
Как решается passed
evaluate_output возвращает и флаг score, и флаг passed — они отвечают на разные вопросы:
score(0..1) — это взвешенное среднее по метрикам, которые выполнялись — градиент качества.passed— это вердикт «выпускать/не выпускать», и оценка для него никогда не используется. Компоновщик читает каждую метрику по типу её утверждения: настроенная вами политика блокирует; критический детектор накладывает вето; критическая проверка, которая была запрошена и не смогла ответить, делает вердикт неизвестным (passed: false), а не чистым; каждый оставшийся детектор объединяется в одну вероятность того, что вывод плохой, взвешенную против коэффициента потерь, который вы указываете вeval.falsePassCost(по умолчанию 1, поэтому отсечение — 0,5).verdict.basisназывает слой, который решил, аverdict.by— метрики, иverdict.alsoперечисляет каждый более поздний слой, который тоже решил бы это;interpretations[]объясняет, почему метрика, которая провалилась, не решила, и какой параметр это изменит, а также называет любой вопрос, который не был оценён, и ввод, который позволил бы это сделать.
Настоящие нарушения безопасности жёстко проваливаются. По умолчанию no_pii, no_injection_patterns и no_blocklist_words являются критическими метриками: если одна проваливается, оценка сообщает passed: false независимо от того, как хорошо оценились другие метрики, и ответ называет виновных в critical_failures. Утёкший SSN нельзя усреднить. Какие встроенные метрики являются критическими — это параметр развёртывания (eval.criticalRules / eval.nonCriticalRules); каждый результат метрики несёт эффективный флаг critical и criticalSource, а list_rules сообщает список, который применяет этот сервер. Пользовательские метрики, развёрнутые с severity: "high" или "critical", жёстко проваливаются так же; серьёзности low/medium влияют только на оценку. Одна граница, которую нужно знать, сформулированная одинаково на всех поверхностях: критическая метрика, которая пропущена (отсутствующий контекст, сломанное определение или regex, убитый на бюджете песочницы), не оценила вывод и не накладывает вето — каждая такая метрика названа в critical_skipped. Шлюз, который должен закрываться при сбое, обрабатывает непустой critical_skipped как неизвестный, а не чистый, и может обрабатывать любой пропуск budgetExceeded в rule_results так же.
Для CI-шлюзов: если вы опускаете eval_type, выполняются все пакеты — полнота, релевантность, безопасность, стоимость и любые пользовательские метрики — и ответ говорит eval_type: "all" с note, что по умолчанию выполнялся, плюс карта categories для каждого пакета. Пакет, которому нечего оценивать (стоимость без cost_usd, релевантность без input), сообщает passed: null там — не оценено, не провалено — и никогда не учитывается в вердикте. Ответ всегда повторяет eval_type, который выполнялся, чтобы ваш шлюз мог проверить покрытие; ориентируйтесь на passed для вердикта и называйте пакет только тогда, когда хотите более узкий запуск.
Создание пользовательской метрики
Два способа добавить метрику. Встроенные метрики едут вместе с одним вызовом evaluate_output (custom_rules, до 10 на вызов); они срабатывают вместе с выбранным вами пакетом eval_type или отдельно с eval_type: "custom". Развёрнутые метрики регистрируются один раз с помощью deploy_rule, сохраняются в custom-rules.json в вашем Iris home и срабатывают на каждом будущем evaluate_output их evalType. Определение имеет одинаковую форму в обоих случаях:
| Поле | Обязательно | Что это |
|---|---|---|
name | да | 1–80 символов; появляется как ruleName в результатах |
type | да | одно из regex_match · regex_no_match · min_length · max_length · contains_keywords · excludes_keywords · json_schema · cost_threshold |
config | да | ключи для этого типа: pattern (+ опциональный flags) для двух regex-типов · min_length / max_length (счётчик символов) · keywords (+ опциональный threshold, 0–1, по умолчанию 1 = все должны появиться) для двух keyword-типов · {} для json_schema · max_cost в USD для cost_threshold |
weight | нет | вес в оценке; по умолчанию 1 |
deploy_rule оборачивает определение с name, опциональным description, evalType (completeness · relevance · safety · cost · custom) и severity. Серьёзность говорит, что означает провал: low/medium только снижают оценку; high/critical жёстко проваливают оценку — passed: false, метрика, названная в critical_failures — независимо от взвешенной оценки. Метрика, которая пропускает (метрика cost_threshold без cost_usd или regex, убитый на бюджете песочницы 100 мс), не оценила вывод и перечислена в critical_skipped вместо этого. Разверните критическую метрику, которая запрещает внутренние hostname в любом выводе агента:
{
"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" }
}
}
Ответ — это сохранённая метрика — сохраните id для delete_rule:
{ "rule": { "id": "rule-588823d0", "name": "no_internal_hostnames", "evalType": "safety", "severity": "critical", "enabled": true, "version": 1, "definition": { "…": "…" } } }
С самого следующего evaluate_output с eval_type: "safety" вывод, упоминающий db-primary.internal.example, возвращается passed: false с critical_failures: ["no_internal_hostnames"] — даже если все пять встроенных метрик безопасности прошли и взвешенная оценка равна 0,895. Regex-паттерны должны проходить проверку ReDoS при развёртывании и всегда выполняются в worker-песочнице под жёстким дедлайном 100 мс. list_rules показывает, что развёрнуто; компоновщик метрик на панели создаёт ту же форму из сбоя, на который вы нажали. Полная справка, оценка по типам и рабочие примеры: docs/custom-rules.md.
Полные схемы инструментов и конфигурация: iris-eval.com
Хостируемые функции
Iris полностью работает на вашей машине сегодня, и всё, что он делает, бесплатно и лицензировано MIT без ограничений и без аккаунта. Хостируемое хранилище, общая история команды и оповещения находятся на стадии рассмотрения, а не разработки. Никаких цен нет, и покупать нечего. Если общая история была бы вам полезна, лист ожидания — это способ узнать, стоит ли её создавать — он ни к чему вас не обязывает.
Два обязательства остаются неизменными: ничто из того, что сейчас бесплатно, не станет платным, и ни один сертификат соответствия не будет заявлен до его получения.
Примеры
- Настройка Claude Desktop — конфигурация MCP для режимов stdio и HTTP
- TypeScript — MCP SDK клиент — подключение и вызов инструментов
- HTTP транспорт (TS + Python) — полный клиентский код для REST-интеграции
- Агент LangGraph, оценённый запуск за запуском (Python) —
IrisCallbackHandlerв колбэках графа; CI запускает тот же граф со скриптовой моделью - Команда CrewAI через OpenTelemetry (Python) — инструментатор OpenInference напрямую к OTLP-порту Iris, рецепт CrewAI как скрипт
- Агент OpenAI Agents SDK через OpenTelemetry (Python) и (JavaScript), а также агент LlamaIndex — каждый рецепт как скрипт, который CI запускает против реального сервера Iris
Сообщество
- GitHub Issues — отчёты об ошибках и запросы функций
- GitHub Discussions — вопросы и идеи
- Руководство по участию — как внести вклад
- HTTP Ingest — детерминированный захват трассировок через
POST /api/v1/traces - Карта возможностей — каждый вопрос, который можно задать Iris, и чего ему не хватает
- Политика версионирования — что обещает каждый номер версии и что должно быть истинным до версии 1.0
Конфигурация и безопасность
Аргументы CLI
| Флаг | По умолчанию | Описание |
|---|---|---|
--transport | stdio | Тип транспорта: stdio или http |
--port | 3000 | Порт HTTP-транспорта |
--db-path | ~/.iris/iris.db | Путь к базе данных SQLite |
--config | ~/.iris/config.json | Путь к файлу конфигурации |
--api-key | — | API-ключ для HTTP-аутентификации (транспорт и панель управления, включая POST /api/v1/traces) |
--dashboard | false | Включить веб-панель управления. Также единственный способ запуска конечной точки приёма POST /api/v1/traces — она никогда не запускается неявно с --transport http |
--dashboard-port | 6920 | Порт панели управления |
--dashboard-host | 127.0.0.1 | Адрес привязки панели управления. По умолчанию loopback — панель управления не аутентифицирована, если не задан --api-key, поэтому привязка за пределами loopback открывает всю вашу историю трассировок |
--demo | false | Заполнить демонстрационную базу данных (отдельно от ваших реальных трассировок) и обслуживать панель управления на её основе |
--demo-clear | false | Удалить демонстрационную базу данных и выйти |
--self-test | false | Запустить офлайн-диагностику установки в изолированном временном домашнем каталоге, затем выйти (0 = здорово, 1 = проверка не пройдена). Также читает настроенную базу данных, только для чтения, и завершается с ошибкой, если эта версия или закреплённый MCP-клиент не могут её открыть |
--purge | false | Удалить все сохранённые трассировки, спаны и оценки из настроенной базы данных, сжать файл и усечь журнал упреждающей записи, чтобы удалённый текст не оставался на диске, затем выйти. Развёрнутые правила, журнал аудита и предпочтения сохраняются. Необратимо. Сначала остановите любой запущенный сервер Iris — файл сжимается на месте. Отказывается сочетаться с --demo, --demo-clear или --self-test |
--version | — | Вывести чистую версию (например, 1.2.3) в stdout и выйти с кодом 0. Ничего не читает в вашем домашнем каталоге Iris |
Три команды принимают свои собственные аргументы и завершаются: iris-eval ingest загружает трассировки из файла или stdin (CI-шлюз, сервер не нужен), iris-eval export traces|evaluations --format csv|jsonl записывает сохранённое, отфильтрованное как списки панели управления, в stdout или --out (docs/api-reference.md), а iris-eval install <client> записывает Iris в конфигурацию MCP-клиента — --uninstall удаляет его, --list показывает найденные на этой машине клиенты и запущенный Iris, --upgrade переводит каждый клиент, запускающий Iris, на эту версию (Подключите своего агента, Обновление). Ни одна из них не запускает сервер.
config.json проверяется при запуске Iris. Ключ, который Iris не читает — опечатка вроде eval.critcalRules, ключ от другого инструмента — или значение неверного типа приводит к отказу запуска с одним предложением, называющим полный ключ, наиболее вероятный ключ или ожидаемый тип. Ничто в файле не игнорируется молча.
Переменные окружения
Каждая переменная документирована в --help. Флаги CLI имеют приоритет над переменными окружения, когда заданы оба.
| Переменная | Описание |
|---|---|
IRIS_TRANSPORT | Тип транспорта (stdio или http) |
IRIS_HOST | Адрес привязки HTTP-транспорта (по умолчанию 127.0.0.1) |
IRIS_PORT | Порт HTTP-транспорта (1-65535, по умолчанию 3000) |
IRIS_HOME | Каталог для всех пользовательских файлов: config.json, iris.db, custom-rules.json, audit.log, preferences.json (по умолчанию ~/.iris) |
IRIS_DB_PATH | Путь к базе данных SQLite (переопределяет IRIS_HOME только для БД) |
IRIS_SQLITE_DRIVER | Какой драйвер SQLite хранит базу данных: native (better-sqlite3, по умолчанию) или node (встроенный node:sqlite Node, Node 22.13+). Не задано: нативный, и когда нативный модуль не может загрузиться (или это сборка, которая прервёт работу на этом Node), Iris предупреждает один раз и переключается на встроенный |
IRIS_SEARCH_BUDGET_MS | Как долго один поиск трассировок (q) может читать, прежде чем ответит найденными на данный момент совпадениями и search.complete: false, в миллисекундах (от 50 до 60000, по умолчанию 1000). Поиск удерживает другие запросы, пока читает, поэтому это также максимальное время ожидания для них. Также storage.searchBudgetMs в config.json |
IRIS_SEARCH_INDEX | on (по умолчанию) или off. off не хранит полнотекстовый индекс трассировок: запись сохраняет трассировку и ничего больше, а поиск трассировок (q) читает сами трассировки в пределах IRIS_SEARCH_BUDGET_MS, сначала новые, поэтому в большом хранилище он может ответить частью совпадений (search.complete: false). Отключение стирает индекс, который хранила база данных; повторное включение создаёт новый в фоновом режиме. Также storage.searchIndex в config.json |
IRIS_LOG_LEVEL | Уровень журналирования: debug, info, warn, error |
IRIS_DASHBOARD | true/1/yes/on включает веб-панель управления; false/0/no/off отключает её (также переопределяет dashboard.enabled в config.json) |
IRIS_DASHBOARD_PORT | Порт панели управления (1-65535, по умолчанию 6920) |
IRIS_WEBHOOK_URL | Получатель вебхука, срабатывающего в определённый момент — объединяется с notify.webhook в config.json (docs/webhooks.md) |
IRIS_WEBHOOK_SECRET | Ключ подписи вебхука (любая строка или whsec_ + base64); формат iris отказывается работать без него |
IRIS_DASHBOARD_HOST | Адрес привязки панели управления (по умолчанию 127.0.0.1) |
IRIS_API_KEY | API-ключ для HTTP-аутентификации. Обязателен для привязки HTTP-транспорта или панели управления за пределами loopback (0.0.0.0, LAN-адрес, контейнер): без него сервер отказывается запускаться |
IRIS_API_KEY_FILE | Путь к файлу, чьё обрезанное содержимое является API-ключом — шаблон секретного файла, который монтируют Docker и Kubernetes, чтобы ключ никогда не находился в блоке окружения. Задайте это или IRIS_API_KEY, но не оба |
IRIS_ALLOW_UNAUTHENTICATED | Установите в 1, чтобы намеренно выполнить привязку вне loopback без ключа (снимает отказ; тогда ваша сеть — ваша граница) |
IRIS_ALLOWED_ORIGINS | Разделённый запятыми список разрешённых источников. Панель управления: CORS-заголовки (поддерживает glob, например http://localhost:*). HTTP-транспорт: точное совпадение Origin для защиты от DNS-ребinding (glob игнорируются; собственные loopback-источники сервера всегда разрешены) |
IRIS_NO_AUTO_LAUNCH | Установите в 1, чтобы отключить автозапуск панели управления при первом запуске |
IRIS_ANTHROPIC_API_KEY | Требуется evaluate_with_llm_judge + verify_citations с provider=anthropic |
IRIS_OPENAI_API_KEY | Требуется evaluate_with_llm_judge + verify_citations с provider=openai |
IRIS_LLM_JUDGE_MAX_COST_USD_PER_EVAL | Жёсткий предел стоимости на один вызов LLM-судьи (по умолчанию 0.25) |
IRIS_RELEVANCE_JUDGE_MODEL | Идентификатор платной модели-судьи (например, claude-haiku-4-5). Когда задан, с ключом этого провайдера, answers_the_ask запрашивает этого LLM-судью при каждой оценке, содержащей входные данные, и принимает решение на основе его вердикта о релевантности — один вызов судьи на оценку, в пределах указанного выше лимита стоимости и двух лимитов ниже. Входные и выходные данные каждой такой оценки отправляются провайдеру этой модели (Anthropic или OpenAI) по вашему ключу, при этом личные данные и учётные данные, помеченные no_pii, заменяются первыми. Не задано (по умолчанию), answers_the_ask читает запрос лексически и даёт рекомендации, ничего не отправляя (docs/llm-as-judge.md) |
IRIS_RELEVANCE_JUDGE_DAILY_BUDGET_USD | Сколько судья релевантности может потратить за UTC-день на одного тенанта (по умолчанию 1). Хранится в базе данных, поэтому перезапуск не сбрасывает его. Вызов делается только если его худший случай укладывается в остаток; после этого answers_the_ask читает запрос лексически и judge.withheld равен daily_budget. 0 останавливает все вызовы |
IRIS_RELEVANCE_JUDGE_MAX_CALLS_PER_REQUEST | Вызовы судьи релевантности, которые может сделать один запрос (по умолчанию 20): пакет OTLP или повторная оценка evaluate_runs оценивает первые 20 трассировок и читает остальные лексически, с judge.withheld: "request_cap" |
IRIS_RELEVANCE_JUDGE_REDACT | on (по умолчанию): каждый спан, помеченный no_pii (личные данные и учётные данные) во входных и выходных данных, заменяется маркером [REDACTED:<kind>#<n>] перед отправкой судье релевантности. off отправляет их как есть |
IRIS_CITATION_ALLOW_FETCH | Установите в 1, чтобы разрешить исходящий HTTP в verify_citations (по умолчанию выключено) |
IRIS_CITATION_DOMAINS | Разделённый запятыми список разрешённых имён хостов для verify_citations (совпадение по суффиксу) |
IRIS_OTEL_ENDPOINT | Включить экспорт трассировок OTLP/HTTP JSON с максимальными усилиями на этот URL коллектора |
IRIS_OTEL_SERVICE_NAME | Атрибут ресурса service.name для экспорта OTel (по умолчанию iris-eval) |
IRIS_OTEL_HEADERS | Разделённые запятыми заголовки k=v для экспорта OTel (например, authorization=Bearer abc) |
IRIS_OTEL_TIMEOUT_MS | Таймаут на экспорт (по умолчанию 15000) |
RATE_LIMIT_SALT | Только для API листа ожидания сайта — требуется при развёртывании сайта iris-eval.com; сервер никогда его не читает |
Безопасность
При использовании HTTP-транспорта Iris включает:
- Аутентификацию по API-ключу с сравнением, устойчивым к таймингу (Bearer для API-клиентов; вход в браузере на панель управления через
?key=) - CORS, ограниченный localhost по умолчанию
- Ограничение скорости на адрес клиента и минуту: 600 запросов к API панели управления (
security.rateLimit.api) и 20 к конечной точке MCP (security.rateLimit.mcp), оба заданы вconfig.json; MCP-запрос сверх лимита получает ошибку JSON-RPC, называющую ключ - Заголовки безопасности Helmet
- Проверку входных данных Zod на всех маршрутах
- ReDoS-безопасное регулярное выражение для пользовательских правил оценки
- Один лимит размера запроса 1 МБ на каждом транспорте (
security.requestSizeLimit): HTTP отвечает413, stdio отвечает ошибкой JSON-RPC и сохраняет сеанс открытым
# Production deployment
iris-eval --transport http --port 3000 --api-key "$(openssl rand -hex 32)" --dashboard
С установленным ключом API-клиенты — MCP-клиенты, capture SDK, POST /api/v1/traces — отправляют Authorization: Bearer <key>. Чтобы открыть дашборд в браузере, добавьте ключ один раз к любому URL дашборда, http://localhost:6920/?key=<api key>: Iris обменивает его на HttpOnly, SameSite=Lax сессионную cookie и перенаправляет на ту же страницу с ключом, удалённым из адресной строки. Страница, открытая без сессии, показывает форму входа, которая выполняет тот же обмен. Ключ никогда не хранится в браузере, а сессии живут только в процессе сервера (не более 256 одновременно; вход, который обнаруживает, что все они заняты, отклоняется, а не вытесняет одну).
Продакшн
Несколько ключей и ротация без разрыва. security.apiKeys в config.json содержит любое количество дополнительных ключей, каждый с id и ровно одним из keyFile (файл, чьё обрезанное содержимое является ключом) или keyHash (sha256 hex ключа, так что файл конфигурации не содержит секретов — printf %s "$KEY" | openssl dgst -sha256), и необязательный expiresAt (ISO 8601), после которого он перестаёт совпадать в этот момент. Для ротации: добавьте новый ключ, переведите клиентов, удалите старый ключ. Ключи в config.json и в файлах ключей вступают в силу без перезапуска (0.20.0): при каждом запросе сервер проверяет, изменился ли config.json или файл ключа, который он называет, и если да, перечитывает ключи перед ответом. Удаление ключа из security.apiKeys или удаление его файла ключа отзывает его при следующем запросе: этот запрос отклоняется, и каждая сессия браузера, открытая с ним, выходит из системы. config.json, который не может быть прочитан (например, наполовину записанный), завершается с ошибкой, и пока он не исправлен, принимается только ключ из IRIS_API_KEY или --api-key. Ключ в IRIS_API_KEY или --api-key сам по себе, а также то, включена ли аутентификация вообще, по-прежнему меняются только при перезапуске. Каждый ключ аутентифицирует, пока он не удалён или не истёк, как на Bearer-пути, так и при входе в браузер; журнал запуска называет идентификаторы. security.rateLimit.mcpKeyBy: "apiKey" считает поминутный бюджет конечной точки MCP на ключ, а не на адрес клиента, так что несколько агентов за одним адресом получают свою собственную минуту.
Iris отказывается запускаться, когда HTTP-транспорт или дашборд привязаны за пределами loopback — 0.0.0.0, LAN-адрес, контейнер — без ключа API, и сообщает об этом одним предложением, называя IRIS_API_KEY. Это включает голый docker run образа, который привязывает 0.0.0.0 внутри контейнера, потому что loopback недоступен через опубликованный порт. Loopback без ключа продолжает работать (с предупреждением на HTTP-транспорте): граница машины является там контролем воздействия.
# 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
Открыт по дизайну на сервере с ключом: GET /health на транспорте и GET /api/v1/health на дашборде отвечают без ключа и вне всяких ограничений скорости, в одной форме: статус, версия, время работы, драйвер SQLite, checks для хранилища, файл развёрнутых правил и миграции (применённые против известных), состояние поискового индекса (search: готов или насколько далеко продвинулась сборка как доля трасс), и присутствует ли ключ судьи — никогда ключ, никогда трасса, никогда их количество. status является ok только когда все проверки таковы; в противном случае это degraded с HTTP 503, который читает собственный HEALTHCHECK Docker-образа. Всё остальное требует Authorization: Bearer <key> или сессии браузера. Хранение выполняется на каждом сервере: трассы и оценки старше retention.days (по умолчанию 30) удаляются при запуске, как только сервер начинает отвечать, и каждые retention.sweepIntervalHours, короткими шагами, которые никогда не заставляют запрос долго ждать; --self-test печатает политику этой установки, а iris://capabilities / GET /api/v1/capabilities несут её как retention.
Вебхук срабатывает в момент (0.16.0): notify.webhook в config.json (или IRIS_WEBHOOK_URL и IRIS_WEBHOOK_SECRET) называет получателя, и Iris отправляет одно подписанное сообщение, когда вердикт не проходит, критическое обнаружение накладывает вето, стоимость является выбросом, частота отказов правила сдвигается или случай отвечен обоими способами впервые — идентификаторы, вердикт, правила и числа, никогда текст агента. Подписано способом Standard Webhooks и способом GitHub одновременно, повторяется с задержкой, охлаждается на агента и правило, никогда не мешает оценке; встроены тела Slack и Discord. docs/webhooks.md.
Ваши данные на диске
Всё, что хранит Iris, находится в вашем домашнем каталоге Iris (~/.iris или IRIS_HOME). iris.db хранит input и output каждой трассы дословно — включая любой текст, который no_pii затем помечает; обнаружение не редактирует, если вы не попросите: storage.redact: "critical_spans" в config.json хранит вывод каждой оценки с заменами промежутков, помеченных критическим детектором, на [REDACTED:<pattern>] (выключено по умолчанию; смещения доказательств всё ещё индексируют текст, который видел вызывающий). storage.synchronous устанавливает, когда запись достигает диска: normal (по умолчанию) синхронизирует журнал упреждающей записи при каждой контрольной точке, так что сбой Iris ничего не теряет и файл не может быть повреждён, но отключение питания или сбой операционной системы могут отменить записи с момента последней синхронизации; full синхронизирует каждый коммит и сохраняет их при обоих, примерно на 1,5 мс больше на запись. При запуске и каждые retention.sweepIntervalHours (по умолчанию 24, 0 отключает таймер) после этого трассы и оценки старше retention.days (по умолчанию 30, 0 отключает, устанавливается в config.json) удаляются, и журнал упреждающей записи контрольной точки. Удаление трассы — по delete_trace или подметанием — стирает текст каждой связанной оценки (вывод, ожидаемый текст и сообщения правил) и ставит штамп erased_at; вердикт, оценки и смещения доказательств остаются. Каждое удаление контрольной точки журнала упреждающей записи перед возвратом, так что удалённый текст не остаётся читаемым в iris.db или iris.db-wal (если поиск читает файл в этот момент или другой процесс читает или пишет его, удаление возвращается без ожидания, и текст покидает файл, как только тот завершится). Чтобы удалить всё сейчас, остановите сервер и запустите --purge: он удаляет каждую сохранённую трассу, промежуток и оценку, уплотняет базу данных и усекает журнал упреждающей записи, так что текст исчезает с диска, и сохраняет ваши развёрнутые правила, журнал аудита и предпочтения. Перед тем как релиз применяет миграцию к существующему iris.db, он копирует файл рядом с ним (iris.db.<from>-to-<to>.<time>.bak, только владелец, сохраняются три новейших; Downgrading): копия содержит трассы как они были, так что подметание хранения удаляет старше retention.days, а --purge удаляет их все. Сервер выполняет копирование и миграции после того, как ответил своему клиенту, в собственном потоке: вызовы инструментов, чтения ресурсов и HTTP-запросы, прибывающие тем временем, ждут их, максимум 30 секунд каждый, и затем отклоняются с предложением, говорящим, что делает сервер (IRIS_STORAGE_ERROR, повторяемый; HTTP 503 с Retry-After). Здоровье отвечает всё время и говорит, что делает обновление. С 0.19.0 при 100 000 трасс, каждая из которых является циклом агента, копирование и миграции заняли около 6 секунд. iris-eval ingest, --purge и --self-test всё ещё обновляются, прежде чем делать что-либо ещё.
Iris не шифрует свои данные в состоянии покоя. iris.db и его файлы журнала упреждающей записи создаются только для владельца (режим 600), а домашний каталог Iris создаётся в режиме 700 (на Windows вместо этого действуют ACL файлов). База данных не хранит ключи LLM-провайдеров: IRIS_ANTHROPIC_API_KEY и IRIS_OPENAI_API_KEY читаются из окружения и никогда не записываются на диск. Она хранит входы и выходы трасс дословно, поэтому поместите домашний каталог Iris на зашифрованный диск или том (FileVault, BitLocker, LUKS или зашифрованный облачный том для /data монтирования Docker-образа).
Экспорт — кнопка Export на страницах Traces и Evaluations дашборда, GET /api/v1/traces/export и /api/v1/evaluations/export или iris-eval export — переносит этот сохранённый текст как есть, так же, как его показывает дашборд: вход и выход трассы дословно, вывод оценки с применённым storage.redact. Обращайтесь с экспортированным файлом как с базой данных, из которой он пришёл.
Устранение неполадок
Первый шаг: запустите самопроверку
npx @iris-eval/mcp-server --self-test
Она проверяет хранилище, детерминированные оценки и дашборд в изолированном временном домашнем каталоге и печатает вердикт по каждому шагу — вывод сбоя называет сломанный шаг. Код выхода 0 означает, что установка здорова.
Iris не запускается / ERR_MODULE_NOT_FOUND
Возможно, у вас кэширована старая версия. Очистите кэш npx и повторите:
npx --yes @iris-eval/mcp-server@latest
Или установите глобально, чтобы полностью избежать проблем с кэшем:
npm install -g @iris-eval/mcp-server@latest
npm install --ignore-scripts сломал привязку SQLite
Iris хранит трассы с помощью better-sqlite3, нативного модуля, который загружает или компилирует свою привязку в установочном скрипте. Если этот скрипт был пропущен — --ignore-scripts в командной строке, ignore-scripts=true в .npmrc (обычно на корпоративных машинах) или зеркало реестра, которое удаляет postinstall — запуск завершается сбоем с длинным дампом «Could not locate the bindings file», перечисляющим дюжину путей, которые он попробовал. Пересоберите этот один модуль:
npm rebuild better-sqlite3
# for a global install:
npm rebuild -g better-sqlite3
Инструменты не отображаются в Claude Code
Инструменты MCP загружаются только при запуске сессии. После добавления iris-eval перезапустите сессию с /clear или перезапустите терминал.
Проверка версии
npx @iris-eval/mcp-server --version
Первая строка журнала запуска также содержит её (Starting Iris MCP server vX.Y.Z), а --self-test печатает её в своей сводке. Для глобальной установки npm ls -g @iris-eval/mcp-server показывает установленную версию.
Обновление
Каждый MCP-клиент на машине использует одну базу данных, ~/.iris/iris.db, а install привязывает каждого клиента к релизу, который записал его конфигурацию. Когда релиз изменяет схему базы данных, первый процесс этого релиза, открывающий файл, обновляет его, и с этого момента клиент, всё ещё привязанный к старому релизу, отказывается запускаться. Поэтому переместите всех клиентов за один шаг, до или сразу после обновления:
npx -y @iris-eval/mcp-server@latest install --upgrade
Он находит каждый конфиг клиента на этой машине, который запускает Iris, перемещает каждую привязку на этот релиз (сохраняя всё, что вы добавили в запись, например --dashboard или блок env), оставляет в покое привязку к более новому релизу и запись, которая запускает что-то другое, кроме npm-пакета, и перечисляет, что он сделал. Перезапустите клиенты, которые он называет. install --list показывает, какой Iris запускает каждый клиент.
Две установки живут вне этих файлов: расширение Claude Desktop (iris-eval.mcpb) перемещается, когда вы открываете более новый пакет, и плагины Claude Code с claude plugin marketplace update iris-eval, а затем claude plugin update iris-eval@iris-eval (и claude plugin update iris-eval-capture@iris-eval для плагина захвата).
Обновление с 0.19.x до 0.20.0. 0.20.0 добавляет поисковый индекс и другие дополнения к базе данных (миграции 015 и позже). Как только любой процесс 0.20.0 открыл ~/.iris/iris.db (расширение Claude Desktop, npx iris-eval или npx @iris-eval/mcp-server без версии), клиент, привязанный к 0.19.x, останавливается с This database was migrated by a newer Iris (…) — migration(s) 015-trace-search, … are unknown to v0.19.0. Upgrade Iris, …. Это сообщение приходит из 0.19.x и не может измениться; исправление — команда выше. Перед обновлением 0.20.0 копирует файл рядом с ним, так что возврат также возможен (ниже).
Запуск, который обновляет базу данных, печатает, что он сделал, в stderr: сделанную копию, какие старые релизы больше не могут открыть файл, и любого клиента на этой машине, привязанного к одному из них, с командой. --self-test читает базу данных без изменений и говорит то же самое, прежде чем вы что-либо запустите.
Для глобальной установки npm update -g @iris-eval/mcp-server, затем iris-eval install --upgrade.
Понижение версии
Релиз, обновивший базу данных, сначала копирует её рядом с оригиналом: iris.db.<from>-to-<to>.<time>.bak в вашем домашнем каталоге Iris (<from> — это релиз, который последним изменил схему файла, <to> — тот, который её обновил; строка при запуске выводит точный путь). Чтобы откатиться:
- Остановите каждого MCP-клиента и любой другой процесс Iris, использующий базу данных.
- Сохраните обновлённый файл на случай возврата: переименуйте
iris.dbвiris.db.upgradedи удалитеiris.db-walиiris.db-shm, если они есть. - Скопируйте резервную копию в
iris.db:cp ~/.iris/iris.db.0.19.0-to-0.20.0.<time>.bak ~/.iris/iris.db. - Закрепите каждого клиента за более старой версией:
npx -y @iris-eval/mcp-server@0.19.0 install <client>для каждого (install --upgradeникогда не переводит клиента назад).
Трассы, сохранённые после обновления, находятся в iris.db.upgraded, а не в резервной копии. Если копия не была создана (строка при запуске объясняет причину, например, переполненный диск), старая версия не сможет открыть обновлённый файл, и путь вперёд — install --upgrade.
Драйвер хранилища
На платформе без готового better-sqlite3 установка всё равно завершается успешно. better-sqlite3 — это опциональная зависимость: когда npm не может ни загрузить готовый бинарный файл для вашей версии Node и платформы, ни скомпилировать его (для компиляции нужны Python и инструментарий C++ — инструменты сборки C++ от Visual Studio на Windows), npm выводит ошибку сборки, пропускает модуль и завершает установку. Iris затем работает на встроенном SQLite от Node и сообщает об этом: при запуске выводится одна строка в stderr с указанием причины, а --self-test показывает driver node: better-sqlite3 is not installed …. Чтобы вернуть нативный драйвер, установите его там, где есть готовый бинарный файл или инструментарий (npm install better-sqlite3 в проекте; для глобальной установки переустановите Iris с npm install -g @iris-eval/mcp-server, когда инструментарий станет доступен). CI устанавливает упакованный сервер с принудительным сбоем нативной сборки при каждом изменении и требует, чтобы установка завершилась, а самотест сохранил и прочитал трассу на встроенном драйвере.
Iris хранит всё в одном файле SQLite, открываемом через better-sqlite3 — нативный аддон, который загружается или компилируется для вашей версии Node и платформы. Когда этот модуль не может загрузиться, Iris переключается на встроенный SQLite от Node (node:sqlite, Node 22.13 или новее) с одним предупреждением в stderr, так что отсутствие готового бинарного файла означает более медленный запуск, а не полный отказ. То же самое происходит перед загрузкой для better-sqlite3, скомпилированного на вашей машине с заголовками Node 24.19 или новее: во всех выпусках 24.x такой бинарный файл прерывает весь процесс при первом освобождении оператора (Assertion failed: (env) != nullptr, nodejs/node#65446), и npm rebuild better-sqlite3 заменяет его на готовый бинарный файл, который безопасен. IRIS_SQLITE_DRIVER=node намеренно выбирает встроенный драйвер, native запрещает откат. Встроенный драйвер открывается с отключённой загрузкой расширений и отключённым trusted_schema; Node выводит свою собственную строку ExperimentalWarning: SQLite is an experimental feature в stderr при загрузке, и Iris её не подавляет. --self-test и GET /health указывают используемый драйвер; каждое число на странице доказательств было измерено на нативном драйвере, а набор тестов запускается на обоих в CI.
Версия Node.js
Iris требует Node.js 22.13 или новее. Node 20 достиг конца жизненного цикла 2026-04-30 и не поддерживается; Node 18 — в апреле 2025 года.
Минимальная версия — 22.13, а не 22.0, потому что 22.13.0 — это первый выпуск, который включает node:sqlite. Это делает его первой версией, на которой каждая поддерживаемая установка Iris имеет второй драйвер хранилища: когда нативный аддон better-sqlite3 не загружается, Iris переключается на встроенный SQLite от Node вместо отказа запускаться. Ниже 22.13 — и на Node 20 на протяжении всего его жизненного цикла — был только один драйвер, и отсутствие готового бинарного файла означало невозможность запуска.
node --version # Must be v22.13.0 or newer
Windows: cmd /c не требуется
/doctor от Claude Code может предлагать обернуть npx в cmd /c. Это не требуется и вызывает проблемы с разбором путей. Используйте npx напрямую:
# 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"
Если Iris полезен для вас, рассмотрите возможность поставить звезду репозиторию — это помогает другим найти его.
Лицензия MIT.