diff --git a/10-platform/mcp.md b/10-platform/mcp.md index cf06af8..fde0add 100644 --- a/10-platform/mcp.md +++ b/10-platform/mcp.md @@ -1,35 +1,49 @@ # MCP: мультиюзер доступ для ИИ-агентов -Схема, применённая в gnexus-creds: сервис — сам MCP-сервер, доступ — через -собственные API-токены сервиса (не через access-токены gnexus-auth), каждый -токен принадлежит пользователю сервиса. +Сервис с MCP-функционалом — сам себе MCP-сервер; доступ — через +**персональные ключи сервиса** (не access-токены gnexus-auth), каждый ключ +принадлежит пользователю сервиса. Канон = реализация **Gnexus Synapse** +(`docs/07-mcp.md`, `app/mcp/server.py`, страница `/mcp-keys`); авторизация +на тулах — снейпшот роли владельца, вместо набора scopes. ## Правила -- Сервис с MCP-функционалом предоставляет **streamable HTTP** эндпоинт - (`/mcp-protocol/`, слэш в конце значим — реверс-прокси не буферизует и не - переписывает тело). Legacy HTTP/SSE-адаптер — только для клиентов без - streamable HTTP. `stdio` не используется: агент и сервис на разных хостах. -- Авторизация MCP — **Bearer токен сервиса** в заголовке `Authorization`. -- Токены выдаются в UI на **отдельной странице «Токены»**: имя, набор scopes, - ревок, `last_used_at`. Токен показывается ровно один раз при создании, - в БД — только хэш. -- Токен привязан к пользователю: действия через MCP выполняются от его имени - и видны в аудите (`channel=mcp`), а недействительный/заблокированный - пользователь gnexus-auth блокирует и его токены (403). -- Доступ ограничивается scopes; лишний scope не выдавать. Чего не хватит — - 403, а не молчаливое сужение выдачи. +- Сервис предоставляет **streamable HTTP** эндпоинт (`/mcp-protocol/`, слэш + в конце значим — реверс-прокси не буферизует и не переписывает тело). + Legacy HTTP/SSE-адаптер — только для клиентов без streamable HTTP. + `stdio` не используется: агент и сервис на разных хостах. +- Авторизация MCP — **Bearer-ключ сервиса** в заголовке `Authorization` + (у Synapse: персональный `mcp_…`; исторический `/mcp` — alias к + `/mcp-protocol/`). +- Токены выдаются в UI на **отдельной странице «MCP-ключи»** (Synapse: + `/mcp-keys`): имя ключа («агент Navi rei»), plaintext показывается **ровно + один раз** при создании, в БД — только `sha256`-хэш (уникальный) и + **хвост-хинт** (последние символы — опознание ключа в списке). +- Лимит активных ключей на пользователя (Synapse: ≤10; превышение — 409 + «отзовите лишний»). +- **Ключ действует ролью владельца на момент выпуска** (снейпшот `system_role` + в строке токена): роль `admin`+ — весь каталог тулов, роль `user` — только + личный набор («me, свои списки»); админ-тул для user-ключа отвечает + `{"error": 403, "detail": …}`. Смена роли на gnexus-auth **не** дотягивается + до выпущенного ключа — старый отзывают, выпускают заново. +- Вместо архива — **ревок** (`revoked_at`); списки показывают и отозванные + (с хвост-хинтом), plaintext никогда не возвращается. +- Админ видит ключи всех пользователей и может отозвать любой. +- **Блокировка пользователя gnexus-auth** (вебхуки `user.blocked`/`deleted`/ + `archived` → локальный флаг) гасит все его личные ключи: гард отвечает тем + же 401, что и при неверном токене — **не раскрывать статус блокировки**. + SSO-логаут MCP-токены не трогает — это самостоятельные креды (у Synapse + так сделано потому, что SSO-сервер не отзывает выданные сервисом ключи). +- Гард MCP — **чистый ASGI-класс** (не BaseHTTPMiddleware — SSE-стримы не + ломает): резолвит `Authorization: Bearer` на каждом запросе (хэш-lookup), + 401 + `www-authenticate: Bearer` при промахе, обновляет `last_used_at`. +- Тулы **не дублируют логику**: переиспользуют функции роутов сервиса (та же + pydantic-валидация, те же коды ошибок); ошибка возвращается как данные + (`{"error": 403|409, "detail": "…"}`) — агент читает причину и исправляет. +- Статический `MCP_TOKEN` из `.env` — **суперадмин-бэкдор**: сохраняет + совместимость с существующими сценариями агентов. Опционален (пусто — + работают только персональные ключи); держать в `.env` + gnexus-creds. - Rate-limit на чувствительные операции. -## Scopes (паттерн gnexus-creds) -| Скоупы | Доступ | -|---|---| -| `mcp, read` | чтение/поиск метаданных | -| `mcp, read, reveal` | + расшифровка секретов | -| `mcp, read, reveal, write` | полный MCP (создание/обновление) | - -`read`/`reveal`/`write` наполняются в контексте конкретного сервиса; без -скоупа `mcp` всё закрыто 403. - ## Подключение агента ```json { @@ -37,24 +51,31 @@ "<сервис>": { "type": "streamable-http", "url": "https://<сервис>.gnexus.space/mcp-protocol/", - "headers": { "Authorization": "Bearer " } + "headers": { "Authorization": "Bearer mcp_<персональный ключ>" } } } } ``` +Claude Code: +```bash +claude mcp add --transport http <сервис> https://<сервис>.gnexus.space/mcp-protocol/ \ + --header "Authorization: Bearer mcp_<персональный ключ>" +``` + +## Схема хранения (`mcp_tokens`) +`user_id` (sub gnexus-auth владельца — ключ выпускает сам себе), +`name` (человекочитаемое назначение), `user_email` (снейпшот для списков), +`system_role` (снейпшот роли выпуска), `token_hash` (sha256, unique), +`token_hint` (хвост), `created_at`, `last_used_at`, `revoked_at`. ## Ссылки -- Референс: gnexus-creds (`gnexus_creds/mcp*.py`, страница Tokens в UI) -- hard-panel пока на едином Bearer GHARD_ADMIN_TOKEN — при переходе на - мультиюзер: токены по этой конвенции, страница «Токены» в UI (см. - [auth.md](auth.md) про матрицу доступа). -- **Gnexus Synapse** — второй референс с допустимым вариантом: те же принципы - (страница `/mcp-keys` в UI, plaintext один раз, sha256 в `mcp_tokens`, - лимит ≤10 активных, admin-обзор и отзыв чужих), отличия: вместо scopes — - **снейпшот роли владельца** на момент выпуска токена (role `user` видит - только личные тулы, `admin`+ — весь каталог); исторический - маршрут `/mcp` остаётся alias'ом к `/mcp-protocol/`, заблокированный в - gnexus-auth пользователь гасит свои MCP-токены одним флагом из вебхука - `user.blocked` (`user_prefs.blocked`; гард отвечает тем же 401, что и при - неверном токене — не раскрывать статус блокировки), а самопривязка - SSO-логаут MCP-токены не трогает. \ No newline at end of file +- Канон: gnexus-synapse — `docs/07-mcp.md` (архитектура гейта и каталог тулов), + `app/mcp/server.py` (`McpTokenGuard`), страница `/mcp-keys`, + `resolve_personal_token`; таблица `mcp_tokens` (`docs/04-database.md`). +- Первый применённый вариант — gnexus-creds (`gnexus_creds/mcp*.py`, страница + Tokens): те же принципы хранения, но вместо снейпшота роли — **scopes** + (`read`/`reveal`/`write`): приемлемая альтернатива там, где нужны + градации внутри одного класса доступа. +- hard-panel пока на единых Bearer-токенах (`GHARD_ADMIN_TOKEN`, см. + [auth.md](auth.md) про матрицу); при переходе на мультиюзер — по этой + странице. \ No newline at end of file