Skip to main content
Glama
X1pheR

bws-secret-delivery-mcp

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