| 2026-10-07 |

MCP: причины ошибок доходят до агента в list-тулах, event_get и send_test_event
...
Сплошной прогон всех тулов (FastMCP call_tool, SQLite, 62 проверки) нашёл
ещё три дефекта того же класса, что и settings_get, — сырой/нечитаемый
ответ вместо {"error": …, "detail": …}:
- list-тулы (11: sources/keys/types/targets/rules/events/deliveries/
push_subscriptions/settings_get + my_events/my_push_subscriptions) при
ошибке возвращали dict, а FastMCP валидирует structured output по
аннотации list[dict] -> агент видел «1 validation error for ...Output:
Input should be a valid list», причина терялась. Теперь ошибка уходит
ToolError-ом с тем же JSON (helper _list_run) — как и было задумано для
админ-отказа.
- event_get с не-UUID id ронял тул сырым ValueError («badly formed
hexadecimal UUID string»): _run ловит только HTTPException и
ValidationError. Теперь 422 с понятным текстом.
- send_test_event при недоступной очереди отдавал текст kombu («Retry limit
exceeded…»): теперь 503 «очередь недоступна, событие создано, воркеру не
передано» — контрольный прогон честно не состоялся.
Проверено: 62/62 (админ-набор, личный набор, ролевой гейт 403 у user-ключа,
архив/restore, выдача и отзыв ключей, приём контрольного события и event_get
по нему).
Eugene Sukhodolskiy
committed
2 days ago
|
MCP: settings_get падал — list_settings требует request
...
После локализации по Accept-Language HTTP-роут list_settings получил
параметр request: Request, а MCP-тул settings_get вызывал его напрямую
из контекста, где HTTP-запроса нет: TypeError: list_settings() missing
1 required positional argument: 'request' на каждом settings_get.
Тело вынесено в общий helper settings_registry(db, locale) — как уже
сделано для dashboard_stats: роут передаёт язык из Accept-Language,
MCP-тул берёт ru-исходник реестра (у MCP-запроса языка нет). Ролевой
гейт не меняется: _admin_run проверяет admin до вызова fn.
Eugene Sukhodolskiy
committed
2 days ago
|
| 2026-10-05 |
Обзор: admin-сводка вместо пустой витрины
...
GET /api/v1/admin/stats (require_admin): агрегаты dashboard_stats() —
события/доставки по статусам, живые sources/types/targets/rules — плюс
живость БД/воркера (celery ping, короткий таймаут; недоступный воркер
не валит эндпоинт) и факт настройки SSO.
Тул stats_get (docs/07) переведён на тот же dashboard_stats() — единое
место, без дублирования запросов (правило mcp.md).
Обзор (DashboardView): для admin — карточка состояния системы (БД/воркер/
SSO), метрики справочников из кита, статусы потока с количеством (GnBadge),
карточка «С чего начать» теперь только пока нет ни одного источника.
Для роли user — как раньше, только healthz.
Eugene Sukhodolskiy
committed
4 days ago
|
| 2026-10-04 |

feat: персональные MCP-ключи — выпуск в SPA, роль владельца у тула
...
Решение владельца (2026-10-04):
- Каждый пользователь выпускает себе MCP-ключ на новой странице /mcp-keys
(доступна любой роли); plaintext показывается один раз, в БД — sha256 + hint.
- Ключ действует ролью владельца на момент выпуска (снейпшот system_role):
admin/superadmin — полный каталог тулов (35), user — только личный набор
(me, my_events_list, my_push_subscriptions_list, my_push_subscription_delete).
- Статический MCP_TOKEN из .env остаётся суперадмин-бэкдором; /mcp монтируется
безусловно (персональные ключи работают и без env-токена).
Backend:
- таблица mcp_tokens (миграция 0008 c9a2d6e71f43) — зеркало ApiKey;
- app/mcp/context.py — ContextVar пользователя, resolve_personal_token;
- McpTokenGuard резолвит стат.токен или mcp_* в БД, пишет last_used_at,
401 на промахе (чистый ASGI, SSE не трогает);
- тулы: user=current_mcp_user() вместо синтетического MCP_USER; админ-тулы
под _admin_run — для user-ключа ToolError с {"error": 403} (не return-данные:
FastMCP валидирует вывод по аннотации, list-тулы бы отвергли dict);
- HTTP: GET/POST /api/v1/me/mcp_tokens (+/revoke), GET/POST
/api/v1/admin/mcp_tokens[.../revoke], лимит 10 активных на пользователя.
SPA: страница McpKeysView (свои ключи + как подключить агент; админ — все
ключи и отзыв), маршрут /mcp-keys (userOk), пункт в навигации обеих ролей.
Доки: docs/07 переписан (три вида доступа, 35 тулов), CLAUDE.md,
docs/08 (каталог 35), .env.example (MCP_TOKEN опционален).
E2e на живом стеке: user-ключ — 403 на админ-тулах и рабочий личный набор;
admin-ключ — полный каталог; last_used_at; бэкдор = superadmin; отзыв -> 401.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
5 days ago
|
| 2026-10-03 |

feat: MCP-сервер встроенным FastMCP (/mcp) + архив вместо удаления
...
MCP (docs/07-mcp.md):
- embedded FastMCP streamable-http на /mcp того же контейнера api;
гейт — чистый ASGI McpTokenGuard по Bearer MCP_TOKEN из .env (пусто —
/mcp выключен). Route, не Mount: Mount('/mcp') не матчит точный /mcp.
- 31 тул в app/mcp/tools.py: выдача ключей клиенту (key_issue — plaintext
один раз), полный CRUD источников/типов/целей/правил, поток (events/
deliveries), push-подписки, настройки, send_test_event.
- тула переиспользуют функции admin-routes напрямую (синхронные def без
срабатывания Depends); HTTPException/ValidationError — как данные.
Архив вместо удаления (миграция 0007 b7d3e8f04a92):
- deleted_at на sources/notification_types/channel_targets/routing_rules +
частичные live-индексы; DELETE = 204 → архив, POST .../restore → 200.
- созд поверх архива → 409 с подсказкой id; ключ архивного источника →
401; тройка архивного типа → 422; целевые правки → 422; архивная цель в
правиле — Delivery skipped с аудитом; история имена архива показывает.
- push-подписки — исключение: жёсткое удаление.
- fix list_settings: оверрайды DB-only ключей (push/smtp, без .env-дефолта)
снова возвращаются в value.
Проверено: mcparch ALL OK (41), mcpcheck ALL OK (28, MCP SDK-клиент),
mecheck/retcheck ALL OK, UI-регрессия (SPA, источники+delete 204) ALL OK.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
6 days ago
|