mcp-warden
Health Warn
- License — License: Apache-2.0
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 5 GitHub stars
Code Warn
- process.env — Environment variable access in docs/demo/render.cjs
- fs module — File system access in docs/demo/render.cjs
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
Local-first credential broker for MCP. AI coding agents (Claude Code, Cursor, Codex…) call APIs, SSH and databases through scoped presets; secrets stay in your OS keychain and never enter the agent's context.
mcp-warden
A local-first credential broker for the Model Context Protocol — lets any AI coding agent (Claude Code, Cursor, Cline, Codex, Continue, Zed) take real actions without ever seeing your secrets.
The agent calls high-level tools (proxy_http); mcp-warden injects the secret server-side, executes the action, and returns only the result. Your context window stays clean. A leaked transcript leaks nothing useful.

How it works
- You store a credential once in the OS keychain via the
mcp-wardenCLI. - You define presets — named, scoped actions (e.g.
github-read,stripe-charges). - The AI agent connects to
mcp-wardenas an MCP server (stdio). - When the agent wants to act, it calls
proxy_httpwith a preset name and a path/method. mcp-wardenvalidates the request against the preset's allow-list, injects the credential, executes, and returns only the response.- Sensitive presets trigger an approval prompt: a native dialog on macOS and Windows, a desktop notification with Approve / Deny on Linux, or a Telegram message on any OS — where
/lockalso works as a kill switch from your phone. Opt in to time-boxed approvals per preset so one OK can cover a short burst of follow-up calls without re-prompt spam. - Every action lands in a local SQLite audit log — including a
windoweddecision for calls authorised by an active approval window, arefuseddecision (with the raw requested path or command) for calls the allow-list turned away, and afaileddecision when the approval prompt could not be shown.
The agent's context window contains zero secrets.
Install
cargo install mcp-warden # from crates.io
cargo install --path . # or from a clone
Pre-built binaries for macOS (x86_64, aarch64) and Linux (x86_64), each with a SHA-256 checksum, are attached to every GitHub release.
90-second quickstart
mcp-warden init
mcp-warden secret add gh_pat # paste your GitHub PAT when prompted
mcp-warden preset add github-read \
--type http \
--base-url https://api.github.com \
--auth-bearer gh_pat \
--allowed-methods GET \
--allowed-paths /user/repos \
--approval auto --confirm-auto
The preset auto-approves only GET /user/repos; any other path is refused before the token is touched. To let the agent read more, widen the allow-list (e.g. --allowed-paths '/user/repos,/repos/OWNER/[^/]+') and prefer a fine-grained PAT limited to specific repos. Patterns match the whole path, as exact strings or regexes anchored as ^(?:…)$, never as prefixes; [^/]+ is safer than .* for one segment. They see the percent-decoded path, so %2F is a separator: a GitLab-style id like /projects/group%2Fproject needs /projects/[^/]+/[^/]+. An HTTP preset with no allowed_paths allows every path under base_url — preset add and doctor warn about it, and the built-in github-read / gitea-read / stripe-read templates ship with approval = "prompt" for that reason.
Wire mcp-warden into your agent — see docs/claude-code-setup.md or docs/cursor-setup.md.
In your agent: ask "list my GitHub repos via mcp-warden". Watch the agent call list_presets, then proxy_http — and watch your PAT stay out of the prompt.
mcp-warden audit --last 5 # see what the agent did
Find and fix plaintext keys
Keys pasted into MCP client configs sit on disk in plaintext, where the agent, a synced dotfile or a screenshot can pick them up. Three commands fix that:
mcp-warden scan # where are plaintext keys? (shows type + length, never values)
mcp-warden import # move them into the keychain and rewire the client entries
mcp-warden run github # what the rewired entry runs: the server, with its key injected
scan reads Claude Code, Claude Desktop, Cursor, Windsurf, VS Code and Codex configs plus .mcp.json / .env files in the current directory (--path DIR for more). import handles keys in a stdio server's env; for keys in arguments, headers, URLs or .env files, scan says what to do instead. To wrap a server by hand: mcp-warden server add github --env GITHUB_PERSONAL_ACCESS_TOKEN -- npx -y @modelcontextprotocol/server-github (the value is read from stdin). Details: docs/scan-and-import.md.
CLI reference
| Command | Description |
|---|---|
mcp-warden init |
Create the config directory and a template config |
mcp-warden secret add <name> |
Store a secret (value read from stdin, never argv) |
mcp-warden secret list |
List secret names (never values) |
mcp-warden secret remove <name> |
Delete a secret from the keychain |
mcp-warden preset add <name> ... |
Define a preset (--confirm-auto required for auto) |
mcp-warden preset list |
Show all presets |
mcp-warden preset test <name> |
Validate a preset against the current keychain |
mcp-warden preset test <name> GET /path |
Policy simulator: would this call be allowed, and why? Sends nothing (--command, --get/--put, --query for other tools) |
mcp-warden preset grant <name> --for 1h |
Time-limited access: calls are refused after the window (--permanent clears it) |
mcp-warden preset revoke <name> |
End access to a preset now (next server start; lock for immediately) |
mcp-warden lock [--reason …] |
Kill switch: every MCP server refuses every call from its next one on (or send /lock to the Telegram bot) |
mcp-warden approval telegram-test |
Check the Telegram setup: token, chat, a test prompt you answer |
mcp-warden unlock |
Remove the lock after a human approves it in the dialog / Telegram |
mcp-warden scan |
Find plaintext keys in MCP client configs and .env files (never prints values) |
mcp-warden import |
Move the keys scan found into the keychain and point the client at mcp-warden run |
mcp-warden server add <name> --env VAR -- <cmd…> |
Wrap another MCP server; its key is read from stdin into the keychain |
mcp-warden run <name> |
Launch a wrapped MCP server with its keys injected (use as the client's command) |
mcp-warden preset scaffold <template> (v0.3.0) |
Generate a preset from a built-in template (--list to discover) |
mcp-warden preset from-openapi <src> (v0.3.0) |
Bootstrap a preset from an OpenAPI 3.x spec (URL or file) |
mcp-warden approval <preset> <policy> [--window N] |
Set approval policy; --window N (sec) enables time-boxed approvals for prompt |
mcp-warden audit [--last N | --since T] |
Read-only view of the audit log (append-only by design), with what the agent asked for — including refused calls — and which MCP client and session made each call |
mcp-warden doctor [--deep] (v0.3.0 for --deep) |
Diagnostic summary; --deep runs safe read-only HTTP probes per preset |
mcp-warden web [--port N] (v0.3.0) |
Local read-only web UI (binds 127.0.0.1 only) |
mcp-warden mcp |
Run the MCP server over stdio (consumed by the AI agent) |
MCP tools exposed
list_presets— read-only catalogue of available actions.proxy_http— execute an HTTP request through a preset; allow-list validation runs before credential injection.ssh_exec(v0.2.0) — run a shell command on an SSH host; command must match preset's anchored regex allow-list; key-based auth + host-key verification.sftp_transfer(v0.2.0) — get/put a file over SFTP; remote path matches preset'ssftp_allowed_paths, local path under$HOMEor/tmp.db_query(v0.2.0) — run a NAMED QUERY against PostgreSQL / MySQL / Redis. SQL/command is fixed at preset definition; agent supplies onlyquery_name+ positional params.get_secret_for_env— last-resort tool when a child process must read a secret fromenv. Always prompts for approval, regardless of preset policy.
Claude Code plugin
For Claude Code there is a plugin that bundles the MCP server config, a skill that teaches Claude to use mcp-warden correctly (discover presets first, never ask for tokens, respect approvals, treat get_secret_for_env as a last resort) and a guided /mcp-warden:setup command. Install the binary first, then in Claude Code:
/plugin marketplace add ermoluk/mcp-warden
/plugin install mcp-warden@mcp-warden
Restart Claude Code and run /mcp-warden:setup. If you previously added the server with claude mcp add, remove that entry so it isn't registered twice. Details: plugins/mcp-warden/.
Other MCP clients: see docs/claude-code-setup.md and docs/cursor-setup.md for manual setup.
What's new in v0.4.0
- Workspace split. The project is now two crates:
mcp-warden-core(library — trait surfaces + shared types) andmcp-warden(binary — CLI, MCP server, executor, web UI, concrete approval transports). Alternate implementations ofKeychainStore,ApprovalTransport,AuditSink, orPresetSourcenow depend only onmcp-warden-coreand don't drag the binary's GUI/runtime stack. - Telegram approval transport wired in. The impl already existed in v0.3.0; v0.4.0 makes it actually selectable. Add to your config file (
mcp-warden doctorprints its path:~/.config/mcp-warden/on Linux,~/Library/Application Support/io.mcp-warden.mcp-warden/on macOS):
then[approval.telegram] bot_token_secret = "tg_bot_token" # name of the keychain entry, not the token value chat_id = 123456789 # your numeric Telegram chat id timeout_seconds = 120 # optionalmcp-warden secret add tg_bot_token(pastes the bot token into the keychain).prompt-policy presets now send Approve / Deny inline-button messages to that chat; if the Telegram prompt times out, macOS asks again in the native dialog (seedocs/approval-transports.md). Override at runtime withMCP_WARDEN_APPROVAL_TRANSPORT={telegram|desktop|tui}.
What's new in v0.3.0
preset scaffold— 8 built-in templates for the most common APIs (github-read,github-write,notion-read,stripe-read,openai-chat,anthropic-messages,gitea-read,cloudflare-dns). Generate a working preset in one command instead of copy-pasting from the cookbook.preset from-openapi <url-or-file>— point it at an OpenAPI 3.0/3.1 JSON or YAML spec and it derivesbase_url,allowed_paths,allowed_methods, and the auth scheme (Bearer or APIKey-Header). Filter by HTTP method or tag.doctor --deep— for each HTTP preset, runs a safe read-only probe (GET against base URL or first literal allowed path), reports ok / auth_failed / auth_ok_404 / network_error / tls_error / skipped, with elapsed time and a redacted body snippet. Skipsprompt-policy presets unless--include-promptis passed.web— a small local-only web UI (binds127.0.0.1, hardcoded) for browsing presets and the audit log without spawning a TUI. Read-only in this release; approval over WebSocket is on the roadmap.- Telegram approval transport — new
ApprovalTransportimpl that sends approval prompts to a Telegram chat with inline Approve / Deny / Approve-for-window buttons. Bot token lives in the OS keychain. Foreign-chat callbacks are rejected. Message usesprotect_content: true. Selected automatically when[approval.telegram]is configured.
See docs/web-ui.md for the web UI design notes.
Security model
mcp-warden's threat model and security invariants live in docs/threat-model.md. At a glance:
- Secrets in the OS keychain only — never on disk in plaintext, never in logs.
- A credential the upstream echoes back in a
proxy_httpresponse (an echo endpoint, an error that quotes the token) is replaced with[redacted by mcp-warden]before the agent sees it. The rest of the response is untrusted upstream data and is passed through as is. - Allow-list validation runs before credential injection.
- Default approval policy is
prompt;autorequires--confirm-autoat preset creation. - The MCP server loads and validates
config.tomlonce at startup and never re-reads it: restart the server after editing presets. - Audit log is append-only — the trait surface itself contains no
delete/clearmethod. mcp-warden lockstops every running server at its next call; a server that has seen the lock stays locked until it restarts, andunlockneeds a human to approve it.- Presets can cap and trim what
proxy_httphands the agent (max_response_bytes,response_fields); a credential the upstream echoes back is always scrubbed. - Wrapped servers (
mcp-warden run) only get keys stored for them (server/<name>/<VAR>), never a preset's secret, and their command is fixed in the config. The key is still visible to the process it is handed to. - Stdout is the MCP protocol channel; all logs go to stderr.
Links
- crates.io:
mcp-warden - MCP Registry name:
mcp-name: io.github.ermoluk/mcp-warden
License
Apache-2.0 — permanent commitment for the OSS core. See LICENSE.
mcp-warden stays fully functional for free, forever — see our open-core commitment.
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found