ADR-0170 : MCP internes uniquement, catalogue MCP de l'entreprise dans SeaweedFS, destruction dès la panne, réveil à l'appel et traçabilité par défaut
Numérotée ADR-0168 à sa création, un numéro déjà pris par l'ADR-0168 sur les moteurs d'inférence. La migration appliquée
sql/postgres/V201__mcp_runtime_lifecycle.sql(fichier immuable, sa somme de contrôle est vérifiée) et l'historique des tâches du projet gardent l'ancien numéro.
Contexte
L'ADR-0167 a posé le Runtime MCP sur le ToolHive Kubernetes Operator. L'utilisateur a fixé trois exigences de plus. Les utilisateurs n'utilisent jamais un serveur MCP en ligne : ils hébergent leurs serveurs en interne ou prennent ceux du catalogue de l'entreprise, stocké dans SeaweedFS (installé avec le Navin Manager). Une instance qui ne fonctionne pas est détruite au plus tôt et recréée quand l'utilisateur l'appelle. Toute l'activité MCP est tracée par défaut et centralisée avec les services d'observabilité de la plateforme (OpenTelemetry, métriques, journaux). Le guide ToolHive « Run MCP servers in Kubernetes » décrit les phases et les conditions d'un MCPServer, la télémétrie (MCPTelemetryConfig), le redémarrage par annotation et le dépannage ; l'opérateur ne détruit ni ne recrée une instance de lui-même et n'a pas de mise en veille.
Décision
- Aucun MCP en ligne. La passerelle MCP n'atteint que des adresses internes : serveurs hébergés par le Navin Manager (
EAI_EGRESS_PRIVATE_ALLOWLIST), instances du Runtime MCP, hôtes internes deEAI_MCP_INTERNAL_HOSTS. Un hôte public est refusé à l'enregistrement et à chaque appel (403mcp_online_forbidden, auditsecurity.egress.denied, métriqueeai_mcp_online_refused_total) ; un nom déclaré qui se résout vers une adresse publique est refusé à la connexion. Un préréglage en ligne de la Bibliothèque Navin (hosting: online) ne s'installe qu'avec l'adresse interne où l'entreprise l'héberge. Le registre MCP du Navin Manager applique la même règle et le service mcp-host lance ses serveurs hors ligne (npm, uv et pip sans téléchargement, sans proxy). - Catalogue MCP de l'entreprise dans SeaweedFS. Les modèles du Runtime MCP sont lus dans l'objet
EAI_MCP_CATALOG_KEYdu profil de stockage plateformeEAI_MCP_CATALOG_STORAGE_PROFILE, publiés par l'équipe plateforme avecpython -m eai.domains.mcp_runtime.catalog_cli publishet relus toutes lesEAI_MCP_CATALOG_REFRESH_SECONDS. Une image doit venir d'un registre interne (EAI_MCP_RUNTIME_IMAGE_REGISTRIES) et être épinglée par digest. Une version illisible ou invalide ne retire jamais les modèles en service : la dernière version valide continue de servir, marquée périmée. En production, sans profil, la création répond 503mcp_catalog_not_configured; les modèles de départ decatalog/mcp-runtime/templates.yamlne servent qu'en dev et en test. - Cycle de vie (migration V201). Une instance qui ne fonctionne pas est détruite aussitôt : MCPServer
Failed, condition*ValidatedàFalse, pods enCrashLoopBackOffou sans image, MCPServer jamais pris en charge par l'opérateur, délai de démarrage dépassé, deux sondes/healthdu proxy en échec, proxy qui ne répond pas à un appel. Ses identifiants sont révoqués et ses ressources supprimées ; elle passe en veille et garde son entrée de passerelle. Une instance inactive, en fin de durée de vie d'une génération ou arrêtée par son propriétaire (POST .../stop) passe en veille de la même façon. L'appel suivant de son propriétaire ou d'un de ses agents la recrée (nouvelle génération, nouveaux identifiants, modèle relu dans le catalogue et politique vérifiée de nouveau) et attend qu'elle soit prête ; une requête qui n'a pas atteint le serveur, ou sans effet de bord, est rejouée une fois. AprèsEAI_MCP_RUNTIME_FAILURE_LIMITpannes dans la fenêtre, ou une panne qu'une recréation ne corrige pas, l'instance reste en échec jusqu'à son démarrage explicite (POST .../start). - Boucle du contrôleur dans les pods de l'API. Seuls les pods de l'API du Control Plane peuvent appeler l'API Kubernetes (jeton du compte de service, RBAC du chart, politique Cilium
deny-kube-apiserver) : la boucle tourne dans ces pods, une réplique à la fois (verrou consultatif PostgreSQL), toutes lesEAI_MCP_RUNTIME_REAPER_SECONDS. Elle remplace la tâchemcp_runtime_reaperdu worker prévue par l'ADR-0167. - Traçabilité par défaut. Chaque espace de noms de projet reçoit le MCPTelemetryConfig
eai-mcp-telemetry(traces et métriques des proxys ToolHive en OTLP/HTTP avec TLS vers le collecteur de la plateforme, CA interne copiée) et Kyverno refuse un MCPServer sanstelemetryConfigRef. La passerelle propage le contexte W3C au proxy et compte chaque appel (eai_mcp_calls_total) ; le journal de cycle de vie porte letrace_idde la requête et la génération ; les métriqueseai_mcp_runtime_*eteai_mcp_catalog_*alimentent cinq alertes ; les journaux des pods vont dans l'index séparéeai-mcp-runtime-audit. - Exploitation par le Navin Manager. Les identités de la ConfigMap
kyverno/eai-mcp-runtime-operatorspeuvent supprimer et redémarrer une instance (annotationmcpserver.toolhive.stacklok.dev/restarted-at), jamais modifier sa spécification.
Alternatives écartées
- Autoriser les MCP en ligne avec une liste d'hôtes : les données d'un projet sortiraient de l'installation, contraire à l'exigence de l'utilisateur.
- Redémarrer sur place une instance en panne (annotation de ToolHive) : ses identifiants temporaires et ses ressources resteraient actifs pendant la panne ; la destruction puis la recréation à l'appel les révoque aussitôt.
- Garder la boucle dans le worker : le worker ne peut pas appeler l'API Kubernetes.
- Lire les modèles dans un dépôt Git : l'utilisateur a choisi SeaweedFS, installé avec le Navin Manager.
Conséquences
Une panne n'immobilise jamais d'identifiants et l'utilisateur retrouve son instance en l'appelant. Le Runtime MCP demande un profil de stockage plateforme vers SeaweedFS et la CA interne (Secret eai-internal-ca) dans le namespace de la release. Les fonctions de ToolHive non reprises (groupes et agrégation, mise à l'échelle horizontale, affinité de session) restent hors du périmètre : une instance par utilisateur et par projet, une réplique.
Preuves
services/control-plane/tests/test_mcp_internal_only.py, services/control-plane/tests/test_mcp_catalog_seaweedfs.py, 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/mcp-host/tests/test_host.py, portal/tests/navin-library.test.ts, deploy/policies/kyverno/mcp-runtime.yaml.