MCP server for creating and managing SOPS-encrypted secret files using age encryption.
- ✓Actively maintained (<30d)
- ✓Clear description
- ✓Topics declared
- ✓Documented (README)
- !Licence file present but not machine-readable
claude mcp add sops-mcp -- python -m -e{
"mcpServers": {
"sops-mcp": {
"command": "python",
"args": ["-m", "-e"],
"env": {
"SOPS_MCP_AGE_PUBLIC_KEY": "<sops_mcp_age_public_key>",
"SOPS_AGE_KEY": "<sops_age_key>"
}
}
}
}SOPS_MCP_AGE_PUBLIC_KEYSOPS_AGE_KEYMCP Servers overview
# sops-mcp
<!-- mcp-name: io.github.privacyplaybook/sops-mcp -->
MCP server for creating and managing [SOPS](https://github.com/getsops/sops)-encrypted secret files using [age](https://github.com/FiloSottile/age) encryption.
Designed for Claude Code (or any MCP client) to produce encrypted `secrets.enc.yaml` files without the model ever seeing plaintext values. All file content is passed as text parameters and returned as text — the server has no filesystem access to the client.
## Why
Two goals drive the design:
**1. Keep secrets in your source tree without leaking them.** For a small project, running a full secrets manager (Vault, AWS Secrets Manager, etc.) is overkill for a handful of credentials. Encrypting secrets at rest in git and decrypting them in your CI/CD pipeline at deploy time is much cheaper:
1. Create `secrets.enc.yaml` via this server — age-encrypted against your public key, safe to commit.
2. Commit it alongside your code.
3. Your CI/CD pipeline holds the age private key, decrypts at deploy time, and injects plaintext as environment variables into your container orchestrator.
The age private key lives in exactly one place: your CI/CD secrets store. Everywhere else — your laptop, your git remote, your container images — sees only ciphertext. See the [worked example](#example-integrating-with-a-cicd-deployment-pipeline) below.
**2. Let an AI coding agent generate secrets it can never read.** Claude (or any MCP client) can create passwords, rotate them, derive hashes, rename and delete them — but plaintext values never cross the MCP boundary back to the model.
The server holds the encryption key; the client only submits requests and receives metadata.
There is deliberately no "decrypt this one secret" tool.
This prevents a prompt injection or a misbehaving agent from exfiltrating a secret.
The simplest setup uses a single age recipient (the one CI private key). If you need several — a CI key plus an operator's key, or separate key sets for separate parties — see [Key domains](#key-domains).
## Secret sources
Every secret is one of three sources, recorded in `_meta_unencrypted`:
- **`generated`** — Cryptographically random values (Python `secrets` / OS CSPRNG). You specify length and charset; the server stores both so it can regenerate on rotation.
- **`external`** — User-provided values encrypted as-is (SMTP credentials, third-party API keys, etc.). Preserved across rotation. Updated via `sops_update_external`.
- **`derived`** — Computed from another key in the same file via a named transform. When the source is rotated (or an external source is updated), the derived value is automatically recomputed in topological order. Useful for things like Authelia's PBKDF2 hashes of OIDC client secrets.
## Design
Three ideas shape the tool surface:
1. **No plaintext crosses the MCP boundary.** Generated secret values are never returned to the client. There is deliberately no "decrypt this one key" tool. If you need plaintext, run `sops decrypt` yourself with the age private key.
2. **Metadata in plaintext.** A `_meta_unencrypted` block sits alongside the encrypted values (using SOPS's `unencrypted_suffix` feature) and records each secret's source, how it was generated, when it was last rotated, and which [key domain](#key-domains) it belongs to. This lets the server list and rotate secrets without decrypting. SOPS's MAC covers these values by default, so a tampered block fails to decrypt. Files that switch that off with `mac_only_encrypted` are refused. Tools that read the block *without* a key still cannot check the MAC, which is why recipients are verified separately.
3. **No in-place value update for `generated` or `derived` secrets.** Those change only via rotation, where neither the server nor the caller has access to the plaintext. `External` secrets (e.g. an upstream API key the user controls) can be updated with `sops_update_external`.
### Transforms (for `derived` secrets)
| Transform | Purpose | Deterministic |
|-----------|---------|---------------|
| `pbkdf2_sha512_authelia` | PBKDF2-SHA512 hash in Authelia's `configuration.yml` format (`$pbkdf2-sha512$310000$...`) | No — random salt per call |
| `sha256_hex` | Hex-encoded SHA-256 digest | Yes |
## Tools
### Creation and listing
| Tool | What it does |
|------|--------------|
| `sops_create_secrets` | Create a new encrypted file with one or more secrets (any mix of sources). |
| `sops_list_secrets` | List keys, sources, and descriptions from a file **without decrypting**. |
| `sops_create_oidc_secret` | Convenience: create an Authelia OIDC client secret as a `generated` + `derived` (`pbkdf2_sha512_authelia`) pair in one call. The hash is returned in the response for pasting into `configuration.yml`. |
| `sops_list_domains` | List the configured key domains, their age recipients, and whether each can decrypt or only encrypt. Never returns private key material. |
### Mutation (require a private key for the target domain)
| Tool | What it does |
|------|--------------|
| `sops_rotate_generated` | Regenerate all `generated` secrets. Derived secrets whose source was rotated are recomputed; others are preserved. External secrets are preserved. |
| `sops_add_secrets` | Add new secrets to an existing file. Supports all three sources. Rejects collisions with existing keys. |
| `sops_update_external` | Replace the value of an `external` secret. Cascades to any derived secrets that reference it. Rejects attempts to update `generated` or `derived`. |
| `sops_rename_secret` | Rename a key, preserving its value and metadata. Updates `from:` references in any derived secrets. |
| `sops_delete_secrets` | Remove one or more keys. Rejects deleting a secret that another derived secret still references (unless the dependent is deleted in the same call). |
| `sops_add_metadata` | Retrofit `_meta_unencrypted` onto a legacy SOPS file that lacks it. Supports `generated`, `external`, and `derived` entries. |
| `sops_rekey` | Re-encrypt a file onto its domain's current recipient list. Run this after a domain's recipients change, or to clear a "recipients do not match" refusal. Requires an explicit `domain`. Leaves a legacy file without a metadata block untouched in that respect, so `sops_add_metadata` still works on it. |
Every tool takes a `domain` argument except `sops_list_domains`, which needs none. It is optional everywhere but `sops_rekey`, where it is required because the call changes who can read the file. See [Key domains](#key-domains).
## Setup
### Prerequisites
- Python 3.11+
- [sops](https://github.com/getsops/sops) CLI binary
- An age keypair (see below)
### Generating an age keypair
If you don't already have one, install [age](https://github.com/FiloSottile/age) and run:
```bash
age-keygen -o age-key.txt
```
The file looks like:
```
# created: 2026-04-22T12:34:56Z
# public key: age1abc...xyz
AGE-SECRET-KEY-1HH...
```
- **Public key** (`age1...`) — pass to this server as `SOPS_MCP_AGE_PUBLIC_KEY`. Safe to share anywhere.
- **Private key** (`AGE-SECRET-KEY-...`) — store as a CI/CD secret (commonly named `SOPS_AGE_KEY`). Never commit to source control. Anyone with this key can decrypt every `secrets.enc.yaml` encrypted to the matching public key.
Back up the private key somewhere safe (password manager, hardware token). Losing it means losing access to every secret you've encrypted.
### Installation
```bash
git clone <repo-url>
cd sops-mcp
python3 -m venv .venv
.venv/bin/pip install -e .
```
### Claude Code configuration
Add to your project's `.mcp.json`:
```json
{
"mcpServers": {
"sops-mcp": {
"command": "/path/to/sops-mcp/.venv/bin/python",
"args": ["-m", "sops_mcp"],
"env": {
"SOPS_MCP_SOPS_BINARY": "/path/to/sops",
"SOPS_MCP_AGE_PUBLIC_KEY": "<your-age-public-key>"
}
}
}
}
```
### Environment variables
| Variable | Required | Purpose |
|----------|----------|---------|
| `SOPS_MCP_AGE_PUBLIC_KEY` | Yes* | Age public key for encryption |
| `SOPS_AGE_RECIPIENTS` | Yes* | Alternative to `SOPS_MCP_AGE_PUBLIC_KEY` |
| `SOPS_MCP_SOPS_BINARY` | No | Path to sops binary (default: `sops`) |
| `SOPS_MCP_LOG_LEVEL` | No | Log level (default: `WARNING`) |
| `SOPS_AGE_KEY` | Sometimes | Age private key for the `default` domain — required to mutate a file belonging to it. Named domains take their keys from the domains file instead. |
| `SOPS_MCP_DOMAINS_FILE` | No | Path to a YAML file defining named [key domains](#key-domains). Use when one server needs more than one recipient set. |
| `SOPS_MCP_DOMAINS` | No | The same domains document [inline](#defining-domains-without-a-file), as YAML or compact JSON. Public recipient sets only — a domain here may not carry `keys` or `key_file`. Merged with `SOPS_MCP_DOMAINS_FILE`. |
| `SOPS_MCP_REQUIRE_DOMAIN` | No | Set to `1` to require every tool call to name its domain. The file's recorded domain and the `default` fallback are both disabled. |
| `SOPS_MCP_TRANSPORT` | No | `stdio` (default) or `sse` |
| `SOPS_MCP_HOST` / `SOPS_MCP_PORT` | No | Bind host/port for SSE transport (default: `127.0.0.1:55090`). Binding to `0.0.0.0` requires `SOPS_MCP_API_TOKEN` — the server refuses to start otherwise. |
| `SOPS_MCP_ALLOWED_HOSTS` | No | Comma-separated allowlist for the SSE `Host` header (DNS rebinding protection). Default: `127.0.0.1,127.0.0.1:*,localhost,localhost:*`. Set explicitly when binding to a non-loopback address — e.g. `mcp.example.com,mcp.example.com:*`. |
| `SOPS_MCP_API_TOKEN` | Sometimes | Required when SSE transport binds to `0.0.0.0`; otherwise optional. When set, SSE requires `Authorization: Bearer <token>`. |
\* One of `SOPS_MCP_AGE_PUBLIC_KEY` or `SOPS_AGE_RECIPIENTS` must be set, unless `SOPS_MCP_DOMAINS_FILE` or `SOPS_MCP_DOMAINS` supplies at least one domain. The server refuses to start with no domains at all.
Both recipient variables accept a comma-separated list, and `SOPS_AGE_KEY` accepts several newline-separaWhat people ask about sops-mcp
What is privacyplaybook/sops-mcp?
+
privacyplaybook/sops-mcp is mcp servers for the Claude AI ecosystem. MCP server for creating and managing SOPS-encrypted secret files using age encryption. It has 0 GitHub stars and its last recorded update is dated 2026-09-10.
How do I install sops-mcp?
+
You can install sops-mcp by cloning the repository (https://github.com/privacyplaybook/sops-mcp) or following the README instructions on GitHub. ClaudeWave also provides quick install blocks on this page.
Is privacyplaybook/sops-mcp safe to use?
+
Our security agent has analyzed privacyplaybook/sops-mcp and assigned a Trust Score of 80/100 (tier: Trusted). See the full breakdown of passed checks and flags on this page.
Who maintains privacyplaybook/sops-mcp?
+
privacyplaybook/sops-mcp is maintained by privacyplaybook. The last recorded GitHub activity is dated 2026-09-10, with 2 open issues.
Are there alternatives to sops-mcp?
+
Yes. On ClaudeWave you can browse similar mcp servers at /categories/mcp, sorted by popularity or recent activity.
Deploy sops-mcp to your cloud
Ship this repo to production in minutes. Each platform spins up its own environment with editable env vars.
Maintain this repo? Add a badge to your README
Drop the badge into your GitHub README to show it's tracked on ClaudeWave. Each badge links back to this page and reflects the live Trust Score.
[](https://claudewave.com/repo/privacyplaybook-sops-mcp)<a href="https://claudewave.com/repo/privacyplaybook-sops-mcp"><img src="https://claudewave.com/api/badge/privacyplaybook-sops-mcp" alt="Featured on ClaudeWave: privacyplaybook/sops-mcp" width="320" height="64" /></a>More MCP Servers
Fair-code workflow automation platform with native AI capabilities. Combine visual building with custom code, self-host or cloud, 400+ integrations.
User-friendly AI Interface (Supports Ollama, OpenAI API, ...)
An open-source AI agent that brings the power of Gemini directly into your terminal.
Real-time global intelligence dashboard. AI-powered news aggregation, geopolitical monitoring, and infrastructure tracking in a unified situational awareness interface
The fastest path to AI-powered full stack observability, even for lean teams.
🕷️ An adaptive Web Scraping framework that handles everything from a single request to a full-scale crawl!