ADR-0167 : Runtime MCP isolé par tenant, projet et utilisateur sur le ToolHive Kubernetes Operator
Contexte
La passerelle MCP (eai.domains.mcp) décide quels utilisateurs et quels agents accèdent à quels serveurs MCP. Il manquait un runtime qui crée réellement des instances isolées, les démarre à la demande et les détruit après usage, avec les identifiants et les autorisations propres à chaque utilisateur. L'hébergement du Navin Manager (ADR-0165) fait tourner un processus par serveur et par projet, pas par utilisateur. Le ToolHive Kubernetes Operator (Stacklok, Apache-2.0, auto-hébergé) déploie des serveurs MCP dans des espaces de noms Kubernetes et les relie à une source OIDC, mais l'authentification n'y est pas activée par défaut, les politiques réseau ne sont pas posées par défaut et la durée d'inactivité n'est pas une capacité vérifiée de ToolHive.
Décision
- Service ToolHive Operator 0.59.0 : charts OCI épinglés, images épinglées par digest, 2 réplicas avec élection de chef, PodDisruptionBudget, CRD conservées, déployé par Argo CD dans
toolhive-systemet installable aussi depuis la page Runtime MCP du Navin Manager. - Contrôleur de cycle de vie dans le Control Plane (
services/control-plane/src/eai/domains/mcp_runtime, migration V200) : création, exécution, inactivité, destruction. Un espace de noms par projetmcp-<12 hex>(Pod Security restricted, ResourceQuota, LimitRange, NetworkPolicy default-deny, MCPOIDCConfig). Par instance : un MCPServerinst-<10 hex>avecoidcConfigReftoujours présent (audience propre à l'instance), une politique Cedar limitée au propriétaire, à ses agents et aux outils autorisés, l'audit activé, une image épinglée par digest et un conteneur sans privilège (racine en lecture seule, aucune capacité, non root, seccomp RuntimeDefault, sans jeton de compte de service) ; un MCPToolConfig ; un Secret propre ; une CiliumNetworkPolicy (le Control Plane vers le proxy de l'instance seulement, le conteneur MCP vers les hôtes de son modèle seulement). - Identifiants temporaires propres à l'instance : bail dynamique OpenBao (moteur database) pour PostgreSQL, secret personnel de l'utilisateur donné par référence pour GitHub. Ils ne sont écrits que dans le Secret de l'instance ; l'identifiant du bail est scellé puis effacé une fois révoqué.
- Politique par projet posée par tenant_admin : modèles, hôtes, rôles de base de données, durée d'inactivité (900 secondes par défaut), durée de vie maximale, quotas par utilisateur et par projet. La durée d'inactivité est une politique de Navin : la tâche
mcp_runtime_reaperdu worker détruit les instances inactives, expirées, orphelines ou bloquées au démarrage, et relance les révocations et les suppressions qui ont échoué. - Passerelle : l'entrée d'une instance n'est appelable que par son propriétaire et ses agents, avec un JWT court signé par la clé du Context Authority (clés publiques à
GET /v1/mcp/runtime/jwks). La liste d'accès par utilisateur et par agent (PUT /v1/mcp/servers/{server_id}/access) s'applique aussi aux serveurs ordinaires. - Destruction : bail révoqué, MCPServer, MCPToolConfig, CiliumNetworkPolicy et Secret supprimés, entrée de passerelle désactivée. Le journal
eai.mcp_runtime_eventsest en ajout seul, sans clé étrangère vers l'instance, et survit à sa destruction ; les journaux des pods des espaces de noms du Runtime MCP vont dans un index OpenSearch séparé (eai-mcp-runtime-audit, rétention 400 jours). - Garde-fous du cluster : ClusterPolicy Kyverno
eai-mcp-runtimeen Enforce (seul le compte de service du Control Plane écrit dans ces espaces de noms ; un MCPServer sans OIDC, sans Cedar, sans audit, sans image épinglée ou sans conteneur durci est refusé ; NetworkPolicy default-deny générée) et ClusterRoleeai-mcp-runtime-controllerdu chart Helm (valeurmcpRuntime.enabled). - Interface : ToolHive UI est une application de bureau locale, sans interface web côté cluster. Le pilotage se fait dans le Navin Manager (page Runtime MCP : état de l'opérateur, instances, destruction) et dans le portail (onglet Instances MCP de la Bibliothèque Navin).
Alternatives écartées
- Hébergement mcp-host seul (ADR-0165) : un processus par serveur et par projet, sans identité par utilisateur ni conteneur isolé. Il reste pour les installations sans Kubernetes.
- ToolHive sans contrôleur Navin : ni durée d'inactivité, ni révocation des identifiants, et une authentification et un réseau à poser à la main.
- Un proxy MCP partagé par tenant : les identifiants d'un utilisateur seraient utilisables par les autres membres du projet.
Conséquences
Chaque utilisateur exécute ses MCP avec ses identifiants et ses autorisations, sans exposer ses ressources aux autres projets ni aux autres membres de son projet. Le Runtime MCP demande un cluster Kubernetes avec ToolHive, Cilium et Kyverno, et EAI_MCP_RUNTIME_ENABLED ; sans lui, la création d'une instance répond 409 mcp_runtime_disabled.
Preuves
services/control-plane/tests/test_mcp_runtime.py, services/control-plane/tests/test_platform_mcp_runtime.py, navin-manager/server/tests/test_mcp_runtime_k8s.py, navin-manager/ui/src/test/mcp-runtime.test.tsx, portal/tests/navin-library.test.ts, deploy/policies/kyverno/mcp-runtime.yaml, deploy/cilium/mcp-runtime.yaml.