loading…
Search for a command to run...
loading…
MCP server for Vaultwarden/Bitwarden vault management. Enables AI agents to securely create, search, read, and update vault items via the official Bitwarden CLI
MCP server for Vaultwarden/Bitwarden vault management. Enables AI agents to securely create, search, read, and update vault items via the official Bitwarden CLI, with safe-by-default redaction and support for both stdio and SSE transports.
Programmatic Vaultwarden/Bitwarden vault management over MCP (Model Context Protocol), backed by the official Bitwarden CLI (bw).
This project exists to let agents and automation create/search/read/update/move vault items without re-implementing Bitwarden’s client-side crypto.
Published package: @icoretech/warden-mcp
POST /sse + health check at GET /healthzGET /metricsznull unless reveal: truewarden-mcp is not only useful for vault administration. A very practical use case is pairing it with an LLM that can also drive a browser.
That lets the agent do end-to-end authenticated workflows such as:
keychain_get_totp for TOTP-based MFAIn practice, this is what makes the server useful for full automation, not just secret lookup. The same MCP session that gives the model browser control can also give it scoped access to the credentials and MFA material needed to finish the workflow.
This package shells out to the official Bitwarden CLI, bw.
Runtime resolution order:
BW_BIN if you set it explicitly@bitwarden/cli optional dependency if it is presentbw from PATHThat means package installation can succeed even when the optional dependency is skipped by the environment. In that case you must install bw separately or point BW_BIN to it.
Explicit fallback install:
npm install -g @bitwarden/cli
Or run with an explicit binary path:
BW_BIN=/absolute/path/to/bw npx -y @icoretech/warden-mcp@latest
warden-mcp intentionally bundles a vetted @bitwarden/cli version instead of
blindly following the newest upstream CLI on every release. New bw releases
can change login and unlock behavior in ways that break automation, so bw
upgrades should be smoke-tested against real Vaultwarden flows before bumping
the bundled version. Official Bitwarden compatibility is intended, but it is
not continuously proven in CI without a real Bitwarden tenant.
This repository's compose smoke now exercises both direct bw auth flows and
the MCP/SDK layers with username/password auth plus user API-key auth against a
real local Vaultwarden, so @bitwarden/cli bumps do not rely on unit coverage
alone. The bundled postinstall compatibility shim rewrites the affected
build/bw.js login strategies in place so the same Vaultwarden fallback can
survive compatible CLI bumps without a version-stamped patch artifact.
--stdio when you want a local MCP host to spawn warden-mcp directly with one fixed Bitwarden profilewarden-mcp service to serve multiple clients or multiple Bitwarden profiles via per-request X-BW-* headersnpx -y @icoretech/warden-mcp@latest --stdio
For stdio mode, you must provide Bitwarden credentials up front via env vars:
BW_HOST=https://vaultwarden.example.com \
[email protected] \
BW_PASSWORD='your-master-password' \
npx -y @icoretech/warden-mcp@latest --stdio
API key login works too:
BW_HOST=https://vaultwarden.example.com \
BW_CLIENTID=user.xxxxx \
BW_CLIENTSECRET=xxxxx \
BW_PASSWORD='your-master-password' \
npx -y @icoretech/warden-mcp@latest --stdio
Start one long-lived MCP server:
npx -y @icoretech/warden-mcp@latest
Verify it is up:
curl -fsS http://localhost:3005/healthz
This mode is what makes warden-mcp different from a simple local wrapper:
X-BW-* headersdocker run --rm -p 3005:3005 ghcr.io/icoretech/warden-mcp:latest
npm install -g @icoretech/warden-mcp@latest
warden-mcp
For local MCP hosts, stdio is the most portable option.
npx -y @icoretech/warden-mcp@latest --stdio
The examples below use Bitwarden API-key auth. If you prefer username/password login, replace BW_CLIENTID + BW_CLIENTSECRET with BW_USER.
These hosts let you register warden-mcp directly from the command line:
# Codex
codex mcp add warden \
--env BW_HOST=https://vaultwarden.example.com \
--env BW_CLIENTID=user.xxxxx \
--env BW_CLIENTSECRET=xxxxx \
--env BW_PASSWORD='your-master-password' \
-- npx -y @icoretech/warden-mcp@latest --stdio
# Claude Code
claude mcp add-json warden '{"command":"npx","args":["-y","@icoretech/warden-mcp@latest","--stdio"],"env":{"BW_HOST":"https://vaultwarden.example.com","BW_CLIENTID":"user.xxxxx","BW_CLIENTSECRET":"xxxxx","BW_PASSWORD":"your-master-password"}}'
# Qwen Code
qwen mcp add warden \
-e BW_HOST=https://vaultwarden.example.com \
-e BW_CLIENTID=user.xxxxx \
-e BW_CLIENTSECRET=xxxxx \
-e BW_PASSWORD=your-master-password \
npx -y @icoretech/warden-mcp@latest --stdio
These hosts all use the same stdio payload shape. Only the config file location changes:
~/.codex/config.toml~/.cursor/mcp.json or .cursor/mcp.json~/Library/Application Support/Claude/claude_desktop_config.json~/.qwen/settings.json or .qwen/settings.jsonShared JSON shape:
{
"mcpServers": {
"warden": {
"command": "npx",
"args": ["-y", "@icoretech/warden-mcp@latest", "--stdio"],
"env": {
"BW_HOST": "https://vaultwarden.example.com",
"BW_CLIENTID": "user.xxxxx",
"BW_CLIENTSECRET": "xxxxx",
"BW_PASSWORD": "your-master-password"
}
}
}
}
Codex uses TOML instead of JSON:
[mcp_servers.warden]
command = "npx"
args = ["-y", "@icoretech/warden-mcp@latest", "--stdio"]
startup_timeout_sec = 30
[mcp_servers.warden.env]
BW_HOST = "https://vaultwarden.example.com"
BW_CLIENTID = "user.xxxxx"
BW_CLIENTSECRET = "xxxxx"
BW_PASSWORD = "your-master-password"
startup_timeout_sec = 30 is a practical Codex default when using npx,
because a cold first launch can spend several seconds downloading and unpacking
the package before MCP initialization begins.
Windsurf uses the same stdio idea but stores it in ~/.codeium/windsurf/mcp_config.json:
{
"mcpServers": {
"warden": {
"command": "npx",
"args": ["-y", "@icoretech/warden-mcp@latest", "--stdio"],
"env": {
"BW_HOST": "https://vaultwarden.example.com",
"BW_CLIENTID": "user.xxxxx",
"BW_CLIENTSECRET": "xxxxx",
"BW_PASSWORD": "your-master-password"
}
}
}
}
If your MCP host supports Streamable HTTP with custom headers, you can connect to one long-lived warden-mcp service instead of spawning a local stdio process.
Start the shared server:
npx -y @icoretech/warden-mcp@latest
Every MCP request must include:
X-BW-HostX-BW-PasswordX-BW-ClientId + X-BW-ClientSecret, or X-BW-UserExample health check:
curl -fsS \
-H 'X-BW-Host: https://vaultwarden.example.com' \
-H 'X-BW-ClientId: user.xxxxx' \
-H 'X-BW-ClientSecret: xxxxx' \
-H 'X-BW-Password: your-master-password' \
http://localhost:3005/healthz
Example MCP endpoint:
http://localhost:3005/sse?v=2
This shared-server mode is useful when:
bw-backed subprocessclaude.ai web, similar hosts)Some web MCP hosts can connect to a Streamable HTTP / SSE endpoint but cannot
attach custom X-BW-* headers. In that case, run warden-mcp as a
single-tenant shared server with fixed server-side credentials and explicit env
fallback enabled:
BW_HOST=https://vaultwarden.example.com \
BW_CLIENTID=user.xxxxx \
BW_CLIENTSECRET=xxxxx \
BW_PASSWORD='your-master-password' \
KEYCHAIN_ALLOW_ENV_FALLBACK=true \
npx -y @icoretech/warden-mcp@latest
Some hosted or browser-based MCP clients expect the server URL to be available
over HTTPS rather than plain http://localhost:3005/....
If you are running warden-mcp locally, that usually means putting one of
these in front of it:
warden-mcpThen point the client at the HTTPS endpoint, for example:
https://warden-mcp.example.com/sse?v=2
This works because headerless HTTP requests can inherit the server's own
BW_* configuration when KEYCHAIN_ALLOW_ENV_FALLBACK=true. You can use
BW_USER + BW_PASSWORD here as well; the example above shows the API-key
variant because it is the default convention used elsewhere in this README.
Important limits:
X-BW-* headers, those headers still take priority over the server envClient examples for shared HTTP mode:
# Claude Code
claude mcp add-json warden '{"type":"http","url":"http://localhost:3005/sse?v=2","headers":{"X-BW-Host":"https://vaultwarden.example.com","X-BW-ClientId":"user.xxxxx","X-BW-ClientSecret":"xxxxx","X-BW-Password":"your-master-password"}}'
// Cursor (~/.cursor/mcp.json)
{
"mcpServers": {
"warden": {
"url": "http://localhost:3005/sse?v=2",
"headers": {
"X-BW-Host": "https://vaultwarden.example.com",
"X-BW-ClientId": "user.xxxxx",
"X-BW-ClientSecret": "xxxxx",
"X-BW-Password": "your-master-password"
}
}
}
}
// Qwen Code (~/.qwen/settings.json)
{
"mcpServers": {
"warden": {
"httpUrl": "http://localhost:3005/sse?v=2",
"headers": {
"X-BW-Host": "https://vaultwarden.example.com",
"X-BW-ClientId": "user.xxxxx",
"X-BW-ClientSecret": "xxxxx",
"X-BW-Password": "your-master-password"
}
}
}
}
// Windsurf (~/.codeium/windsurf/mcp_config.json)
{
"mcpServers": {
"warden": {
"serverUrl": "http://localhost:3005/sse?v=2",
"headers": {
"X-BW-Host": "https://vaultwarden.example.com",
"X-BW-ClientId": "user.xxxxx",
"X-BW-ClientSecret": "xxxxx",
"X-BW-Password": "your-master-password"
}
}
}
}
Codex currently fits better with stdio here, because its MCP config supports a bearer token env var for remote servers but not arbitrary custom X-BW-* header injection.
bw --version
If that fails after install, your environment likely skipped the optional @bitwarden/cli dependency. Install it explicitly:
npm install -g @bitwarden/cli
The server executes bw commands on your behalf:
KEYCHAIN_ALLOW_ENV_FALLBACK=true to enable it for single-tenant HTTP deployments.BW_* env vars at startup.bw state under KEYCHAIN_BW_HOME_ROOT and pins BITWARDENCLI_APPDATA_DIR inside that profile so the Bitwarden CLI keeps a stable local device/app identity across restarts instead of looking like a fresh client every time.bw sync (internal; not exposed as an MCP tool).Timeout handling is also process-tree aware: if a bw command hangs, warden-mcp kills the full spawned process group rather than only the direct parent process. That prevents timed-out auth attempts from leaving orphaned bw/shell child processes behind.
X-BW-Host (must be an HTTPS origin, for example https://vaultwarden.example.com)X-BW-Password (master password; required to unlock)X-BW-ClientId + X-BW-ClientSecret (API key login), orX-BW-User (email for user/pass login; still uses X-BW-Password)X-BW-Unlock-Interval (seconds)There is no built-in auth layer in v1. Run it only on a trusted network boundary (localhost, private subnet, VPN, etc.).
Credential resolution:
X-BW-* headers on every request by default. Without them, tools return an error.BW_* env vars at startup (single-tenant).KEYCHAIN_ALLOW_ENV_FALLBACK=true. Security warning: this means any client that can reach the HTTP endpoint gets full vault access without providing credentials. Only use this behind network-level access control.Mutation control:
READONLY=true to block all write operations (create/edit/delete/move/restore/attachments).NOREVEAL=true to force all reveal parameters to false server-side. Clients can still request reveal: true, but the server will silently downgrade to redacted output. This prevents prompt injection from tricking an LLM agent into exfiltrating secrets.KEYCHAIN_TEXT_COMPAT_MODE=structured_json to mirror any structured OK tool result into TextContent as serialized JSON. This is useful for text-only MCP clients that ignore structuredContent, but it also duplicates revealed secrets into the plain-text transcript.keychain_*. Override TOOL_PREFIX to change the namespace and TOOL_SEPARATOR to change the separator (default _, set . for legacy clients).KEYCHAIN_SESSION_MAX_COUNT (default 32)KEYCHAIN_SESSION_TTL_MS (default 900000)KEYCHAIN_SESSION_SWEEP_INTERVAL_MS (default 60000)KEYCHAIN_MAX_HEAP_USED_MB (default 1536, set 0 to disable memory fuse)KEYCHAIN_METRICS_LOG_INTERVAL_MS (default 0, disabled)NOREVEAL / KEYCHAIN_NOREVEAL (default false; force all reveals to false)KEYCHAIN_ALLOW_ENV_FALLBACK (default false; HTTP env-var credential fallback)KEYCHAIN_TEXT_COMPAT_MODE (default unset; set to structured_json to copy structured OK results into TextContent)Redaction defaults (item reads):
password, totpnumber, codessn, passportNumber, licenseNumbertype: 1)attachments[].url (signed download URL token)passwordHistory[].passwordReveal rules:
reveal: true where applicable (default is false).get_password, get_totp, get_notes, generate, get_password_history) return structuredContent.result = { kind, value, revealed }.reveal is omitted/false, value is null (or historic passwords are null) and revealed: false.If you run warden-mcp beyond local development, review these items:
TLS everywhere. Always terminate TLS in front of the HTTP endpoint. X-BW-* headers carry master passwords in cleartext — without TLS they are visible to anyone on the network.
Network isolation. Bind the server to 127.0.0.1 or place it behind an authenticated reverse proxy. The service has no built-in authentication; anyone who can reach /sse can issue vault operations.
Do not enable KEYCHAIN_ALLOW_ENV_FALLBACK on shared networks. This flag makes the server's own vault credentials available to any HTTP client that omits headers. Only use it in single-tenant setups where the network is fully trusted.
Enable READONLY=true when writes are not needed. This blocks all mutating tools at the MCP layer, limiting blast radius if an agent or client is compromised.
Restrict filesystem access to /data/bw-profiles. The bw CLI stores decrypted state under its HOME directory. Ensure the profile directory is not world-readable and is mounted with appropriate permissions (the Docker image runs as non-root by default).
Disable debug logging in production. KEYCHAIN_DEBUG_BW and KEYCHAIN_DEBUG_HTTP emit request details and CLI invocations to stdout. Debug logs may include session metadata and request structure. Keep them off unless actively troubleshooting.
Set NOREVEAL=true when secrets should never leave the server. This forces all reveal parameters to false server-side, regardless of what the client requests. Use this when the MCP host is an LLM agent that could be influenced by prompt injection — it prevents tricked agents from exfiltrating passwords or TOTP codes.
Monitor /metricsz. The endpoint is intentionally unauthenticated (for scraper compatibility) but exposes session counts, heap usage, and rejection counters. If this data is sensitive in your environment, restrict access at the network level.
Only enable KEYCHAIN_TEXT_COMPAT_MODE=structured_json for text-only clients you trust. It improves compatibility with clients that ignore structuredContent, but any revealed secret will also appear in plain-text TextContent, making transcript leakage easier.
Run the published package in HTTP mode and verify the server is up:
npx -y @icoretech/warden-mcp@latest
curl -fsS http://localhost:3005/healthz
Starts a local Vaultwarden + HTTPS proxy (for bw), bootstraps a test user, and runs the MCP server.
cp .env.example .env
make up
curl -fsS http://localhost:3005/healthz
Run integration tests:
make test
make test now runs both compose-backed auth paths and verifies them at the
raw CLI plus MCP/SDK layers:
.env.testtmp/vaultwarden-bootstrap/apikey.env, generated by the bootstrap step and kept out of git via tmp/The compose bootstrap step depends on the Playwright Docker image matching the
playwright npm package version. If either moves, update the other in the same
change.
Run session flood regression locally (guardrail sanity):
npm run test:session-regression
npm install
cp .env.example .env
npm run dev
Vault/session:
keychain_statuskeychain_sync (pull latest vault data from server via bw sync)keychain_sdk_version (returns the Bitwarden SDK version used by the CLI)keychain_encode (base64-encode a string via bw encode)keychain_generate (returns a generated secret only when reveal: true)Items:
keychain_search_items, keychain_get_item, keychain_update_itemkeychain_create_login, keychain_create_note, keychain_create_card, keychain_create_identity, keychain_create_ssh_keykeychain_delete_item, keychain_restore_itemFolders:
keychain_list_folders, keychain_create_folder, keychain_edit_folder, keychain_delete_folderOrgs/collections:
keychain_list_organizations, keychain_list_collectionskeychain_list_org_collections, keychain_create_org_collection, keychain_edit_org_collection, keychain_delete_org_collectionkeychain_move_item_to_organizationAttachments:
keychain_create_attachment, keychain_delete_attachment, keychain_get_attachmentSends:
keychain_send_list, keychain_send_template, keychain_send_getkeychain_send_create (quick create via bw send)keychain_send_create_encoded, keychain_send_edit (advanced create/edit via bw send create|edit)keychain_send_remove_password, keychain_send_deletekeychain_receiveDirect “bw get …” helpers:
keychain_get_username (returns { kind:"username", value, revealed:true })keychain_get_password / keychain_get_totp / keychain_get_notes (only return real values when reveal: true)keychain_get_uri, keychain_get_exposedkeychain_get_folder, keychain_get_collection, keychain_get_organization, keychain_get_org_collectionkeychain_get_password_history (only returns historic passwords when reveal: true)bw list items --search (and thus keychain_search_items) does not reliably search inside custom field values.bw supports native SSH key item creation).See AGENTS.md for repo guidelines, dev commands, and testing conventions.
Добавь это в claude_desktop_config.json и перезапусти Claude Desktop.
{
"mcpServers": {
"warden-mcp-server": {
"command": "npx",
"args": []
}
}
}