Agent Vault
Human-in-the-loop secret access for AI coding agents, enabling approval workflow for password manager secrets via MCP.
README
Agent Vault
Proof of Concept. Agent Vault is a working prototype that demonstrates human-in-the-loop secret access for AI agents. The core approval workflow, audit logging, and multi-vault support are functional, but this is not production-hardened software. Notable gaps include unauthenticated approval endpoints (a co-located agent can self-approve β see the Security Whitepaper), no approval endpoint authentication, and limited provider coverage. See what's missing for the full list.
Human-in-the-loop secret access for AI coding agents.
Agent Vault is an MCP server that sits between AI agents (Claude Code, Cursor, Windsurf, etc.) and your password manager. When an agent needs a secret, you get a link β tap approve on your phone, and the agent gets the value. Deny, and it doesn't.
No secrets are baked into config files. No blanket access. You approve each request in real time, from wherever you are.
Read the Security Whitepaper for the full threat model, architecture, and rationale.
Agent: "I need DATABASE_URL to run this migration"
β π Approve access: https://abc123.ngrok-free.app/approve/xK9mQ2...
β You tap the link on your phone β Approve
β Agent gets the value β continues working
Why this exists
AI coding agents are increasingly capable β they can run migrations, deploy services, hit APIs. But giving them blanket access to your secrets is a bad idea, and copy-pasting credentials into chat is worse.
Agent Vault gives you a middle ground: agents can request secrets programmatically, and you approve or deny each request from your phone with a single tap. It works with any MCP-compatible agent and any password manager (currently supports 1Password and env files, with a simple interface to add more).
How it works
βββββββββββββββ ββββββββββββββββββββ ββββββββββββββββ
β AI Agent ββββββΆβ Agent Vault ββββββΆβ 1Password β
β (Claude Code,β MCP β β SDK β .env file β
β Cursor, etc)βββββββ ββββββββββββββ βββββββ (more soon) β
βββββββββββββββ β β Approval β β ββββββββββββββββ
β β Server ββββββngrokβββΆ Your Phone
β ββββββββββββββ β
β ββββββββββββββ β
β β Audit Log β β
β ββββββββββββββ β
ββββββββββββββββββββ
- The agent calls
get_secretwith a name and a reason - Agent Vault creates a unique approval URL and opens it via ngrok
- The URL appears in your terminal/chat β tap it on your phone
- You see what's being requested and why, then tap Approve or Deny
- The agent receives the secret value (or a denial) and continues
- Everything is logged to a local SQLite audit trail
The agent blocks until you respond. It can't proceed without your decision.
Quick start
Prerequisites
- Node.js 18+
- An ngrok account (free tier works fine) β you need an auth token
Install
# Global install (recommended)
npm install -g agent-vault
# Or run directly with npx β no install needed
npx agent-vault
For 1Password support, also install the SDK:
npm install -g @1password/sdk
Add to Claude Code
Add to your MCP configuration (~/.claude/claude_code_config.json):
{
"mcpServers": {
"agent-vault": {
"command": "npx",
"args": ["agent-vault"],
"env": {
"NGROK_AUTHTOKEN": "your_ngrok_token",
"OP_SERVICE_ACCOUNT_TOKEN": "your_1password_token"
}
}
}
}
That's it. Every project on your machine can use it β no cloning, no per-project install.
Add to Cursor / other MCP clients
Agent Vault uses stdio transport. Point your client at npx agent-vault with the environment variables above.
Usage examples
1. Local development β your own machine
The simplest setup. Install once, use in every project.
# One-time global install
npm install -g agent-vault
Add to your Claude Code config (~/.claude/claude_code_config.json) β also one-time:
{
"mcpServers": {
"agent-vault": {
"command": "npx",
"args": ["agent-vault"],
"env": {
"NGROK_AUTHTOKEN": "your_ngrok_token",
"OP_SERVICE_ACCOUNT_TOKEN": "your_1password_token"
}
}
}
}
Then in any project, create an agent-vault.config.json to define what vaults are available:
{
"vaults": {
"dev": {
"type": "1password",
"serviceAccountToken": "env:OP_SERVICE_ACCOUNT_TOKEN",
"ttl": 15,
"ttlScope": "vault"
}
},
"ngrokAuthToken": "env:NGROK_AUTHTOKEN"
}
Or for a simple env file setup, create .env.secrets in the project and point to it:
{
"vaults": {
"local": { "type": "env", "file": ".env.secrets", "ttl": 15 }
}
}
Now when you ask Claude Code to do something that needs credentials:
You: "Connect to the database and check if the users table has the new column"
Agent: calls list_secrets β sees DATABASE_URL is available in the "dev" vault
Agent: calls get_secret("dev", "DATABASE_URL", "Need to connect to verify users table schema")
β π Approve access: https://abc123.ngrok-free.app/approve/xK9mQ2...
β You tap the link on your phone β see "DATABASE_URL" + the reason β Approve
Agent: receives the connection string β runs the query β reports back
With ttl: 15 and ttlScope: "vault", after that first approval the agent can access any secret in the dev vault for 15 minutes without asking again.
2. Sandbox / testing β no password manager needed
Want to try Agent Vault without connecting to 1Password or any real secrets? The env file provider works as a standalone sandbox.
# Create a test secrets file
cat > .env.secrets << 'EOF'
TEST_API_KEY=test-key-12345
TEST_DATABASE_URL=postgresql://test:test@localhost:5432/testdb
EOF
# Create a minimal config
echo '{"vaults":{"test":{"type":"env","file":".env.secrets","ttl":5}}}' > agent-vault.config.json
# Run it (no ngrok needed for local testing)
npx agent-vault
You can test the MCP tools directly using the MCP Inspector:
npx @modelcontextprotocol/inspector npx agent-vault
This opens a browser UI where you can call list_secrets and get_secret manually, see the approval URL, and test the full flow without needing an AI agent at all.
For a completely offline test (no ngrok), you can open the approval URL on your local machine at http://localhost:9999/approve/... β the ngrok URL just makes it reachable from your phone.
3. Headless / remote β Claude Code on a cloud VM or container
This is the primary use case Agent Vault was built for. You're running an agent on a remote machine and want to approve secret access from your phone.
The key insight: ngrok gives you a public URL automatically, so it doesn't matter that the machine has no screen. The approval link works from anywhere.
# On the remote machine β one-time install
npm install -g agent-vault
MCP config is the same everywhere:
{
"mcpServers": {
"agent-vault": {
"command": "npx",
"args": ["agent-vault"],
"env": {
"NGROK_AUTHTOKEN": "your_ngrok_token",
"OP_SERVICE_ACCOUNT_TOKEN": "your_1password_token"
}
}
}
}
Then create agent-vault.config.json in the project to define the vault structure. For 1Password β no secrets ever touch disk:
{
"vaults": {
"prod": {
"type": "1password",
"serviceAccountToken": "env:OP_SERVICE_ACCOUNT_TOKEN",
"ttl": 0,
"ttlScope": "secret"
}
},
"ngrokAuthToken": "env:NGROK_AUTHTOKEN"
}
How it plays out in practice:
You're on your phone, monitoring a headless Claude Code session.
Claude Code: "I need to deploy the migration. Requesting database credentials."
β π Approve access to "Production DB Password":
https://e4f2.ngrok-free.app/approve/Rk3mZp9xQ2nW...
You tap the link. Your phone opens a clean dark page:
βββββββββββββββββββββββββββββββ
β Secret Access Request β
β β
β Secret: Production DB β
β Reason: Run migration β
β 0042_add_roles β
β Time: 2:34 PM β
β β
β [ Approve ] [ Deny ] β
βββββββββββββββββββββββββββββββ
You tap Approve. Claude Code gets the credential, runs the migration,
and you never had to SSH in, open a terminal, or copy-paste anything.
Recommended TTL settings
| Scenario | TTL | Why |
|---|---|---|
| Quick local task | 0 (always ask) |
You're right there, approvals are instant |
| Long local session | 15 |
Don't interrupt flow for repeated access |
| Headless remote agent | 30 |
Reduce phone tapping during extended runs |
| CI/CD pipeline | 60 |
Approve once at the start, let it finish |
| Production / sensitive | 0 (always ask) |
Every access should be deliberate |
Configuration
Agent Vault uses a config file for structure (committable to your repo) and environment variables for secrets (tokens, auth). This lets teams share vault configuration while each person sets their own credentials.
Config file
Create agent-vault.config.json in your project root:
{
"vaults": {
"dev": {
"type": "1password",
"serviceAccountToken": "env:OP_DEV_TOKEN",
"vaultIds": ["abc123"],
"ttl": 15
},
"prod": {
"type": "1password",
"serviceAccountToken": "env:OP_PROD_TOKEN",
"vaultIds": ["def456"],
"ttl": 0
},
"local": {
"type": "env",
"file": ".env.secrets",
"ttl": 30
}
},
"ngrokAuthToken": "env:NGROK_AUTHTOKEN",
"port": 9999
}
Commit this file to your repo. It contains no secrets β just structure. The "env:VAR_NAME" syntax tells Agent Vault to read the actual value from an environment variable at runtime.
Then each team member just sets their own .env or shell exports:
# .env (gitignored) or shell exports
OP_DEV_TOKEN=ops_your_dev_service_account_token
OP_PROD_TOKEN=ops_your_prod_service_account_token
NGROK_AUTHTOKEN=your_ngrok_token
Agent Vault searches for the config file in this order:
- Path specified by
AGENT_VAULT_CONFIGenv var agent-vault.config.jsonin the current working directory~/.agent-vault.config.jsonin your home directory
Vault configuration
Each vault entry supports:
| Field | Type | Required | Description |
|---|---|---|---|
type |
"env" or "1password" |
yes | Provider type |
ttl |
number | no | Approval window in minutes (default: 0 = always ask) |
ttlScope |
"secret" or "vault" |
no | What the approval window covers (default: "secret") |
writable |
boolean | no | Whether agents can create/update secrets (default: false) |
file |
string | env only | Path to .env-style secrets file (relative to config file) |
serviceAccountToken |
string | 1password only | Service account token or "env:VAR_NAME" reference |
vaultIds |
string[] | no | 1Password vault IDs to expose (default: all accessible) |
write.vaultId |
string | 1password only | Vault ID to create new items in (required if writable: true) |
write.category |
string | no | 1Password item category (default: "login") |
Top-level configuration
| Field | Type | Default | Description |
|---|---|---|---|
ngrokAuthToken |
string | β | ngrok auth token or "env:VAR_NAME" reference |
port |
number | 9999 |
Local port for the approval HTTP server |
MCP tools
Agent Vault exposes two tools to the agent:
list_secrets
Lists available secret names across all configured vaults. Never reveals values β just tells the agent what's available and which vault it's in.
Parameters: none
Example response:
[dev]
- DATABASE_URL
- API_KEY
[prod]
- DATABASE_URL (production)
- STRIPE_SECRET_KEY (production)
[local]
- TEST_TOKEN
get_secret
Requests access to a single secret. Use get_secrets (below) when you need multiple β it's a better experience for the approver.
Parameters:
| Name | Type | Description |
|---|---|---|
vault |
string | The vault name (as defined in your config) |
name |
string | The name or ID of the secret |
reason |
string | Why the agent needs this secret (shown to the approver) |
The tool call blocks until you approve or deny. The agent cannot proceed without your decision.
On approval: returns the secret value as plain text.
On denial: returns a denial message.
get_secrets
Requests access to multiple secrets in a single approval. The approver sees the full list and approves or denies all at once β much better than getting pinged once per secret.
Parameters:
| Name | Type | Description |
|---|---|---|
vault |
string | The vault name |
names |
string[] | List of secret names/IDs to access |
reason |
string | Why the agent needs these secrets |
Example: an agent needs both DATABASE_URL and DATABASE_PASSWORD to run a migration. Instead of two separate approval links, you get one that says "2 secrets: DATABASE_URL, DATABASE_PASSWORD" β tap approve once.
If some secrets already have active approval windows, only the ones that need approval are shown in the request. If all are already permitted, the call returns immediately with no approval needed.
set_secret
Create or update a secret in a writable vault. The approval page shows a WRITE badge and a masked preview of the value (e.g. sk-********************abc) so you can sanity-check without the full value being displayed.
Parameters:
| Name | Type | Description |
|---|---|---|
vault |
string | The vault name (must have writable: true in config) |
name |
string | The name/ID for the secret |
value |
string | The secret value to store |
reason |
string | Why the agent is creating/updating this secret |
Use this when the agent generates a credential (API key, token, password) and needs to persist it safely. The secret goes straight to the vault instead of being printed to chat where it gets lost or leaked.
set_secrets
Batch version β create or update multiple secrets with a single approval. Ideal for bootstrapping flows.
Parameters:
| Name | Type | Description |
|---|---|---|
vault |
string | The vault name (must have writable: true) |
secrets |
{name, value}[] |
List of secrets to create/update |
reason |
string | Why the agent is creating these secrets |
Enabling writes
Writes are disabled by default. To enable, set writable: true on the vault in your config:
{
"vaults": {
"dev": {
"type": "1password",
"serviceAccountToken": "env:OP_DEV_TOKEN",
"writable": true,
"write": {
"vaultId": "your-1password-vault-id",
"category": "login"
},
"ttl": 15,
"ttlScope": "vault"
}
}
}
For env file vaults, no extra config is needed β writable: true is sufficient and secrets are appended to (or updated in) the .env file.
For 1Password vaults, the write section specifies which vault ID to create items in and what item category to use (defaults to "login"). The service account must have write access to that vault.
Providers
env file (default)
Reads secrets from a .env-style file. Supports comments, quoted values, and standard KEY=value format. Good for testing or simple setups.
AGENT_VAULT_PROVIDER=env
AGENT_VAULT_ENV_FILE=/path/to/.env.secrets
Example .env.secrets:
# Database
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
# API keys
STRIPE_SECRET_KEY="sk_live_..."
OPENAI_API_KEY='sk-...'
1Password
Uses a 1Password service account for production vault access. Secrets are referenced using op:// URIs.
AGENT_VAULT_PROVIDER=1password
OP_SERVICE_ACCOUNT_TOKEN=your_service_account_token
# Optional: restrict to specific vaults
AGENT_VAULT_1P_VAULTS=vault_id_1,vault_id_2
Requires the 1Password SDK as an additional dependency:
npm install @1password/sdk
When listing secrets, items appear with their vault ID as the group. When requesting a secret, use the op://vault/item/field format.
Approval windows (TTL)
By default, every secret access requires explicit approval (ttl: 0). For long sessions where an agent needs repeated access, you can configure approval windows β a permission cache that lets the agent re-fetch secrets without re-asking.
No secrets are stored. The approval window just permits the agent to fetch again from the provider. When the window expires, it has to ask again.
Per-secret scope (default)
{
"type": "1password",
"ttl": 15,
"ttlScope": "secret"
}
After you approve DATABASE_URL, the agent can re-fetch DATABASE_URL for 15 minutes without asking. But if it needs API_KEY, that's a separate approval.
Per-vault scope
{
"type": "1password",
"ttl": 30,
"ttlScope": "vault"
}
After you approve any secret in this vault, the agent can access all secrets in this vault for 30 minutes. One tap unlocks the whole vault for the window. Good for dev environments where you trust the vault contents and don't want to be pinged repeatedly.
Combining scopes across vaults
A common pattern: loose permissions for dev, strict for prod.
{
"vaults": {
"dev": {
"type": "1password",
"serviceAccountToken": "env:OP_DEV_TOKEN",
"ttl": 30,
"ttlScope": "vault"
},
"prod": {
"type": "1password",
"serviceAccountToken": "env:OP_PROD_TOKEN",
"ttl": 0,
"ttlScope": "secret"
}
}
}
Dev vault: approve once, agent has free access for 30 minutes. Prod vault: every single secret, every single time.
Audit log
Every secret access is recorded in a local SQLite database (agent-vault.db by default):
| Column | Description |
|---|---|
timestamp |
When the request was made |
secret_name |
Which secret was requested |
reason |
Why the agent said it needed it |
action |
approved, denied, or auto_approved |
ttl_expires_at |
When the auto-approval window expires (if applicable) |
You can query it directly:
sqlite3 agent-vault.db "SELECT * FROM audit ORDER BY timestamp DESC LIMIT 20"
Observability webhooks
Agent Vault can forward every access event to external logging, analytics, or SIEM endpoints. Events fire asynchronously and never block the agent β if a webhook fails, it's logged to stderr and the agent continues.
Configuration
Add a webhooks array to your config:
{
"vaults": { ... },
"webhooks": [
{
"url": "https://logs.example.com/api/events",
"authorization": "env:LOGGING_API_KEY",
"events": "all"
},
{
"url": "https://your-datadog-intake.example.com/v1/input",
"authorization": "env:DD_API_KEY",
"events": ["approved", "denied"]
}
]
}
| Field | Type | Default | Description |
|---|---|---|---|
url |
string | required | Endpoint to POST events to |
authorization |
string | β | Authorization header value (supports env: references) |
events |
"all" or array |
"all" |
Which events to send: "approved", "denied", "auto_approved" |
Event payload
Every event is a JSON POST with this shape:
{
"event": "secret_access",
"timestamp": "2026-04-10T14:32:01.000Z",
"vault": "dev",
"secrets": ["DATABASE_URL", "API_KEY"],
"reason": "Need database credentials to run migration 0042",
"action": "approved",
"scope": "vault",
"ttlExpiresAt": "2026-04-10T14:47:01.000Z"
}
| Field | Description |
|---|---|
event |
Always "secret_access" |
timestamp |
ISO 8601 when the decision was made |
vault |
Which vault was accessed |
secrets |
Array of secret names in the request |
reason |
The agent's stated reason |
action |
"approved", "denied", or "auto_approved" |
scope |
"secret" or "vault" β what the approval covered |
ttlExpiresAt |
When the approval window expires (null if no TTL) |
Example integrations
Datadog Logs:
{
"url": "https://http-intake.logs.datadoghq.com/api/v2/logs",
"authorization": "env:DD_API_KEY",
"events": "all"
}
Splunk HEC:
{
"url": "https://your-splunk:8088/services/collector/event",
"authorization": "env:SPLUNK_HEC_TOKEN",
"events": "all"
}
Custom webhook (Slack, Discord, etc.):
{
"url": "https://hooks.slack.com/services/T00/B00/xxx",
"events": ["denied"]
}
Sending only "denied" events to Slack is a useful pattern β you get alerted when an agent is denied access, which may indicate something unexpected is happening.
Security considerations
- Secrets are transmitted over HTTPS via the ngrok tunnel. The approval page never displays the secret value β only the name and reason.
- Approval URLs are single-use with random 16-character IDs. Once approved or denied, the URL is invalidated.
- ngrok tunnels are ephemeral β they only exist while Agent Vault is running. No persistent public endpoint.
- The agent receives the secret in the MCP tool response. What the agent does with it after that is outside Agent Vault's control β this is the same trust boundary as typing a secret into your terminal.
- Service account tokens (ngrok, 1Password) should be treated as sensitive. Don't commit them to version control. The
.gitignorealready excludes.env.secrets. - The approval server has no authentication beyond the unguessable URL. Anyone with the link can approve or deny. For high-security environments, consider running ngrok with IP restrictions or using a VPN.
Adding a new provider
Implement the SecretProvider interface:
import type { SecretEntry, SecretProvider } from "./providers/provider.js";
export class MyProvider implements SecretProvider {
readonly name = "my-provider";
async listSecrets(): Promise<SecretEntry[]> {
// Return secret names/IDs β never values
return [
{ id: "secret-1", name: "DATABASE_URL", group: "production" }
];
}
async getSecret(id: string): Promise<string> {
// Fetch and return the actual secret value
return "the-secret-value";
}
}
Then add it to the provider switch in src/index.ts:
case "my-provider": {
return new MyProvider(/* config */);
}
The interface is intentionally minimal β three members, no lifecycle methods, no configuration schema. The goal is to make it trivial to add Bitwarden, HashiCorp Vault, AWS Secrets Manager, or anything else.
Project structure
src/
βββ index.ts Entry point β wires provider, approval server, and MCP server
βββ server.ts MCP server with list_secrets and get_secret tools
βββ approval.ts Express HTTP server + ngrok tunnel for approve/deny pages
βββ audit.ts SQLite audit log with TTL-based auto-approval checks
βββ providers/
βββ provider.ts SecretProvider interface
βββ env-provider.ts .env file provider
βββ onepassword-provider.ts 1Password service account provider
βββ index.ts Re-exports
Setup prompts
Copy-paste these prompts into your AI agent to have it set up Agent Vault for you.
Local setup with env file
Set up agent-vault so you can request secrets from me with my approval instead
of me pasting them into chat. It's an npm package β no cloning needed.
Docs: https://github.com/loonshoot/agent_vault
1. Add agent-vault to my Claude Code MCP config (~/.claude/claude_code_config.json):
{
"mcpServers": {
"agent-vault": {
"command": "npx",
"args": ["agent-vault"],
"env": {
"NGROK_AUTHTOKEN": "<I will provide>"
}
}
}
}
2. Create an `agent-vault.config.json` in THIS project directory with:
{
"vaults": {
"local": {
"type": "env",
"file": ".env.secrets",
"ttl": 15
}
}
}
3. Create a `.env.secrets` file in this project with placeholder values for:
- DATABASE_URL
- API_KEY
4. Show me what you configured so I can fill in the real values and my NGROK_AUTHTOKEN
Setup with 1Password
Set up agent-vault so you can securely request and store secrets from my 1Password
when you need them. When you need a credential, you call a tool, I get a link on
my phone, and I tap approve or deny. When you generate a credential, you save it
straight to the vault instead of printing it to chat. It's an npm package β no
cloning needed.
Docs: https://github.com/loonshoot/agent_vault
1. Add agent-vault to my Claude Code MCP config (~/.claude/claude_code_config.json):
{
"mcpServers": {
"agent-vault": {
"command": "npx",
"args": ["agent-vault"],
"env": {
"OP_SERVICE_ACCOUNT_TOKEN": "<I will provide>",
"NGROK_AUTHTOKEN": "<I will provide>"
}
}
}
}
2. Create an agent-vault.config.json in THIS project directory:
{
"vaults": {
"1password": {
"type": "1password",
"serviceAccountToken": "env:OP_SERVICE_ACCOUNT_TOKEN",
"ttl": 15,
"ttlScope": "vault",
"writable": true,
"write": {
"vaultId": "<I will provide>",
"category": "login"
}
}
},
"ngrokAuthToken": "env:NGROK_AUTHTOKEN"
}
3. Show me the config and remind me I need:
- OP_SERVICE_ACCOUNT_TOKEN: 1Password service account token with read+write
(1password.com β Integrations β Service Accounts β create with access to my vault)
- NGROK_AUTHTOKEN: free at ngrok.com/signup β Your Authtoken
- The 1Password vault ID for the write.vaultId field
4. Once I provide those values, update the config, then test by calling
list_secrets, then get_secret on one of them so I can test the approval flow.
From then on:
- Use get_secret / get_secrets when you need credentials β I'll approve from my phone
- Use set_secret / set_secrets when you generate credentials (API keys, tokens, etc.)
β save them to the vault instead of printing them to chat
Remote / headless setup
Set up agent-vault so I can approve secret access from my phone while you work
on this remote machine. It's an npm package β no cloning needed.
Docs: https://github.com/loonshoot/agent_vault
1. Run `npm install -g agent-vault` on this machine
2. Add agent-vault to your MCP config:
{
"mcpServers": {
"agent-vault": {
"command": "npx",
"args": ["agent-vault"],
"env": {
"NGROK_AUTHTOKEN": "<I will provide>"
}
}
}
}
3. Create an `agent-vault.config.json` in this project with:
{
"vaults": {
"secrets": {
"type": "env",
"file": ".env.secrets",
"ttl": 30
}
}
}
4. Create a `.env.secrets` with placeholder values for:
- DATABASE_URL
- DEPLOY_KEY
- API_SECRET
5. Show me the config. When I give you the NGROK_AUTHTOKEN and secret values,
update them. After that, whenever you need a secret, use the get_secret tool β
I'll get a link on my phone to approve it.
Customize the vault names and secret keys in each prompt to match your project.
Agent instructions
Add the following to your project's CLAUDE.md (or equivalent agent instructions file) so your agent knows how to use Agent Vault automatically:
## Secrets
This project uses agent-vault for secret access. NEVER ask the user to paste
secrets, credentials, API keys, or tokens into chat.
When you need a secret:
1. Call `list_secrets` to see what's available across configured vaults
2. Call `get_secret` or `get_secrets` with the vault name, secret name(s),
and a clear reason why you need them
3. Wait for the user to approve via the link β do not proceed without approval
4. Use the returned value directly β do not log, echo, or store it anywhere
When you generate a secret (API key, token, password, connection string):
1. Call `set_secret` or `set_secrets` to persist it to a writable vault
2. Wait for the user to approve the write
3. Do NOT print the raw secret value to chat β it goes straight to the vault
4. Confirm the save was successful and reference the secret by name
Rules:
- Always use batch tools (`get_secrets`, `set_secrets`) when working with
more than one secret β sends a single approval instead of one per secret
- Always provide a specific, honest reason (e.g. "Need DATABASE_URL to run the
pending migration" not "need credentials")
- If access is denied, do not re-request the same secret β ask the user what
they'd like you to do instead
- Never write secrets to files, environment variables, logs, or commit history
- If a command needs a secret as an argument, prefer environment variable
injection (e.g. `DATABASE_URL=<value> npm run migrate`) over passing it
as a CLI flag where it would appear in process lists
- When bootstrapping a new service, use `set_secrets` to save all generated
credentials in one batch rather than printing them to chat
For teams, commit this to your repo so every agent session follows the same rules.
What's missing
Agent Vault demonstrates the concept but has known gaps that must be addressed before use in sensitive environments:
| Gap | Risk | Planned mitigation |
|---|---|---|
| No approval endpoint auth | A co-located agent can curl the approval endpoint to self-approve |
HMAC-signed approval tokens, push-based approval |
| Shared network | Agent and approval server on the same host β no network isolation | Support running the approval server on a separate host |
| Limited providers | Only 1Password and env files | Bitwarden, HashiCorp Vault, AWS/GCP/Azure secret managers |
| No secret redaction | Approved secrets appear in MCP tool responses and may be logged by the agent platform | Out of scope (transport-layer concern), but worth noting |
| SQLite audit only | Local audit log is deletable by the agent | Remote/append-only audit backends |
| ngrok dependency | Free tier has session limits; single point of failure for remote approval | Support alternative tunneling (Cloudflare Tunnel, Tailscale Funnel) or direct HTTPS |
For the full threat model and hardening roadmap, see the Security Whitepaper.
Roadmap
- [ ] Bitwarden Secrets Manager provider
- [ ] HashiCorp Vault provider
- [ ] AWS Secrets Manager provider
- [ ] Secret classification levels (auto-approve low-risk, always prompt for high-risk)
- [ ] Multiple approval channels (Telegram, Slack, ntfy.sh) as alternatives to the link
- [ ] Bulk approval ("approve all secrets for this session")
- [ ] Web dashboard for audit log
License
MIT
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.