MCP Server
fnox mcp starts a Model Context Protocol server over stdio, allowing AI agents like Claude Code to access secrets without having them directly in the environment.
Why?
When you give an AI agent GITHUB_TOKEN as an environment variable, it can use that token however it wants. The MCP server acts as a session-scoped secret broker — secrets are resolved on first access and cached in memory for the session.
Quick Setup
1. Configure secrets normally
# fnox.toml
[providers]
age = { type = "age" }
[secrets]
GITHUB_TOKEN = { provider = "age", value = "AGE-SECRET-KEY-..." }
API_KEY = { provider = "age", value = "AGE-SECRET-KEY-..." }2. (Optional) Configure which tools to expose
[mcp]
tools = ["get_secret", "exec"] # default: both enabledThe tools array controls which tools are available to the agent. For example, to only allow executing commands without exposing raw secrets, set tools = ["exec"]. To only allow retrieving secrets directly, set tools = ["get_secret"].
3. Configure your AI agent
For Claude Code, add to .claude/settings.json:
{
"mcpServers": {
"fnox": {
"command": "fnox",
"args": ["mcp"]
}
}
}To use a specific profile:
{
"mcpServers": {
"fnox": {
"command": "fnox",
"args": ["-P", "staging", "mcp"]
}
}
}Tools
get_secret
Retrieves a single secret by name. The agent provides the secret name (must match a key in your fnox.toml secrets section) and receives the resolved value.
exec
Executes a command with all secrets injected as environment variables. The agent provides a command and arguments, and receives stdout/stderr output. Note that the agent controls the command, so it could run printenv or echo $SECRET to read injected values — exec provides audit visibility (you can see what commands were run), not secret isolation.
How It Works
- The MCP server starts in non-interactive mode (no stdin prompts)
- On the first tool call, all
env = trueprofile secrets are resolved in a single batch — this amortizes the cost of yubikey taps or SSO prompts. Secrets configured withenv = falseare resolved on-demand when individually requested viaget_secret. - Resolved secrets are cached in process memory for the session
- Subsequent tool calls use the cache
- When the agent disconnects (EOF), the process exits and all secrets are cleared from memory
Security Considerations
- Secrets live only in process memory — except for
as_file = truesecrets, which are written to ephemeral temp files for subprocess injection and deleted when the command completes - The
exectool captures stdout/stderr (does not inherit stdio, which would corrupt the JSON-RPC stream) and caps output at 1 MiB to prevent unbounded memory usage - Non-interactive mode prevents provider auth prompts from interfering with the protocol
- With
tools = ["exec"], agents don't receive secrets viaget_secret, but can still read injected env vars by running commands likeprintenv. This provides audit visibility rather than strict secret isolation - Disabled tools are not advertised in
tools/list— agents only see tools they can actually call