Agent Lab MCP Server
Agent Lab
Образовательное приложение для изучения технического потока ИИ-агентов, работающих с корпоративными данными. Проект демонстрирует контракты, протоколы, вызовы инструментов, структурированные результаты и санитизированные трассы.
Статус
Этапы 1–11 — Фундамент, MCP, Agent Runtime, Auth/RBAC, A2A, оценки, облако, CI/CD, детерминированные контракты, отказоустойчивость и семантическая устойчивость:
фронтенд React + Vite;
бэкенд Node.js + Express;
схема и миграции PostgreSQL;
пользователи приложения
adminиviewer;детерминированные календарные периоды;
технический контракт
TraceEvent;модульные тесты и визуальная оболочка Agent Lab;
официальный MCP-сервер поверх транспорта
stdio;семь MCP-инструментов только для чтения со структурированными ответами;
параметризованные PostgreSQL-запросы через роль с минимальными привилегиями.
Ollama с
qwen3:8bдля локального вывода и tool calling без затрат на токены;OpenAI Responses API сохранён как опциональный провайдер;
локальный MCP Client с обнаружением и выполнением инструментов;
grounded-оркестратор и конечная точка
POST /api/agent/query;интерфейс запросов с ответом и реальной технической трассой.
аутентификация с непрозрачными сессиями, хранящимися в PostgreSQL;
cookie
HttpOnly,SameSite=Strictи настраиваемый срок действия;авторизация RBAC с профилями
adminиviewer;аудируемое создание пользователей и транзакционное удаление данных HR;
два агента, публикующие Agent Cards A2A 1.0;
делегирование HR → Finance через JSON-RPC
SendMessage;финансовая задача с жизненным циклом и структурированным Artifact;
отчёт о потерях из-за отсутствий, полученный через MCP.
набор поведенческих оценок с детерминированными assertions;
эталонные случаи, пустой результат и свежесть PostgreSQL;
изолированная динамическая фикстура с гарантированной очисткой и проверкой остатков.
бюджетный timeout, ограниченный транзиентный retry и circuit breaker для горячего экземпляра;
безопасная деградация, сохраняющая grounded
answerPayload, если падает только нарратив;оценка отказоустойчивости с контролируемой инъекцией сбоев.
интерпретация нейтрального, неформального и риоплатского испанского через семантическое предложение LLM;
серверная валидация capability, схемы, периода, полярности и лимитов до MCP;
типизированные решения о уточнении и неподдерживаемом запросе без доступа к PostgreSQL;
версионированный лингвистический бенчмарк с baseline before/after и стабильностью между повторными запусками.
Приложение развёрнуто со статическим фронтендом на Render, serverless-бэкендом на Vercel и PostgreSQL на Neon. GitHub Actions применяет quality gates и smoke-тесты против продакшена.
Related MCP server: mcp-data-gateway
Структура
frontend/ React, inspector técnico y system index
backend/ API, dominio, migraciones, acceso PostgreSQL y trazasТребования
Node.js 22 или выше.
npm 10 или выше.
Ollama 0.32 или выше и локальная модель
qwen3:8b.Облачный PostgreSQL с тремя отдельными учётными данными, если провайдер это позволяет.
Установка
npm --prefix backend install
npm --prefix frontend installСкопируйте backend/.env.example в backend/.env и заполните URL-адреса провайдера PostgreSQL. Никогда не используйте владельческие учётные данные в DATABASE_READONLY_URL или DATABASE_ADMIN_URL.
Провайдер по умолчанию — локальный Ollama:
LLM_PROVIDER=ollama
OLLAMA_HOST=http://127.0.0.1:11434
OLLAMA_MODEL=qwen3:8bУстановите Ollama и один раз загрузите модель командой ollama pull qwen3:8b. Вывод использует локальные CPU/GPU и хранилище, без затрат на платный API.
OpenAI по-прежнему доступен как альтернатива: настройте LLM_PROVIDER=openai, OPENAI_API_KEY и OPENAI_MODEL. Ключ принадлежит только backend/.env, который игнорируется Git. Его никогда нельзя отправлять на фронтенд или включать в трассы.
База данных
Создайте базу PostgreSQL у облачного провайдера.
Создайте или настройте роли по
backend/ops/database-roles.example.sql.Настройте переменные окружения.
Выполните:
npm --prefix backend run db:migrate
npm --prefix backend run db:seed
npm --prefix backend run db:smoke
npm --prefix backend run db:verify-permissionsdb:smoke использует исключительно DATABASE_READONLY_URL и запрашивает представление hr_late_arrivals с параметрами дат.
Seed требует SEED_ADMIN_PASSWORD и SEED_VIEWER_PASSWORD, обе — не менее 12 символов. В репозитории нет паролей по умолчанию.
Разработка
В двух терминалах:
npm run dev:backend
npm run dev:frontendФронтенд:
http://localhost:5173Бэкенд:
http://localhost:3000
Проверка без облачной базы
npm test
npm run typecheck
npm run buildТесты календаря проверяют:
текущий месяц с 1-го числа по сегодняшний день включительно;
полный предыдущий календарный месяц;
февраль високосного года;
последние 30 календарных дней.
Временные интервалы
Интерфейс говорит об инклюзивных датах, но внутри используются полуоткрытые интервалы:
startInclusive <= timestamp < endExclusiveЭто позволяет не зависеть от 23:59:59 и корректно сохраняет точность PostgreSQL.
Трассируемость
Фронтенд показывает технические события с:
названием события;
технологией;
компонентом;
категорией;
концепциями;
санитизированными входом и выходом;
длительностью и статусом.
Учётные данные, токены сессий и внутренние рассуждения модели не отображаются.
MCP Server
Сервер использует официальный SDK Model Context Protocol для предоставления:
count_employees: подсчитывает общее количество сотрудников, активных и неактивных;list_employees: выводит полный справочник с табельным номером, именем, отделом и статусом;find_employee: ищет по имени или номеру сотрудника;summarize_employee_delays: агрегирует исторические опоздания человека по имени или табельному номеру;list_late_arrivals: выводит опоздания за период и по опциональному сотруднику;list_employees_without_late_arrivals: вычисляет в PostgreSQL, какие активные сотрудники не имели опозданий за период;list_absences: выводит отсутствия за период и по опциональному сотруднику.
Инструменты объявляют readOnlyHint, валидируют вход и выход с помощью Zod и возвращают как текстовое содержимое, так и structuredContent. Результаты включают источник, дату запроса, применённый период, общее количество и признак усечения. Каждый вызов заново обращается к PostgreSQL; на этом этапе кэша нет.
Для запуска локального MCP-сервера:
npm run mcp:serverДля проверки обнаружения, реальных вызовов, посеянных данных и пустого результата:
npm run mcp:smokestdout зарезервирован под протокол MCP; операционные ошибки отправляются в stderr, а ответ клиенту санитизируется.
Agent Runtime
Реализованный поток:
React → POST /api/agent/query → HrAgentOrchestrator
→ Ollama local + qwen3:8b (tool calling)
→ MCP Client → MCP Server → PostgreSQL
→ tool result → Ollama → respuesta + TraceEvent[]MCP Client обнаруживает доступные инструменты, но роутер передаёт модели единственное определение allowlist, управляемой исполнением. Схемы function calling строгие, а параллельные вызовы отключены, чтобы каждое выполнение было легко инспектировать.
grounded: true означает, что оркестратор проверил вызов одобренного инструмента и получил structuredContent перед запросом финального ответа. Это не означает математической гарантии по каждому токену, произведённому моделью; это качество должно измеряться оценками.
System prompt требует, чтобы корпоративные данные поступали исключительно из инструментов, чтобы пустые результаты сообщались явно, а полученное содержимое трактовалось как данные, а не как инструкции.
Детерминированный интеграционный тест без потребления API:
npm run agent:smokeРеальный тест с локальным Ollama, MCP и Neon:
npm run agent:smoke:ollamaРеальный тест с Groq, MCP и Neon:
npm run agent:smoke:groqОпциональный тест с OpenAI, MCP и Neon:
npm run agent:smoke:openaiКонечная точка:
POST /api/agent/query
Content-Type: application/json
{"question":"¿Qué empleados llegaron tarde durante el último mes?"}Ответ содержит answer, model, grounded, toolsUsed и последовательность санитизированных технических событий. Он не включает токены, учётные данные или внутренние рассуждения.
Семантическая устойчивость, валидированная маршрутизация и детерминированное представление
LLM получает семь управляемых capabilities и предлагает ровно одно решение. Бэкенд не доверяет этому предложению: он валидирует allowlist, схему Zod, выраженный пользователем период, полярность и бизнес-лимиты перед разрешением MCP-вызова:
LLM propone → backend valida → MCP ejecuta → PostgreSQL → payload deterministaCapability | Инструмент |
подсчёт сотрудников |
|
вывод справочника |
|
поиск человека |
|
сводка исторических опозданий |
|
запрос опозданий за период |
|
запрос тех, у кого не было опозданий |
|
запрос отсутствий за период |
|
Помимо семи MCP-инструментов, планировщик имеет два внутренних решения, которые никогда не доходят до MCP: request_clarification и reject_unsupported_query. Первое возвращает agent_clarification_required, когда отсутствует период или есть неоднозначность; второе возвращает unsupported_agent_query, когда запрос требует несуществующей capability, ранжирования, частоты или фильтра. Публичный каталог доступен через GET /api/agent/capabilities и также присутствует в System index.
Неформальные выражения интерпретируются по смыслу. Например, llegar, entrar, caer, fichar или marcar tarde могут относиться к late_arrivals; «sin tardanzas» и «siempre puntual» интерпретируются как ноль событий только в пределах явного периода. Выражения вроде «banda», «una bocha», «siempre» или «seguido» никогда не превращаются в выдуманные количества.
После выполнения MCP AnswerPresentation валидирует structuredContent через дискриминируемое объединение Zod. API возвращает две отдельные поверхности:
presentation: типизированный, детерминированныйanswerPayload, отображаемый конкретным React-компонентом;answer: grounded-нарратив, сгенерированный LLM, видимый на вторичной панели, помеченной как недетерминированный.
Количества, таблицы, даты, пустые состояния и метаданные источника отображаются из presentation; они не извлекаются из текста модели. Этап 11 не изменяет этот контракт Этапа 9. Трасса включает llm.semantic_proposal.completed, agent.semantic_decision.validated и presentation.payload.validated для разделения предложения, валидации и детерминированного представления.
Отрицательный запрос реализуется как разность множеств: активные сотрудники минус сотрудники хотя бы с одним опозданием в пределах периода. PostgreSQL выполняет эту семантику через NOT EXISTS; LLM не вычисляет дополнение. Если Groq возвращает пустой финальный ответ или пытается выполнить второй tool call во время завершения, адаптер выполняет единственный текстовый повтор с теми же grounded-данными. Событие llm.grounded_response.completed сообщает recovery=not_required, тип применённого retry или откат к детерминированному представлению.
Отказоустойчивость LLM-провайдера
Этап 10 обрабатывает внешние сбои с помощью четырёх явных механизмов:
Техника | Политика демонстрации | Результат |
timeout budget | 12 секунд на попытку | прерывает вызов, превышающий бюджет |
bounded retry | 1 транзиентный повтор | повторяет |
circuit breaker | размыкается при 3 сбоях; пробный half-open через 30 секунд | позволяет не давить на провайдера, который остаётся недоступным |
graceful degradation | только после успешного MCP-запроса | сохраняет |
Публичный эндпоинт GET /api/resilience раскрывает политику и санитизированное состояние контура, но никогда — учётные данные. Агент переиспользуется внутри каждого горячего инстанса, чтобы circuit breaker сохранял состояние между запросами. В Vercel каждый инстанс имеет собственный контур; глобальная координация потребовала бы распределённого хранилища и не оправдана для этой лаборатории.
Если падает начальное планирование, MCP-вызова и grounded-данных ещё нет, и API возвращает типизированную ошибку (llm_timeout, llm_rate_limited, llm_provider_unavailable или llm_circuit_open). Если падает только финальная формулировка, API отвечает успешно с детерминированной таблицей и выдаёт llm.grounded_response.degraded.
Аутентификация и авторизация
Учётные данные проверяются по bcrypt-хешам в app_users. При аутентификации бэкенд создаёт случайный токен, сохраняет только его SHA-256-хеш в app_sessions и передаёт токен через cookie HttpOnly. Фронтенд никогда не обращается к токену.
Длительность настраивается через SESSION_TTL_HOURS=8.
Права приложения:
viewer: может обращаться к агенту и просматривать технический индекс;admin: включает возможности запросов, создание пользователей и контролируемое удаление операционных данных.
Административное удаление не выполняет DROP DATABASE. Оно удаляет attendance_records, employees и departments в рамках одной транзакции; сохраняет схему, пользователей, сессии и audit_events. Требуется буквальное подтверждение DELETE HR DATA, результат фиксируется в аудите.
Роль PostgreSQL app_admin не имеет DROP, CREATE DATABASE, суперпользователя или членства neon_superuser. Это разделение демонстрирует, что RBAC приложения и привилегии базы данных — разные слои.
Реальный тест обоих пользователей и полного цикла сессии:
npm run auth:smokeАгенты и A2A
Проект реализует A2A Protocol 1.0 с официальным SDK @a2a-js/sdk:
HR Grounding Agent: запросы сотрудников и посещаемости, grounded через MCP;Absence Finance Agent: детерминированный экономический анализ отсутствий.
Agent Cards:
/.well-known/agent-card.json
/.well-known/hr-agent-card.json
/.well-known/finance-agent-card.jsonФинансовый поток:
Usuario → HR Agent / A2A Client
→ descubre Finance Agent Card
→ JSON-RPC SendMessage
→ Finance Agent Task: submitted → working
→ MCP list_absences → PostgreSQL
→ calculadora determinista
→ A2A Artifact application/json
→ Task completed → reporte + TraceEvent[]Эндпоинты A2A используют внутренний случайный bearer-токен. Agent Card описывает схему безопасности, но никогда не содержит учётных данных.
В базе нет зарплат. Поэтому отчёт требует явных параметров: валюта, дневная стоимость, премия за замену и влияние на производительность. Формула:
días × costo diario × (1 + prima de reemplazo + impacto de productividad)LLM не выполняет арифметику. Детерминированная TypeScript-функция вычисляет суммы с округлением до двух знаков. Если MCP сообщает, что результат был усечён, агент отклоняет расчёт, чтобы не выдать неполный отчёт.
Реализация использует A2A-задачи в памяти, потому что поток короткий и синхронный. Для нескольких инстансов или длинных задач TaskStore придётся перенести в постоянное хранилище.
Реальный тест Agent Card, A2A, MCP и Neon:
npm run a2a:smokeОценки агента
Юнит-тесты проверяют функции и контракты с контролируемыми зависимостями. Набор оценок измеряет полное поведение реального агента с настроенной моделью, MCP и PostgreSQL.
Реализованные случаи:
employee-count: проверяет, что вопрос о количестве направляется исключительно вcount_employees;employee-directory: проверяет, что запрос имён направляется вlist_employeesи получает справочник;employee-delay-summary: проверяет детерминированную агрегацию задержек Bruno Silva черезsummarize_employee_delays;employees-without-late-arrivals: проверяет маршрутизацию отрицания, разность множеств и ожидаемый результатEMP-003;known-late-arrivals: сравнивает инструмент, grounding и количество с посеянным набором данных;unknown-employee: требует пустого результата PostgreSQL и явного ответа без выдуманных данных;source-of-truth-freshness: вставляет временного уникального сотрудника и опоздание, запрашивает только что созданную запись и проверяет, что агент видит обновление.finalization-failure-degradation: внедряет контролируемый сбой после MCP и проверяет, что PostgreSQL-полезная нагрузка остаётся доступной.semantic-robustness-v1: выполняет 80 нейтральных, формальных, неформальных, риоплатенских и пограничных формулировок; измеряет намерение, решение, аргументы, темпоральность, неоднозначность и стабильность.
Динамическая фикстура использует административную роль только во время подготовки и очистки. Запрос агента продолжает использовать роль только для чтения. Блок finally удаляет по UUID и точному номеру сотрудника; в конце дополнительный запрос требует, чтобы не осталось сотрудников EVAL-% или посещений с источником agent-evaluation.
Реальный запуск с настроенным LLM-провайдером, MCP и Neon:
npm run evals:run
npm run resilience:eval
npm run semantic:eval
npm run semantic:stabilitysemantic:eval один раз проходит 80 случаев, а semantic:stability повторяет критический набор пять раз. Оба сообщают validDecisionRate, intentRecognitionRate, toolSelectionRate, argumentExtractionRate, temporalInterpretationRate, exactOutcomeRate, stabilityRate, ambiguityPassRate и unsupportedPassRate. По умолчанию они ждут 30 секунд между вызовами, чтобы уважать бесплатный бюджет токенов Groq и отделять лимиты провайдера от семантической нестабильности. Базовый уровень Stage 10 сохраняется в backend/evals/baselines/, а результаты Stage 11 — в backend/evals/results/.
Остальные команды возвращают воспроизводимый JSON с passRate, длительностью, ожидаемыми/фактическими проверками и grounded-доказательствами по каждому случаю. Они завершаются ненулевым кодом, если оценка не удалась или осталась временная фикстура. Эталонный случай предполагает наличие демонстрационного сида.
Облачное развёртывание
Репозиторий хранит frontend/ и backend/ отдельно, с двумя поверхностями развёртывания:
agent-lab-ignac: фронтенд Vite как Render Static Site;agent-lab-api-ignac: бэкенд Express как Vercel Function с Fluid Compute.
Продакшн-URL:
приложение:
https://agent-lab-ignac.onrender.com;API:
https://agent-lab-api-ignac.vercel.app;прямой health check:
https://agent-lab-api-ignac.vercel.app/api/health.
render.yaml управляет только фронтендом и переписывает /api/* на https://agent-lab-api-ignac.vercel.app. Для браузера аутентификация и cookie остаются под origin фронтенда; токен сессии остаётся HttpOnly и не передаётся в React.
backend/vercel.json объявляет Express, максимум 300 секунд и регион gru1 (Сан-Паулу), близкий к базе Neon. Vercel обнаруживает ленивый handler, экспортируемый из src/app.ts; приложение и его пулы инициализируются при первом запросе к инстансу. src/server.ts сохраняет app.listen() для локальной разработки.
Транспорт MCP выбирается через MCP_TRANSPORT:
stdio: локальная разработка; клиент запускает отдельный MCP-процесс;in_process: Vercel; клиент и сервер MCP соединяются парой транспортов в памяти, не теряя протокол, контракты, валидацию или обнаружение инструментов.
В локальной разработке LLM_PROVIDER=ollama сохраняет qwen3:8b. В Vercel LLM_PROVIDER=groq использует openai/gpt-oss-20b, поддерживающий function calling. Адаптер Groq принудительно включает хотя бы один инструмент и возвращает его результат модели для создания grounded-ответа.
Требуемые переменные продакшена в проекте Vercel:
NODE_ENV=production
FRONTEND_ORIGIN=https://agent-lab-ignac.onrender.com
APP_TIMEZONE=America/Argentina/Buenos_Aires
SESSION_TTL_HOURS=8
PUBLIC_BASE_URL=https://agent-lab-api-ignac.vercel.app
MCP_TRANSPORT=in_process
LLM_PROVIDER=groq
GROQ_MODEL=openai/gpt-oss-20b
GROQ_API_KEY=<secret>
LLM_TIMEOUT_MS=12000
LLM_TRANSIENT_RETRIES=1
LLM_CIRCUIT_FAILURE_THRESHOLD=3
LLM_CIRCUIT_RESET_MS=30000
DATABASE_READONLY_URL=<secret>
DATABASE_ADMIN_URL=<secret>
A2A_INTERNAL_TOKEN=<secret-aleatorio-de-32-o-mas-caracteres>Публичный бэкенд добавляет заголовки через Helmet, rate limits, явную обработку ошибок и GET /api/health. Лимиты в памяти демонстрационные и работают на горячий инстанс; распределённое продакшн-приложение использовало бы общее хранилище. PostgreSQL сохраняет пользователей, сессии и данные, поэтому serverless-файловая система остаётся одноразовой.
CI/CD и quality gates
Каждый push в main и каждый pull request запускают .github/workflows/ci.yml. Бэкенд и фронтенд проверяются в независимых и воспроизводимых jobs на Node.js 22:
checkout → npm ci → typecheck → build → tests → audit de dependencias productivasnpm ci устанавливает ровно дерево, зафиксированное каждым package-lock.json. Jobs имеют только права на чтение репозитория, имеют таймаут и отменяют предыдущие запуски той же ветки. Никакие продакшен-учётные данные не передаются в CI workflow.
Vercel подключён к репозиторию с backend/ как Root Directory; принятый коммит в main запускает serverless-развёртывание. Render поддерживает статический фронтенд из frontend/. Это разделение различает два контроля:
quality gate до рантайма: типы, компиляция, тесты и аудит;
smoke test после развёртывания: публичный HTTP-контракт, реально развёрнутый.
.github/workflows/production-smoke.yml слушает успешные статусы развёртывания и также позволяет ручной запуск. scripts/production-smoke.mjs проверяет:
прямой health бэкенда Vercel;
контракт
/api/systemи текущий этап;публичный контракт
/api/resilience;прокси
/api/*, обслуживаемый под origin Render;доступность HTML-документа фронтенда.
Локальный запуск того же продакшен-контракта:
node scripts/production-smoke.mjsThis server cannot be deployed
Maintenance
Related MCP Connectors
Query PostgreSQL databases in plain English — LLM-generated, safety-validated SQL.
Safe, read-only Postgres and MySQL access for AI agents. Audit log + column-level controls.
- dataOAuthco.thinair
PostgreSQL, MySQL, and SQL Server in one session. 26 read-only MCP tools for AI agents.
Gateway between LLM agents and world data through eight tools and a bundled endpoint catalog.
Related MCP Servers
- AlicenseBqualityBmaintenanceEnables Claude, Cursor, and other MCP clients to query PeopleForce HRIS data (employees, time-off, recruitment) via 27 read-only tools.284MIT
- AlicenseNot gradedqualityDmaintenanceEnables AI agents to query a PostgreSQL database through a small set of controlled, read-only tools for schema inspection, row lookup, and aggregate statistics.1MIT
- FlicenseNot gradedqualityDmaintenanceEnables querying an HR analytics database (hr_db) using natural language, providing tools to get database schema and run read-only SQL queries for HR metrics like headcount, attrition, and payroll.-
- FlicenseNot gradedqualityCmaintenanceEnables Claude Desktop to securely query a SQLite HR database, providing tools to check leave balances and list pending approvals through natural language.-