Serveurs MCP adossés à une application : pourquoi Hermes vérifie d'abord que cette application est installée


Vous installez Blender. Vous activez l’add-on MCP officiel à l’intérieur. Puis vous suivez un tutoriel, vous laissez Hermes installer un plugin Blender, et l’installation répond « terminé » — et de fait, get_objects_summary apparaît dans la liste des tools. Vous demandez alors : « Quels objets sont dans ma scène en ce moment ? » La réponse tombe : « tool not connected ».

Vous ouvrez mcp.json. L’adresse est bonne. Vous redémarrez Blender. Toujours rien. Le vrai problème, c’est que ce port n’a jamais figuré dans mcp.json au départ — Blender en choisit un au démarrage et l’écrit dans un fichier d’exécution qui lui est propre. L’adresse que vous aviez écrite à la main dans la config était périmée depuis le premier jour.

Trois changements récents viennent fermer exactement cette catégorie de « la config a l’air bonne, ça ne se connecte simplement pas » : les plugins peuvent déclarer l’application dont ils ont besoin (#119049), le runtime ne se fie qu’au fichier d’endpoint écrit par l’app (#119050), et le catalogue de plugins a gagné son premier cas réel, Blender Lab (#119365).

Pourquoi « mettre l’adresse dans la config » ne pouvait pas fonctionner

Les applications locales de type outil parlent à Hermes via un pont loopback. Pour éviter de se disputer les ports, une application de bureau en alloue typiquement un dynamiquement au démarrage, puis écrit son adresse http courante, un token temporaire et son pid de processus dans un fichier d’exécution — pour Blender, c’est le pont sur le port 9876. Deux conséquences en découlent :

  • Une URL codée en dur finit par devenir obsolète, et elle le devient en silence. La config est syntaxiquement parfaite ; seule la connexion est morte.
  • Un outil qui refuse de se connecter n’avait autrefois qu’un seul message : « pas connecté ». Impossible de savoir si l’application était fermée, si le port avait bougé ou si la version était trop ancienne.

Le design s’est donc inversé : ne pas deviner — demander à l’application.

Un plugin peut déclarer quelle application il lui faut, dans plugin.json

Les plugins portables (ceux qui embarquent un plugin.json plus des fichiers, sans code natif) ont gagné un bloc de déclaration sous extensions."com.nousresearch.hermes".servers. Le nom du serveur doit correspondre à une entrée du mcp.json du plugin :

"extensions": {
  "com.nousresearch.hermes": {
    "servers": {
      "blender": {
        "app": {
          "darwin": {
            "presence": "bundle",
            "location": "/Applications/Blender.app",
            "version": { "kind": "plist" }
          },
          "win32": {
            "presence": "executable",
            "location": "%ProgramFiles%/Blender Foundation/Blender/blender.exe",
            "version": { "kind": "uninstall_registry", "display_name_prefix": "Blender" }
          },
          "linux": {
            "presence": "executable",
            "location": "/usr/bin/blender",
            "version": { "kind": "none" }
          }
        },
        "requires": { "app": true, "min_version": "5.1" },
        "liveness": {
          "kind": "server_json",
          "path": "%LOCALAPPDATA%/Blender/server.json",
          "fields": { "url": "http", "token": "token", "pid": "pid" }
        }
      }
    }
  }
}

Le parseur est strict sur chacune de ces lignes, ce qu’il vaut mieux savoir avant d’en écrire un :

  • app est indexé par système d’exploitation et n’accepte que darwin, win32 et linux, avec au moins un bloc obligatoire. location doit être absolu ou commencer par ~, %VAR% ou $VAR ; tout ce qui contient .. ou un schéma d’URL est rejeté d’emblée.
  • presence a deux valeurs : bundle (un bundle d’application macOS) et executable.
  • Une source de version (version.kind) vaut plist (darwin uniquement), uninstall_registry (win32 uniquement, et elle exige un display_name_prefix), pe_resource (win32) ou none. Si vous définissez requires.min_version, chaque bloc d’OS doit réellement pouvoir lire une version — un bloc none échoue à la validation.
  • liveness.kind vaut server_json ou none. Choisir server_json exige path ; les clés optionnelles sont endpoint_path (défaut /mcp) et les surcharges de noms de champs pid_key, url_key, token_key (défauts pid, http, token).

Notez la séparation entre les deux blocs : app est de la donnée (« où vit l’application »), requires est de la politique (« si j’exige qu’elle soit là »). Cette séparation est volontaire.

Le filtre à l’installation : ne pas installer ce qui ne peut pas fonctionner

Avant, une application manquante était purement un problème d’exécution — le plugin atterrissait dans son répertoire, ses tools s’enregistraient, et vous ne découvriez le vide qu’au moment d’en appeler un. Désormais l’installeur vérifie chaque serveur déclaré avant la bascule atomique et refuse dans l’un de ces cas :

Refus Déclencheur
missing_app L’application déclarée n’est pas sur cette machine
version_too_old L’app est là, mais plus ancienne que requires.min_version
unsupported_os Cet hôte n’est couvert par aucun bloc d’OS
Incompatibilité de plateforme La liste platforms d’une entrée de catalogue ne correspond pas à la famille d’OS de l’hôte (windows / macos sont acceptés comme alias de win32 / darwin)

Pour les installations depuis le catalogue, la vérification a lieu avant le clone ou l’écriture de la cible du plugin, si bien que vous ne vous retrouvez jamais avec un répertoire à moitié installé. La validation rapporte aussi l’état de disponibilité sous forme de ligne informative :

hermes plugins validate --install-deps

À l’exécution, seul le fichier que l’app écrit fait autorité

C’est la partie la plus ferme du changement. Chaque tentative de connexion à un serveur déclaré relit d’abord ce fichier d’exécution et utilise l’endpoint qu’il contient :

  • Le fichier d’exécution fait autorité. Un fichier manquant, un endpoint malformé ou un pid mort lèvent LiveEndpointUnavailable — et Hermes ne retombe pas sur l’URL configurée statiquement. Un test tient cette ligne : test_missing_runtime_file_never_falls_back.
  • Le token est enregistré avant d’être utilisé. Un token temporaire trouvé dans le fichier d’exécution est transmis au rédacteur avant que Hermes n’en construise un en-tête Authorization, pour qu’il ne fuite pas quand vous collez des logs ou relisez une transcription plus tard.
  • Les exigences d’application conditionnent aussi l’admission des tools. Un serveur portant des exigences d’application ne voit pas ses tools proposés dans l’ensemble disponible tant que l’hôte ne peut pas les satisfaire.

Quand un tool a disparu, dites pourquoi

La pire version de cette expérience n’est pas « le tool est absent » — c’est « le tool est absent et personne ne vous dira pourquoi ». Les tools déclarés mais indisponibles renvoient désormais un état structuré : l’état courant, les détails de l’application, une action utilisateur concrète et une politique de retry. Le catalogue des tools différés et le résumé de recherche vide conservent eux aussi une ligne stable pour chaque serveur déclaré masqué.

Cinq états sont distinguables :

État Signification Grosso modo, quoi faire
missing_app Non installée Installer l’app, puis installer/activer le plugin
version_too_old Installée mais trop ancienne Mettre l’app à niveau
app_not_running Installée, pas lancée La lancer et laisser le pont démarrer
endpoint_unavailable Fichier d’exécution manquant ou endpoint invalide Redémarrer le pont depuis l’intérieur de l’app
no_interactive_session Nécessite une session de connexion graphique Le lancer dans une session de bureau, pas dans un service headless

Notez aussi que la politique de retry n’est jamais que after_user_action (faites la chose ci-dessus, puis réessayez) ou never_here (cet hôte ne la satisfera jamais, par ex. l’OS n’est pas pris en charge). « Réessayer » et « arrêter d’essayer » sont deux réponses différentes, et le tool vous donne désormais la bonne.

Le cas réel déjà présent au catalogue : Blender Lab

Le catalogue officiel de plugins porte désormais une entrée blender, qui enveloppe ce dépôt public : hermes-plugin-blender.

# installe le sha épinglé de l'entrée de catalogue, pas la pointe d'une branche
hermes plugins install NousResearch/hermes-plugin-blender
hermes plugins enable blender

Ce qu’il fait est simple : il lance le serveur MCP officiel Blender Lab en stdio (source amont épinglée au commit 2cea8d56, v1.0.3), et ce serveur se connecte au pont de l’add-on Blender sur 127.0.0.1:9876. Un skill de workflow original l’accompagne.

Trois choses à savoir avant de vous y appuyer :

  1. Les prérequis sont à votre charge. Blender 5.1+, l’add-on MCP officiel (installé, activé, accès en ligne autorisé, pont démarré), plus Git et uv sur la machine backend. Installer le paquet n’installe pas Blender, ne modifie pas ses préférences, n’installe pas l’add-on et n’ouvre aucune scène.
  2. Préchauffez l’environnement uv avant la première connexion. Le premier build amont prend des minutes ; c’est ce qui a été mesuré sous Windows, et un timeout MCP normal l’interrompra :
uv tool run --from "git+https://projects.blender.org/lab/blender_mcp.git@2cea8d566dde07fbac28a61d698909d69724e853#subdirectory=mcp" blender-mcp --help
  1. Ce wrapper n’utilise pas encore le filtre d’application. Son README indique sans détour que la détection automatique d’application, de version et de liveness n’est ni implémentée ni vérifiée par le wrapper, et que les prérequis restent manuels. Autrement dit : le contrat app / requires / liveness est actif dans le cœur, et ce plugin-là ne l’a pas encore adopté — la séquence habituelle « la capacité d’abord, les plugins suivent ».

Une dernière chose qui mérite d’être dite ouvertement : Blender Lab prévient lui-même que ses tools exécutent du Python arbitraire dans Blender, sans protection contre la suppression de fichiers ni l’exfiltration de données. Gardez le pont en loopback uniquement, utilisez-le sur des workflows de confiance et privilégiez une scène jetable. Ce n’est pas un sandbox.

Asymétries entre plateformes et coins encore ouverts

  • Les vérifications d’hôte ne sont pas symétriques d’une plateforme à l’autre. Windows exige une session de processus non nulle (soit la session console active, soit un window station de processus). Linux exige un identifiant de session de processus lisible et le répertoire d’exécution de l’utilisateur courant. macOS renvoie true — la bibliothèque standard n’offre aucun moyen sans sous-processus de s’enquérir de la session graphique courante.
  • Des builds d’application avec authentification désactivée peuvent ne jamais écrire le fichier d’exécution. La PR liste cela comme une question ouverte en attente de vérification matérielle ; un tel build resterait en endpoint_unavailable en permanence.
  • La frontière d’activation n’a pas bougé. Quand un plugin se charge en cours d’exécution, ses handlers côté gateway (commandes slash, callbacks de boutons, transforms entrants) se câblent immédiatement — mais les tools et les sections de system-prompt attendent encore la session suivante (l’invariant du cache de prompt), et les serveurs MCP portables exigent un mcp.reload explicite.

Pour conclure

Si votre plugin parle à un logiciel de bureau, le problème n’a jamais été que la config était mal écrite — c’était qu’un humain ne devrait pas écrire cette adresse du tout. Désormais un plugin déclare sa dépendance applicative, l’installeur refuse avant de toucher à votre profile, le runtime ne se fie qu’à l’endpoint que l’app publie elle-même, et un tool mort finit par vous dire quel maillon de la chaîne a lâché.

Pour le tableau plugin plus large, voyez le modèle de confiance du catalogue de plugins et le catalogue MCP distant officiel ; si ce qui vous préoccupe est la frontière de sécurité de tools locaux comme celui-ci, Sécurité du catalogue MCP est la lecture la plus proche. Pour la variante vendue dans le dépôt, le guide des skills locaux au projet couvre .hermes/skills/ et son filtre de confiance.