Screen tool calls in Hermes Agent
Hermes Agent starts each MCP server in its configuration as a local process and talks to it over stdio. agent-chaperone goes in front of that process, and a plugin puts the same screens in front of the tools Hermes runs itself: its terminal, its file edits and its web fetches.
This page covers both. The plugin needs 0.4.0 or later, and so does wrap for Hermes's YAML configuration.
Install it #
npm install -g agent-chaperoneA global install means Hermes starts the proxy directly, and it is where the plugin below finds the command and its own files.
Put a server behind it #
Hermes reads servers from ~/.hermes/config.yaml, under mcp_servers. agent-chaperone wrap makes the edit. It prints what it would change and writes nothing until you add --write, keeps the original beside the file, and writes back only the entries it changed, so the comments in the file stay.
agent-chaperone wrap ~/.hermes/config.yaml
agent-chaperone wrap ~/.hermes/config.yaml --writeA server entry before and after:
mcp_servers:
filesystem:
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "/home/me/project"]mcp_servers:
filesystem:
command: agent-chaperone
args:
- --server
- filesystem
- --
- npx
- -y
- "@modelcontextprotocol/server-filesystem"
- /home/me/projectEverything after -- is the server that would have run anyway. --server names the section of the policy file that applies, after the name you gave the server in Hermes. Keys such as env, timeout and enabled are kept as they were. --unwrap takes it all back out.
Give it the API key #
The model screens need TYPESAFE_API_KEY. Hermes does not hand a server its own environment: a stdio server gets a short list of safe variables, such as PATH and HOME, plus whatever the entry's env names. A key exported in your shell does not reach the proxy on its own.
Put the key in ~/.hermes/.env and name it in the entry. Hermes fills in ${...} from that file when it starts the server, so the key itself stays out of config.yaml:
mcp_servers:
filesystem:
command: agent-chaperone
args: ["--server", "filesystem", "--", "npx", "-y", "@modelcontextprotocol/server-filesystem", "/home/me/project"]
env:
TYPESAFE_API_KEY: "${TYPESAFE_API_KEY}"The proxy removes its own keys before it starts the server behind it, so the server never receives TYPESAFE_API_KEY.
Without a key the proxy still starts, the deterministic rules still run, and every judgment records that no model was asked. It never refuses to start because a key is missing.
Screen Hermes's own tools #
The terminal, file edits and web fetches Hermes runs itself never cross an MCP connection. A plugin reaches them. Hermes ignores what a shell hook prints after a tool has run, so only a plugin can change what the model reads, and this one hands each call and each result to agent-chaperone hook pre and hook post. It ships in the npm package:
cp -R "$(npm root -g)/agent-chaperone/integrations/hermes" ~/.hermes/plugins/agent-chaperone
hermes plugins enable agent-chaperoneHermes loads no plugin until it is enabled, which is what the second line does. It adds the name to plugins.enabled in config.yaml, and you can write that by hand instead.
A held call goes to Hermes's own approval prompt. Its "always" answer is remembered for that exact call, so it never allows every later use of the tool. Hermes approves the prompt by itself when approvals.mode is off or YOLO mode is on, and refuses the call when nobody is there to answer.
A result judged unsafe is withheld: the plugin hands Hermes a notice in place of the tool's result. The tools it screens, and what each command answers, are in the hooks reference. MCP tools are left to the proxy, so a wrapped server is not screened twice.
The plugin starts the command with Hermes's own environment, which includes ~/.hermes/.env, so the key from the step above reaches it too.
Check that it is working #
hermes mcp test filesystem
hermes plugins listThe first connects to the wrapped server, which starts the proxy in front of it. If it fails, hermes logs mcp shows what the process printed. The second should show agent-chaperone as enabled.
Then use the server and the terminal once from the agent, and read what was decided:
agent-chaperone logEach line is one call or result, with the decision and, in shadow mode, what enforcing would have done instead. Calls from the plugin are recorded under the server name built-in. A line ending in [not screened] was decided without a model, which means the key did not arrive.
What this does not cover #
- A live run, yet. The plugin and the YAML support were written against Hermes Agent 0.16.0's published contract and its source, and tested on payloads in its shape. They have not been checked in a live run of Hermes yet.
- A result that is not a string. A tool that returns images alongside text hands Hermes a structure rather than a string, and the plugin leaves it as it came, unscreened.
- A failure of the plugin's own. A missing
agent-chaperone, a crash, or an answer that does not parse lets the call through rather than blocking it. The plugin waits 25 seconds, under the 30 Hermes allows a plugin before it refuses the call itself. - A server that authenticates with headers. Headers written in the Hermes entry stay with Hermes and never reach the proxy, which is why
wrapleaves such an entry alone. Put it behind the proxy by hand, as the MCP guide shows, and pass the token with--header-env. - What a server or a tool does rather than what the call says. This is not a sandbox. It reads the call and the result, and a tool that does something its arguments did not describe is outside it.
Next #
It starts in shadow mode, which blocks nothing. The MCP guide covers what each screen asks, how to read a shadow-mode log, and when to switch to enforce. The exact wording of every question is in the design document.