feat(ai): mcp-k8s через ToolHive — read-only доступ агента к кластеру #14

Open
hermes wants to merge 2 commits from feat/mcp-k8s into main
Collaborator

ToolHive поднят — через него поднимается MCP-сервер для Kubernetes. Одна задача = один MR: только MCP, без внешней экспозиции и без OIDC.

Сервер: containers/kubernetes-mcp-server, а не mkp из реестра ToolHive

Первый вариант был ghcr.io/stackloklabs/mkp/server (сервер k8s из реестра ToolHive) — просто потому, что он в реестре. Сравнение не в его пользу, поэтому переключились:

containers/kubernetes-mcp-server stackloklabs/mkp
Звёзды / форки 2104 / 439 59 / —
Последний коммит 2026-09-17 2026-09-11
Лицензия, язык Apache-2.0, Go Apache-2.0, Go
Read-only режим есть (--read-only, --disable-destructive) нет
Отключение наборов инструментов есть (--toolsets) нет
Скрыть ресурс от инструментов есть (denied_resources, disabled_tools) нет
Гайд по деплою с read-only SA есть (docs/getting-started-kubernetes.md) нет
Официальный Helm-чарт есть, с OAuth нет
Сообщество канал в CNCF Slack, мейнтейнеры Red Hat внутри Stacklok

Ключевое: у mkp нет ни одного «безопасного» режима — единственная граница там RBAC. У этого сервера есть штатный --read-only (полный запрет create/update/delete), --toolsets и фильтрация инструментов, то есть защита не одной, а двумя-тремя независимыми линиями.

Что сделано

  • kubernetes/apps/ai/mcp-k8s/ks.yaml — Flux Kustomization с targetNamespace: ai и dependsOn: [toolhive-operator]. Зависимость внутри своего ns, поэтому namespace в dependsOn нет — поле понадобится, когда зависимость будет в другом ns (Dragonfly CR в ai → оператор в database).
  • app/serviceaccount.yaml — SA mcp-k8s. Нужен явно: по CRD spec.serviceAccount означает «уже существующий», иначе оператор создаст свой.
  • app/rbac.yaml — ClusterRoleBinding на встроенную view + небольшой ClusterRole на кластерные объекты и метрики.
  • app/mcpserver.yaml — MCPServer mcp-k8s.

Права

Базовое чтение — встроенная роль view, а не самодельный список с resources: ["*"]: она покрывает namespaced-ресурсы (включая pods/log) и не включает secrets, а это именно то, что нужно — cluster-secrets, токен Proxmox CSI и прочая креда агенту не нужны. view описывает только namespaced-объекты, поэтому кластерное (nodes, namespaces, persistentvolumes, storage-классы) и метрики (metrics.k8s.io, чтобы работал top) добавлены отдельным ClusterRole. Записи и pods/exec нет нигде.

Поверх этого на самом сервере включён --read-only, а --toolsets core убирает наборы helm, tekton, kubevirt, kiali и netobserv — чарты в кластере ставит Flux, агенту там делать нечего.

Образ

quay.io/containers/kubernetes_mcp_server:v0.0.66@sha256:6d650f4bd6ac303ad82713c997e73a2d001602f9bf17392c9b9a0e30e29c6423 — тег проверен в API quay (от 2026-07-31, manifest list на 4 архитектуры). У чартов проекта образ на quay, ghcr-зеркала нет: если из кластера quay окажется медленным или недоступным, закрываем это proxy-cache проектом в нашем Harbor, но это уже отдельная задача.

Важно про аутентификацию

oidcConfigRef не задан, значит прокси ToolHive работает без аутентификации: любой, кто достучится до порта, получит API под правами SA. Поэтому наружу сервер не выводим (ни LB-IP, ни HTTPRoute) — пока агент вне кластера, адрес внутрикластерный. Внешний доступ появится вместе с OIDC после Authelia; тогда же имеет смысл denied_resources в TOML, чтобы прятать ресурсы на уровне инструментов, а не только правами.

Проверено

  • kubectl kustomize kubernetes/apps/ai — собирается, Flux Kustomization рендерится в ns ai.
  • kubectl kustomize kubernetes/apps/ai/mcp-k8s/app — SA, ClusterRole, два binding, MCPServer.

Дальше

  • searxng в ai + Dragonfly CR там же с dependsOn: [{name: dragonfly-operator, namespace: database}].
  • Authelia → OIDC для прокси ToolHive (и заодно для Hermes-дашборда и Vault).
ToolHive поднят — через него поднимается MCP-сервер для Kubernetes. Одна задача = один MR: только MCP, без внешней экспозиции и без OIDC. **Сервер: containers/kubernetes-mcp-server, а не mkp из реестра ToolHive** Первый вариант был `ghcr.io/stackloklabs/mkp/server` (сервер `k8s` из реестра ToolHive) — просто потому, что он в реестре. Сравнение не в его пользу, поэтому переключились: | | containers/kubernetes-mcp-server | stackloklabs/mkp | |---|---|---| | Звёзды / форки | 2104 / 439 | 59 / — | | Последний коммит | 2026-09-17 | 2026-09-11 | | Лицензия, язык | Apache-2.0, Go | Apache-2.0, Go | | Read-only режим | есть (`--read-only`, `--disable-destructive`) | нет | | Отключение наборов инструментов | есть (`--toolsets`) | нет | | Скрыть ресурс от инструментов | есть (`denied_resources`, `disabled_tools`) | нет | | Гайд по деплою с read-only SA | есть (docs/getting-started-kubernetes.md) | нет | | Официальный Helm-чарт | есть, с OAuth | нет | | Сообщество | канал в CNCF Slack, мейнтейнеры Red Hat | внутри Stacklok | Ключевое: у mkp нет ни одного «безопасного» режима — единственная граница там RBAC. У этого сервера есть штатный `--read-only` (полный запрет create/update/delete), `--toolsets` и фильтрация инструментов, то есть защита не одной, а двумя-тремя независимыми линиями. **Что сделано** - `kubernetes/apps/ai/mcp-k8s/ks.yaml` — Flux Kustomization с `targetNamespace: ai` и `dependsOn: [toolhive-operator]`. Зависимость внутри своего ns, поэтому `namespace` в `dependsOn` нет — поле понадобится, когда зависимость будет в другом ns (Dragonfly CR в `ai` → оператор в `database`). - `app/serviceaccount.yaml` — SA `mcp-k8s`. Нужен явно: по CRD `spec.serviceAccount` означает «уже существующий», иначе оператор создаст свой. - `app/rbac.yaml` — ClusterRoleBinding на встроенную `view` + небольшой ClusterRole на кластерные объекты и метрики. - `app/mcpserver.yaml` — MCPServer `mcp-k8s`. **Права** Базовое чтение — встроенная роль `view`, а не самодельный список с `resources: ["*"]`: она покрывает namespaced-ресурсы (включая `pods/log`) и **не** включает secrets, а это именно то, что нужно — `cluster-secrets`, токен Proxmox CSI и прочая креда агенту не нужны. `view` описывает только namespaced-объекты, поэтому кластерное (`nodes`, `namespaces`, `persistentvolumes`, storage-классы) и метрики (`metrics.k8s.io`, чтобы работал `top`) добавлены отдельным ClusterRole. Записи и `pods/exec` нет нигде. Поверх этого на самом сервере включён `--read-only`, а `--toolsets core` убирает наборы helm, tekton, kubevirt, kiali и netobserv — чарты в кластере ставит Flux, агенту там делать нечего. **Образ** `quay.io/containers/kubernetes_mcp_server:v0.0.66@sha256:6d650f4bd6ac303ad82713c997e73a2d001602f9bf17392c9b9a0e30e29c6423` — тег проверен в API quay (от 2026-07-31, manifest list на 4 архитектуры). У чартов проекта образ на quay, ghcr-зеркала нет: если из кластера quay окажется медленным или недоступным, закрываем это proxy-cache проектом в нашем Harbor, но это уже отдельная задача. **Важно про аутентификацию** `oidcConfigRef` не задан, значит прокси ToolHive работает **без аутентификации**: любой, кто достучится до порта, получит API под правами SA. Поэтому наружу сервер не выводим (ни LB-IP, ни HTTPRoute) — пока агент вне кластера, адрес внутрикластерный. Внешний доступ появится вместе с OIDC после Authelia; тогда же имеет смысл `denied_resources` в TOML, чтобы прятать ресурсы на уровне инструментов, а не только правами. **Проверено** - `kubectl kustomize kubernetes/apps/ai` — собирается, Flux Kustomization рендерится в ns `ai`. - `kubectl kustomize kubernetes/apps/ai/mcp-k8s/app` — SA, ClusterRole, два binding, MCPServer. **Дальше** - searxng в `ai` + Dragonfly CR там же с `dependsOn: [{name: dragonfly-operator, namespace: database}]`. - Authelia → OIDC для прокси ToolHive (и заодно для Hermes-дашборда и Vault).
hermes added 1 commit 2026-09-17 23:10:23 +00:00
feat(ai): mcp-k8s через ToolHive (read-only RBAC)
Flate / Flate - Filter (pull_request) Successful in 5s
Flate / Flate (pull_request) Successful in 18s
Flate / Flate - Success (pull_request) Canceled after 0s
c4cf3ba798
MCP-сервер k8s из реестра ToolHive: ghcr.io/stackloklabs/mkp/server:0.4.3, transport streamable-http.
Отдельный SA mcp-k8s и ClusterRole mcp-k8s-readonly (только get/list/watch).
Core-группа перечислена поимённо, чтобы не открывать secrets; остальные группы явным списком,
потому что apiGroups: [*] включает и core-группу. Наружу не выводим: без oidcConfigRef прокси
ToolHive неаутентифицирован.
hermes added 1 commit 2026-09-17 23:17:37 +00:00
fix(ai): mcp-k8s — containers/kubernetes-mcp-server вместо mkp
Flate / Flate - Success (pull_request) Blocked by required conditions
Flate / Flate - Filter (pull_request) Successful in 5s
Flate / Flate (pull_request) Successful in 17s
c2c9b273f2
У mkp нет ни одного безопасного режима, только RBAC. У containers/kubernetes-mcp-server есть
штатный --read-only, --toolsets и denied_resources, официальный гайд по read-only SA и чарт.
Образ quay.io/containers/kubernetes_mcp_server:v0.0.66 с дайджестом (тег проверен в API quay).
RBAC: вместо самодельного списка — встроенная view (secrets в неё не входят) + отдельный
ClusterRole на кластерные объекты и метрики. Аргументы: --port 8080 --read-only --toolsets core.
Some checks are pending
Flate / Flate - Success (pull_request) Blocked by required conditions
Flate / Flate - Filter (pull_request) Successful in 5s
Flate / Flate (pull_request) Successful in 17s
You are not authorized to merge this pull request.
This pull request can be merged automatically.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/mcp-k8s:feat/mcp-k8s
git checkout feat/mcp-k8s
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: grachevko/home-ops#14