Vault MCP Server
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Vault MCP Serversave my Stripe key and use it to call the charges API"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Vault MCP Server
Other languages: Chinese
A deployable, multi-user credential vault exposing both:
a web console (login + TOTP 2FA + per-user credential management), and
a remote MCP endpoint (
/mcp, Streamable HTTP) so an AI client can use secrets without them ever being typed into a chat.
Secrets are encrypted at rest with AES-256-GCM. The master key is provided
by the platform: Windows DPAPI (zero-config, bound to your account) or
PBKDF2-HMAC-SHA256 of VAULT_MASTER_PASSWORD (Linux / servers). Each user
has an isolated namespace; access control is enforced server-side.
⚠️ On a remote/trusted server the host holds
VAULT_MASTER_PASSWORDand can decrypt every user's vault. That is the intended trade-off of a hosted vault versus the purely local DPAPI model. Protect that password like a root key.
Requirements
Runtime
Item | Requirement |
Python | 3.10 or newer ( |
Operating system | Linux, macOS, or Windows |
Disk | A few MB for the code plus the encrypted store (grows with your data) |
Network | Outbound HTTPS only if you enable Cloudflare Turnstile or use |
Python packages — installed automatically from requirements.txt:
Package | Purpose |
| AES-256-GCM, PBKDF2 |
| HTTP server |
| Form parsing and HTML templates |
|
|
| Remote MCP transport |
| Renders the image CAPTCHA |
Optional extras
Pillow — needed for the
numericandimageCAPTCHA modes. Without it those modes degrade to an arithmetic question rather than failing open.nginx (or another TLS terminator) — required for a production deployment.
Cloudflare — only if you want the Access edge gate or Turnstile.
On Windows the vault uses DPAPI and needs no master password. On Linux / macOS / Docker you must set
VAULT_FORCE_PBKDF2=1and supplyVAULT_MASTER_PASSWORD.
Related MCP server: mcpvault
Installation
1. Get the code
git clone https://github.com/LianghaiLi-root/vault-mcp-server.git
cd vault-mcp-server2. Create a virtual environment and install
python3 -m venv .venv
. .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txtOr install it as a package, which also gives you the vault-mcp-server and
vault-mcp-cli commands:
pip install .3. Create your configuration
cp config.example.yaml config.yamlOpen config.yaml and set the public hostname(s) you will serve under
mcp.allowed_hosts — the remote MCP endpoint rejects any other Host header
with HTTP 421 (this is deliberate anti-DNS-rebinding protection).
4. Set the environment
export VAULT_MASTER_PASSWORD="$(openssl rand -hex 32)" # Linux / macOS only
export VAULT_FORCE_PBKDF2=1
export VAULT_DIR=/var/lib/vault-mcp/vault5. Provision your first user
python -m vault_mcp.cli add-user admin --role adminThis writes a scrypt password hash and a fresh MCP token into config.yaml.
Later accounts default to the ordinary role:
python -m vault_mcp.cli add-user alice6. Run
VAULT_COOKIE_SECURE=1 uvicorn vault_mcp.server:app --host 0.0.0.0 --port 8080
# or simply:
python -m vault_mcpOpen http://<host>:8080/, log in, and optionally enable Google Authenticator
under Two-factor authentication. Set VAULT_COOKIE_SECURE=1 only when the
site is served over HTTPS.
Docker
echo "VAULT_MASTER_PASSWORD=$(openssl rand -hex 32)" > .env
python -m vault_mcp.cli add-user admin --role admin # generates config.yaml + token
docker compose up --buildMount your own config.yaml and a volume for /data (the vault store).
Features
Role model
Role | Permissions |
| Super-user. May rename itself, add / rename / delete any user, reset anyone's password and 2FA, re-issue tokens, and sees the full user list plus the global hardening switches. |
| Ordinary account. Manages only itself. It cannot see the user list, has no Ops / Users tab, and every |
An account without a role key is treated as user. Always keep at least one
admin; the last remaining admin cannot be deleted.
Web console
Glassmorphism UI; tabs are shown according to your role.
Tab | Visible to | What you can do |
Credentials | everyone | Add / edit / delete typed credentials (SSH / web / API / DB / generic). The form renders the fields for the selected type automatically. |
Security | everyone | Enroll / reset TOTP 2FA, view and regenerate your MCP token, change your login password. |
Ops / Users |
| Server status; user management (add user, rename user, reset password, reset 2FA, re-issue MCP token, delete user) written straight back to |
Web hardening
Admin-only, persisted under the security key of config.yaml.
Cloudflare Access pre-check — when on, every browser request must carry a Cloudflare Access JWT (
Cf-Access-Jwt-Assertionheader orCF_Authorizationcookie). Requests without it get 403 before the login page even renders. Pair it with Cloudflare Zero Trust for a "before login" access layer.Force secure cookie — adds the
Secureflag to the session cookie.Custom edge header — a shared secret injected by your own nginx / CDN (e.g.
add_header X-Edge-Secret "xxx";); a mismatch is 403.Login throttling — N failures from one source locks it out for M seconds (429).
Edge-check exemptions:
/mcpand any request carrying a valid Bearer token skip the edge check. The former is machine-facing and authenticates on its own; the latter is a machine credential a casual visitor cannot forge.This exemption is what keeps the feature safe to use: the hardening switches are themselves configured through
POST /api/admin/security(which accepts a Bearer token). If the gate also blocked that call, enabling Cloudflare Access would lock the admin out of every authenticated path — including the one needed to switch it back off — leaving hand-editingconfig.yamlon the host as the only recovery. The UI asks for confirmation before enabling the gate so you configure the Cloudflare Access application first.
Human verification
Login-page CAPTCHA, admin-only, persisted under the captcha key of
config.yaml. It blocks automated credential-stuffing before the password
comparison. Pick one mode:
Mode | What it is |
| Disabled |
| Digits only, rendered as an image |
| Letters + digits, rendered as an image with noise |
| Cloudflare Turnstile — automatic, usually just a checkbox |
Images are rendered to PNG with Pillow, deliberately not SVG — text inside an SVG is plain markup a bot can read, which would defeat the purpose. Without Pillow the mode degrades to an arithmetic question; it never degrades to "no challenge". The admin card warns you when that happens.
Turnstile cannot be enabled without credentials. Both the site key and the secret key are required, and a save missing either is rejected with a message naming what is absent. The secret is never echoed back to the browser — the UI only reports whether one is stored. Send a new value to replace it, or use "clear secret" to wipe it.
Answers stay server-side: the browser only receives an opaque id. Answers live in memory, expire after 5 minutes and are single-use, so a captured id cannot be replayed. Failed challenges count toward the login throttle.
A wrong key produces a readable error on the login page (including the Cloudflare error code) instead of a silent blank box.
MCP tools
vault_save, vault_get, vault_describe, vault_list, vault_delete, and
vault_http (an AI-blind HTTP call — the secret is injected server-side and
never returned to the model).
Reads are field-aware, so a client can fetch the host and port along with the password instead of only the password:
vault_describe(name)— the shape of a record: every stored field key, the value of non-secret fields (host,port,username,url, ...), alengthfor secrets, plusmissingandcompleteso an incomplete entry is visible before you rely on it.vault_get(name, field="host")— one named field, verbatim.vault_get(name)on its own still returns just the primary secret, so existing clients are unaffected.vault_list()— adds each record's field keys and itscompleteflag.
vault_save is strict about shape: an unknown type, an unknown field name, or a
missing required field (host for ssh, url for web, token for api)
returns an error and stores nothing, so a record can never land in a state the
read path cannot serve.
Credential types
Every credential carries a type that drives its input fields in the web
console, and tells an AI client which field is the primary secret returned by
vault_get / vault_http:
Type | Fields |
| Secret / password |
| Host / IP · port · username · password · private key |
| URL · username / e-mail · password |
| Token / API key · endpoint URL |
| Host · port · database · username · password |
The list view shows a type badge, and each entry has Edit and Delete
actions. Editing prefills every field; renaming an entry replaces the old record
with the new name. All fields are encrypted at rest in a single AES-256-GCM
blob — only name, type and note live in plaintext metadata, so the list
(and vault_list) never expose values.
Remote MCP client
Point your MCP client at http://<host>:8080/mcp and authenticate with the
user's mcp_token as a Bearer token. The five tools — vault_save,
vault_get, vault_describe, vault_list, vault_delete — operate only on
that user's namespace. There is also an AI-blind tool:
vault_http(name, url, method, headers, body, secret_header)— the server fetchesurlwith the stored secret injected server-side (placeholder{{secret}}in headers/body, or a namedsecret_header), and returns only the HTTP response. The secret is never returned to the model and is redacted from the response. Onlyhttpsis allowed; the optionalhttp.allow_hostssetting restricts destinations. Use this instead ofvault_getwhenever the AI only needs to call an API, not see the secret.
DNS-rebinding protection
The /mcp endpoint validates the Host header and answers HTTP 421 for
anything not whitelisted. List your public domain(s) in config.yaml:
mcp:
allowed_hosts:
- vault.example.com
- 127.0.0.1:8080
- localhost:8080allowed_origins defaults to https://<each allowed_host>. This protection is
never disabled — it is a deliberate anti-DNS-rebinding guard.
Example mcp.json snippet for WorkBuddy:
{
"mcpServers": {
"vault-remote": {
"url": "https://vault.example.com/mcp",
"headers": { "Authorization": "Bearer <admin mcp_token>" }
}
}
}Note: your client must support remote (URL-based) MCP. If it only supports local stdio, run
python -m vault_mcp.stdioon the server and connect over stdio, or put a local stdio-to-HTTP shim in front.
Layout
Path | Purpose |
| Crypto + per-user encrypted storage (stdlib + |
| scrypt password hashing, TOTP (RFC 6238), HMAC session tokens |
| Loads/writes |
| Login human verification: image rendering, single-use answer store, Cloudflare Turnstile verification |
| Credential type catalog + shape rules (required fields, completeness, secret masking, write validation), shared by both front-ends |
| FastAPI app: web console + remote MCP ( |
| Local stdio MCP server (agent use on this machine) |
| Operator CLI to provision users / tokens |
|
|
| Package metadata, dependencies, console entry points |
| systemd unit, nginx TLS proxy, |
| Operator config template (users + server + vault dir) |
| Pytest suite (see Tests) |
⚠️ Read this before editing the web templates
DASH_TPL_SRC/LOGIN_TPL_SRCinvault_mcp/server.pyare non-raw triple-quoted Python strings with JavaScript inside. A\nwritten in them is compiled by Python into a real newline, which splits the JS string literal and makes the whole<script>block fail withSyntaxError: Invalid or unexpected token— every event handler on the page dies silently (dead tabs, an apparently-empty type dropdown). It bites in comments too.Write
\\ninstead, or build the string as['a','b'].join(NL)with an explicitconst NL='\\n';.To stop this regressing, the server runs a self-check at import time: it reads the
.pysource and scans inside<script>...</script>for the raw sequence, raising with an exact line number on a hit. Note this bug cannot be detected by inspecting a runtime string — after compilation the backslash is already gone — so the guard must look at the raw source.
Tests
pip install -e ".[dev]"
pytest # unit + in-process tool tests + stdio transport
pytest -m integration # only the subprocess/transport layer
pytest -m live # opt-in: hits a real endpoint (see below)The suite never reads a real vault. tests/conftest.py writes its own
config.yaml, points VAULT_DIR at a temporary directory before any vault
module is imported, and re-points it per test case — so running pytest on a
box that hosts a live credential store cannot touch it.
File | Covers |
| Type catalog, required/complete field rules, secret masking, write validation |
| Both tool layers ( |
| Real |
| Live |
The live tests carry no endpoint, token or credential name: they are read from
the environment and the probe record gets a random name that is deleted again.
pyproject.toml (addopts = -m "not live") keeps them out of a default run, so
no deployment detail ever lands in the repository or in a CI log:
MCP_URL=https://vault.example.com/mcp MCP_TOKEN=... pytest -m liveProduction deployment (systemd + nginx)
Files in deploy/ wire the server behind TLS on a Linux box:
# 1. install as a dedicated, unprivileged user
useradd --system --home /opt/vault-mcp-server --create-home vault
git clone https://github.com/LianghaiLi-root/vault-mcp-server.git /opt/vault-mcp-server
cd /opt/vault-mcp-server
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
# 2. provision config + secrets
cp config.example.yaml config.yaml
.venv/bin/python -m vault_mcp.cli add-user admin --role admin
cp deploy/env.example .env
# edit .env: set VAULT_MASTER_PASSWORD (openssl rand -hex 32), then chmod 600 .env
# 3. systemd
cp deploy/vault-mcp.service /etc/systemd/system/
mkdir -p /var/lib/vault-mcp && chown -R vault:vault /var/lib/vault-mcp
systemctl daemon-reload && systemctl enable --now vault-mcp
# 4. nginx (TLS)
cp deploy/nginx-vault-mcp.conf /etc/nginx/sites-available/vault-mcp.conf
ln -s /etc/nginx/sites-available/vault-mcp.conf /etc/nginx/sites-enabled/
# edit server_name + certificate paths, then: nginx -t && systemctl reload nginxThe unit binds uvicorn to 127.0.0.1:8080 only; nginx terminates TLS and proxies
both / (web console) and /mcp (remote MCP). Set VAULT_COOKIE_SECURE=1 once
TLS is in place.
systemd sandbox note. The shipped unit uses
ProtectSystem=fullwithReadWritePaths=/var/lib/vault-mcp /opt/vault-mcp-serverand pins the code directory read-only viaReadOnlyPaths. Do not switch toProtectSystem=strict: it remounts the whole filesystem read-only and systemd silently drops aReadWritePathsentry nested inside an already read-only tree, which breaks every admin write with[Errno 30] Read-only file system: '.../config.yaml.tmp'.
Security checklist
VAULT_MASTER_PASSWORDfrom a secret manager, never committed.TLS in front (reverse proxy / Cloudflare); set
VAULT_COOKIE_SECURE=1.config.yaml(hashes + tokens) not committed;.gitignored.Per-user 2FA enabled after first login.
Rate-limit / WAF the
/loginand/mcpendpoints.Consider a CAPTCHA mode if the login page is reachable from the internet.
License
MIT — see LICENSE.
This server cannot be deployed
Maintenance
Related MCP Connectors
Encrypted secret store and rotation for autonomous agent credentials
A secret store for AI agents: the agent never sees the plaintext.
Secrets for developers and agents—secure context and workflows without exposing secret values.
- JustOnceOAuthai.justonce
Persistent memory for AI assistants — one shared, OAuth-secured vault for every MCP client.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEncrypted credential vault with a 21-tool MCP server. Store and manage LLM API keys, service API keys, and OAuth credentials — then let your AI agent list, reveal, rotate, rename, pause, and proxy calls through them.MIT
- AlicenseNot gradedqualityDmaintenanceLocal, encrypted credential vault for AI agents that enables secure multi-account management across services like Supabase, GitHub, Vercel, and Stripe, allowing seamless account switching without restarting the chat client.9 npm2MIT
- AlicenseNot gradedqualityDmaintenanceEnables AI agents and MCP clients to securely store, retrieve, and manage encrypted credentials without hardcoding API keys.AGPL 3.0
- AlicenseNot gradedqualityAmaintenanceProvides a zero-knowledge credential vault for AI agents, allowing secure storage, retrieval, and management of secrets with cross-agent delegation and tamper-evident audit.MIT