incident-mcp
incident-mcp
Инцидентный MCP-сервер для агента дежурного инженера (on-call). FastMCP (Python 3.12, uv), транспорт stdio, единый asyncpg-пул на процесс.
Сервер читает локальный инцидентный стенд из homework-stand/: Postgres
с логами, деплоями и реестром инцидентов плюс payments-api под нагрузкой.
В стенде есть намеренный дефект — деградация latency учебного эндпоинта;
ищется он по данным через tools сервера, а не чтением исходников
(см. «Разбор инцидента» ниже).
Состав
├── src/incident_mcp/ MCP-сервер
│ ├── app.py инстанс FastMCP, lifespan (пул), инструкции агента
│ ├── db.py доступ к БД: пул, fetch/execute, ToolError-обёртки
│ ├── schemas.py схемы аргументов: enum-типы, парсер длительностей
│ ├── tools_read.py read-tools (без побочных эффектов)
│ ├── tools_write.py write-tools (меняют состояние в Postgres)
│ └── server.py stdio entry point (в stdout только MCP-сообщения)
├── tests/ unit + интеграционные (на живом стенде)
├── homework-stand/ стенд: docker compose (Postgres, payments-api, simulator)
└── memory/ проектная память агентов (brief, decisions, progress)Обоснование состава tools
Каждый tool — один шаг разбора инцидента; универсального query(sql) нет
намеренно, агент не видит SQL и не может обойти доменные ограничения.
tool | шаг разбора | почему отдельным tool'ом |
| найти открытый инцидент | входная точка сценария; фильтры severity/status/time_range |
| карточка одного инцидента | сводка разбора по id; неизвестный id — ошибка со списком доступных |
| сопоставить деградацию с релизом | рост latency сразу после деплоя — главный подозреваемый |
| WARN/ERROR вокруг начала деградации | сообщения — пользовательские данные (в стенде есть prompt-injection строка; tool отдаёт её как данные) |
| форма деградации latency | без него агент увидит только точку, а не кривую; возвращает готовый агрегат (корзины, avg, p95, hit-rate), а не сырые строки |
| типовые симптомы и шаги диагностики | читается перед началом разбора |
| кому эскалировать | команда, on-call, зависимости |
| взять инцидент в работу | write-операция, отдельный tool, в description явно написано «WRITE-ОПЕРАЦИЯ» |
| зафиксировать выводы разбора | write-операция, отдельный tool |
Описания tools важнее обычного: в OpenCode они попадают в один список со
встроенными (bash, чтение файлов). Если из description не понятно, когда
брать metrics_latency вместо bash/psql, агент возьмёт bash. Поэтому у
каждого tool есть title и description, отвечающие на три вопроса: что
делает, когда применять, какие ограничения и side effects. Аргументы
валидируются inputSchema (enum-типы Literal, Field(ge=..., le=...),
парсер длительностей 1m..7d); невалидный ввод возвращает структурированную
ошибку isError с поправимым текстом, сервер не падает.
Быстрый старт
# 1. Стенд
cd homework-stand
cp .env.example .env
docker compose up -d --build
docker compose run --rm simulator # ~5 минут: засев истории + живой трафик
# 2. MCP-сервер (из корня)
cp .env.example .env
uv sync
uv run incident-mcp # stdio-серверПроверка через MCP Inspector
Inspector (v2) работает в скриптовом CLI-режиме: цель (команда сервера)
до --, опции после.
# lifecycle: initialize (DSN сервер возьмёт из .env сам)
npx @modelcontextprotocol/inspector --cli uv run incident-mcp -- \
--method initialize --format json \
--cwd "$(pwd)" -e FASTMCP_CHECK_FOR_UPDATES=off
# tools/list — 9 tools с title/description/inputSchema
npx @modelcontextprotocol/inspector --cli uv run incident-mcp -- \
--method tools/list --format json \
--cwd "$(pwd)" -e FASTMCP_CHECK_FOR_UPDATES=off
# вызов tool
npx @modelcontextprotocol/inspector --cli uv run incident-mcp -- \
--method tools/call --tool-name metrics_latency --format json \
--tool-args-json '{"endpoint":"/api/v1/orders/{order_id}/price","time_range":"1h","bucket":"1m"}' \
--cwd "$(pwd)" -e FASTMCP_CHECK_FOR_UPDATES=off
# невалидные аргументы -> isError:true, сервер жив
npx @modelcontextprotocol/inspector --cli uv run incident-mcp -- \
--method tools/call --tool-name metrics_latency --format json \
--tool-args-json '{"endpoint":"/api/v1/orders/{order_id}/price","bucket":"xyz"}' \
--cwd "$(pwd)" -e FASTMCP_CHECK_FOR_UPDATES=offПрогон всех 9 tools и четырёх невалидных вызовов (полный лог —
docs/inspector-checks.txt) дал: initialize → serverInfo
incident-mcp 3.4.7, protocolVersion 2025-11-25; tools/list → 9 tools;
каждый tool вернул данные; невалидные аргументы →
{"isError":true} с текстом («Invalid time range 'xyz'...», «Incident
'INC-999' not found. Known incident ids: ...», pydantic-ошибка
limit: Input should be greater than or equal to 1).
Подключение к OpenCode
opencode.json (ключевая часть):
{
"mcp": {
"incident-mcp": {
"type": "local",
"command": ["uv", "run", "incident-mcp"],
"cwd": "/абсолютный/путь/к/incident-mcp",
"enabled": true,
"timeout": 30000,
"environment": {
"FASTMCP_CHECK_FOR_UPDATES": "off"
}
}
}
}Нюансы, проверенные на практике:
DSN в конфиге не дублируется:
STAND_DATABASE_URLберётся только из.env. Сервер ищет.envпо абсолютному пути от файла модуля (src/incident_mcp/server.py→ корень проекта), поэтому загрузка не зависит от cwd, с которого opencode запускает процесс. Переменная из окружения имеет приоритет (load_dotenvне перезаписывает существующие).cwd— абсолютный: относительный opencode резолвит от корня workspace и процесс не находит проект.FASTMCP_CHECK_FOR_UPDATES— строка"off": в FastMCP 3.x полеcheck_for_updatesэтоLiteral["stable","prerelease","off"], значениеfalseроняет процесс pydantic-ошибкой при импорте, до MCP-handshake.После подключения tools сервера видны агенту с префиксом
incident-mcp_*(incidents_search, metrics_latency, ...).
Проверка кода
uv run pytest # 31 тест; интеграционные скипаются без стенда (порт 5433)
uv run ruff format src tests # 9 files left unchanged
uv run ruff check src tests # All checks passed!
uv run mypy src # Success: no issues found in 7 source filesЛог реального диалога (ReAct-сценарий)
Сценарий пройден агентом (OpenCode + этот MCP-сервер) на живом стенде, прогон симулятора 2026-08-12 18:32–18:37. Цепочка вызовов:
incidents_search(service="payments", status="open", time_range="1h")→INC-001, severity high, открыт 18:31:41, «Рост времени ответа /api/v1/orders/{order_id}/price».runbook_get(service="payments")→ план: кривая latency, контрастный эндпоинт, деплои, логи кэша.metrics_latency(endpoint="/api/v1/orders/{order_id}/price", time_range="1h", bucket="1m")→ форма деградации: до 18:32 фоновый трафик avg 3–8 мс при hit-rate ~100%; с 18:32 монотонный рост — avg 39.2 → 80.0 → 139.2 → 198.5 → 265.1 → 336.9 мс, p95 51.5 → 457.5 мс, cache_hit_pct = 0.0 во всех корзинах.metrics_latency(endpoint="/api/v1/catalog/items", ...)→ здоровый эндпоинт ровен: avg 0.4–0.7 мс. Проблема локальна для расчёта цены.deploys_recent(service="payments")→ v1.5.0 «refactor: unified response cache» (i.petrov), задеплоен 18:29:41 — за 2 минуты до открытия инцидента и за 3 минуты до начала деградации.logs_query(service="payments-api", time_range="1h", level="WARN")→ «response cache grew to 5000...9495 entries, hit_rate=0.0»: кэш наполняется, но попаданий ноль.level="ERROR"→ пусто.
Гипотеза (со ссылками на данные): деплой v1.5.0 сломал кэш ответов — записи создаются, но поиск никогда не находит их (hit_rate=0.0 при растущем числе записей); значит, ключ записи не совпадает с ключом поиска, скорее всего в ключ попадает поле, уникальное для каждого запроса. Каждый запрос идёт по медленному пути расчёта цены, и под нагрузкой latency монотонно растёт.
Далее write-tools: incident_acknowledge("INC-001") →
{"status":"acknowledged","changed":true};
incident_create_summary("INC-001", ...) → сводка сохранена в
incidents.summary.
Подтверждение по исходникам (после гипотезы): api/cache.py,
build_key включал request_id — уникальный для каждого запроса,
поэтому каждый lookup был промахом. Фикс: request_id из ключа убран
(ключ = эндпоинт + параметры).
Разбор инцидента INC-001
Симптом: монотонный рост latency
/api/v1/orders/{order_id}/priceпод нагрузкой 20 rps при ровном/api/v1/catalog/items.Триггер: деплой payments v1.5.0 «refactor: unified response cache» (18:29:41), деградация с 18:32.
Корень: в ключе кэша ответов был
request_id— кэш наполнялся, но не отдавал ни одного ответа (hit_rate 0.0), каждый запрос шёл по медленному пути (агрегация по журналу price_events).Фикс:
request_idисключён изbuild_key(homework-stand/api/cache.py, main.py), образ api пересобран (docker compose up -d --build api— заодно сбрасывает in-memory кэш для честного сравнения).
До/после: два реальных прогона
Оба прогона — симулятор стенда: 20 rps × 300 c = 6000 запросов,
замер — metrics_latency (1m-корзины) и /internal/cache-stats.
До фикса (сломанный кэш, прогон 18:32–18:37):
минута | req | avg_ms | p95_ms | hit% |
18:32 | 280 | 39.2 | 51.5 | 0.0 |
18:33 | 959 | 80.0 | 113.8 | 0.0 |
18:34 | 959 | 139.2 | 178.6 | 0.0 |
18:35 | 951 | 198.5 | 243.7 | 0.0 |
18:36 | 966 | 265.1 | 323.7 | 0.0 |
18:37 | 685 | 336.9 | 457.5 | 0.0 |
После фикса (прогон 18:41–18:46):
минута | req | avg_ms | p95_ms | hit% |
18:41 | 342 | 20.8 | 54.4 | 39.2 |
18:42 | 960 | 13.4 | 54.8 | 71.0 |
18:43 | 960 | 10.6 | 56.8 | 81.7 |
18:44 | 959 | 20.0 | 73.5 | 71.0 |
18:45 | 962 | 25.8 | 88.9 | 69.1 |
18:46 | 617 | 39.3 | 105.4 | 59.6 |
Итог:
показатель | до | после | изменение |
cache hit-rate | 0.0% все минуты | 39–82% (69% за прогон; 3314 hits / 1486 misses) | поднялся с 0% |
avg latency, пик | 336.9 мс | 39.3 мс (минимум 10.6) | в 8.6 раза ниже |
p95 latency, пик | 457.5 мс | 105.4 мс | в 4.3 раза ниже |
Небольшой подъём avg в конце прогона «после» — рост медленного пути вместе с журналом price_events (357 тыс. строк к концу прогона) на фоне TTL-промахов; кэш при этом продолжает работать (hit-rate > 0).
Подробности про стенд — в homework-stand/README.md.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/DyakonovAlex/incident-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server