Keyholder MCP¶
Your AI agent uses the credential. It never sees it.
Keyholder MCP is an MCP server over OpenBao. The agent writes a reference; Keyholder puts the real value into the command only when it runs, and hands the output back with the value masked. Access ends on its own.
The agent asks keyholder_run
curl -sS \ -H "Authorization: Bearer {{vault://my-project/dev/API_TOKEN}}" \ {{vault://my-project/dev/API_URL}}
The API receives
Authorization: Bearer
tok-fake-not-a-real-secret
The agent reads back
api says: hello, caller with
header Authorization: Bearer
[hidden:vault://my-project/dev/API_TOKEN]
A recorded run with a fake token, checked on every CI build: see the quickstart.
Why not just give the agent the key¶
Agents need API keys, cloud credentials and tokens to do real work. Pasted into the chat, a .env file or a command the model writes, a credential ends up in the transcript, in logs and in places you will forget. The other OpenBao and Vault MCP servers we found have a tool that reads a secret for the model. Keyholder has none: the model only ever holds names.
A run, start to finish¶
- You store the secret
In OpenBao, one path per project. The OpenBao web UI or the
baoCLI, outside the chat. - You issue a session
A wrapped, single-use token with a TTL, given to
keyholder-mcpthrough its environment. - The agent runs a command
It sends
{{vault://…}}. Keyholder resolves it at execution, runs an allowed command without a shell, masks the output. - Access ends
At the TTL every call fails with a clear message. You renew outside the chat; the agent cannot.
How it compares¶
| Keyholder MCP | mcp-vault | openbao-mcp | vault-mcp-server | |
|---|---|---|---|---|
| Backend | OpenBao | OpenBao, embedded | OpenBao | HashiCorp Vault |
| Model receives secret values | Never | Yes, documented | Has a read tool | Yes, documented |
| Value injected at run time | Yes | Not documented | Not documented | Not documented |
| Output masked | Yes | Not documented | Not documented | Not documented |
| Cloud credentials per run | AWS, GCP; revoked after the run | Not documented | Not documented | Not documented |
| Temporary access | Wrapped token, TTL | Wrapping, SSH CA, PKI | Not documented | Not documented |
| What the agent can do | Run allowed commands | 39 tools: secrets, SSH, PKI, policies | Vault operations | Mounts, KV, PKI |
From each project's README on 2026-10-14. They manage the vault for the agent; Keyholder only lets the agent use it. Details and sources: How it compares.
What it protects, and what it does not¶
Protects
- Values stay out of the chat, tool results, errors and logs.
- A leaked session token dies with its TTL; a wrapped token works once.
- Only commands you name run, and never through a shell.
- References outside the project scope are refused.
Does not
- Hide the value from the command it runs: that command needs it.
- Catch an encoded value: an allowed interpreter can escape the mask, as the masking tutorial shows.
- Stop a hostile agent running as your own OS user.
The full list is in the security model.
Start in five minutes¶
bao server -dev -dev-root-token-id=root-dev # in-memory, for a first try
export BAO_ADDR=http://127.0.0.1:8200 BAO_TOKEN=root-dev
bao kv put -mount=secret my-project/dev API_TOKEN=your-api-token
bao policy write my-project-read deploy/policy-project-read.hcl
bao token create -policy=my-project-read -ttl=8h -explicit-max-ttl=8h -wrap-ttl=5m -field=wrapping_token
Then add keyholder-mcp to Claude Code, Cursor or Codex with that wrapped token. Every step, explained: Getting started. Every behaviour, with real output: Tutorials.
Version 0.3.0, MIT licensed. Source, issues and releases on GitHub. Cloud CLIs get a credential made for each run: cloud credentials. For real secrets, run a persistent vault. Next on the roadmap: access requests from the agent, SSH.