bws-secret-delivery-mcp
by X1pheR
README.md
# bws-secret-delivery-mcp
A deliberately narrow Model Context Protocol server for [Bitwarden Secrets Manager](https://bitwarden.com/products/secrets-manager/). It uses Bitwarden's official `bws` CLI to resolve secrets and deliver them directly to approved local files **without returning secret values to the MCP client or model**.
This community project is not affiliated with or endorsed by Bitwarden, Inc.
## Why this exists
General-purpose Bitwarden/BWS MCP servers are useful when an agent genuinely needs to see or manage secrets. Infrastructure automation often needs a different boundary: the agent should be able to select a credential profile and say *which secret goes to which approved consumer file* without receiving the secret itself.
`bws-secret-delivery-mcp` intentionally does less:
- metadata-only project and secret discovery;
- one or more named Machine Account credential profiles;
- optional expected-project scope checks per profile;
- direct secret-to-file delivery;
- complete raw env-file generation from selected secrets;
- per-profile output-directory allowlists;
- no plaintext secret retrieval;
- no arbitrary command execution;
- no creation, editing or deletion of Bitwarden secrets or projects.
## Tool surface
| Tool | Behavior |
|---|---|
| `bws_status` | Verify configuration and authenticated BSM access for one or all profiles. |
| `bws_project_list` | List project metadata for one or all profiles. |
| `bws_secret_list` | List secret metadata; values and notes are omitted. |
| `bws_secret_write_file` | Atomically write one secret value to an approved file. |
| `bws_secret_write_raw_env_file` | Atomically write a complete raw `KEY=value` env file from selected secrets. |
There is intentionally no `get_secret`, `run`, `create`, `edit`, or `delete` tool.
Every tool accepts an optional `profile`. Read tools query every configured profile when it is omitted. Write tools require an explicit profile whenever more than one profile is configured.
## Requirements
- Python 3.12+
- Bitwarden Secrets Manager CLI (`bws`)
- Dedicated Bitwarden Machine Account access tokens with only the required project access
- An MCP client that supports stdio servers
Install `bws` using Bitwarden's official installation instructions.
## Configuration
### Named profiles
For central administrative control planes, set `BWS_PROFILES_FILE` to an absolute path containing non-secret JSON configuration. Machine Account tokens remain in separate private files.
```json
{
"profiles": {
"app-a": {
"access_token_file": "/run/secrets/app-a-bws-token",
"server_url": "https://vault.bitwarden.eu",
"expected_project_names": ["App A Runtime"],
"allowed_output_directories": ["/srv/app-a/.secrets"]
},
"metadata-only": {
"access_token_file": "/run/secrets/metadata-bws-token",
"server_url": "https://vault.bitwarden.eu",
"expected_project_names": ["Metadata Runtime"]
}
}
}
```
Profile fields:
| Field | Required | Meaning |
|---|---:|---|
| `access_token_file` | yes | Absolute private file containing one Machine Account token. No group/other permissions are allowed. |
| `server_url` | no | Explicit Bitwarden server URL for EU or self-hosted deployments. |
| `expected_project_names` | no | Exact accessible project-name set expected for this credential. Secret operations fail closed on a mismatch. |
| `allowed_output_directories` | no | Existing local directories that this profile may write below. Omit for metadata-only/read-only delivery behavior. |
| `default_file_mode` | no | Octal mode for newly created files, otherwise the global default. |
Global environment variables:
| Variable | Required | Default | Meaning |
|---|---:|---|---|
| `BWS_PROFILES_FILE` | for multi-profile mode | - | Absolute non-symlink JSON profile configuration file. |
| `BWS_BIN` | no | `bws` | Path or command name for the official `bws` executable. |
| `BWS_TIMEOUT_SECONDS` | no | `30` | CLI timeout, maximum 120 seconds. |
| `BWS_DEFAULT_FILE_MODE` | no | `0600` | Mode for new output files. Execute/world permissions are rejected. Existing file mode and ownership are preserved. |
Example MCP registration:
```json
{
"mcpServers": {
"bws-secret-delivery": {
"command": "uv",
"args": [
"run",
"--frozen",
"--directory",
"/opt/bws-secret-delivery-mcp",
"bws-secret-delivery-mcp"
],
"env": {
"BWS_PROFILES_FILE": "/etc/bws-secret-delivery/profiles.json",
"BWS_BIN": "/usr/local/bin/bws"
}
}
}
}
```
Keep output roots narrow. A dedicated consumer secret directory is safer than allowing an entire application or stack tree.
### Legacy single-profile mode
Version 0.2 retains the original environment-only configuration for simple deployments:
| Variable | Required | Default | Meaning |
|---|---:|---|---|
| `BWS_ACCESS_TOKEN_FILE` | yes | - | Private Machine Account token file. |
| `BWS_ALLOWED_OUTPUT_DIRS` | yes | - | `os.pathsep`-separated existing output roots. |
| `BWS_SERVER_URL` | no | Bitwarden default | Server URL for the single profile. |
Do not set `BWS_ACCESS_TOKEN_FILE` together with `BWS_PROFILES_FILE`.
## Project-scope guard
If `expected_project_names` is configured, secret discovery and delivery first verify that the Machine Account can see **exactly** that project-name set. This makes accidental future privilege expansion fail closed instead of silently broadening the MCP's authority.
`bws_project_list` remains available to inspect accessible project metadata when diagnosing a scope mismatch.
## Raw env files
`bws_secret_write_raw_env_file` writes one complete file in deterministic key order. Values are emitted exactly after `KEY=`. Values containing NUL, LF or CR are rejected because the format is intentionally line-oriented.
For Docker Compose 2.30+ use the file with `format: raw` so Compose does not interpret `$`, quotes or other characters in secret values:
```yaml
env_file:
- path: ./.secrets/runtime.env
format: raw
```
Example tool input in multi-profile mode:
```json
{
"profile": "app-a",
"target_path": "/srv/app-a/.secrets/runtime.env",
"secrets": {
"DATABASE_PASSWORD": "database-password",
"API_TOKEN": "2e71f52b-0000-0000-0000-000000000000"
}
}
```
Each mapping value is either an exact Bitwarden secret key or a secret UUID. If a key is ambiguous within a profile, supply `project_id` or use a UUID.
## File-safety behavior
- Token files and the profiles file must be regular non-symlink files.
- Token files must grant no group or other permissions.
- `target_path` must be absolute.
- Its resolved parent must remain inside the selected profile's configured output roots.
- Existing symlinks and non-regular target files are rejected.
- Existing targets with permissions for `other` are rejected.
- Writes use a temporary file in the same directory, `fsync`, atomic `os.replace()`, then a parent-directory `fsync`.
- Existing mode and ownership are preserved when possible.
- MCP responses contain profile, target and secret metadata, never secret values or value lengths.
- Provider stdout from secret operations and provider stderr from failures are never echoed into MCP errors.
## Development
```bash
python -m venv .venv
. .venv/bin/activate
pip install -e '.[test]'
pytest
```
## Scope
This server is for **interactive/operator-driven secret delivery and bounded central administration**. It is not intended to replace declarative secret renderers, deployment orchestration, Kubernetes secret operators, or a full Bitwarden administration client.
## License
MIT
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessSyncing