onelink-vault
Provides an MCP secret gateway over Bitwarden cloud using the official bw CLI. It enables per-agent vault access with tools to sync, list, retrieve items/passwords, create items, update fields, rotate stored passwords, and perform one-way secret handoffs between registered agents. It also supports a human intake link for pasting secrets or screenshots with local OCR and saving them into an agent's Bitwarden organization collection.
Provides the same MCP secret gateway capabilities for self-hosted Vaultwarden as a Bitwarden-compatible vault backend. Agents can use per-agent credentials and collections to sync, list, retrieve, create, update, rotate, and hand off secrets, with support for Vaultwarden TLS URLs and attachment access from the gateway container.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@onelink-vaultcreate a secret handoff link for the staging API key"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
onelink-vault

A self-hosted, multi-agent secret gateway for Bitwarden cloud and Vaultwarden.
It wraps the official bw CLI in a Streamable HTTP MCP service: per-agent bearer
keys, separate vault accounts and CLI homes, opt-in cross-vault delegation,
SQLite audit metadata, and a short-lived human-to-vault intake link with local
screenshot OCR.
Why this exists
Bitwarden's official MCP server already exists. Use it when you want the official integration for a vault. onelink-vault is not a replacement or an official Bitwarden product: its focus is a shared, multi-agent / fleet-scoped gateway and human handoff. Each agent gets its own identity and Bitwarden permissions; a human can paste a secret or a screenshot into a one-time link rather than an agent chat. OCR proposes text, the human reviews and confirms, then the text and original image are saved into the requesting agent's own organization collection.
Related MCP server: VaultBridge
Five-minute local setup
Prerequisites: Docker with Compose, a Bitwarden-compatible account with a master
password and personal API key, and (for collection-backed writes/intake) an
organization collection named exactly after your agent, e.g. agent-a. Create
accounts and grant collection permissions using your server's normal UI. The
service does not register accounts or modify organization membership.
Account and organization preparation may take longer than five minutes.
In the web vault, create the account and organization, create an agent-a
collection, and grant that account access to it. The personal API key is
under Settings → Security → Keys → View API key (confirm the master password);
use its client_id and client_secret, not an organization API key.
For self-hosted Vaultwarden, use a TLS URL reachable from inside the gateway
container, not the host's 127.0.0.1. Set Vaultwarden's DOMAIN to its canonical
HTTPS URL: the CLI also follows server-generated attachment URLs, so successful
login alone does not prove attachment downloads can reach the server. Use a
trusted certificate. For an isolated test with a private CA, mount its public
CA certificate into the gateway and add NODE_EXTRA_CA_CERTS: /certs/ca.pem to
the Compose service environment; setting an extra variable in .env alone does
not pass it through. Do not disable TLS verification. The web vault also requires
a secure browser origin; a private-network plain-HTTP hostname is not sufficient.
cp .env.example .env
# Edit .env: set ADMIN_KEY to a locally generated random value.
# VW_URL defaults to US Bitwarden cloud; EU uses https://vault.bitwarden.eu.
# For Vaultwarden, set VW_URL=https://vault.example.com.
docker compose up -d --build
curl --fail http://127.0.0.1:8000/healthGenerate a key locally with python -c 'import secrets; print(secrets.token_urlsafe(32))'.
Do not commit .env, caller credentials, state volumes, or generated keys.
The image runs as UID/GID 1000 and exposes port 8000 only on loopback.
Register an agent without putting secrets in command arguments or shell history:
python scripts/add_caller.pyRun the helper on your host (Python 3, no extra dependencies).
The helper does not load .env: if you changed PORT, run it with that same
port, for example PORT=18000 python scripts/add_caller.py, and use that port
for the health check too. Leave ADMIN_KEY in .env; enter it privately at the
helper's prompt rather than exporting or placing it on the command line.
It prompts privately for the admin key, account API client ID, API client secret,
and master password; asks for an agent name and write permission; then registers
the caller over loopback. Its generated bearer token is shown once for storage
in the agent's private configuration. This grants no additional Bitwarden
permissions: provision least-privilege collection access in Bitwarden first.
Enable the human intake link
Provide a trusted HTTPS reverse proxy for this service (same origin for the form and
/intake/*API). Configure certificates and routing yourself; onelink-vault does not provision Internet exposure or tunnels.Set
INTAKE_PUBLIC_BASE_URL=https://vault-gateway.example.comin.envand recreate the service withdocker compose up -d.Keep
/admin/*restricted to local operator access; expose only/mcpand the exact/intake/,/intake/session,/intake/submit,/intake/confirm,/intake/statusroutes as needed. Do not log request bodies or auth headers.The agent calls
vault.intake_create(title="Service credential")and sends the returned URL privately to the human. Default TTL is 30 minutes; allowed range is 60–3600 seconds. Do not put secrets in the title/idempotency key.The human opens the link, pastes text or a PNG/JPEG/WebP screenshot (up to 5 MiB), reviews local OCR, corrects it, and confirms. The screenshot is stored as an attachment; account/organization attachment entitlement is required.
Poll
vault.intake_status(request_id=...). Onlysavedmeans a successful independent text/scope/attachment readback. Failed/uncertain writes can be retried with the same frozen content and request, without creating duplicates.
The browser needs HTTPS for the secure session cookie. Local HTTP MCP works without intake; intake itself deliberately rejects non-HTTPS origins.
MCP clients
Endpoint: https://vault-gateway.example.com/mcp (Streamable HTTP, not legacy
SSE). Header: Authorization: Bearer <agent-token>. Give each client its own
key. These are generic configuration examples; none of these four client
applications were exercised end-to-end for this release. Protect config files
and do not place literal keys in version-controlled project files.
Claude Code
.mcp.json (or your private user-level MCP configuration):
{
"mcpServers": {
"my-agent-vault": {
"type": "http",
"url": "https://vault-gateway.example.com/mcp",
"headers": {"Authorization": "Bearer ${VAULT_GATEWAY_TOKEN}"}
}
}
}Set VAULT_GATEWAY_TOKEN in the client environment. See
Claude Code MCP docs; environment expansion
and policy availability depend on your installed client version.
Codex CLI
In ~/.codex/config.toml:
[mcp_servers.my_agent_vault]
url = "https://vault-gateway.example.com/mcp"
bearer_token_env_var = "VAULT_GATEWAY_TOKEN"Export the token in Codex's environment. The url and bearer_token_env_var
fields were checked against Codex MCP documentation.
OpenClaw
Unverified, version-dependent: OpenClaw integrations may use a separate MCP
bridge such as mcporter; a universal native openclaw.json schema could not be
verified. Do not copy a Claude-style block into OpenClaw's main config blindly.
For an installation already using mcporter, this is a proposed private
config/mcporter.json entry; verify URL/header syntax with that installed
bridge's documentation before use:
{
"mcpServers": {
"my-agent-vault": {
"baseUrl": "https://vault-gateway.example.com/mcp",
"headers": {"Authorization": "Bearer <agent-token>"}
}
}
}This is a bridge configuration, not a claim of native OpenClaw support.
Hermes Agent
In ~/.hermes/config.yaml:
mcp_servers:
my_agent_vault:
url: https://vault-gateway.example.com/mcp
headers:
Authorization: "Bearer ${VAULT_GATEWAY_TOKEN}"
timeout: 120
connect_timeout: 30Set the token in Hermes's private environment and restart. URL/header syntax and runtime environment substitution were checked against the Hermes MCP documentation.
Tools and operator endpoints
vault.sync,vault.list,vault.get_item,vault.get_password.vault.create_item,vault.update_field,vault.rotate_passwordrequirewrite_ok. Rotation changes the stored vault password only; it does not update the upstream service's credentials.vault.give: create-only one-way handoff to another registered agent; requires write permission. This is an explicit exception to ordinary own-vault access: any write-enabled caller may create a new item in another registered caller's accessible collection. It does not grant read/update access to that caller.vault.intake_create,vault.intake_statusare registered only when intake is enabled; status returns references, not submitted secrets.Admin endpoints:
POST /admin/add_caller,/admin/rotate,/admin/remove_caller,/admin/deliver;GET /admin/callers. All requireX-Admin-Key. Admin delivery can update an existing same-name own-collection item. Removing a caller revokes the gateway key, not its Bitwarden account.
Collection selection for normal creation: explicitly requested collection, exact
caller-named collection, otherwise the only non-shared collection; if no
collection can be selected it may create a personal-vault item. Intake is
stricter: it requires one unambiguous caller-named organization collection
(compatibility aliases <caller>-legacy and a stripped -hermes suffix are
also recognized). Avoid aliases for new deployments. Collection permissions
come from the underlying Bitwarden account, not from name filtering alone.
Configuration
Variable | Default | Purpose |
|
| CLI server URL; EU/self-hosted supported |
| empty | Empty disables admin endpoints |
|
| Compose loopback host port |
|
| Private caller credential store |
|
| Per-caller CLI config/cache |
|
| SQLite invocation metadata |
| empty | Fixed HTTPS origin; empty disables intake |
|
| Encrypted temporary intake state + key |
| empty | Comma-separated delegated reader names |
| empty | Delegated writers; must be a subset of readers |
|
| Positive finite CLI deadline |
|
| Positive finite child cleanup deadline |
Compose fixes state paths in the volume; edit its environment mapping if you
need different paths. Native Python deployments can override every path.
The caller store maps each name to token, write_ok, bw_clientid,
bw_clientsecret, and bw_password. Legacy token-only entries are read-only
and cannot use vault operations. Do not reuse tokens or accounts across agents.
Security model and limits
Trusted gateway: the service holds plaintext API credentials and master passwords in its caller store and unlocked session keys in memory. Bitwarden encryption does not protect secrets from this gateway or its host operator. Treat the state volume, process environment, backups, and host as sensitive.
Per-agent isolation is enforced by bearer identity, separate CLI homes and separate Bitwarden accounts. It is not a sandbox. An account can read all items Bitwarden grants it; a collection argument is not an authorization rule.
Cross-vault read/write is off by default and operator-configured only. Never grant it to an untrusted agent. The admin key is effectively root here.
Secrets explicitly retrieved via MCP become visible to the agent/client and potentially its transcript. The intake path avoids initial chat submission; it does not prevent later authorized retrieval.
Intake URLs are bearer capabilities, not human identity verification. Anyone with the link can open it; the current browser session can be replaced by a later opener before completion. Send links privately. Confirmed saves are one-time; retries only reconcile the same content and vault record.
Intake has same-origin checks, session cookies, CSRF, CSP, request-size limits, local rate limits, TTL, and encrypted pending payloads. The encryption key is on the same volume: this protects against casual inspection, not host theft. Payloads are cleared on saved/expired state; metadata is retained seven days. SQLite deletion is not guaranteed forensic erasure on every filesystem.
Screenshot staging uses Linux
/dev/shm, anonymous RAM files, and verified attachment readback. Host swap/core dumps may still capture plaintext. OCR is local Tesseract (English image installed); no third-party OCR API is used.Run one worker and one service replica per state directory. Locks, rate limits and recovery are process-local. Do not scale with shared SQLite state.
Audit records tool names, caller, item names/IDs, status, timing, cross-vault target and sanitized errors, not intentionally submitted secret values. Names and IDs can still be sensitive: do not embed secrets in item titles. Intake uses its own request-state metadata, not the general invocation table.
Place authentication-aware TLS and access controls in front of remote use. Do not log request bodies, capability fragments, headers, or form contents. There is no formal security audit, multi-tenant hardening guarantee, or SLA.
Development and verification
Python 3.12 is the container baseline. On Linux:
python -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt
python -m pytestUnit tests use mocked CLI/vault I/O and temporary local state, with no account or
network required. A local Tesseract test is skipped if the binary is absent.
tests/browser_paste_check.py is an optional offline Playwright browser check;
install Playwright + Chromium separately to run it. It is not part of pytest.
A throwaway Vaultwarden integration run verified the Docker build, real account
API-key login/unlock, collection-scoped create/read/list, unauthenticated MCP
rejection, and browser-confirmed text/screenshot intake over HTTPS. Both intake
requests reached saved; independent CLI download matched all original PNG
bytes, and image text was recognized by Tesseract inside the image. A failed
attachment download caused by an incorrect test-server DOMAIN was retried
successfully without duplicate records after fixing the server URL.
This is not a Bitwarden cloud, third-party MCP-client, or production TLS-proxy
certification; those still require operator integration testing.
Dependency minimum ranges are not a reproducible lockfile; pin and review them
for your deployment.
Author
Built by Niyaz Irsaliev for his own fleet of AI agents. The story behind it: https://irsaliev.com/writing/an-army-of-bots-and-one-vault/
License
Apache License 2.0, copyright 2026 Niyaz Irsaliev. See NOTICE.
This server cannot be deployed
Related MCP Connectors
A secret store for AI agents: the agent never sees the plaintext.
Zero-secret MCP gateway for AI agents: risk-scored, audited calls with human-in-the-loop approval.
Encrypted secret store and rotation for autonomous agent credentials
Secrets for developers and agents—secure context and workflows without exposing secret values.
Related MCP Servers
- FlicenseBqualityDmaintenanceEnables secure retrieval of credentials from 1Password vaults for use by AI agents, supporting integration with browser automation for automated login.1-
- AlicenseNot gradedqualityDmaintenanceSecret management MCP server for AI coding agents that prevents secrets from entering the LLM context window by returning metadata only and using side-channel injection. Integrates with Bitwarden and offers hooks for auto-capture and leak prevention.1MIT
- AlicenseDqualityDmaintenanceEnables secure Bitwarden organization vault control with AI agents, featuring human-in-the-loop approval, ACID-compliant transactions, and redacted access to keep secrets hidden from LLMs.10MIT
- AlicenseNot gradedqualityAmaintenanceEnables AI agents to securely retrieve passwords, API keys, and other items from a Vaultwarden/Bitwarden vault using MCP tools with fuzzy search and caching.1MIT