Developer Guide
Write a Python MCP Server for Hermes Agent
Skills give a Hermes agent knowledge. An MCP server gives it code that runs. This is the short path from an empty Python file to a tool your agent can call — plus the two Hermes-specific details that are not obvious from the SDK docs.
Skill or MCP server?
Decide this first, because it saves building the wrong thing. A skill is a document: it teaches the agent a procedure using tools it already has. An MCP server is executable code: it gives the agent a capability it did not have — reaching an internal API, querying a database, touching a system only your network can see. If the agent already could do it and just does it badly, write a skill.
Install the SDK
pip install "mcp[cli]"
# or, with uv:
uv add "mcp[cli]"The [cli] extra provides mcp dev and mcp run, which let you exercise the server on its own before Hermes is in the picture.
from mcp.server.fastmcp import FastMCP. On a current install that raises ModuleNotFoundError — the SDK ships a stub at that path purely to tell you why. The class is now MCPServer, from mcp.server.mcpserver. Pin mcp<2 only if you need to run v1 code unchanged.The minimal server
# server.py
from mcp.server.mcpserver import MCPServer
mcp = MCPServer("mytools")
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers and return the result."""
return a + b
if __name__ == "__main__":
mcp.run()Type hints become the input schema. The docstring becomes the tool description, and it is the line that decides whether the agent ever calls your tool — write it as guidance about when to use the tool, not just what it computes.
Async when you wait on something
import httpx
from mcp.server.mcpserver import MCPServer
mcp = MCPServer("mytools")
@mcp.tool()
async def fetch_status(url: str) -> str:
"""Fetch a URL and return its HTTP status code."""
async with httpx.AsyncClient(timeout=10) as client:
response = await client.get(url)
return f"{response.status_code}"Register it
hermes mcp add mytools \
--command python \
--connect-timeout 60 \
--args server.py--args must be the last option. It takes everything that follows, so --args server.py --connect-timeout 60 quietly passes the timeout flag to your script and leaves Hermes on its default. The command reports success either way.
Pass an absolute path to server.py. A relative path resolves against whatever working directory the Hermes process has, which is rarely the one you registered it from — so it can work in your shell and fail as a background service, days later, looking like the server broke by itself.
The full option set is --url, --command, --args, --auth (oauth or header), --preset, --connect-timeout and --env for stdio environment variables as KEY=VALUE.
Remote servers and headers
There is no --header flag, so a remote server that needs an API key is configured in ~/.hermes/config.yaml:
mcp_servers:
mytools:
url: "https://tools.example.com/mcp"
headers:
authorization: "Bearer YOUR_TOKEN"
connect_timeout: 60
enabled: trueThe remote schema also accepts auth for OAuth 2.1, client_cert for mTLS, identity_header for per-user identity, and timeout alongside connect_timeout. Restart Hermes after editing the file.
Then prove it works
Do not infer success from a good chat reply. An agent that cannot see a tool will usually answer without it rather than say the tool is missing, so a plausible answer is the most common false positive there is. Ask for something only your tool could produce, and confirm the effect at the far end — a row written, a log line, a real status code from a URL you control.
Writing tools a model uses well
- Keep the tool count low. Every description competes for context; sixty tools is a worse agent than six.
- Return prose, not raw payloads. The reader is a language model.
- Make errors actionable. “Rate limited, retry in 60s” beats a stack trace.
- Validate inputs. Schemas shape what the model sends; they do not guarantee it is sane.
The same server works on OpenClaw too — MCP is the shared protocol, and only the registration differs. If you would rather not run the agent host yourself, OpenClaw Launch deploys a managed Hermes Agent in about thirty seconds.
Python MCP servers and Hermes Agent FAQ
How do I add a Python MCP server to Hermes Agent?
hermes mcp add mytools --command python --connect-timeout 60 --args server.py. That writes an entry under mcp_servers in ~/.hermes/config.yaml, which you can also edit by hand.
Why must --args come last?
Because it consumes everything after it. A flag written behind --args is passed to your Python script instead of to Hermes, and the command still succeeds — so the server starts with the wrong configuration and nothing reports an error.
How do I pass an auth header to a remote MCP server?
Through config.yaml, not the CLI. hermes mcp add supports --url, --command, --args, --auth, --preset, --connect-timeout and --env — there is no --header flag. Add a headers mapping to the server entry instead.
Do tool functions need to be async?
No. A plain def works and is what the SDK's minimal example uses. Use async def when the tool awaits I/O. Note the SDK runs sync tools in a worker thread, so blocking inside a plain def is safe — the hazard is blocking inside an async def, which stalls the event loop.
Can I use the same server with OpenClaw?
Yes. MCP is the shared protocol, so one server works with both runtimes — only the registration command and config file differ. See the OpenClaw version of this guide.