--- title: 'Hermes : les 32 outils MCP' url: https://blog.guigpap.com/fr/hermes/outils-mcp/ url_md: https://blog.guigpap.com/fr/hermes/outils-mcp.md category: hermes date: '2026-08-07' maturite: production techno: - n8n - odoo - telegram - docker application: - ai - automation - operations --- # Hermes : les 32 outils MCP > Catalogue des outils métier exposés à Hermes par N8N, gateways à entrées typées, double canal et garde-fous sur les actions sensibles ## 1. Quoi ? — Définition et contexte Un agent sans outils ne peut que parler. Ce qui rend [Hermes](/fr/hermes/) utile, c'est le catalogue de **32 outils métier** qu'il peut appeler pour lire et modifier l'état réel du système : projets Odoo, conteneurs Docker, métriques Prometheus, notes du vault Obsidian. Ces outils ne vivent pas dans le conteneur de l'agent. Ils sont définis dans un **workflow N8N** (`Hermes MCP Tools`, 33 nodes, actif) qui les expose via un unique MCP Server Trigger protégé par Bearer. Le conteneur ne connaît qu'une URL et un token. ### Les deux canaux MCP | Canal | Cible | Outils | Usage | |-------|-------|--------|-------| | `n8n-tools` | `n8n:5678/mcp/hermes-tools` | 32 outils métier curés | Le flow normal, 99 % des appels | | `n8n-admin` | `n8n-mcp:3000/mcp` | 4 outils, filtrés sur 24 exposés | Diagnostic de workflows uniquement | > **Caution - Pourquoi filtrer le canal admin** > > Le serveur `n8n-mcp` expose 24 outils, dont `n8n_delete_workflow` et `n8n_manage_credentials`. Le brancher tel quel reviendrait à donner à l'agent les clés de toute l'automatisation pour lui permettre de lire un log d'exécution. La clause `tools.include` du fichier de config le réduit à quatre outils de lecture seule — `n8n_list_workflows`, `n8n_get_workflow`, `n8n_executions`, `n8n_validate_workflow` — et une règle du system prompt interdit de l'utiliser pour une action métier. ### Architecture ```mermaid flowchart TD H["Hermes · gateway"] subgraph N8N["N8N · Hermes MCP Tools · 33 nodes"] direction TB Trig["MCP Server Trigger · Bearer"] Odoo["23 nodes odooTool natifs"] GW["4 sous-workflows gateway · entrées typées"] Trig --- Odoo Trig --- GW end subgraph Cibles["Cibles"] direction TB ERP["Odoo · XML-RPC"] Docker["Docker Actions · SSH"] Prom["Prometheus · SSH + curl"] Vault["vps-vault · SSH + git"] Content["Telegram Content Pipeline"] end Adm["n8n-mcp · 4 outils diagnostic"] H -->|"Bearer · flow normal"| Trig H -.->|"Bearer · debug seul"| Adm Odoo --> ERP GW --> Docker GW --> Prom GW --> Vault GW --> Content ``` --- ## 2. Pourquoi ? — Enjeux et motivations ### Pourquoi des outils curés plutôt qu'un accès direct ? Il aurait été plus rapide de donner à l'agent un accès générique à l'API Odoo et de le laisser composer ses appels XML-RPC. Trois raisons de ne pas le faire. | Enjeu | Ce qu'apporte un outil dédié | |-------|------------------------------| | **Surface** | Un outil ne fait qu'une chose. `odoo_update_task` ne peut pas supprimer un projet, quelle que soit la créativité du modèle | | **Schéma** | Chaque paramètre a un nom, un type et une description. Le modèle n'a pas à deviner la forme d'un domaine Odoo | | **Point de contrôle** | Un node N8N entre l'agent et la cible permet de valider, plafonner, échapper et journaliser avant exécution | Le coût est réel — chaque nouvel outil est un node à créer — mais il se paie une fois, et il rend le comportement de l'agent prévisible. ### Pourquoi des gateways à entrées typées ? Les outils non-Odoo ne pointent jamais directement sur les workflows existants. Chaque domaine passe par un **wrapper** qui définit ses propres entrées, puis délègue. Sans wrapper, l'agent devrait connaître le contrat interne de `Docker Actions` ou du `Telegram Content Pipeline` — des workflows conçus pour d'autres appelants, dont la forme peut changer. Le wrapper découple : il donne à l'agent un schéma MCP propre et stable, et absorbe les évolutions internes. --- ## 3. Comment ? — Mise en œuvre technique ### Le catalogue complet **Odoo — projets et tâches (10)** | Outil | Rôle | |-------|------| | `odoo_list_projects` | Liste id + nom des projets | | `odoo_create_project` | Crée un projet (l'ORM crée le compte analytique associé) | | `odoo_update_project` | Met à jour un champ (nom / description) | | `odoo_archive_project` | Archive ou désarchive | | `odoo_list_task_stages` | Étapes kanban d'un projet (id, nom, séquence, repliée) | | `odoo_move_task_stage` | Déplace une tâche dans le kanban | | `odoo_list_tasks` | Tâches d'un projet | | `odoo_get_task` | Détail d'une tâche, description incluse | | `odoo_create_task` | Crée une tâche | | `odoo_update_task` | Met à jour un champ (nom / description / échéance) | **Odoo — contacts (3)** : `odoo_search_contacts` (recherche par nom, retourne coordonnées et adresse), `odoo_create_contact`, `odoo_update_contact`. **Odoo — CRM (6)** : `crm_list_stages` (étapes du pipeline avec séquence et `is_won`), `crm_list_leads`, `crm_create_lead` (`type=opportunity` forcé pour que le lead atterrisse dans le pipeline), `crm_update_lead`, `crm_move_stage`, `crm_delete_lead` — **sensible**. **Odoo — timesheets (4)** : `timesheet_report` (lignes depuis une date), `timesheet_log_hours`, `timesheet_update`, `timesheet_delete` — **sensible**. **Vault Obsidian (4)** : `vault_search` (grep insensible à la casse, 3 correspondances par fichier, plafond 60 lignes), `vault_list` (fichiers suivis, plafond 300), `vault_read` (plafond 30 000 caractères, retourne un `contentHash`), `vault_write` (écriture confinée). **Infrastructure (5)** : `docker_read` (status / logs / liste), `docker_manage` (start / stop / restart / update — **sensible**), `monitoring_alerts`, `monitoring_query` (PromQL instantané), `content_generate` (brouillon de note, article ou recherche). > **Note - Les six outils Plaud ont quitté ce catalogue** > > Jusqu'au 6 août 2026, six outils `plaud_*` (enregistrements vocaux, transcripts, notes IA) passaient par un gateway N8N et un *community node* non officiel, avec un token Bearer extrait du webapp qui expirait périodiquement. Plaud ayant publié son propre serveur MCP, ces outils sont désormais servis directement au conteneur (`npx -y @plaud-ai/mcp@latest`) : le gateway et sa maintenance de token disparaissent. Deux capacités restent sans équivalent officiel — le statut des traitements IA en cours et le téléchargement de l'audio brut vers Telegram. ### Les quatre gateways | Gateway | Entrées | Délègue à | |---------|---------|-----------| | Docker | `dockerStack`, `dockerAction` | `Docker Actions` | | Monitoring | `mode` (alerts / query), `promql` | SSH local → curl Prometheus `127.0.0.1:9090` | | Content | `contentType`, `text`, `url` | `Telegram Content Pipeline` | | Vault | `vaultAction`, `vaultQuery`, `vaultContent`, `vaultBaseHash` | SSH local → git + grep sur `~/vaults/vps-vault` | Prometheus n'écoute que sur le loopback du host : le gateway monitoring passe donc par SSH puis curl, comme le health check Docker. Le PromQL est échappé avant insertion dans la commande. ### L'écriture confinée dans le vault `vault_write` est le seul outil qui modifie durablement quelque chose hors d'Odoo. Sa validation se fait en JavaScript **avant** construction de la commande shell : liste blanche de préfixes (`research/`, `inbox/`), extension `.md` obligatoire, anti-traversal, contrôle des caractères, plafond de 150 000 octets — comptés en octets, pas en caractères. Le point intéressant est le **read-before-overwrite**, sans état partagé : `vault_read` renvoie un `contentHash` (sha256 tronqué à 12 hexadécimaux), et écraser un fichier existant exige de repasser ce hash en `Base_Hash`. Sans hash, la réponse est `__EXISTS__` ; si le fichier a changé depuis la lecture, `__STALE_READ__`. > **Tip - Les erreurs ne renvoient jamais le hash courant** > > C'est délibéré. Si `__STALE_READ__` retournait le hash à jour, le modèle n'aurait qu'à le recopier pour écraser un fichier qu'il n'a pas lu — le garde-fou deviendrait une formalité. L'erreur oblige à repasser par `vault_read`. > **Note - Contourner MAX_ARG_STRLEN** > > Le contenu transite en base64 par blocs de 64 Ko, une commande SSH par bloc vers un fichier temporaire, parce que Linux plafonne chaque argument de commande shell à 128 Ko (`Argument list too long`, constaté sur une note de 120 000 caractères). La commande finale vérifie la taille exacte du base64 avant décodage : un bloc perdu produit `__WRITE_FAILED__`, jamais un fichier corrompu commité. La séquence se termine par un commit et un push immédiat, ce qui propage la note vers les clients Obsidian. ### Les erreurs structurées Les gateways ne lèvent pas d'exception sur une entrée invalide : ils renvoient un marqueur que le formateur traduit en `{success: false, error, hint}`. `__NOT_FOUND__`, `__BAD_PATH__`, `__BAD_ACTION__`, `__EMPTY_QUERY__`, `__EXISTS__`, `__STALE_READ__`. L'effet de bord est important : ces réponses **ne déclenchent pas** le [Global Error Handler](/fr/workflows/error-handler/). Une faute de frappe de l'agent est une réponse métier, pas un incident d'infrastructure — elle repart directement dans la conversation avec un indice sur la correction à apporter. ### Quatre pièges de la couche MCP N8N > **Caution - Les paramètres $fromAI sont requis par défaut** > > Le MCP Server Trigger marque chaque paramètre `$fromAI` comme obligatoire. Rendre un champ optionnel demande de passer une valeur par défaut en quatrième argument : `$fromAI('x', '...', 'string', '')`. Sans ça, l'agent doit inventer une valeur pour chaque champ facultatif. > **Caution - Les descriptions d'outils Odoo sont ignorées** > > Le trigger sert la description générique du wrapper `odooTool` (« Create an item in Odoo ») quelle que soit la `toolDescription` renseignée. Toute la sémantique repose donc sur le **nom** de l'outil et sur les descriptions `$fromAI` de chaque champ — d'où des noms explicites comme `crm_move_stage` plutôt que `odoo_update_5`. > **Caution - Le catalogue est mis en cache par session MCP** > > Ajouter ou modifier un outil dans N8N ne suffit pas : le conteneur garde le catalogue pour la durée de la session. `docker compose restart hermes` est nécessaire après tout changement de schéma, sinon l'agent continue d'appeler l'ancienne signature. > **Danger - Un sous-node désactivé fait exécuter le mauvais outil** > > Le plus vicieux, découvert en production le 7 août 2026 en retirant les six outils Plaud. Les avoir simplement **désactivés** a suffi à faire router 27 des 32 outils vers le mauvais node : `docker_read` exécutait `odoo_create_contact` et renvoyait « Contacts require a name », `odoo_list_projects` exécutait `docker_manage`. > > En cause, `getConnectedTools()` côté N8N, qui associe chaque outil à son node source **par index**, en assemblant deux listes construites différemment — l'une filtre les nodes désactivés, l'autre les conserve. Un seul node désactivé décale tout ce qui suit. En mode queue, le processus principal résout bien l'outil par son nom, mais ne transmet au worker que le nom du node source, déjà faux. > > La panne est silencieuse : `tools/list` reste correct, donc rien ne semble cassé jusqu'à ce qu'un outil renvoie l'erreur d'un autre domaine. **Pour retirer un outil, il faut supprimer le node, jamais le désactiver.** ### Sécurité | Surface | Protection | |---------|-----------| | Endpoint MCP | Bearer obligatoire — 403 sans token, vérifié en E2E | | Depuis Internet | `/mcp/*` et `/mcp-test/*` renvoient 404 côté Caddy ; seul `n8n:5678` en interne fonctionne | | `docker_manage` | Whitelist d'actions dans `Docker Actions` ; `security-stack` non actionnable | | Actions destructrices | `docker_manage`, `crm_delete_lead`, `timesheet_delete` exigent une confirmation explicite, imposée par le system prompt | | Écriture vault | Préfixes en liste blanche, anti-traversal, read-before-overwrite | > **Danger - La confirmation repose sur le prompt, pas sur la plomberie** > > Il n'existe **aucune chaîne d'approbation** en aval de `docker_manage` sur ce chemin — contrairement au [workflow d'approbation](/fr/workflows/approval-workflow/) utilisé ailleurs. Le garde-fou est une règle du system prompt qui oblige l'agent à demander confirmation avant chaque appel. Ça fonctionne en pratique (vérifié en E2E : la confirmation est bien demandée avant un restart réel), mais c'est une garantie comportementale, pas technique. La vraie barrière reste la whitelist d'actions côté `Docker Actions`. --- ## 4. Et si ? — Perspectives et limites ### Limites actuelles | Limite | Impact | Mitigation | |--------|--------|------------| | **`employee_id` figé à 1** | `timesheet_log_hours` écrit toujours sur le même employé | Sans objet aujourd'hui, à paramétrer si l'équipe grandit | | **Redémarrage après chaque évolution** | Toute modification de schéma impose un restart | Regrouper les changements d'outils | | **Pas d'approbation en aval** | Les actions sensibles ne sont protégées que par le prompt | Whitelist côté `Docker Actions` ; chaîne d'approbation à câbler | | **Aucun sous-node désactivable** | Désactiver un outil casse le routage de tous les suivants | Supprimer le node plutôt que le désactiver (voir le quatrième piège) | ### Scénarios d'évolution **Si les actions sensibles doivent être vraiment bloquantes** : - Réutiliser le `MCP Confirmation Handler` déjà en place pour CLI Ollama plutôt que d'en écrire un second. - Le gateway posterait un webhook et attendrait le callback Telegram, comme le fait déjà `cli-ollama`. **Si le catalogue continue de grossir** : - 32 outils tiennent encore dans un prompt, mais chaque outil coûte des tokens à chaque tour. - Piste : des toolsets activables par contexte, sur le modèle du `/mcp` par conversation du [système conversationnel](/fr/workflows/systeme-conversationnel/). **Si un outil doit écrire ailleurs que dans le vault** : - Le patron `vault_write` est réutilisable tel quel : liste blanche de préfixes, validation avant construction de commande, read-before-overwrite par hash. - La partie coûteuse — le transfert chunké et la vérification d'intégrité — est déjà écrite. ### Dépannage ```bash # Le catalogue vu par l'agent docker exec hermes hermes mcp test n8n-tools # Tester l'endpoint directement (403 attendu sans token) docker exec n8n sh -c 'curl -s -o /dev/null -w "%{http_code}\n" -X POST \ http://n8n:5678/mcp/hermes-tools' # Vérifier que l'endpoint reste fermé depuis l'extérieur (404 attendu) curl -so /dev/null -w '%{http_code}\n' https://n8n.guigpap.com/mcp/hermes-tools ``` --- ## Pages liées ### Hermes - [Hermes Agent](/fr/hermes/) — Le déploiement et son modèle de sécurité - [Skills](/fr/hermes/skills/) — Les procédures qui exploitent ces outils - [Mémoire Mnemosyne](/fr/hermes/memoire-mnemosyne/) — Les outils de mémoire, côté conteneur ### Workflows - [Error Handler](/fr/workflows/error-handler/) — Workflow d'erreur des gateways - [Docker Updates](/fr/workflows/docker-updates/) — `Docker Actions`, cible du gateway Docker - [Content Pipeline](/fr/workflows/content-pipeline/) — Cible du gateway contenu, et le vault `vps-vault` ### Référence - [Glossaire](/fr/reference/glossary/) — MCP, Agent autonome ## Métadonnées agent - Cet article est issu du blog GuiGPaP Lab. - Contexte global du blog: https://blog.guigpap.com/llms.txt - Contact auteur: https://odoo.guigpap.com/mon-cv - Licence: CC-BY-SA 4.0