Blog

Hermes Agent MCP setup: config, auth, and the common 401s

Aperisa ยท 9 min read

hermes mcp test can connect, list every tool, and report success, and the first real tool call can still come back 401. Some servers answer tools/list without looking at the token at all. A passing test proves less than it looks.

Hermes Agent MCP setup for a remote server is one entry under mcp_servers in ~/.hermes/config.yaml: a url, plus a headers mapping with Authorization: "Bearer ${MCP_RECORDS_API_KEY}", where the variable holds the bare token and nothing else. Most of the 401 reports we read in the Hermes issue tracker trace back to that last part: the header shape, the prefix, or a placeholder Hermes never filled in. Our view: get the shape right once, then test auth with a real tool call, not a connect.

Checked on 2026-10-27 against the Hermes docs on the main branch (commit 1ae3c55e4b) and the latest release, v0.22.5 (October 23, 2026). We read the docs, the source and the issues. We have not run this on a live Hermes install.

How do you set up a remote MCP server in Hermes Agent?

The MCP config reference gives the shape. With a placeholder URL and tool names:

mcp_servers:
  records:
    url: "https://your-server.example/mcp"
    headers:
      Authorization: "Bearer ${MCP_RECORDS_API_KEY}"
    tools:
      include: [search, read_document]

What each part does, per the docs:

  • url makes it an HTTP server. Streamable HTTP is the default, and transport: sse switches to SSE.
  • ${VAR} and the Cursor-style ${env:VAR} both work anywhere in the entry. They resolve from the active profile's secret scope (the profile's .env or an enabled secret-source plugin), then the process environment. Keep the literal token out of config.yaml.
  • Tools register as mcp__<server>__<tool>, so the example gives mcp__records__search. Filters in tools.include use the server's original tool names.
  • After an edit, run /reload-mcp in the session. A running gateway watches config.yaml and connects a newly added entry within about a minute.

The CLI route does the same thing with a prompt:

hermes mcp add records --url https://your-server.example/mcp --auth header
hermes mcp test records

With --auth header, hermes mcp add asks for "API key / Bearer token", saves it to the profile's .env as MCP_RECORDS_API_KEY, and writes the template Authorization: Bearer ${MCP_RECORDS_API_KEY} into config.yaml. That's from hermes_cli/mcp_config.py on main.

What shape do Hermes MCP headers need?

A YAML mapping, one header per line under headers:. Issue #53915, open since June 28, 2026, shows what a JSON string does instead:

# breaks: headers is a string here, not a mapping
headers: '{"Authorization":"Bearer cfut_xxx"}'

Hermes called dict() on the string, and the connect task crashed with ValueError: dictionary update sequence element #0 has length 1; 2 is required, which showed up in the log as CancelledError. The issue is still open. Write the mapping.

The same split applies to the value. The prefix lives in config.yaml as "Bearer ${MCP_RECORDS_API_KEY}", quoted. The variable holds only the token.

Why does Hermes MCP return 401?

Six causes from the issues and docs, in the order we'd check them.

1. The header says "Bearer" twice

Issue #37792 (June 3, 2026, Hermes v0.15.1) includes a curl test against an n8n MCP server:

Authorization header Result
Bearer ${MCP_N8N_API_KEY} (literal) 401
Bearer <jwt> 200
Bearer Bearer <jwt> 401

The template already adds Bearer . Paste a token that starts with Bearer and the server gets it twice. PR #38571, merged June 4, 2026, added _strip_bearer_prefix, which removes a leading Bearer when Hermes itself saves the token. In the main-branch source, the CLI prompt, the dashboard and the TUI all save through that path. It does not run on a value you type into .env by hand, or when hermes mcp add reuses a key that is already set. This prints 1 if the prefix is doubled, without printing the token:

grep -cE '^(export )?MCP_RECORDS_API_KEY *= *["'\'']?Bearer' ~/.hermes/.env

The pattern also matches the quoted form Hermes writes for a value with a space (KEY="Bearer x") and export KEY= lines. ~/.hermes/.env is the default profile. A named profile keeps its own file at ~/.hermes/profiles/<name>/.env, so check that one instead.

2. The placeholder went out as text

In the same issue, the add probe sent Bearer ${MCP_N8N_API_KEY} literally, because that path skipped variable resolution. PR #38571 moved resolution into the shared probe, so add, test, login and configure all fill it in.

The docs say an unset variable in a remote server's url or headers now fails closed instead of sending, with this status: MCP server 'name': ${VAR} in url/headers is not set in this profile's .env or secret source. If you see it, the variable is missing from the profile Hermes is running. The token itself may be fine. One exception: open issue #133616 reports that standalone probes such as hermes mcp test don't load enabled secret-source plugins, so a credential supplied by a plugin can be reported as missing there even when the agent can resolve it.

3. A stray space or newline

Hermes logs a warning, once per config path, when a value has hidden leading or trailing whitespace, and says this often causes authentication failures. Search agent.log for "hidden leading or trailing whitespace" and fix the source, which may be the .env line rather than config.yaml.

4. You rotated the token and Hermes kept the old one

Issue #92565 (open since August 22, 2026) reports that live MCP sessions are keyed by server name only. Change the credential, run /reload-mcp, and in the reporter's words: "The old credential is still in use. Only a full restart picks up the change." Fix PR #92596 is open as of October 27. Until it lands, restart Hermes or the gateway after a rotation, or the revoked token keeps producing 401s.

5. The test passed, the tool call didn't

This is the hook. In the reply that closed #37792, the Hermes team wrote that test "only appeared to work because n8n serves tools/list without auth." Google's hosted Gmail server behaves the same way, per #89412: initialize and tools/list return 200 with no token, and only tools/call returns 401.

Issue #121285 (open since September 24, 2026) shows that with mcp==2.0.0, a 401 on tools/call reaches the model as MCP call failed: MCPError: Server returned an error response, and the auth recovery path never runs. If your agent reports a vague MCP error on its first call, treat it as a possible 401.

6. The server wants OAuth, not a static key

The Hermes MCP guide says most hosted MCP servers require OAuth 2.1 instead of a static token. Set auth: oauth and run hermes mcp login <name> from a separate terminal, since the in-session auto-reload gives an interactive flow only 30 seconds. Servers without dynamic client registration need your own OAuth client under oauth.client_id and oauth.client_secret. Keep both as references, as the guide's Asana example does:

    oauth:
      client_id: "${ASANA_CLIENT_ID}"
      client_secret: "${ASANA_CLIENT_SECRET}"

Google's hosted Gmail, Drive and Calendar servers have their own open issues, covered in Gmail MCP and dynamic client registration.

How do you debug a Hermes MCP 401?

  • hermes mcp test <name>. When the SDK can only say "Server returned an error response", Hermes appends the HTTP status, the URL and the start of the response body. The docs read a 401/403 as a wrong token or OAuth grant.
  • The startup summary in agent.log names each server that failed and why, for example notion (HTTP 401 from POST https://mcp.notion.com/mcp).
  • hermes logs --level debug shows the exact endpoint each connect attempt used.

Then send the same header with curl. Load the token without echoing it or writing it to shell history:

read -rs MCP_RECORDS_API_KEY && export MCP_RECORDS_API_KEY
curl -s -o /dev/null -w "%{http_code}\n" https://your-server.example/mcp \
  -H "Authorization: Bearer $MCP_RECORDS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'

A 401 here settles it: the token is wrong, and Hermes isn't the problem. A 200 means less on servers that skip auth on initialize (see cause 5). If curl gets 200 and Hermes gets 401 on the same call, go back to causes 1 to 3.

Where does Aperisa fit?

Aperisa is a remote MCP server. Your data is available to any harness that connects over Streamable HTTP with a Bearer key: OpenClaw, Hermes, Cursor, Codex, Antigravity, n8n or your own code. Give each agent its own key and set what it can reach: which data areas, work or personal, and how much detail, from full records down to totals only. Aperisa enforces those limits on the server.

A tool list is not an auth test

The setup is a url, a headers mapping, and a variable holding the bare token. The 401s come from the edges: a doubled prefix, an unresolved placeholder, a stale session, and servers that only check the token on a real call. Test with a tool call, and restart after you rotate a key.

If you're new to the protocol side, start with what an MCP server is. For the agent's other half, see Hermes Agent memory and a shared context layer for multi-agent setups.

Aperisa connects as a remote MCP server over Streamable HTTP with a Bearer key, and each key can be limited by data area and level of detail. Try it out.

FAQ

Where does Hermes Agent keep its MCP server config? In ~/.hermes/config.yaml under mcp_servers for the default profile. A remote server needs a url and, for a static key, a headers mapping. After you edit it, run /reload-mcp in the session. A running gateway watches the file and connects a new entry within about a minute.

Should the token variable include the word Bearer? No. The header in config.yaml carries the prefix, as Authorization: "Bearer ${MCP_RECORDS_API_KEY}", and the variable holds the bare token. A variable that starts with Bearer sends the prefix twice, and the server answers 401.

Why does hermes mcp test pass but tool calls fail with 401? Some servers answer initialize and tools/list without checking the token, and only check it on tools/call. A passing test then proves the URL, not the credential. The reply that closed Hermes issue #37792 says this is why the test only appeared to work there.

Do I need to restart Hermes after rotating an MCP token? Yes, for now. Issue #92565 (open as of October 27, 2026) reports that /reload-mcp keeps the existing session when only the credential changed, and only a full restart picks up the new token. A fix is in an open pull request.

Sources

Questions

Where does Hermes Agent keep its MCP server config?

In ~/.hermes/config.yaml under mcp_servers for the default profile. A remote server needs a url and, for a static key, a headers mapping. After you edit it, run /reload-mcp in the session. A running gateway watches the file and connects a new entry within about a minute.

Should the token variable include the word Bearer?

No. The header in config.yaml carries the prefix, as Authorization: "Bearer ${MCP_RECORDS_API_KEY}", and the variable holds the bare token. A variable that starts with Bearer sends the prefix twice, and the server answers 401.

Why does hermes mcp test pass but tool calls fail with 401?

Some servers answer initialize and tools/list without checking the token, and only check it on tools/call. A passing test then proves the URL, not the credential. The reply that closed Hermes issue #37792 says this is why the test only appeared to work there.

Do I need to restart Hermes after rotating an MCP token?

Yes, for now. Issue #92565 (open as of October 27, 2026) reports that /reload-mcp keeps the existing session when only the credential changed, and only a full restart picks up the new token. A fix is in an open pull request.

Published

Keep reading