--- title: 'Hermes: the 32 MCP tools' url: https://blog.guigpap.com/en/hermes/outils-mcp/ url_md: https://blog.guigpap.com/en/hermes/outils-mcp.md category: hermes date: '2026-08-07' maturite: production techno: - n8n - odoo - telegram - docker application: - ai - automation - operations --- # Hermes: the 32 MCP tools > Catalogue of the business tools exposed to Hermes by N8N, typed-input gateways, dual channel and guardrails on sensitive actions ## 1. What? — Definition and context An agent without tools can only talk. What makes [Hermes](/en/hermes/) useful is the catalogue of **32 business tools** it can call to read and change the real state of the system: Odoo projects, Docker containers, Prometheus metrics, notes in the Obsidian vault. These tools do not live in the agent's container. They are defined in an **N8N workflow** (`Hermes MCP Tools`, 33 nodes, active) that exposes them through a single Bearer-protected MCP Server Trigger. The container knows nothing but a URL and a token. ### The two MCP channels | Channel | Target | Tools | Usage | |-------|-------|--------|-------| | `n8n-tools` | `n8n:5678/mcp/hermes-tools` | 32 curated business tools | The normal flow, 99% of calls | | `n8n-admin` | `n8n-mcp:3000/mcp` | 4 tools, filtered from 24 exposed | Workflow diagnostics only | > **Caution - Why filter the admin channel** > > The `n8n-mcp` server exposes 24 tools, including `n8n_delete_workflow` and `n8n_manage_credentials`. Wiring it as-is would hand the agent the keys to the entire automation estate just so it could read an execution log. The `tools.include` clause in the config file cuts it down to four read-only tools — `n8n_list_workflows`, `n8n_get_workflow`, `n8n_executions`, `n8n_validate_workflow` — and a system prompt rule forbids using it for a business action. ### Architecture ```mermaid flowchart TD H["Hermes · gateway"] subgraph N8N["N8N · Hermes MCP Tools · 33 nodes"] direction TB Trig["MCP Server Trigger · Bearer"] Odoo["23 native odooTool nodes"] GW["4 gateway sub-workflows · typed inputs"] Trig --- Odoo Trig --- GW end subgraph Cibles["Targets"] 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 diagnostic tools"] H -->|"Bearer · normal flow"| Trig H -.->|"Bearer · debug only"| Adm Odoo --> ERP GW --> Docker GW --> Prom GW --> Vault GW --> Content ``` --- ## 2. Why? — Stakes and motivations ### Why curated tools rather than direct access? It would have been quicker to give the agent generic access to the Odoo API and let it compose its own XML-RPC calls. Three reasons not to. | Stake | What a dedicated tool brings | |-------|------------------------------| | **Surface** | A tool does one thing. `odoo_update_task` cannot delete a project, however creative the model gets | | **Schema** | Every parameter has a name, a type and a description. The model does not have to guess the shape of an Odoo domain | | **Control point** | An N8N node between the agent and the target allows validating, capping, escaping and logging before execution | The cost is real — each new tool is a node to create — but it is paid once, and it makes the agent's behaviour predictable. ### Why typed-input gateways? The non-Odoo tools never point straight at the existing workflows. Each domain goes through a **wrapper** that defines its own inputs, then delegates. Without a wrapper, the agent would have to know the internal contract of `Docker Actions` or of the `Telegram Content Pipeline` — workflows designed for other callers, whose shape can change. The wrapper decouples: it gives the agent a clean, stable MCP schema and absorbs internal changes. --- ## 3. How? — Technical implementation ### The full catalogue **Odoo — projects and tasks (10)** | Tool | Role | |-------|------| | `odoo_list_projects` | Lists project id + name | | `odoo_create_project` | Creates a project (the ORM creates the matching analytic account) | | `odoo_update_project` | Updates one field (name / description) | | `odoo_archive_project` | Archives or unarchives | | `odoo_list_task_stages` | Kanban stages of a project (id, name, sequence, folded) | | `odoo_move_task_stage` | Moves a task across the kanban | | `odoo_list_tasks` | Tasks of a project | | `odoo_get_task` | Task detail, description included | | `odoo_create_task` | Creates a task | | `odoo_update_task` | Updates one field (name / description / deadline) | **Odoo — contacts (3)**: `odoo_search_contacts` (search by name, returns contact details and address), `odoo_create_contact`, `odoo_update_contact`. **Odoo — CRM (6)**: `crm_list_stages` (pipeline stages with sequence and `is_won`), `crm_list_leads`, `crm_create_lead` (`type=opportunity` forced so the lead lands in the pipeline), `crm_update_lead`, `crm_move_stage`, `crm_delete_lead` — **sensitive**. **Odoo — timesheets (4)**: `timesheet_report` (lines from a date), `timesheet_log_hours`, `timesheet_update`, `timesheet_delete` — **sensitive**. **Obsidian vault (4)**: `vault_search` (case-insensitive grep, 3 matches per file, 60-line cap), `vault_list` (tracked files, 300 cap), `vault_read` (30,000-character cap, returns a `contentHash`), `vault_write` (confined write). **Infrastructure (5)**: `docker_read` (status / logs / list), `docker_manage` (start / stop / restart / update — **sensitive**), `monitoring_alerts`, `monitoring_query` (instant PromQL), `content_generate` (draft note, article or research). > **Note - The six Plaud tools have left this catalogue** > > Until 6 August 2026, six `plaud_*` tools (voice recordings, transcripts, AI notes) went through an N8N gateway and an unofficial *community node*, with a Bearer token extracted from the webapp that expired periodically. Now that Plaud has published its own MCP server, those tools are served straight to the container (`npx -y @plaud-ai/mcp@latest`): the gateway and its token maintenance disappear. Two capabilities remain without an official equivalent — the status of in-flight AI processing, and downloading raw audio to Telegram. ### The four gateways | Gateway | Inputs | Delegates to | |---------|---------|-----------| | Docker | `dockerStack`, `dockerAction` | `Docker Actions` | | Monitoring | `mode` (alerts / query), `promql` | Local SSH → curl Prometheus `127.0.0.1:9090` | | Content | `contentType`, `text`, `url` | `Telegram Content Pipeline` | | Vault | `vaultAction`, `vaultQuery`, `vaultContent`, `vaultBaseHash` | Local SSH → git + grep on `~/vaults/vps-vault` | Prometheus only listens on the host loopback: the monitoring gateway therefore goes through SSH then curl, like the Docker health check. The PromQL is escaped before insertion into the command. ### Confined writes in the vault `vault_write` is the only tool that durably changes anything outside Odoo. Its validation happens in JavaScript **before** the shell command is built: prefix whitelist (`research/`, `inbox/`), mandatory `.md` extension, anti-traversal, character checks, a 150,000-byte cap — counted in bytes, not characters. The interesting part is **read-before-overwrite**, with no shared state: `vault_read` returns a `contentHash` (sha256 truncated to 12 hex characters), and overwriting an existing file requires passing that hash back as `Base_Hash`. Without a hash, the reply is `__EXISTS__`; if the file has changed since the read, `__STALE_READ__`. > **Tip - Errors never return the current hash** > > That is deliberate. If `__STALE_READ__` returned the up-to-date hash, the model would only have to copy it back to overwrite a file it never read — the guardrail would become a formality. The error forces another trip through `vault_read`. > **Note - Working around MAX_ARG_STRLEN** > > The content travels as base64 in 64 KB blocks, one SSH command per block into a temporary file, because Linux caps each shell command argument at 128 KB (`Argument list too long`, observed on a 120,000-character note). The final command checks the exact base64 size before decoding: a lost block produces `__WRITE_FAILED__`, never a corrupted committed file. The sequence ends with a commit and an immediate push, which propagates the note to the Obsidian clients. ### Structured errors The gateways do not raise an exception on invalid input: they return a marker that the formatter translates into `{success: false, error, hint}`. `__NOT_FOUND__`, `__BAD_PATH__`, `__BAD_ACTION__`, `__EMPTY_QUERY__`, `__EXISTS__`, `__STALE_READ__`. The side effect matters: these responses **do not trigger** the [Global Error Handler](/en/workflows/error-handler/). A typo from the agent is a business response, not an infrastructure incident — it goes straight back into the conversation with a hint about the fix. ### Four pitfalls of the N8N MCP layer > **Caution - $fromAI parameters are required by default** > > The MCP Server Trigger marks every `$fromAI` parameter as mandatory. Making a field optional requires passing a default value as the fourth argument: `$fromAI('x', '...', 'string', '')`. Without it, the agent has to invent a value for every optional field. > **Caution - Odoo tool descriptions are ignored** > > The trigger serves the generic description of the `odooTool` wrapper ("Create an item in Odoo") whatever `toolDescription` you fill in. All the semantics therefore rest on the tool's **name** and on the `$fromAI` descriptions of each field — hence explicit names like `crm_move_stage` rather than `odoo_update_5`. > **Caution - The catalogue is cached per MCP session** > > Adding or changing a tool in N8N is not enough: the container keeps the catalogue for the duration of the session. `docker compose restart hermes` is required after any schema change, otherwise the agent keeps calling the old signature. > **Danger - A disabled sub-node makes the wrong tool execute** > > The nastiest one, found in production on 7 August 2026 while removing the six Plaud tools. Merely **disabling** them was enough to route 27 of the 32 tools to the wrong node: `docker_read` was executing `odoo_create_contact` and returning "Contacts require a name", `odoo_list_projects` was executing `docker_manage`. > > The cause is `getConnectedTools()` on the N8N side, which maps each tool to its source node **by index**, assembling two lists built differently — one filters out disabled nodes, the other keeps them. A single disabled node shifts everything after it. In queue mode, the main process does resolve the tool by name, but only passes the worker the source node's name, already wrong. > > The failure is silent: `tools/list` stays correct, so nothing looks broken until a tool returns an error from another domain. **To remove a tool, delete the node, never disable it.** ### Security | Surface | Protection | |---------|-----------| | MCP endpoint | Bearer required — 403 without a token, verified end-to-end | | From the Internet | `/mcp/*` and `/mcp-test/*` return 404 at Caddy; only internal `n8n:5678` works | | `docker_manage` | Action whitelist in `Docker Actions`; `security-stack` not actionable | | Destructive actions | `docker_manage`, `crm_delete_lead`, `timesheet_delete` require explicit confirmation, enforced by the system prompt | | Vault writes | Whitelisted prefixes, anti-traversal, read-before-overwrite | > **Danger - Confirmation rests on the prompt, not on the plumbing** > > There is **no approval chain** downstream of `docker_manage` on this path — unlike the [approval workflow](/en/workflows/approval-workflow/) used elsewhere. The guardrail is a system prompt rule forcing the agent to ask for confirmation before every call. It works in practice (verified end-to-end: confirmation is indeed requested before a real restart), but it is a behavioural guarantee, not a technical one. The real barrier remains the action whitelist in `Docker Actions`. --- ## 4. What if? — Outlook and limits ### Current limits | Limit | Impact | Mitigation | |--------|--------|------------| | **`employee_id` hard-coded to 1** | `timesheet_log_hours` always writes against the same employee | Moot today, to be parameterised if the team grows | | **Restart after every change** | Any schema change forces a restart | Batch tool changes together | | **No downstream approval** | Sensitive actions are protected by the prompt alone | Whitelist in `Docker Actions`; approval chain still to be wired | | **No sub-node can be disabled** | Disabling a tool breaks the routing of every subsequent one | Delete the node rather than disable it (see the fourth pitfall) | ### Evolution scenarios **If sensitive actions must be truly blocking**: - Reuse the `MCP Confirmation Handler` already in place for CLI Ollama rather than writing a second one. - The gateway would post a webhook and wait for the Telegram callback, exactly as `cli-ollama` already does. **If the catalogue keeps growing**: - 32 tools still fit in a prompt, but every tool costs tokens on every turn. - Lead: context-activated toolsets, on the model of the per-conversation `/mcp` in the [conversational system](/en/workflows/systeme-conversationnel/). **If a tool has to write somewhere other than the vault**: - The `vault_write` pattern is reusable as-is: prefix whitelist, validation before command construction, hash-based read-before-overwrite. - The expensive part — chunked transfer and integrity checking — is already written. ### Troubleshooting ```bash # The catalogue as the agent sees it docker exec hermes hermes mcp test n8n-tools # Test the endpoint directly (403 expected without a token) docker exec n8n sh -c 'curl -s -o /dev/null -w "%{http_code}\n" -X POST \ http://n8n:5678/mcp/hermes-tools' # Check the endpoint stays closed from outside (404 expected) curl -so /dev/null -w '%{http_code}\n' https://n8n.guigpap.com/mcp/hermes-tools ``` --- ## Related pages ### Hermes - [Hermes Agent](/en/hermes/) — The deployment and its security model - [Skills](/en/hermes/skills/) — The procedures that exercise these tools - [Mnemosyne memory](/en/hermes/memoire-mnemosyne/) — The memory tools, container side ### Workflows - [Error Handler](/en/workflows/error-handler/) — Error workflow for the gateways - [Docker Updates](/en/workflows/docker-updates/) — `Docker Actions`, target of the Docker gateway - [Content Pipeline](/en/workflows/content-pipeline/) — Target of the content gateway, and the `vps-vault` vault ### Reference - [Glossary](/en/reference/glossary/) — MCP, Autonomous agent ## 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