Runtime MCP par utilisateur et par projet
Le Runtime MCP démarre une instance MCP isolée pour un utilisateur dans un projet, avec les identifiants de cet utilisateur uniquement, et la détruit quand elle est inactive, tombe en panne ou n'est plus utile (ADR-0167, ADR-0170). Cette page explique comment un développeur ou un utilisateur démarre, appelle et suit une instance, et ce que la plateforme garantit sur l'identité, les limites et les traces. L'installation, le durcissement et l'exploitation du ToolHive Operator sont dans Runtime MCP (exploitation) ; cette page ne les répète pas.
Avant de commencer
- L'opérateur de la plateforme a activé le runtime (
EAI_MCP_RUNTIME_ENABLED, ToolHive Operator installé). Sans cela, l'onglet Instances MCP affiche "Le Runtime MCP n'est pas activé sur la plateforme" et la création répond 409mcp_runtime_disabled. - Le catalogue MCP de l'entreprise est publié dans SeaweedFS. En production sans lui, la création répond 503
mcp_catalog_not_configured. - Droits : la relation
editorsur le projet (ou le rôletenant_adminoudeveloper) pour démarrer une instance ;tenant_adminpour fixer la politique du projet.
Composition d'une instance
| Élément | Valeur |
|---|---|
| Namespace du projet | mcp-<12 hex> (sha256 du tenant et du projet), Pod Security restricted, ResourceQuota (EAI_MCP_RUNTIME_QUOTA_CPU, EAI_MCP_RUNTIME_QUOTA_MEMORY), LimitRange, NetworkPolicy default-deny |
| Instance | MCPServer inst-<10 hex> avec sa propre audience, politique Cedar limitée au propriétaire, à ses agents et aux outils autorisés, MCPToolConfig, Secret et CiliumNetworkPolicy propres |
| Image | Issue du modèle du catalogue de l'entreprise, registre interne (EAI_MCP_RUNTIME_IMAGE_REGISTRIES), épinglée par empreinte |
| Identifiants | none, secret_ref (secret personnel de l'utilisateur donné par référence) ou openbao_database (bail OpenBao dynamique) |
| Entrée de la passerelle | Serveur rt-<10 hex> du projet, auth.type mcp_runtime, appelable par son propriétaire et ses agents uniquement |
Les modèles de départ de catalog/mcp-runtime/templates.yaml sont postgres, github, fetch et osv ; ils
servent en dev et en test uniquement, la production lit le catalogue de l'entreprise.
États d'une instance
| État | Signification | Effet d'un appel |
|---|---|---|
provisioning | Ressources ToolHive écrites, en attente du MCPServer Ready | Attend jusqu'à EAI_MCP_RUNTIME_WAKE_TIMEOUT_SECONDS, puis mcp_instance_starting |
ready | Appelable | Relayé au proxy ToolHive |
sleeping | Inactive, fin de vie d'une génération, arrêtée par son propriétaire, ou détruite après une panne : identifiants révoqués, ressources supprimées, entrée de la passerelle conservée | Recrée l'instance (nouvelle génération, nouveaux identifiants, modèle relu, politique revérifiée) et attend |
failed | EAI_MCP_RUNTIME_FAILURE_LIMIT pannes dans EAI_MCP_RUNTIME_FAILURE_WINDOW_SECONDS, ou une panne que la recréation ne guérit pas | Refusé avec mcp_instance_failed jusqu'à ce que son propriétaire la redémarre |
terminating, terminated | Détruite définitivement ; le journal est conservé | Refusé avec mcp_instance_not_ready |
Fixer la politique du projet
-
Écrire la politique (tenant_admin). PUT /v1/projects/{project_id}/mcp/policy fixe les modèles, les hôtes et les rôles de base de données autorisés, la durée d'inactivité, la durée de vie et les quotas. Un
allowed_templatesvide autorise tous les modèles ; les hôtes et les rôles doivent être listés pour être utilisés.client.mcp_runtime.put_policy("alpha", allowed_templates=["github", "postgres"],allowed_hosts=["<pg-host>"], allowed_database_roles=["readonly"],idle_timeout_seconds=900, max_lifetime_seconds=28800,max_instances_per_user=3, max_instances_per_project=20)Résultat attendu : la réponse contient la politique avec
configured: true; la modification est auditée enmcp.runtime.policy.En cas d'échec : 422
mcp_policy_invalidliste les modèles inconnus, les hôtes invalides et les rôles invalides.
| Champ | Par défaut | Bornes |
|---|---|---|
allowed_templates | [] (tous les modèles) | 100 noms |
allowed_hosts | [] | 200 noms DNS ou *.suffixe |
allowed_database_roles | [] | 100 noms |
idle_timeout_seconds | 900 | 60 à 86400 |
max_lifetime_seconds | 28800 | 300 à 86400 |
max_instances_per_user | 3 | 1 à 20 |
max_instances_per_project | 20 | 1 à 200 |
Démarrer et appeler une instance
-
Lister les modèles et les instances. GET /v1/projects/{project_id}/mcp/instances renvoie
enabled, la politique, l'état du catalogue (source, version, périmé), les modèles et vos instances. Dans le portail : Bibliothèque Navin > Instances MCP.Résultat attendu : chaque modèle affiche "Autorisé" ou "Non autorisé par la politique" et son type d'identifiants.
En cas d'échec : "Le catalogue ne peut pas être relu" signifie que la dernière version valide du catalogue est utilisée.
-
Démarrer une instance. POST /v1/projects/{project_id}/mcp/instances avec le modèle, ses paramètres, une référence d'identifiant quand le modèle en demande une, les agents autorisés à l'appeler avec vous (20 au plus), les outils autorisés et une durée d'inactivité (jamais au-dessus de celle de la politique). Dans le portail : Activer sur le modèle.
instance = client.mcp_runtime.create_instance("alpha", "github", credential_ref="openbao://users/alice/github-token",agents=["agent:support-bot"], allowed_tools=["search_issues"], idle_timeout_seconds=600)Résultat attendu : 202 avec
status: provisioning;credential_activevaut true quand un bail a été émis.En cas d'échec : 403
mcp_template_not_allowed,mcp_host_not_allowedoumcp_database_role_not_allowed(politique), 409mcp_instance_quotaoumcp_instance_exists, 422mcp_credential_required,mcp_agent_invalid,mcp_tool_invalidoumcp_param_invalid, 502mcp_credentials_unavailable(OpenBao). -
La suivre jusqu'à ce qu'elle soit prête. GET /v1/projects/{project_id}/mcp/instances/{instance_id} relit son état dans ToolHive et renvoie son journal (
events).Résultat attendu :
status: readyet unserver_id; le journal contientcreated,credentials_issued,provisionedetready.En cas d'échec :
failure_reasondonne la cause (voir le tableau de dépannage). -
L'appeler par la passerelle. L'entrée est un serveur MCP ordinaire du projet : appelez-la avec la session du SDK ou POST /v1/mcp/gateway/{server_id}, en tant que propriétaire ou que l'un de ses agents.
session = client.mcp.session(instance["server_id"])session.initialize()session.call_tool("search_issues", {"query": "is:open label:bug"})Résultat attendu : la réponse de l'outil ; une instance en veille est recréée avant l'appel.
En cas d'échec : l'erreur JSON-RPC -32004 porte
data.code(mcp_instance_starting,mcp_instance_busy,mcp_instance_unavailable,mcp_instance_failed,mcp_instance_not_ready) etretry_after_seconds.
Arrêter, redémarrer et détruire
-
L'arrêter tout de suite. POST /v1/projects/{project_id}/mcp/instances/{instance_id}/stop (portail : Arrêter) : identifiants révoqués, ressources supprimées, entrée de la passerelle conservée.
Résultat attendu :
status: sleeping,sleep_reason: user.En cas d'échec :
mcp_instance_busypendant une autre opération de cycle de vie ; réessayez. -
La redémarrer. POST /v1/projects/{project_id}/mcp/instances/{instance_id}/start (portail : Démarrer) redémarre une instance en veille ou en échec et remet à zéro son compteur de pannes.
Résultat attendu : 202,
status: provisioning, une nouvellegeneration.En cas d'échec : le modèle ou la politique la refuse de nouveau ; la raison est dans
failure_reason. -
La détruire à la fin de la tâche. DELETE /v1/projects/{project_id}/mcp/instances/{instance_id} (portail : Détruire) : identifiants révoqués, ressources supprimées, entrée de la passerelle désactivée, journal conservé.
Résultat attendu :
status: terminatedet l'événement d'auditmcp.instance.destroy.En cas d'échec :
terminatingreste tant qu'une suppression échoue ; la boucle du contrôleur la réessaie toutes lesEAI_MCP_RUNTIME_REAPER_SECONDS.
Sessions
- La passerelle garde une session MCP (
Mcp-Session-Id) par entrée de la passerelle. Elle envoieinitializequand l'entrée n'a pas de session, et une fois de plus quand le serveur répond 404 pour une session expirée. - Une instance recréée est un nouveau proxy : la passerelle abandonne l'ancienne session et réinitialise.
- Quand le proxy ne répond pas (connexion refusée, 502, 503, 504), l'instance est détruite tout de suite. L'appel
est réessayé une fois sur l'instance recréée quand la requête n'a jamais atteint le serveur ou quand la méthode n'a
pas d'effet de bord (
initialize, tools/list,resources/list,resources/read,prompts/list,ping) ; un tools/call qui a pu s'exécuter n'est jamais répété et répondmcp_instance_unavailable.
Propagation de l'identité
- L'appelant s'authentifie auprès du Control Plane (jeton Keycloak ou clé d'API) ; la passerelle vérifie le
projet, la liste d'accès de l'entrée (propriétaire et agents) et
can_invokedans OpenFGA. - Pour chaque appel, la passerelle signe un JWT court avec la clé de la Context Authority : émetteur
EAI_MCP_RUNTIME_ISSUER, sujet l'appelant, audience l'instance (une audience par instance, un jeton n'est donc jamais rejoué sur une autre), durée de vieEAI_MCP_RUNTIME_TOKEN_TTL_SECONDS(300 secondes par défaut). - Le proxy ToolHive vérifie le jeton avec GET /v1/mcp/runtime/jwks (clés publiques uniquement), puis applique la politique Cedar de l'instance : seuls son propriétaire et ses agents appellent ses outils autorisés.
- Le conteneur MCP reçoit les identifiants du propriétaire uniquement, écrits dans le Secret de l'instance ; l'identifiant du bail est scellé et effacé une fois révoqué, un secret personnel n'est gardé que par sa référence.
Ne partagez jamais une instance en donnant ses identifiants à un autre utilisateur : chaque utilisateur démarre sa
propre instance. Un agent listé dans agents agit avec les identifiants du propriétaire de l'instance.
Limites
| Limite | Valeur | Où elle se règle |
|---|---|---|
| Instances par utilisateur et par projet | 3 et 20 par défaut (seules provisioning et ready comptent) | Politique du projet |
| Durée d'inactivité | 900 secondes par défaut, jamais au-dessus de la politique | Politique, instance |
| Durée de vie d'une génération | 28800 secondes par défaut | Politique |
| Attente d'un appel pour son instance | 90 secondes par défaut | EAI_MCP_RUNTIME_WAKE_TIMEOUT_SECONDS |
| Délai de préparation | 600 secondes par défaut | EAI_MCP_RUNTIME_PROVISIONING_TIMEOUT_SECONDS |
Pannes avant failed | 3 en 900 secondes par défaut | EAI_MCP_RUNTIME_FAILURE_LIMIT, EAI_MCP_RUNTIME_FAILURE_WINDOW_SECONDS |
| CPU et mémoire d'un namespace de projet | 8 CPU et 16Gi par défaut | EAI_MCP_RUNTIME_QUOTA_CPU, EAI_MCP_RUNTIME_QUOTA_MEMORY |
| CPU et mémoire d'une instance | Issus de son modèle (resources) | Catalogue de l'entreprise |
| Réseau du conteneur MCP | Hôtes de son modèle uniquement | CiliumNetworkPolicy de l'instance |
Observabilité des appels
| Signal | Contenu |
|---|---|
| Trace | Span eai.mcp.proxy de la passerelle ; le contexte W3C (traceparent) continue dans le proxy ToolHive, dont les traces vont au collecteur de la plateforme (EAI_MCP_RUNTIME_OTEL_ENDPOINT, échantillonnage EAI_MCP_RUNTIME_OTEL_SAMPLING_RATE) |
| Métriques | eai_mcp_calls_total{target,method,outcome} (cible instance, hosted ou internal), eai_mcp_runtime_instances, eai_mcp_runtime_lifecycle_total, eai_mcp_runtime_failures_total, eai_mcp_runtime_wake_seconds |
| Journal du cycle de vie | events de GET /v1/projects/{project_id}/mcp/instances/{instance_id} : événement, acteur, détails, génération et trace_id de la requête ; en ajout seul, conservé après l'instance |
| Audit | mcp.instance.create, mcp.instance.destroy, mcp.runtime.policy, et mcp.tools.call (et les autres méthodes) pour chaque appel, dans GET /v1/audit/events |
| Journaux des pods | Index séparé eai-mcp-runtime-audit (400 jours), lu par les responsables sécurité |
-
Retrouver la trace d'un appel. Ouvrez le journal de l'instance dans le portail (Journal) et copiez l'"Identifiant de trace de la requête" de l'événement.
Résultat attendu : la trace montre le span de la passerelle et les spans du proxy ToolHive.
En cas d'échec : un événement écrit hors d'une requête (boucle du contrôleur) n'a pas de
trace_id.
Étapes suivantes
Dépannage
| Symptôme | Cause | Correction |
|---|---|---|
409 mcp_runtime_disabled | Runtime non activé sur la plateforme | Demandez à l'opérateur (exploitation) |
503 mcp_catalog_not_configured | Aucun catalogue de l'entreprise en production | L'équipe plateforme publie le catalogue |
failure_reason: image_pull | Image absente du registre interne | Copiez l'image avec son empreinte |
failure_reason: spec_rejected | ToolHive a refusé la configuration | Corrigez le modèle ; redémarrez l'instance |
failure_reason: provisioning_timeout ou not_reconciled | Opérateur arrêté ou surchargé | Vérifiez l'état de l'opérateur dans le Navin Manager |
failure_reason: health ou unreachable | Le proxy a échoué à ses sondes ou n'a pas répondu | Rappelez : l'instance est recréée |
failure_reason: template_removed ou policy | Modèle retiré du catalogue, ou plus autorisé | Changez la politique ou choisissez un autre modèle |
JSON-RPC mcp_instance_failed | Trop de pannes dans la fenêtre | Redémarrez-la avec POST .../start après avoir corrigé la cause |
JSON-RPC mcp_principal_not_allowed | L'appelant n'est ni le propriétaire ni l'un de ses agents | Démarrez votre propre instance |