Hermes MCP include : une liste vide bloque désormais tout, partout


Vous avez branché un serveur MCP de système de fichiers sur le Hermes Agent de votre équipe et, par prudence, vous avez décoché tous les outils qu’il fournit : vous comptiez les réactiver un par un le jour où quelqu’un en aurait vraiment besoin. La configuration contient bien tools.include: [], et pour vous l’affaire était réglée. Jusqu’au jour où hermes mcp list affiche all à côté de ce serveur et où l’agent continue de lire et d’écrire dans ces répertoires. La liste vide n’était pas interprétée comme un blocage total, mais comme « cette clé n’est pas définie », donc « aucun filtre », donc tout est autorisé.

Une phrase de contexte : MCP (Model Context Protocol) est le protocole qui permet à Hermes d’appeler les outils exposés par des serveurs externes, et tools.include est la liste blanche d’outils de ce serveur — seuls les outils qui y figurent peuvent être appelés.

Trois états possibles, et un seul bloque tout

Il n’existe en réalité que trois états, et leurs significations n’ont rien à voir :

  • Absent : aucun bloc tools, ou un bloc tools sans la clé include. C’est « aucun filtre » : tous les outils exposés par le serveur sont activés.
  • Partiellement rempli : include: [read_file, list_directory]. Une véritable liste blanche ; seuls ces outils peuvent être appelés.
  • Explicitement vide : include: []. C’est un blocage total : rien n’est enregistré.

Le piège, c’est que les deux derniers cas se confondent en une seule branche de code avec une facilité déconcertante. Une liste vide est falsy en Python, si bien qu’à partir du moment où vous écrivez cfg.get("include") or [], « l’utilisateur a écrit une liste vide » et « l’utilisateur n’a rien écrit » deviennent indiscernables — et le second cas est exactement le contraire de l’intention.

À noter : le runtime avait toujours raison. tools/mcp_tool_registration.py n’enregistre rien lorsqu’il voit include: [], donc pas un seul outil n’atteint l’agent. Votre verrou était bel et bien actif. C’est l’interface qui vous mentait.

La liste vide a disparu dans la moitié de l’interface

Puisque le runtime se comportait correctement, le bug ne pouvait venir que des lecteurs. Plusieurs surfaces lisaient include via or [] ou un simple test de véracité : la liste vide et la clé absente étaient donc traitées comme une seule et même chose, affichée comme « tous les outils activés ». La PR #109188 (fusionnée dans main le 2026-09-12) corrige les lecteurs qui avaient divergé. Son intitulé : « fix(mcp): include: [] no longer reopens as “all tools enabled” in CLI, catalog and desktop » ; elle ferme l’issue #12865 et le diff est de +107 / −31 sur 8 fichiers.

Un tableau rend la divergence évidente (« avant » et « après » signifient avant et après la PR #109188) :

Point de contrôle Avant Après
hermes mcp list all 0 selected
ligne de liste de hermes tools all tools enabled [include only: (none)]
sélecteur de hermes tools tous les outils précochés ; valider les réactivait tous rien de précoché ; valider conserve include: []
runtime _make_tool_filter("demo")("a") False False (inchangé)

Quatre changements méritent d’être retenus :

  • _tool_filters dans hermes_cli/mcp_config.py : si la clé contient une liste, cette liste est renvoyée (liste vide comprise) ; None n’est renvoyé que si la clé manque ou n’est pas une liste. La sortie de liste et le sélecteur de configure testent désormais is not None plutôt que la véracité.
  • hermes_cli/tools_config_mcp.py : l’état précoché du sélecteur de hermes tools, et la ligne de liste qui affiche maintenant [include only: (none)].
  • hermes_cli/mcp_catalog.py : la vérification préalable lors d’une réinstallation interactive respecte un [] antérieur au lieu de retomber sur la valeur par défaut du catalogue.
  • apps/desktop/src/lib/mcp-tool-filter.ts : isToolEnabled considère « include existe » comme une liste blanche — nous y revenons juste après.

Le commentaire dans le code est sans ambiguïté :

Un include: [] explicite est une vraie liste blanche (blocage total) ; le runtime n’enregistre aucun outil, il ne doit donc pas être replié ici en « aucun filtre ».

Un point à dire clairement : le côté runtime avait déjà été corrigé dans un autre commit (fb1ec36a4b86). Cette PR s’occupe des lecteurs qui divergeaient encore. Ce n’est donc pas une nouvelle fonctionnalité, mais le réalignement de toutes les surfaces sur une sémantique que le runtime avait déjà tranchée.

Le piège le plus invisible du bureau : supprimer la clé et tout récupérer

Dans le panneau MCP du bureau, isToolEnabled de apps/desktop/src/lib/mcp-tool-filter.ts comprend désormais « include est présent » comme une liste blanche. Le changement paraît mineur, mais il ferme un piège plus vicieux que tout ce qui existe côté CLI : lorsque vous désactivez le dernier outil inclus, le code conserve include: [] au lieu de supprimer la clé.

Pourquoi c’est important : au niveau du fichier de configuration, une liste vide et une clé absente ne sont pas du tout la même chose.

  • Conserver include: [] = blocage total explicite ; rien n’est enregistré.
  • Supprimer la clé include = aucun filtre ; le serveur se rouvre entièrement, en silence.

Dans l’interface, les deux se ressemblent trait pour trait : aucune case n’est cochée, tout simplement. Supprimer la clé ressemble à un rangement anodin, alors que cela arrache le verrou. Si votre équipe a des scripts ou des habitudes qui « nettoient les champs vides », c’est exactement le schéma à surveiller.

Pour l’origine : la partie bureau a été récupérée par cherry-pick depuis #52874 (@Bartok9, lui-même un sauvetage de #13096 par @dingn42).

Comment verrouiller réellement un serveur MCP

Écrivez l’intention explicitement dans le YAML, au lieu de faire confiance aux valeurs par défaut de l’interface :

mcp_servers:
  demo:
    command: npx
    args: ["-y", "@modelcontextprotocol/server-filesystem", "/srv/data"]
    tools:
      include: []        # blocage total explicite : aucun outil enregistré
  demo2: {}              # pas de clé tools = aucun filtre, tout est activé

Vérifiez ensuite, dans cet ordre :

  1. Lancez hermes mcp list. À côté de demo, vous devez voir 0 selected, et non all.
  2. Lancez hermes tools. La ligne doit afficher [include only: (none)] ; dans le sélecteur, aucun outil ne doit être précoché, et valider directement ne doit rien activer.
  3. Le jour où vous voulez autoriser un outil précis, ajoutez son nom à la liste include — ne supprimez pas la clé.

Si vous montez de version, profitez-en pour auditer la configuration existante : pour chaque serveur dans l’état « je voulais tout bloquer et personne n’y a touché depuis », vérifiez s’il contient réellement include: [] ou s’il n’a tout simplement pas d’include. Les deux se ressemblent presque à l’identique et font exactement l’inverse.

Pour aller plus loin : pour utiliser des variables de contexte dans la configuration MCP, voir Hermes MCP config context variables ; pour la frontière de sécurité autour de la surface d’outils MCP, voir le Hermes MCP catalog security guide ; si vous hésitez encore sur les serveurs à connecter, le Hermes official remote MCP catalog est un bon point de départ ; et si Hermes n’est pas encore installé, le guide d’installation couvre les prérequis et la première étape.

En résumé

  • tools.include: [] n’a jamais été « non configuré ». C’est un blocage total. Le runtime l’a toujours lu ainsi ; plusieurs surfaces le lisaient mal.
  • La PR #109188 (fusionnée dans main le 2026-09-12) réaligne hermes mcp list, hermes mcp configure, hermes tools, la réinstallation du catalogue et le panneau MCP du bureau sur le runtime : une liste vide explicite signifie 0 sélectionné.
  • Le piège qui fait le plus mal est la suppression de la clé : include: [] et l’absence d’include se ressemblent dans l’interface et signifient le contraire.
  • La vérification coûte presque rien : si hermes mcp list affiche 0 selected, votre verrou tient.