Aller au contenu principal

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 409 mcp_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 editor sur le projet (ou le rôle tenant_admin ou developer) pour démarrer une instance ; tenant_admin pour fixer la politique du projet.

Composition d'une instance​

ÉlémentValeur
Namespace du projetmcp-<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
InstanceMCPServer 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
ImageIssue du modèle du catalogue de l'entreprise, registre interne (EAI_MCP_RUNTIME_IMAGE_REGISTRIES), épinglée par empreinte
Identifiantsnone, secret_ref (secret personnel de l'utilisateur donné par référence) ou openbao_database (bail OpenBao dynamique)
Entrée de la passerelleServeur 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​

ÉtatSignificationEffet d'un appel
provisioningRessources ToolHive écrites, en attente du MCPServer ReadyAttend jusqu'à EAI_MCP_RUNTIME_WAKE_TIMEOUT_SECONDS, puis mcp_instance_starting
readyAppelableRelayé au proxy ToolHive
sleepingInactive, 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éeRecrée l'instance (nouvelle génération, nouveaux identifiants, modèle relu, politique revérifiée) et attend
failedEAI_MCP_RUNTIME_FAILURE_LIMIT pannes dans EAI_MCP_RUNTIME_FAILURE_WINDOW_SECONDS, ou une panne que la recréation ne guérit pasRefusé avec mcp_instance_failed jusqu'à ce que son propriétaire la redémarre
terminating, terminatedDétruite définitivement ; le journal est conservéRefusé avec mcp_instance_not_ready

Fixer la politique du projet​

  1. É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_templates vide 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 en mcp.runtime.policy.

    En cas d'échec : 422 mcp_policy_invalid liste les modèles inconnus, les hôtes invalides et les rôles invalides.

ChampPar défautBornes
allowed_templates[] (tous les modèles)100 noms
allowed_hosts[]200 noms DNS ou *.suffixe
allowed_database_roles[]100 noms
idle_timeout_seconds90060 à 86400
max_lifetime_seconds28800300 à 86400
max_instances_per_user31 à 20
max_instances_per_project201 à 200

Démarrer et appeler une instance​

  1. 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.

  2. 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_active vaut true quand un bail a été émis.

    En cas d'échec : 403 mcp_template_not_allowed, mcp_host_not_allowed ou mcp_database_role_not_allowed (politique), 409 mcp_instance_quota ou mcp_instance_exists, 422 mcp_credential_required, mcp_agent_invalid, mcp_tool_invalid ou mcp_param_invalid, 502 mcp_credentials_unavailable (OpenBao).

  3. 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: ready et un server_id ; le journal contient created, credentials_issued, provisioned et ready.

    En cas d'échec : failure_reason donne la cause (voir le tableau de dépannage).

  4. 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) et retry_after_seconds.

Arrêter, redémarrer et détruire​

  1. 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_busy pendant une autre opération de cycle de vie ; réessayez.

  2. 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 nouvelle generation.

    En cas d'échec : le modèle ou la politique la refuse de nouveau ; la raison est dans failure_reason.

  3. 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: terminated et l'événement d'audit mcp.instance.destroy.

    En cas d'échec : terminating reste tant qu'une suppression échoue ; la boucle du contrôleur la réessaie toutes les EAI_MCP_RUNTIME_REAPER_SECONDS.

Sessions​

  • La passerelle garde une session MCP (Mcp-Session-Id) par entrée de la passerelle. Elle envoie initialize quand 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épond mcp_instance_unavailable.

Propagation de l'identité​

  1. 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_invoke dans OpenFGA.
  2. 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 vie EAI_MCP_RUNTIME_TOKEN_TTL_SECONDS (300 secondes par défaut).
  3. 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.
  4. 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.
danger

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​

LimiteValeurOù elle se règle
Instances par utilisateur et par projet3 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 politiquePolitique, instance
Durée de vie d'une génération28800 secondes par défautPolitique
Attente d'un appel pour son instance90 secondes par défautEAI_MCP_RUNTIME_WAKE_TIMEOUT_SECONDS
Délai de préparation600 secondes par défautEAI_MCP_RUNTIME_PROVISIONING_TIMEOUT_SECONDS
Pannes avant failed3 en 900 secondes par défautEAI_MCP_RUNTIME_FAILURE_LIMIT, EAI_MCP_RUNTIME_FAILURE_WINDOW_SECONDS
CPU et mémoire d'un namespace de projet8 CPU et 16Gi par défautEAI_MCP_RUNTIME_QUOTA_CPU, EAI_MCP_RUNTIME_QUOTA_MEMORY
CPU et mémoire d'une instanceIssus de son modèle (resources)Catalogue de l'entreprise
Réseau du conteneur MCPHôtes de son modèle uniquementCiliumNetworkPolicy de l'instance

Observabilité des appels​

SignalContenu
TraceSpan 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étriqueseai_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 vieevents 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
Auditmcp.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 podsIndex séparé eai-mcp-runtime-audit (400 jours), lu par les responsables sécurité
  1. 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ômeCauseCorrection
409 mcp_runtime_disabledRuntime non activé sur la plateformeDemandez à l'opérateur (exploitation)
503 mcp_catalog_not_configuredAucun catalogue de l'entreprise en productionL'équipe plateforme publie le catalogue
failure_reason: image_pullImage absente du registre interneCopiez l'image avec son empreinte
failure_reason: spec_rejectedToolHive a refusé la configurationCorrigez le modèle ; redémarrez l'instance
failure_reason: provisioning_timeout ou not_reconciledOpérateur arrêté ou surchargéVérifiez l'état de l'opérateur dans le Navin Manager
failure_reason: health ou unreachableLe proxy a échoué à ses sondes ou n'a pas réponduRappelez : l'instance est recréée
failure_reason: template_removed ou policyModèle retiré du catalogue, ou plus autoriséChangez la politique ou choisissez un autre modèle
JSON-RPC mcp_instance_failedTrop de pannes dans la fenêtreRedémarrez-la avec POST .../start après avoir corrigé la cause
JSON-RPC mcp_principal_not_allowedL'appelant n'est ni le propriétaire ni l'un de ses agentsDémarrez votre propre instance