Skip to content

Hermes: the 32 MCP tools

An agent without tools can only talk. What makes 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.

ChannelTargetToolsUsage
n8n-toolsn8n:5678/mcp/hermes-tools32 curated business toolsThe normal flow, 99% of calls
n8n-adminn8n-mcp:3000/mcp4 tools, filtered from 24 exposedWorkflow diagnostics only

Targets

N8N · Hermes MCP Tools · 33 nodes

Bearer · normal flow

Bearer · debug only

Hermes · gateway

MCP Server Trigger · Bearer

23 native odooTool nodes

4 gateway sub-workflows · typed inputs

Odoo · XML-RPC

Docker Actions · SSH

Prometheus · SSH + curl

vps-vault · SSH + git

Telegram Content Pipeline

n8n-mcp · 4 diagnostic tools


Why curated tools rather than direct access?

Section titled “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.

StakeWhat a dedicated tool brings
SurfaceA tool does one thing. odoo_update_task cannot delete a project, however creative the model gets
SchemaEvery parameter has a name, a type and a description. The model does not have to guess the shape of an Odoo domain
Control pointAn 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.

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.


Odoo — projects and tasks (10)

ToolRole
odoo_list_projectsLists project id + name
odoo_create_projectCreates a project (the ORM creates the matching analytic account)
odoo_update_projectUpdates one field (name / description)
odoo_archive_projectArchives or unarchives
odoo_list_task_stagesKanban stages of a project (id, name, sequence, folded)
odoo_move_task_stageMoves a task across the kanban
odoo_list_tasksTasks of a project
odoo_get_taskTask detail, description included
odoo_create_taskCreates a task
odoo_update_taskUpdates 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_leadsensitive.

Odoo — timesheets (4): timesheet_report (lines from a date), timesheet_log_hours, timesheet_update, timesheet_deletesensitive.

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

GatewayInputsDelegates to
DockerdockerStack, dockerActionDocker Actions
Monitoringmode (alerts / query), promqlLocal SSH → curl Prometheus 127.0.0.1:9090
ContentcontentType, text, urlTelegram Content Pipeline
VaultvaultAction, vaultQuery, vaultContent, vaultBaseHashLocal 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.

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

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

SurfaceProtection
MCP endpointBearer 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_manageAction whitelist in Docker Actions; security-stack not actionable
Destructive actionsdocker_manage, crm_delete_lead, timesheet_delete require explicit confirmation, enforced by the system prompt
Vault writesWhitelisted prefixes, anti-traversal, read-before-overwrite

LimitImpactMitigation
employee_id hard-coded to 1timesheet_log_hours always writes against the same employeeMoot today, to be parameterised if the team grows
Restart after every changeAny schema change forces a restartBatch tool changes together
No downstream approvalSensitive actions are protected by the prompt aloneWhitelist in Docker Actions; approval chain still to be wired
No sub-node can be disabledDisabling a tool breaks the routing of every subsequent oneDelete the node rather than disable it (see the fourth pitfall)

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.

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.
Fenêtre de terminal
# 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