LABSCO SUMMARY
Redacted bodies by default, which is the right default for a recorder.
Anything that logs every tool call is one config mistake away from writing secrets to disk, so --body-mode redacted — sizes only — being the default matters more than it sounds; hash gives you comparability without contents, and full is labelled local and development only. The other reason to reach for this is the merge rule: a global config sets a floor a project cannot lower, so a team-wide deny stays denied no matter what lands in someone's repo.
What it is
A proxy that sits between an MCP client and a real server, intercepting every JSON-RPC message. It exposes stdio to the client and speaks stdio, HTTP or SSE to the server behind it, without changing that server's code.
What you get
- Policy at the proxy layer: a
heimdall.config.tsnames, per server, which tools, prompts and resources may reach the agent, with allow and deny lists toolPoliciesadds argument-level constraints on top of name matching, including path scoping and awarn_onlymode- Local and global configs merge security-first: denies union, allows intersect, and a global rule cannot be loosened locally
- Resource locks with write mode, TTL and read mode, so two agents do not act on the same resource at once
- Every request and response becomes a span with timing, attributes and body, stored in SQLite, PostgreSQL or MySQL
--otlpadditionally exports spans to an OTLP HTTP endpoint such as Jaeger or Tempo, while still saving them to the store- Host policies plus a Claude Code
PreToolUsehook and an OpenCode plugin let lock enforcement reach the client side
Requirements
npm install -g @cardor/heimdall-mcp, then wrap the real server in your client config: heimdall-mcp --store sqlite://~/.mcp-traces/traces.db -- npx @modelcontextprotocol/server-filesystem /tmp. --store is required. --out http or --out sse with --target wraps an already-running remote server instead of spawning one. --body-mode defaults to redacted, with hash and full as the other options.
Setup effort
One command — npm install -g @cardor/heimdall-mcp
USE CASES
01
Seeing what an MCP server is actually doing
Record every call with latency and inspect the traces in Jaeger or straight out of the database.
02
Making a filesystem server read-only
Allow read and list tools, deny the write ones, without forking the server.
03
Setting a floor the team cannot loosen
Put the denies in the global config; local configs can add restrictions but never remove them.
04
Keeping two agents off the same resource
Resource locks with a TTL make the second caller wait instead of colliding.
BEFORE YOU INSTALL
What to know first
Requires
npm on PATH
INSTALLATION
Connect your client
Server setup
TERMINAL
npm install -g @cardor/heimdall-mcpMCP Clients
Claude Desktop
CLAUDE DESKTOP · JSON
{
"mcpServers": {
"my-server": {
"command": "heimdall-mcp",
"args": [
"--store",
"sqlite://~/.mcp-traces/traces.db",
"--",
"node",
"my-server.js"
]
}
}
}Everything after -- is the server heimdall-mcp starts and records — in the README's example, node my-server.js. Yours is the command of the server you want to wrap.
Claude Code — lock hook
CLAUDE CODE — LOCK HOOK · JSON
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit|MultiEdit|NotebookEdit",
"hooks": [
{
"type": "command",
"command": "node <YOUR_PATH_TO_NODE_MODULES>/@cardor/heimdall-mcp/bin/hooks/claude-pretooluse.js"
}
]
}
]
}
}OpenCode — plugin
Register the lock plugin in ~/.config/opencode/opencode.jsonc; this adds the installed package's bin/plugins/opencode-heimdall.js to the plugin array:
OPENCODE — PLUGIN · TERMINAL
heimdall-mcp init --hooks opencode