MCP Server
TinyVault speaks the Model Context Protocol (MCP), the open standard that lets AI agents call tools, read resources, and use prompts. The same tvault binary that is your CLI is also the MCP server, so an agent can manage your vault without ever seeing a secret value.
What it is
tvault serves MCP over stdio through a hidden subcommand:
tvault mcp-serverYou rarely run this by hand — your MCP host (Claude Code, Claude Desktop, or any MCP client) launches it for you. The server is a thin policy-and-redaction layer over the same vault API the CLI uses: every call is checked against an access policy, privileged actions are audited, and outputs are filtered so plaintext stays out of the conversation.
It exposes 21 tools, 3 resources, and 2 prompts, built on the official modelcontextprotocol go-sdk.
Same vault, same crypto
The MCP server is not a separate datastore. It unlocks the one encrypted bbolt vault at ~/.tvault/vault.db and reads and writes through the identical AES-256-GCM path as the CLI and studio. See Architecture.
The core promise: values stay out of the model
The design goal is that a secret value never needs to enter the model's context. The tools are shaped to give an agent a way to use a secret without reading it:
| Tool | What the agent gets back |
|---|---|
vault_run_with_secrets | Injects secrets as env vars into a subprocess; returns {exit_code, stdout, stderr} with values redacted |
vault_export_env | Writes a 0600 file to disk; returns only {path, count, keys} |
vault_generate_secret | Generates and stores a secret; returns only {stored: true} |
vault_seal_for_recipients | Returns commit-safe ciphertext (or just a path) |
vault_get_secret | The one exception — returns the cleartext value with a warning |
vault_get_secret is the single tool that returns a raw value. It returns {key, value, warning}, where the warning reminds the caller the value is now in the model context. Prefer vault_run_with_secrets for using a value, and the search tools (vault_search_secrets, vault_list_secrets_by_prefix) for finding keys. The Tools Reference documents all 21.
Redaction is a safety net, not a control
With redact_output on, the server scrubs secret values from vault_run_with_secrets stdout/stderr, but it only replaces literal values longer than 3 characters with [REDACTED:KEY]. A subprocess that transforms a value — base64-encodes it, reverses it, splits it across lines — can evade redaction. Treat it as defense in depth, not a guarantee, and trust the commands you let an agent run.
Connecting an MCP client
Any MCP-capable client can launch tvault mcp-server. Because the server unlocks the vault from TVAULT_PASSPHRASE (there is no prompt over stdio), the only real decision is how each client supplies that passphrase. The most robust, secret-out-of-config pattern is a tiny launcher script — defined below — and the per-client commands here point at it (tvault-mcp). Swap in plain tvault mcp-server if you prefer to manage the passphrase yourself.
Claude Code
claude mcp add tinyvault -s user -- tvault-mcpOr edit .claude/settings.local.json directly. Claude Code expands ${TVAULT_PASSPHRASE} from its environment, so no secret is written to the file:
{
"mcpServers": {
"tinyvault": {
"command": "tvault",
"args": ["mcp-server"],
"env": { "TVAULT_PASSPHRASE": "${TVAULT_PASSPHRASE}" }
}
}
}Codex
codex mcp add tinyvault -- tvault-mcpWrites an [mcp_servers.tinyvault] block to ~/.codex/config.toml.
opencode
Add to the mcp object in ~/.config/opencode/opencode.json:
{
"mcp": {
"tinyvault": {
"type": "local",
"command": ["tvault-mcp"],
"enabled": true
}
}
}opencode also resolves {env:TVAULT_PASSPHRASE} inside an environment block if you'd rather pass it explicitly.
Hermes
hermes mcp add tinyvault --command tvault-mcp
hermes mcp test tinyvault # verify once the passphrase is setClaude Desktop
Add the same mcpServers block as Claude Code to ~/Library/Application Support/Claude/claude_desktop_config.json. GUI apps don't inherit your shell environment, so the launcher (which reads the passphrase from a file) is the easiest way to feed it the passphrase.
Keeping the passphrase out of every config
The cleanest setup is a one-line launcher that loads the passphrase from a single place and execs the server — so none of the agent configs above hold the secret:
#!/bin/sh
# ~/.local/bin/tvault-mcp (chmod +x)
[ -f "$HOME/.config/secrets/env" ] && . "$HOME/.config/secrets/env"
export PATH="/opt/homebrew/bin:/usr/local/bin:$HOME/.local/bin:$PATH"
exec tvault mcp-server "$@"Point every client at tvault-mcp and keep export TVAULT_PASSPHRASE=… in ~/.config/secrets/env (or your secrets manager / the macOS Keychain). This works for CLI and GUI clients and keeps the passphrase out of all the JSON/TOML/YAML configs.
Never commit a passphrase
If you do embed TVAULT_PASSPHRASE in a config file (e.g. .claude/settings.local.json), it must never be committed — add it to .gitignore. For passphrase-free setups, run the local agent or use an identity key.
Optional: point at a non-default vault
The MCP server honors the global persistent flags. To serve a vault in a custom directory, pass --vault in args, for example ["mcp-server", "--vault", "/path/to/vault-dir"]. See Configuration and Environment Variables.
Resources and prompts
Alongside the tools, the server publishes three read-only resources (JSON, metadata only, filtered by your policy):
| Resource URI | Contents |
|---|---|
vault://status | Lock state, project count, vault id, creation time |
vault://projects | Project names, descriptions, secret counts |
vault://projects/{name}/keys | Key names and metadata for one project (no values) |
It also publishes two prompts that scaffold common agent flows:
| Prompt | Arguments |
|---|---|
setup-project | name (required) |
inject-secrets | project (optional), command (required) |
The recommended agent pattern
The most context-safe way for an agent to work with TinyVault is:
Discover the surface once. Run
tvault docs featuresat the start of a session to get a JSON manifest of every feature and tool — no guessing.bashtvault docs featuresFind keys relationally, not by enumerating values. Use
vault_search_secrets(filter by prefix, name pattern, time window, version) orvault_list_secrets_by_prefix(autocomplete-style) to locate the key you need. Both return metadata only.Use a value without reading it. Call
vault_run_with_secretsto run a command with the secret injected as an env var. The value flows into the subprocess, not the conversation.
This keeps vault_get_secret reserved for the rare case where the model genuinely must see a value — and even then, the warning makes that explicit.
Everything privileged is audited
Reveals, writes, runs, exports, and rollbacks are recorded in the vault's audit log, just like the equivalent CLI commands. The model can inspect recent activity with vault_audit_log and vault_audit_log_since, but audit entries never contain secret values.
The model also cannot escalate its own access. The policy is loaded from disk at server start and cannot be changed at runtime. An agent can call vault_status to learn what it is allowed to do, but not to widen it.
Exit codes
If the server fails to start, the exit code tells you why: 3 vault locked, 5 not initialized, 6 wrong passphrase. (0 is ok, 1 is a generic error, 4 is not found.)
See also
- Tools Reference — all 21 tools, their inputs, and exactly what each returns
- Access Policy — scope an agent with
mcp-policy.yaml(modes, globs, exec gate) - Local Agent — unlock once, skip the passphrase prompt
- Security & Threat Model — what redaction and tokens do and do not protect