██╗ ██╗ █████╗ ██╗ ██╗████████╗
██║ ██║██╔══██╗██║ ██╔╝╚══██╔══╝
██║ ██║███████║█████╔╝ ██║
╚██╗ ██╔╝██╔══██║██╔═██╗ ██║
╚████╔╝ ██║ ██║██║ ██╗ ██║
╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝
One config. All your AI coding tools. Zero credentials on disk.
configure once
┌─────────────────┐
│ ~/.agents/ │
│ mcp-config │──► Claude Code ~/.claude.json
│ policy.json │──► Cursor ~/.cursor/mcp.json
│ secrets │──► Gemini CLI ~/.gemini/settings.json
│ skills/ │──► Codex ~/.codex/config.toml
└─────────────────┘──► OpenCode ~/.config/opencode/opencode.json
└──► Windsurf ~/.codeium/windsurf/mcp_config.json
Your AI coding tools write API keys into plaintext JSON files. Those files sync to iCloud, end up in dotfiles repos, and get copy-pasted every time a new tool ships. There is no policy layer, no audit trail, and no single source of truth.
vakt fixes this. One ~/.agents/ directory holds your entire MCP setup with no credential values inside. Secrets resolve from your OS keychain at sync time. vakt sync writes correct config to every installed tool instantly. A policy engine enforces what each MCP server is allowed to do before any tool call goes through.
Configure once. Sync everywhere. Rotate a key in one place. Audit everything.
macOS / Linux via Homebrew (recommended):
brew tap tn819/vakt https://github.com/tn819/vakt
brew install vaktOne-line installer:
# Install (download a single binary from GitHub releases)
curl -fsSL https://github.com/tn819/vakt/releases/latest/download/vakt -o /usr/local/bin/vakt
chmod +x /usr/local/bin/vakt
# Or run from source
git clone https://github.com/tn819/vakt
cd vakt && bun install
export PATH="$PATH:$(pwd)/src"
vakt init # scaffold ~/.agents/
vakt import-from-everywhere # pull in your existing provider configs
vakt secrets set GITHUB_TOKEN ghp_... # store in keychain, not in a file
vakt sync # write to every installed CLI| Capability | vakt | Smithery | mcp-get | Manual |
|---|---|---|---|---|
| Multi-provider sync (6 tools) | ✓ | — | — | — |
| Keychain-backed secrets | ✓ | — | — | — |
| Runtime policy enforcement | ✓ | — | — | — |
| Audit log (SQLite + SIEM export) | ✓ | — | — | — |
| OTel distributed tracing | ✓ | — | — | — |
| Official registry verification | ✓ | ✓ | ✓ | — |
| Cloud sandbox routing (E2B) | ✓ | — | — | — |
| Model router (local/remote LLM) | ✓ | — | — | — |
| Skills portability | ✓ | — | — | — |
AI tools: Claude Code · Cursor · Gemini CLI · Codex · OpenCode · Windsurf · Mistral Vibe
Secrets: macOS Keychain · pass/GPG · env file (CI)
Observability: Grafana Tempo · Jaeger · Honeycomb · Datadog APM · New Relic · SigNoz · Axiom
SIEM / compliance evidence: Elastic SIEM · Splunk · Microsoft Sentinel · audit export satisfies SOC 2 CC6.8/CC7.2 and ISO 27001 A.12.4
Runtime DLP: crust (MCP traffic scanning, 34 built-in patterns)
Sandboxes: E2B (built-in) · Daytona · microsandbox · Kata Containers
- What vakt unlocks end-to-end
- Three principles
- Why this exists
- Security in depth
- Standardization in depth
- Policy engine
- MCP Registry
- Model Router
- Interoperability in depth
- Commands
- Supported providers
- Directory structure
- Skills
- Testing
vakt is a complete runtime layer for agentic CLIs encompassing the whole ecosystem from credentials, MCP, auditability, and skills — not just a config manager. Here is the full deployment picture:
┌─────────────────────────────────────────────┐
Agent │ vakt │
(Claude/Cursor/…) ──┤ │
│ policy.json ──► proxy ──► MCP server │
│ audit.db ◄── ◄── │
│ OTel spans ──────────────────────────► │
│ runtime: local | cloud sandbox │
└─────────────────────────────────────────────┘
~/.agents/ contains no secrets — only named references (secret:GITHUB_TOKEN). The entire directory is safe to commit, push to a dotfiles repo, or copy to a remote machine. One vakt sync and every installed tool has the complete setup.
# On a new machine or a CI runner
git clone your-dotfiles
cd your-dotfiles && vakt sync
# → all providers configured, zero credential exposurevakt integrates the E2B runtime so any MCP server can be moved off your local machine into an isolated cloud sandbox — same config, same policy, same audit trail.
vakt config set runtime.e2b.api_key secret:E2B_API_KEY
vakt runtime set github e2b # route this server to the cloud
vakt runtime set filesystem local # keep this one local
vakt runtime list # view all assignmentsThe community maintains a broader catalogue of sandbox technologies at awesome-sandbox. Key options relevant to MCP server isolation:
| Sandbox | Technology | SaaS | Self-host | Notes |
|---|---|---|---|---|
| E2B | Firecracker microVMs | ✓ | ✓ | Built for AI agent workloads; already integrated |
| Daytona | Containers | ✓ | ✓ | <200ms startup; dev-environment focused |
| microsandbox | libkrun microVMs | — | ✓ | Lightweight self-hosted alternative |
| Kata Containers | MicroVMs on Kubernetes | — | ✓ | VM-level isolation, container UX |
| Fly.io | Firecracker | ✓ | — | Persistent storage + global networking |
| gVisor | Syscall interception | via Cloud Run | ✓ | Google's approach; used in GKE Sandbox |
Each provider has a playbook covering prerequisites, implementation guide, and e2e tests. The one-liner setups below store credentials in your keychain and make the config shareable with any collaborator or CI runner.
Docker (local — no credentials needed, start here) · playbook · e2e tests
vakt config set runtime.docker.image node:20-slim
vakt runtime set my-coder dockerE2B (Firecracker microVMs) · playbook · e2e tests
vakt secrets set E2B_API_KEY e2b_... # stored in keychain
vakt config set runtime.e2b.api_key secret:E2B_API_KEY
vakt runtime set my-coder e2bDaytona (containers, <200ms) · playbook · e2e tests
vakt secrets set DAYTONA_API_KEY dt_...
vakt config set runtime.daytona.api_url https://app.daytona.io/api
vakt config set runtime.daytona.api_key secret:DAYTONA_API_KEY
vakt runtime set my-coder daytonamicrosandbox (self-hosted libkrun) · playbook · e2e tests
msb daemon start # no API key needed
vakt config set runtime.microsandbox.api_url http://localhost:7681
vakt runtime set my-coder microsandboxKata Containers (MicroVMs on Kubernetes) · playbook · e2e tests
vakt config set runtime.kata.kubeconfig ~/.kube/config
vakt config set runtime.kata.namespace vakt-agents
vakt config set runtime.kata.runtime_class kata-qemu
vakt runtime set my-coder kataFly.io (Firecracker + global edge) · playbook · e2e tests
vakt secrets set FLY_API_TOKEN $(fly auth token)
vakt config set runtime.fly.api_token secret:FLY_API_TOKEN
vakt config set runtime.fly.app vakt-agent-sandbox
vakt runtime set my-coder flygVisor (syscall interception) · playbook · e2e tests
# Cloud Run variant
vakt secrets set GCP_SA_KEY "$(cat service-account.json | base64)"
vakt config set runtime.gvisor.backend cloud-run
vakt config set runtime.gvisor.project my-gcp-project
vakt runtime set my-coder gvisor
# Local runsc variant (Linux)
vakt config set runtime.gvisor.backend docker
vakt config set runtime.gvisor.runtime_class runsc
vakt runtime set my-coder gvisorCoder.com (persistent workspaces — repo + toolchain pre-installed by Terraform template) · playbook · e2e tests
vakt secrets set CODER_TOKEN "$(coder tokens create vakt --lifetime 8760h)"
vakt secrets set CODER_URL https://coder.example.com
vakt config set runtime.coder.url secret:CODER_URL
vakt config set runtime.coder.token secret:CODER_TOKEN
vakt config set runtime.coder.org default
vakt config set runtime.coder.template my-agent-template
vakt runtime set my-coder coderConfig is always shareable — secrets stay in your keychain, never in ~/.agents/:
# Teammate onboarding: they run their own vakt secrets set, then:
vakt sync # → all providers configured with their own credentialsvakt sync --with-proxy rewrites every provider config so all MCP traffic flows through vakt first. policy.json is evaluated on every tools/call before it reaches the server — with no changes to the server or the client.
vakt sync --with-proxy
# → provider configs now read: { "command": "vakt", "args": ["proxy", "github"] }
# → policy.json evaluated on every tool call, fail-closed by defaultEvery tool call is recorded in ~/.agents/audit.db (SQLite, zero dependencies) with server name, tool name, policy result, session ID, provider, and timing. Query it any time:
vakt audit show --server github --last 24h
vakt audit export --since 2025-01-01 | jq '[.[] | select(.policy_result == "deny")]'Pipe the export into any SIEM or log platform for compliance evidence. For teams requiring formal compliance:
- SOC 2 Type II — tool call logs satisfy CC6.8 (logical access) and CC7.2 (monitoring)
- ISO 27001 / A.12.4 — audit logging and monitoring controls
- Elastic SIEM — ingest
audit exportJSON via Filebeat - Splunk — ship via HEC or Splunk Connect for JSON
- Microsoft Sentinel — custom connector from audit export
vakt emits an OTLP trace span for every tool call (server name, tool name, policy result, session ID, latency). Point it at any OTLP-compatible backend:
vakt config set otel.endpoint http://localhost:4317 # any OTLP gRPC endpoint
vakt config set otel.enabled true| Backend | Type | Endpoint format |
|---|---|---|
| Grafana Tempo + Grafana Cloud | OSS / SaaS | https://tempo-prod-*.grafana.net:443 |
| Jaeger | OSS | http://localhost:4317 (OTLP gRPC) |
| Honeycomb | SaaS | https://api.honeycomb.io:443 |
| Datadog APM | SaaS | https://trace.agent.datadoghq.com |
| New Relic | SaaS | https://otlp.nr-data.net:4317 |
| SigNoz | OSS / SaaS | http://localhost:4317 |
| OpenObserve | OSS / SaaS | http://localhost:5081/api/default |
| Axiom | SaaS | https://api.axiom.co |
Traces are emitted lazily — the OTel SDK is never loaded if no endpoint is configured.
Most AI tools write your API keys directly into dotfiles like ~/.cursor/mcp.json. Those files get swept into iCloud, Dropbox, dotfile repos, and screenshots. vakt treats this as unacceptable by design.
~/.agents/mcp-config.json contains only named references — secret:GITHUB_TOKEN — never the values. Secrets are resolved from your OS keychain at sync time and exist in memory only. You can commit, share, or cat your entire ~/.agents/ directory with zero risk.
Every provider uses a different shape for the same data. Cursor wants mcpServers. OpenCode wants mcp with combined command arrays. Codex wants TOML. Gemini wants mcpServers but with different HTTP field names.
vakt defines a single canonical schema and translates to each provider's format at sync time. Adding a new provider is a JSON entry in providers.json — no code changes. The translation layer is data-driven and fully inspectable.
Skills, server definitions, and preferences live in ~/.agents/ in open formats — not locked inside any vendor's directory. vakt sync populates every installed tool instantly. vakt import-from-everywhere consolidates anything you've already built. Your setup is fully portable across CLIs, machines, and teammates.
| Problem | How vakt solves it |
|---|---|
| Built a great MCP server — only works in one tool | vakt sync instantly deploys it to every installed CLI |
| Spent hours perfecting a skill — not portable | Symlinked from ~/.agents/skills/ into every provider |
| New AI tool ships — start over from scratch | One sync command, full context, zero setup |
| MCP config scattered and duplicated across 6 tools | Single ~/.agents/mcp-config.json as source of truth |
| API keys in plaintext JSON files | Resolved from OS keychain at sync time, never persisted |
| Every tool uses a different config format | Canonical schema with per-provider translation layer |
| Config tied to a single machine | ~/.agents/ is safe to version-control and share — no secrets inside |
| Can't audit what credentials you've handed to AI tools | Every secret is a named reference — full visibility, zero exposure |
| No control over which tools MCP servers can invoke | Per-server tool policy with glob matching and fail-closed defaults |
Full threat model and responsible disclosure: SECURITY.md
- Zero plaintext secrets on disk.
~/.agents/mcp-config.jsonnever contains credential values — only named references (secret:MY_KEY). Resolved values exist in memory only, for the duration of a sync. - OS keychain by default. macOS Keychain on macOS,
pass(GPG-encrypted) on Linux. No custom encryption — the same store your browser and SSH agent trust. - Provider configs are not the source of truth. What vakt writes to
~/.cursor/mcp.jsonetc. is the resolved output for that tool's process. It can be regenerated at any time.~/.agents/is the only thing you need to protect — and it contains no secrets. - No secrets in shell profiles.
GITHUB_TOKEN=...in.bashrcis a credential leak. vakt's keychain backend bypasses shell profiles entirely. - Auditable by design.
cat ~/.agents/mcp-config.jsonand share it freely. Every credential is a named reference you can audit without exposing anything.
| Threat | vakt's defence |
|---|---|
| Dotfiles repo accidentally public | mcp-config.json is safe to commit — no secrets inside |
iCloud / Dropbox syncing ~/.cursor/ |
Credentials come from keychain at sync time, not stored long-term in provider dirs |
| Screenshot or screen share leaks config | Nothing sensitive in any file vakt owns |
| Compromised AI tool reads its own config | No credentials in ~/.agents/ — only opaque named references |
Shoulder surfing during vakt list |
List output never prints secret values |
{
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "secret:GITHUB_TOKEN"
}
},
"my-api": {
"transport": "http",
"url": "https://api.example.com/mcp",
"headers": {
"Authorization": "Bearer secret:MY_API_KEY"
}
}
}At sync time, secret:GITHUB_TOKEN is resolved from your keychain and written into the provider config. The reference string is what lives on disk, gets committed, and gets shared — never the value.
Backends: macOS Keychain · pass / GPG (Linux) · base64 env file (CI / fallback)
vakt secures the configuration layer — secrets never reach a config file. But MCP servers can still leak sensitive data at runtime: a tool response that echoes back an API key, a log line that includes a token, an agent that exfiltrates data through a seemingly innocuous output.
crust covers the runtime layer. It wraps any MCP server as a stdio proxy, intercepting JSON-RPC traffic in both directions and scanning it against 34 built-in DLP patterns before it reaches the agent or gets written to logs.
| Layer | Tool | What it protects |
|---|---|---|
| Configuration | vakt | Secrets never written to config files or ~/.agents/ |
| Runtime / logs | crust | Secrets and sensitive data scrubbed from MCP traffic and logs |
The AI tool ecosystem has no agreed config standard. Each provider invented its own:
| Provider | Format | Server key | HTTP field | stdio shape |
|---|---|---|---|---|
| Cursor | JSON | mcpServers |
url |
command + args |
| OpenCode | JSON | mcp |
url |
combined command array |
| Gemini | JSON | mcpServers |
httpUrl |
command + args |
| Codex | TOML | mcp_servers |
url |
command + args |
| Windsurf | JSON | mcpServers |
serverUrl |
command + args |
vakt's canonical schema maps cleanly to all of these. Adding a new provider is a JSON entry in providers.json — no code changes.
Path variables in mcp-config.json are also standardized:
{ "paths": { "code": "~/Projects", "vault": "~/Documents/vault" } }{ "command": "npx", "args": ["server-filesystem", "{{paths.code}}"] }vakt enforces per-server tool policy at sync time and (via the daemon proxy) at runtime. Policy lives in ~/.agents/policy.json:
{
"version": "1",
"default": "deny",
"registryPolicy": "warn-unverified",
"servers": {
"github": {
"tools": {
"allow": ["list_repos", "get_file", "create_issue"],
"deny": ["delete_repo"]
}
},
"*": {
"tools": { "deny": ["*exec*", "*shell*", "*eval*"] }
}
}
}Rules use glob matching (* as wildcard). Priority: specific deny > wildcard deny > specific allow > wildcard allow > default. Fail-closed by default.
registryPolicy options:
allow-unverified— sync any server (default)warn-unverified— warn on servers not in the MCP registryregistry-only— block sync if any server is unverified
vakt integrates with the official MCP registry. Search and install servers by registry ID — secrets are pre-wired automatically:
vakt search github
# io.github.modelcontextprotocol/server-github The official GitHub MCP server
# ...
vakt add-server gh io.github.modelcontextprotocol/server-github
# ✓ Added gh from registry
# Secrets needed:
# vakt secrets set GITHUB_PERSONAL_ACCESS_TOKEN <value>Registry-resolved servers store their registry and version fields in mcp-config.json, enabling policy enforcement and future upgrade detection.
Route completion requests between local and frontier LLMs based on request characteristics. Save costs by sending routine completions to local models while reserving frontier APIs for complex requests.
vakt route # Start router on default port 4000
vakt route --port 4001 # Custom port
vakt route --test --tokens 15000 # Test routing logicConfigure in ~/.agents/config.json:
{
"modelRouter": {
"port": 4000,
"backends": {
"local": { "url": "http://localhost:8000/v1", "maxCtx": 8192 },
"mistral": { "url": "https://api.mistral.ai/v1", "apiKey": "secret:MISTRAL_KEY", "maxCtx": 131072 }
},
"rules": [
{ "if": { "promptTokens": { "gt": 16000 } }, "use": "mistral" },
{ "if": { "toolCount": { "gt": 5 } }, "use": "mistral" },
{ "if": { "hasCode": true }, "use": ["mistral", "local"] },
{ "use": "local" }
]
}
}Point your AI tool at http://localhost:4000/v1 — vakt routes transparently.
# Just installed Windsurf for the first time
vakt sync
# → ~/.codeium/windsurf/mcp_config.json written with all your servers
# → ~/.codeium/windsurf/skills/ symlinked to your skills
# Done. Full context, zero setup.vakt import-from-everywhere
# Reads: ~/.cursor/mcp.json, ~/.gemini/settings.json, ~/.mcp.json,
# ~/.codex/config.toml, ~/.config/opencode/opencode.json ...
# Merges into ~/.agents/mcp-config.json (skips duplicates)# On your machine
cat ~/.agents/mcp-config.json # safe to share — no secrets
git push
# On their machine
git pull
vakt secrets set GITHUB_TOKEN ghp_... # they use their own keychain
vakt sync
# → identical MCP setup, their own credentialsvakt init Scaffold ~/.agents/, import existing configs
vakt import-from-everywhere Pull MCP servers and skills from all detected providers
vakt sync Write config to every installed provider
vakt sync --dry-run Preview what would be written
vakt search <query> Search the MCP registry
vakt add-server NAME REGISTRY-ID Add a server from the MCP registry
vakt add-server NAME CMD [ARGS] Register a stdio MCP server directly
vakt add-server NAME --http URL Register an HTTP MCP server
vakt add-skill ./path/to/skill Link a local skill directory
vakt add-skill https://... Clone and link a skill from git
vakt list Show servers, skills, and secrets
vakt list servers
vakt list skills
vakt list secrets
vakt secrets set KEY VALUE Store a secret in your OS keychain
vakt secrets get KEY Retrieve a secret
vakt secrets delete KEY Remove a secret
vakt secrets list List all stored secret keys (values never shown)
vakt config list Show current config
vakt config set paths.code ~/Projects
vakt config set otel.endpoint http://collector:4317
vakt audit show Show recent MCP tool call audit log
vakt audit show --server github Filter by server name
vakt audit show --last 24h Show last 24 hours (1h|24h|7d|4w)
vakt audit export Export audit log as JSON
vakt audit export --since <iso> Filter by date
vakt daemon start Start the background daemon
vakt daemon stop Stop the daemon
vakt daemon status Show daemon and server process status
| Provider | Config written | Skills |
|---|---|---|
| Claude Code | ~/.claude.json |
~/.claude/skills/ |
| Cursor | ~/.cursor/mcp.json |
~/.cursor/skills/ |
| Gemini CLI | ~/.gemini/settings.json |
native (~/.agents/skills/) |
| Codex | ~/.codex/config.toml |
~/.codex/skills/ |
| OpenCode | ~/.config/opencode/opencode.json |
~/.config/opencode/skills/ |
| Windsurf | ~/.codeium/windsurf/mcp_config.json |
~/.codeium/windsurf/skills/ |
| Mistral Vibe | ~/.vibe/config.toml |
~/.vibe/skills/ |
New provider? Add an entry to providers.json. No code changes required.
~/.agents/
├── config.json # paths, provider list, secrets backend, otel config
├── mcp-config.json # MCP server definitions (safe to commit — no secrets)
├── policy.json # tool allow/deny rules per server (optional)
├── audit.db # SQLite audit log of tool calls and sync events
├── AGENTS.md # shared agent preferences / persona
└── skills/
├── gh-cli/ # symlinked into every provider
├── sql-reviewer/
└── ...
Skills are SKILL.md files with YAML frontmatter — instructions that travel with the agent into any context. vakt symlinks ~/.agents/skills/ into every provider's skills directory.
---
name: sql-reviewer
description: Review SQL queries for performance and safety issues
---
When reviewing SQL, always check for...vakt add-skill https://github.com/vercel-labs/agent-skills react-best-practicesBrowse: skills.sh · Spec: agentskills.io
bun test tests/unit/ # fast unit tests (~30ms)
bats --recursive tests/ # full e2e suite (~2min)Tests run in a fully sandboxed HOME — nothing touches your real config files or keychain.
MIT