Newer
Older
gn-synapse / docs / 07-mcp.md

07 — MCP-сервер: управление Synapse ИИ-агентом

Synapse встраивает FastMCP (пакет mcp) — тот же контейнер API, тот же процесс uvicorn. Эндпоинт /mcp, транспорт streamable-http (stateless), доступ — статический Bearer-токен MCP_TOKEN из .env.

Зачем: ИИ-агент (Claude Code и совместимые) управляет Synapse целиком — выдаёт API-ключи клиентам (сервисам-источникам), регистрирует типы, ведёт правила маршрутизации, смотрит поток событий и доставок, меняет настройки — без ручной работы в админке. Правка 2026-10-03.

Включение

  1. MCP_TOKEN=<hex> в .env (и gnexus-creds). Пусто/не задано — /mcp не существует: GET уходит в SPA, POST отвечает 405.
  2. Перезапуск контейнера api.
  3. Регистрация в Claude Code:
claude mcp add --transport http synapse http://localhost:8013/mcp \
  --header "Authorization: Bearer <MCP_TOKEN>"

Генерация токена: openssl rand -hex 32. Токен — как ключ прод-сервера (внутри API полномочия MCD = superadmin), хранить в .env + gnexus-creds, никогда не в репозитории.

Архитектура

  • Тулы не дублируют логику: сервер переиспользует admin-route функции (app/api/admin_routes.py) напрямую — та же pydantic-валидация, те же коды ошибок и тексты. Автор запросов — синтетический superadmin (токен и есть доверие).
  • Ошибка тулза возвращается как данные: {"error": 409, "detail": "…"} — агент читает причину и может её исправить (например restore архивной записи).
  • Синхронная SQLAlchemy-сессия на вызов; FastMCP исполняет синхронные тулы в threadpool.
  • Гейт — чистый ASGI-класс (McpTokenGuard, app/mcp/server.py): проверяет Authorization: Bearer на каждом запросе, 401 (www-authenticate: Bearer) при несовпадении; не BaseHTTPMiddleware — SSE-стримы не ломает.
  • Выключение: убрать MCP_TOKEN → сервер не монтируется.

Каталог тулов (31)

Группа Тулы
Состояние system_status (БД + Celery ping + SSO) · stats_get (счётчики по статусам, живые справочники)
Источники sources_list (query, include_archived) · source_create · source_archive · source_restore
Ключи keys_list · key_issue (plaintext один раз в token) · key_revoke
Типы types_list (source_id, query, include_archived) · type_register · type_archive · type_restore
Цели targets_list (channel, enabled, include_archived) · target_create · target_patch · target_archive · target_restore
Правила rules_list (enabled, include_archived) · rule_create · rule_patch · rule_archive · rule_restore
Поток events_list (status, source_name) · event_get · deliveries_list (status, channel)
Push push_subscriptions_list (user_id) · push_subscription_delete (жёстко — устройства пользователей)
Настройки settings_get (секреты write-only: value=null, факт в set) · settings_put (правила docs/06: "" — не менять, null — сброс; настройка push-канала — рецепт VAPID в docs/06 «Как задать VAPID», выполняется shell'ом в контейнере)
Контроль send_test_event (source_name, subject, action, payload, priority) — полный маршрут в воркере

Архив вместо удаления (семантика DELETE)

DELETE не удаляет — ставит deleted_at (архив). Действует на sources, notification_types, channel_targets, routing_rules. Пуш-подписки — исключение: устройства пользователей удаляются физически (push-нотификации не переживают несуществующую подписку).

Что это значит:

  • DELETE отвечает 204; вторая архивация того же id — 404 («не найден»).
  • Списки по умолчанию живое; include_archived=true покажет архив (deleted_at в объектах; в UI-списках поле всегда null).
  • Архивный источник: ключ получает 401 «Источник ключа в архиве» — события не принимаются; ключи отозвать нельзя, но и выдать новый нельзя (404 «сначала restore»).
  • Архивный тип: тройка приёма → 422 «не зарегистрирован».
  • Архивная цель в живом правиле: матчится, но доставка уходит в skipped с причиной «цель в архиве» (аудит, видно в deliveries).
  • Create поверх архива: имя занято архивной записью → 409 с подсказкой «есть в архиве (id=N) — восстанови (restore) или выбери другое имя».
  • Restore: POST /api/v1/admin/{sources|types|targets|rules}/{id}/restore → 200 с объектом; конфликт имени с живой записью → 409. Restore типа требует живого источника (409).
  • История не режется: старые события/доставки показывают имена архивных источников и целей.

REST-эквивалент доступен и из админ-API (те же эндпоинты), UI-клиент SPA ничего не заметил (204-ответы прежние).

Пример сценариев

Выдать ключ клиенту:

1. source_create(name="bugtrail", description="Трекер ошибок…")
2. key_issue(source_id=<id>, name="основной")
   → token: "syn_…" — передать клиенту один раз
3. type_register(source_id, subject="issue", action="created")
4. Третьесторонний сервис шлёт POST /api/v1/events с Bearer syn_…

Проверить правило:

1. rule_create(name="critical → Navi rei", actions=[{"channel":"s2s","target_id":3}],
               conditions={"priority_min":"critical"})
2. send_test_event(source_name="monitoring", subject="container", action="down",
                   payload={"container":"api"}, priority="critical")
3. events_list(source_name="monitoring") → доставed в цель

Ограничения

  • Stateless транспорт: без сессий между вызовами (каждый вызов независим) — агент просто вызывает тула последовательно.
  • Секреты настроек остаются write-only (settings_get их не отдаёт).
  • send_test_event и просмотр потока требуют живого воркера (Celery) — system_status покажет.
  • Транспорт — внутренняя сеть/локальный порт API: наружу /mcp не публикуем.