MCP clients¶
keyholder-mcp speaks MCP over stdio, so any MCP client can start it. This page shows Claude Code, Cursor and Codex.
Keep the token out of config files¶
Every client below can read the wrapped token from your shell environment instead of a file. Export it in the terminal that starts the client:
export BAO_WRAPPING_TOKEN="$(bao token create -policy=my-project-read -ttl=8h -explicit-max-ttl=8h -wrap-ttl=5m -field=wrapping_token)"
Even if it ends up in a file, a wrapped token is single use: once keyholder-mcp unwraps it, the value is worthless. Each server start needs a fresh one, and that is how access is renewed outside the chat.
Claude Code¶
With the CLI (official docs):
claude mcp add \
--env BAO_ADDR=http://127.0.0.1:8200 \
--env KEYHOLDER_SCOPE=my-project \
--env KEYHOLDER_ALLOW=curl,aws,gcloud \
--transport stdio --scope project \
keyholder -- keyholder-mcp
The server name must not come right after an --env, or the CLI reads it as another KEY=value; keep --transport or --scope in between. With --scope project the entry is written to .mcp.json; add the BAO_WRAPPING_TOKEN line shown below so the token comes from your environment.
In .mcp.json at the project root, Claude Code expands ${VAR} from your environment:
{
"mcpServers": {
"keyholder": {
"type": "stdio",
"command": "keyholder-mcp",
"env": {
"BAO_ADDR": "http://127.0.0.1:8200",
"BAO_WRAPPING_TOKEN": "${BAO_WRAPPING_TOKEN}",
"KEYHOLDER_SCOPE": "my-project",
"KEYHOLDER_ALLOW": "curl,aws,gcloud"
}
}
}
}
Check it with claude mcp get keyholder or /mcp inside Claude Code.
Cursor¶
In .cursor/mcp.json (project) or ~/.cursor/mcp.json (global) (official docs); Cursor expands ${env:NAME}:
{
"mcpServers": {
"keyholder": {
"type": "stdio",
"command": "keyholder-mcp",
"env": {
"BAO_ADDR": "http://127.0.0.1:8200",
"BAO_WRAPPING_TOKEN": "${env:BAO_WRAPPING_TOKEN}",
"KEYHOLDER_SCOPE": "my-project",
"KEYHOLDER_ALLOW": "curl,aws,gcloud"
}
}
}
}
Codex¶
In ~/.codex/config.toml (official docs); env_vars forwards variables from your environment:
[mcp_servers.keyholder]
command = "keyholder-mcp"
env_vars = ["BAO_WRAPPING_TOKEN"]
[mcp_servers.keyholder.env]
BAO_ADDR = "http://127.0.0.1:8200"
KEYHOLDER_SCOPE = "my-project"
KEYHOLDER_ALLOW = "curl,aws,gcloud"
Or with the CLI:
codex mcp add keyholder --env BAO_ADDR=http://127.0.0.1:8200 --env KEYHOLDER_SCOPE=my-project \
--env KEYHOLDER_ALLOW=curl,aws,gcloud -- keyholder-mcp
Codex asks before each MCP tool call. codex exec cannot ask, so every call fails with MCP tool call requires approval, but approval policy is never unless the server's tools are approved in the config:
[mcp_servers.keyholder]
default_tools_approval_mode = "approve"
The guardrails still apply: KEYHOLDER_ALLOW and KEYHOLDER_SCOPE decide what runs, not the approval.
Other clients¶
Any client that starts a stdio server with a command and environment variables works: the command is keyholder-mcp, with no arguments, and the variables are in Configuration.
What the agent sees¶
| Tool | Returns |
|---|---|
keyholder_status |
Vault and session status, TTL, policies; never the token |
keyholder_list |
vault:// reference names in the scope |
keyholder_run |
Exit code, masked stdout and stderr, references used |
Inputs and outputs field by field: Configuration → Tools.
Verified
The commands and file formats on this page come from each client's official documentation, linked above. The tutorials exercise the server with khcall; the clients themselves were not run in CI.