Vault MCP
MCP server for credential isolation in LLM agents. Your bot uses passwords and API keys — but never sees them.

Without Vault MCP With Vault MCP
User → "password: MyP@ss!" → LLM User → ●●●●●● → Vault → encrypted disk
LLM → vault_login("jira") → Vault → Chrome
LLM now has your password LLM sees only { status: "ok" }
in context, logs, history Password never enters LLM context
Why
AI agents are getting real access to real systems. They log into websites, call APIs, manage infrastructure. The standard pattern is dangerous:
"Here's my Stripe key: sk-live-abc123, please check my charges"
That API key is now in the LLM's context window, conversation logs, provider's training pipeline (maybe), and any tool that reads the conversation. One leaked prompt — and your credentials are exposed.
Vault MCP solves this with a simple principle: the agent operates credentials, but never sees them.
┌─────────────────────────────────────────────────────────────┐
│ │
│ Credential lifecycle │
│ │
│ User types password │
│ │ │
│ ▼ │
│ ┌───────────┐ AES-256-GCM ┌───────────────┐ │
│ │ Browser │ ──────────────────► │ Encrypted │ │
│ │ form │ │ store (.json) │ │
│ └───────────┘ └───────┬───────┘ │
│ │ │
│ LLM calls vault_login() │ │
│ │ │ │
│ ▼ ▼ │
│ ┌───────────┐ ┌───────────┐ │
│ │ Vault MCP │ ◄────── │ Decrypt │ │
│ │ server │ │ in memory │ │
│ └─────┬─────┘ └───────────┘ │
│ │ │
│ ▼ │
│ ┌───────────┐ │
│ │ Chrome │ Fill form via CDP │
│ │ DevTools │ Password in browser only │
│ └─────┬─────┘ │
│ │ │
│ ▼ │
│ { status: "ok" } ◄── only this goes │
│ back to LLM │
│ │
└─────────────────────────────────────────────────────────────┘
How It Works
5 MCP tools. The agent calls them like any tool. The difference — credential data never appears in the response.
vault_add | vault_add("jira") | { status, site_id } | Browser form opens, user enters password, encrypted to disk |
vault_login | vault_login("jira") | { status, page_title } | Decrypt, fill login form via Chrome CDP, clear password field |
vault_api_request | vault_api_request("stripe", url) | { status, body } | Decrypt API key, inject into headers, sanitize response |
vault_list | vault_list() | [{ siteId, type }] | List credential metadata — no secrets |
vault_status | vault_status("jira") | { active, lastUsed } | Metadata + audit count — no secrets |
Scenarios
1. First-time Login
The agent needs credentials it doesn't have. It asks the user to add them via a secure form — then logs in via Chrome.
User AI Agent Vault MCP Chrome
│ │ │ │
│ "Log me into │ │ │
│ Jira" │ │ │
├───────────────────►│ │ │
│ │── vault_list() ────►│ │
│ │◄── [] (empty) ─────┤ │
│ │ │ │
│ │── vault_add ───────►│ │
│ │ ("jira") │ │
│ │ │ │
│ ┌────────────────────────────┐ │ │
│ │ Browser form opens │ │ │
│ │ localhost:9900/add │ │ │
│ │ │ │ │
│ │ Email: [me@work.com] │ │ │
│ │ Password: [●●●●●●●●●●] │ │ │
│ │ URL: [jira.com] │ │ │
│ │ │ │ │
│ │ [Add to Vault] │ │ │
│ └────────────┬───────────────┘ │ │
│ │ │ │
│ └── POST ────────────────►│── encrypt │
│ │── save to disk │
│ │◄─ { status: ok } ───┤ │
│ │ │ │
│ │ Password is NOT │ │
│ │ in this response │ │
│ │ │ │
│ │── vault_login ─────►│── decrypt ──┐ │
│ │ ("jira") │◄────────────┘ │
│ │ │── fill email ────►│
│ │ │── fill pass ────►│
│ │ │── click submit ──►│
│ │ │── clear pass ───►│
│ │ │◄─ page loaded ───┤
│ │◄─ { status: ok, ───┤ │
│ │ title: "Jira" } │ │
│ │ │ │
│◄── "You're logged │ │ │
│ into Jira!" │ │ │
2. API Key Proxy
The agent makes API calls. Vault injects the key into headers and scrubs it from the response.
AI Agent Vault MCP Stripe API
│ │ │
│── vault_api_request ►│ │
│ service: "stripe" │── decrypt API key │
│ url: "/v1/charges" │ │
│ │── GET /v1/charges ────────►│
│ │ Authorization: │
│ │ Bearer sk-live-**** │
│ │◄── { data: [...] } ───────┤
│ │ │
│ │── scan response for │
│ │ leaked key │
│ │ (replace with ***) │
│ │ │
│◄── { status: ok, ───┤ │
│ body: "..." } │ │
│ │ │
│ API key NOT in │ │
│ this response │ │
3. Returning User
Credentials already stored — the agent goes straight to login:
User AI Agent Vault MCP Chrome
│ │ │ │
│ "Open GitHub" │ │ │
├───────────────────►│ │ │
│ │── vault_list() ────►│ │
│ │◄─ [{ siteId: ─┤ │
│ │ "github", │ │
│ │ active: true }] │ │
│ │ │ │
│ │── vault_login ─────►│── decrypt ───┐ │
│ │ ("github") │◄─────────────┘ │
│ │ │── CDP login ─────►│
│ │ │◄─ success ───────┤
│ │◄─ { status: ok } ───┤ │
│ │ │ │
│◄── "Done!" │ │ │
4. Instant Revocation
Remove access — the agent can no longer use the credential:
Admin (CLI) Vault MCP AI Agent
│ │ │
│── vault-mcp remove │ │
│ "jira" │── delete + audit log │
│◄── "Removed" │ │
│ │ │
│ │ ... later ... │
│ │ │
│ │◄── vault_login ──────┤
│ │ ("jira") │
│ │── { FAIL: ──►│
│ │ "not found" } │
5. Tamper-Proof Audit Trail
Every credential use is logged with a SHA-256 hash chain:
~/.vault-mcp/audit.jsonl
┌─────────────────────────────────────────────────────────────┐
│ evt_001 │ credential.created │ jira │ success │ hash_1 │
│ │ │ │ │ │ │
│ evt_002 │ credential.used │ jira │ success │ ▼ │
│ │ bot: claude │ │ prevHash: hash_1 │
│ │ │ │ │ hash_2 │
│ │ │ │ │ │ │
│ evt_003 │ credential.used │ jira │ success │ ▼ │
│ │ bot: claude │ │ prevHash: hash_2 │
│ │ │ │ │ hash_3 │
└─────────────────────────────────────────────────────────────┘
Tamper with any entry → chain breaks → detected
$ vault-mcp audit
Chain integrity: VALID (3 entries)
6. Web Chat Integration
For browser-based AI chats, the credential form appears inline — no new tab:
Web Chat (browser) Vault MCP (server)
┌──────────────────────┐ ┌──────────────────┐
│ import { VaultUI } │◄══ WS ═════►│ WebSocket :9901 │
│ from 'vault-mcp/web'│ │ │
│ │ │ AI agent calls │
│ ┌────────────────┐ │◄── event ───│ vault_add() │
│ │ 🔒 Vault │ │ │ │
│ │ Password: ●●●● │ │ │ │
│ │ [Grant Access] │ │── POST ────►│ encrypt + save │
│ └────────────────┘ │ │ │
└──────────────────────┘ └──────────────────┘
Password goes via HTTP POST (not WebSocket).
WebSocket is only for signaling "credential needed" / "credential saved".
Quickstart
git clone https://github.com/Chill-AI-Space/vault-mcp.git
cd vault-mcp && npm install && npm run build
claude mcp add -s user vault -- node ~/vault-mcp/dist/index.js
Or add credentials via CLI:
vault-mcp add --site github --email you@example.com --url https://github.com/login
Web SDK (for browser-based AI chats)
import { VaultUI } from 'vault-mcp/web';
const vault = new VaultUI({
vaultUrl: 'http://localhost:9900',
wsUrl: 'ws://localhost:9901',
onRequest: (siteId) => console.log(`Credential needed: ${siteId}`),
onSaved: (siteId) => console.log(`Saved: ${siteId}`),
});
vault.connect();
Also works as a script tag:
<script type="module">
import { VaultUI } from './node_modules/vault-mcp/dist/web/vault-ui.js';
new VaultUI().connect();
</script>
Bundle size: ~6KB, zero runtime dependencies, Shadow DOM (no CSS conflicts).
CLI
vault-mcp add
vault-mcp list
vault-mcp remove <site_id>
vault-mcp audit [site_id]
vault-mcp dashboard
Architecture
vault-mcp/
├── src/
│ ├── index.ts ── CLI or MCP mode (auto-detect)
│ ├── server.ts ── MCP server with 5 tools
│ ├── cli.ts ── CLI commands
│ ├── tools/
│ │ ├── vault-add.ts ── Browser form OR WebSocket modal
│ │ ├── vault-login.ts ── Decrypt → Chrome CDP → fill form
│ │ ├── vault-api.ts ── Decrypt → inject headers → sanitize
│ │ ├── vault-list.ts ── Metadata only
│ │ └── vault-status.ts ── Metadata + audit stats
│ ├── ws/
│ │ └── server.ts ── WebSocket server (127.0.0.1:9901)
│ ├── web/
│ │ ├── vault-ui.ts ── Web SDK (WS client + modal)
│ │ └── modal.ts ── Shadow DOM modal (dark theme)
│ ├── store/
│ │ ├── encrypted-store.ts ── AES-256-GCM, unique IV per credential
│ │ └── keychain.ts ── Master key management
│ ├── browser/
│ │ └── cdp-bridge.ts ── Playwright CDP bridge
│ ├── audit/
│ │ └── logger.ts ── JSONL + SHA-256 hash chain
│ └── dashboard/
│ ├── server.ts ── HTTP + WS bootstrap
│ ├── index.html ── Dashboard UI
│ └── add.html ── Credential entry form
├── dist/web/
│ └── vault-ui.js ── Bundled Web SDK (~6KB)
└── test/ ── 36 tests
Configuration
VAULT_MASTER_KEY | auto-generated | Encryption key (scrypt → 32 bytes) |
VAULT_CDP_URL | http://localhost:9222 | Chrome DevTools endpoint |
Storage
~/.vault-mcp/
├── credentials.json ── AES-256-GCM encrypted (unique IV per entry)
├── audit.jsonl ── Append-only, SHA-256 hash chain
└── .master-key ── Master key (mode 0600, auto-generated)
Security
Protects against Does NOT protect against
───────────────── ────────────────────────
✓ LLM context leakage ✗ Compromised host (root access)
✓ Plaintext credential storage ✗ Malicious MCP client
✓ Audit log tampering ✗ Browser-level memory attacks
✓ Accidental exposure in logs ✗ Network MITM to target sites
See SECURITY.md for full threat model.
Testing
npm test
npm run test:watch
License
MIT