Cluster Operator MCP
Pour l'exploitation ou le dépannage sans client MCP ni IA, voir le Manuel opérateur — Gateway MCP et Task Runtime.
| Élément | Valeur |
|---|---|
| Namespace | cluster-observer |
| Image | ghcr.io/containers/kubernetes-mcp-server:v0.0.67@sha256:e47f6ccc5347e1ea6be495d8d9ab55ac8a995fe87800fb984fa770cebe14ad44 |
| Réplicas | 1 |
| Transport | Streamable HTTP sur le Service ClusterIP cluster-observer:8080 |
| Exposition | Cloudflare Tunnel + Access Service Auth par agent ; Secure MCP Tunnel OpenAI pour ChatGPT ; porte Bearer dédiée mcp-mammouth.djayzone.com pour Mammouth AI |
| Autorisations | Observation en liste blanche ; patch uniquement sur workloads existants et objets Flux, sans création/suppression |
Usage actuel
Le workload fournit un accès MCP de diagnostic et d'exploitation ciblée au cluster sans distribuer de kubeconfig administrateur. Le Service reste ClusterIP : les accès distants passent par les tunnels authentifiés prévus par les manifests actifs.
Les barrières restent cumulatives : outils de suppression, scale et exécution
désactivés, ressources sensibles refusées, RBAC sans Secret/ConfigMap/RBAC et
NetworkPolicy limitée à CoreDNS et à l'API Kubernetes.
La lecture de logs est explicitement bornée à la sous-ressource pods/log avec
le seul verbe Kubernetes get.
Lecture des logs
Le serveur amont v0.0.67 expose l'outil natif pods_log avec les paramètres
name, namespace, container, tail et previous. Le besoin fonctionnel
pods_logs(namespace, pod, container, tailLines, sinceSeconds, previous) est
couvert par correspondance pod -> name et tailLines -> tail.
sinceSeconds et le renommage exact de l'outil en pods_logs ne sont pas
supportés par cette version du serveur ; ils nécessitent une évolution amont ou
une couche MCP dédiée. Le connecteur ne simule pas ces paramètres.
Actions opérateur ciblées
L'outil amont resources_create_or_update peut mettre à jour une ressource
existante. Le ServiceAccount ne possède aucun verbe create ou delete : les
mutations sont limitées à patch sur :
Deployment,StatefulSetetDaemonSet, pour actualiserspec.template.metadata.annotations["kubectl.kubernetes.io/restartedAt"];Kustomization,GitRepository,HelmRepositoryetHelmRelease, pour actualisermetadata.annotations["reconcile.fluxcd.io/requestedAt"];HelmRelease, pourreconcile.fluxcd.io/forceAtuniquement lorsqu'un force reconcile est explicitement demandé.
L'agent lit d'abord le manifeste complet, obtient une confirmation explicite, ne modifie que l'annotation prévue, réapplique le manifeste complet puis vérifie les conditions et le rollout. Cette capacité ne doit jamais servir à contourner GitOps ou à modifier une spécification applicative.
Le RBAC restreint les types de ressources, mais pas les champs d'un
patch. La restriction aux annotations constitue donc le contrat
d'utilisation de cette première version. Une future couche MCP spécialisée ou
une politique d'admission dédiée pourra rendre cette contrainte techniquement
incontournable.
Contrôles opérateur
Après une réconciliation Flux :
kubectl -n cluster-observer get deployment,service,pods
kubectl auth can-i --as=system:serviceaccount:cluster-observer:cluster-observer get pods
kubectl auth can-i --as=system:serviceaccount:cluster-observer:cluster-observer get pods/log
kubectl auth can-i --as=system:serviceaccount:cluster-observer:cluster-observer list pods/log
kubectl auth can-i --as=system:serviceaccount:cluster-observer:cluster-observer get secrets
kubectl auth can-i --as=system:serviceaccount:cluster-observer:cluster-observer create deployments
kubectl auth can-i --as=system:serviceaccount:cluster-observer:cluster-observer patch deployments
kubectl auth can-i --as=system:serviceaccount:cluster-observer:cluster-observer patch kustomizations.kustomize.toolkit.fluxcd.io -n flux-system
kubectl auth can-i --as=system:serviceaccount:cluster-observer:cluster-observer delete deployments
Les résultats attendus sont respectivement yes, yes, no, no, no,
yes, yes, no. Un test MCP ciblé peut ensuite appeler pods_log sur un
pod non sensible avec une petite valeur de tail et vérifier qu'aucune capacité
de mutation n'est apparue.
Exposition Bearer dédiée à Mammouth AI
Mammouth AI accepte un MCP personnalisé en Streamable HTTP avec une clé d'API,
mais ne peut pas émettre les deux en-têtes Cloudflare Access Service Auth. Un
hostname distinct lui est donc réservé, sans modifier mcp.djayzone.com :
| Élément | Valeur |
|---|---|
| Endpoint Mammouth | https://mcp-mammouth.djayzone.com/mcp |
| Passerelle | mammouth-mcp-proxy (2 réplicas, ClusterIP :8080) |
| Authentification | Authorization: Bearer …, clé dédiée de 256 bits |
| Secret | cluster-observer/mammouth-mcp-api-key, créé hors Git |
| Backend | http://mcp-gateway.cluster-observer.svc.cluster.local:8080/mcp |
La passerelle ne dispose d'aucun ServiceAccount, d'aucun droit Kubernetes et
d'aucun accès sortant hors CoreDNS et Service MCP. Elle compare la clé en temps
constant, répond 401 sans indiquer quelle partie a échoué, ne transmet jamais
l'en-tête Authorization au serveur MCP et relaie les réponses sans
bufferisation pour préserver les flux SSE. Le corps des requêtes est borné à
1 MiB et le débit à 2 req/s avec une rafale de 4, comme la section [http] du
serveur MCP.
Le contrôle Gatus « MCP Mammouth public » interroge l'endpoint sans clé et
attend 401 : la fermeture de la porte est donc surveillée en continu, sans
qu'aucun secret n'existe dans Git.
La création de la clé, sa rotation, sa révocation, la route Cloudflare Tunnel et
le retour arrière sont décrits dans
apps/ovh-primary/cluster-observer/README.md. Retirer le Secret coupe Mammouth
seul, sans effet sur les autres agents.
Connecteur MCP unique (Gateway multi-domaines)
Depuis le Lot 0 de #434, le
namespace héberge aussi mcp-gateway : un seul endpoint MCP derrière lequel
plusieurs domaines d'outils sont exposés. Le Cluster Operator MCP y devient le
domaine cluster.* au lieu de rester un connecteur indépendant.
| Élément | Valeur |
|---|---|
| Workload | mcp-gateway (2 réplicas, ClusterIP :8080, chemin /mcp) |
| Image | python:3.14-alpine@sha256:f6a589d43c42b9e7f7dc67a12d37132491f362859a5d750607710cc56da3bc72 |
| Protocole | MCP 2025-06-18, Streamable HTTP |
| Domaines actifs | 11, aucun domaine planifié — ai.*, github.*, cluster.*, n8n.*, notrecouple.*, plane.*, slack.*, dagger.*, task.*, vehicle.* et gateway.*. L'inventaire exact des outils est découvert dynamiquement avec gateway.describe afin d'éviter de figer un compteur rapidement obsolète. |
| Authentification | inchangée : Cloudflare Access Service Auth par agent, porte Bearer dédiée pour Mammouth AI |
| Contrôles de transport | Origin validé contre une allowlist versionnée (403 sinon) ; seule la révision 2025-06-18 est annoncée, toute autre MCP-Protocol-Version est rejetée en 400 |
| Privilèges Kubernetes | aucun — ni ServiceAccount ni token Kubernetes |
Écriture plane.* |
PLANE_WRITE_MODE=api : lectures et mutations directes par l’API Plane, sans commit Git |
Déclenchement n8n.* |
par le webhook de production du workflow — l’API publique de n8n n’exécute pas. Un workflow n’est déclenchable que s’il porte le tag N8N_EXPOSED_TAG (mcp), possède un nœud Webhook actif et est lui-même activé |
| Credentials | mcp-gateway-github pour github.* ; mcp-gateway-plane pour plane.* ; mcp-gateway-n8n pour n8n.* — tous provisionnés hors Git et montés en fichiers, jamais en variables d’environnement. slack.* n’en porte aucune : le jeton reste dans le composant slack-mcp |
Administration n8n.* |
le domaine peut créer, modifier, supprimer, activer/désactiver un workflow et gérer explicitement son exposition avec n8n.set_workflow_exposure. La création ou l'activation n'expose jamais implicitement un workflow. |
Un agent appelle d'abord gateway.describe pour connaître les domaines réellement actifs et leur surface exacte. Au 7 octobre 2026, planned_domains est vide : tous les domaines actuellement déclarés par le contrat sont actifs. La découverte dynamique reste la source de vérité plutôt qu'une liste statique dans la documentation.
Les outils sont publiés sous la forme <domaine>.<outil>. Un client qui
normalise le nom en cluster_pods_list est routé correctement : le routeur
résout le nom exact, puis un nom replié, et refuse de deviner en cas
d'ambiguïté.
Un backend momentanément injoignable ne retire que son propre namespace de
tools/list ; les autres domaines continuent de répondre. Une exception dans un
domaine devient un résultat en erreur, jamais une panne du Gateway.
Domaine n8n.*
n8n reste l'orchestrateur — événements, workflows, retries et dispatch. Le
Gateway lit ses workflows, les administre et peut les déclencher ; l'état durable,
les checkpoints et les leases des tâches appartiennent à Task Runtime via task.*.
Le domaine n8n.* ne transforme pas n8n en proxy synchrone.
Le déclenchement passe par le webhook de production du workflow, parce que
l'API publique de n8n n'a aucun endpoint d'exécution. Cette contrainte sert de
contrôle d'accès : un workflow n'est déclenchable que s'il porte le tag
N8N_EXPOSED_TAG (mcp), possède un nœud Webhook actif, et est lui-même
activé — sinon n8n n'a pas enregistré son chemin de production.
Le domaine administre aussi les workflows : create_workflow, update_workflow, delete_workflow, activate_workflow, deactivate_workflow et set_workflow_exposure. L'exposition reste une opération séparée et explicite : créer ou activer un workflow ne lui ajoute jamais automatiquement le tag mcp. set_workflow_exposure ajoute ou retire uniquement ce tag et préserve les autres tags. Un workflow n'est déclenchable que si les trois conditions restent réunies : tag mcp, webhook actif et workflow activé.
trigger_workflow rend la main sans attendre la fin. Si le workflow dépasse le
délai, la réponse est status: "sent" et non une erreur : il a démarré, seule
sa réponse manque, et signaler un échec inviterait à un doublon. Le résultat se
lit avec list_executions puis get_execution.
Domaine slack.*
Slack est le bus de communication humain ↔ agents, jamais une source de vérité durable pour le code, le backlog ou l'état runtime.
Le Gateway ne parle pas directement à l'API Slack : il passe par le composant
slack-mcp, un pont HTTP interne déployé dans son propre namespace, qui détient
le jeton xoxb- et traduit vers https://slack.com/api. Cinq outils : channels_list, messages_list, messages_send, thread_reply et ops_event_publish. Ce dernier publie des événements opérationnels dédupliqués pour les intégrations prévues ; les agents respectent toujours les règles de routage de AGENTS.md et ne simulent pas manuellement les intégrations des canaux protégés.
Le jeton slack-mcp-secrets est injecté en variable d'environnement dans
slack-mcp — contrairement aux credentials du Gateway, qui sont montées en
fichiers et dont le contrat de rendu refuse toute origine secretKeyRef. Cette
différence est volontaire au niveau du composant, mais elle mérite d'être sue :
le jeton Slack est visible dans l'environnement du processus slack-mcp.
Domaines task.*, ai.* et vehicle.*
task.*porte l'état durable, les checkpoints, le fallback provider et les leases glissants de 10 minutes (get,acquire,renew,checkpoint,handoff,release,complete,fail). La présence de l'API ne suffit pas à déclarer un lot fonctionnel terminé : une preuve de tâche réelle reste exigée par Plane.ai.*expose les routes providers autorisées du runtime agentique, notamment ChatGPT par abonnement OAuth et Mammouth lorsque sa route est disponible.vehicle.*expose les lectures et commandes gardées du bridge Hyundai Bluelink sans donner d'accès direct au backend interne.
Domaine notrecouple.*
Le domaine notrecouple.* est volontairement limité à la recette DEV. Il expose les actions de test nécessaires à notrecouple-dev sans fournir de mutation générique de la production. Une panne de ce domaine n'empêche pas l'exploitation manuelle de DEV ni de PROD.
Domaine plane.*
Plane est lu et modifié directement par son API. PLANE_WRITE_MODE=api est
actif : plane.set_state, plane.update_work_item et
plane.create_work_item ne produisent plus de commit sur la branche
historique plane. plane.create_module crée directement un Module/Epic avec
une clé durable et un comportement idempotent.
Le Secret mcp-gateway-plane, provisionné hors Git avec la clé api-token,
alimente les deux sens. Si la credential ou l'API manque, le domaine échoue
explicitement sans basculer silencieusement vers Git.
Le workflow GitHub ne réconcilie plus .plane/** vers Plane. Il conserve
temporairement la lecture de références historiques pour corréler les Pull
Requests ; Plane reste la source de vérité du pilotage.
Les mutations protégées exigent le contexte _task.task_id et _task.lease_id
obtenu via task.acquire. tools/list et
gateway.describe(include_schemas=true) publient les mêmes schémas effectifs,
y compris ce verrou et le champ facultatif _gateway_context de corrélation.
Ce dernier ne remplace pas le verrou. Les deux contextes sont retirés avant le
backend métier ; aucun lease brut n’est journalisé. Après déploiement d’une
évolution des schémas, rafraîchir les signatures du connecteur et vérifier une
recette réelle avant de conclure que les écritures fonctionnent depuis le client.
Point d’entrée MCP unique
mcp-gateway est le point d’entrée MCP unique et stable pour tous les
consommateurs externes. Les services MCP spécialisés sont des backends internes
et ne doivent pas être exposés ou configurés directement auprès de ChatGPT,
Claude, Codex, Mammouth ou d’un futur client.
Les trois portes suivent le même invariant :
- le Secure MCP Tunnel OpenAI cible
mcp-gatewayviaMCP_SERVER_URL; - le proxy Bearer Mammouth cible
mcp-gatewayviaMCP_UPSTREAM_URL; - le hostname Cloudflare
mcp.djayzone.com, piloté à distance, cible le Servicemcp-gateway:8080sans modification de la politique Access.
La découverte dynamique tools/list rend tout nouveau domaine disponible aux
consommateurs existants sans modifier leur destination.
Retour arrière
Repasser read_only = true, retirer les règles RBAC patch, puis
fusionner la PR de rollback. Le hash du ConfigMap provoquera le rollout du
serveur MCP via Flux.
Pour le Gateway seul : repointer mcp.djayzone.com vers cluster-observer:8080,
remettre l'URL amont des portes déjà basculées, puis retirer gateway.yaml de
apps/ovh-primary/cluster-observer/kustomization.yaml avec son entrée
configMapGenerator et les ajouts d'allowlist réseau (networkpolicy.yaml,
cloudflared.yaml, openai-tunnel.yaml, mammouth-proxy.yaml,
ci-dispatcher/manifests.yaml). Le serveur MCP, son RBAC et les autres agents
restent inchangés.
Modification des modules Plane
Le domaine Plane expose plane.update_module en mode API pour changer le nom ou la description d’un module existant, sans recréer son UUID ni modifier ses relations. La résolution refuse les noms ambigus et les collisions ; un PATCH minimal puis une relecture confirment les champs. Les répétitions identiques sont sans écriture. La description vide explicite permet d’effacer le texte. Les autres champs et les modes Git/dual sont refusés. Voir apps/ovh-primary/cluster-observer/plane-module-status.md pour la procédure et le smoke après déploiement.