Skip to main content
Glama
privacyplaybook

sops-mcp

sops-mcp

MCP server for creating and managing SOPS-encrypted secret files using 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 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.

Related MCP server: Janee

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 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.

Setup

Prerequisites

  • Python 3.11+

  • sops CLI binary

  • An age keypair (see below)

Generating an age keypair

If you don't already have one, install age and run:

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

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:

{
  "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. Use when one server needs more than one recipient set.

SOPS_MCP_DOMAINS

No

The same domains document inline, 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-separated keys. Together they form the default domain.

Key domains

A domain is a named set of age recipients plus, optionally, the private keys the server holds for them. It is the unit the server encrypts to and decrypts with.

You do not need to configure one. The environment variables above become a domain called default, and a single-recipient setup never has to mention domains again.

Why they exist

Every mutation tool decrypts, changes, and re-encrypts. Re-encryption uses the server's configured recipients — so if a file was encrypted to a CI key and an operator key, but the server only knows about CI, the operator used to be dropped from the file silently. Domains fix that by making the recipient set a named, checked thing:

  • A mutation compares the file's actual recipients against the domain's. If they differ it refuses, rather than quietly changing who can read the file.

  • One server process can hold several independent key sets.

  • sops_rekey performs a deliberate recipient change, which is the sops updatekeys workflow.

Several recipients, one domain

The common case needs no domains file. List every recipient in the usual variable:

SOPS_MCP_AGE_PUBLIC_KEY="age1ci...,age1operator..."
SOPS_AGE_KEY="AGE-SECRET-KEY-1CI..."

Files are now encrypted to both, and mutations keep both. The server only needs whichever private key it will actually decrypt with.

Several domains

Point SOPS_MCP_DOMAINS_FILE at a YAML file:

version: 1
domains:
  homelab:
    recipients:
      - age1ci...          # CI runner
      - age1operator...    # operator laptop
    keys:
      - AGE-SECRET-KEY-1CI...
  client-acme:
    recipients:
      - age1acme...
    key_file: /run/secrets/acme.agekey   # an age keys file
  archive:
    recipients:
      - age1archive...
    # no keys: this domain can encrypt but never decrypt

What version: means

version: is the schema version of this configuration document — which fields a domain may carry and how they are laid out. It is not a version of the keys, the recipients, or anything you rotate. Rotating a domain's recipients does not change it. It stays 1 until a release changes the document format itself.

It is optional; omit it and 1 is assumed. If present it must match exactly, so a version: 2 document is refused by a server that only understands 1 rather than being half-read. That is the point of the field: a domain with an unrecognised field is rejected, so without a version check an older server would blame one field name when the real problem is that the whole document is newer than it is.

Note that the version inside a secrets file's _meta_unencrypted block is a different number, versioning the metadata schema in that file. The two are unrelated and both happen to be 1.

The domains file must not be writable by group or other, and must be owned by the user the server runs as or by root. That check applies whether or not it holds key material, because the file decides which recipients everything is encrypted to.

A file holding private keys — the domains file with inline keys:, or any key_file — must additionally not be world-readable. Group-readable is allowed with a warning, so a container secret mounted root-owned and readable by the runtime group works. In the published image the server runs as uid 65532, so a Docker or compose secret needs a uid:/gid:/mode: that lets that user read it; mode: 0640 with a matching group is the usual answer.

Startup checks that every private key parses, and warns about any whose public half is not in its domain's recipient list. That is a warning rather than an error because it is the normal state mid recipient-rotation: the old key still opens files encrypted before the change, and sops_rekey is how those get migrated.

Defining domains without a file

A domains file is the only place private keys may live, because it is the only one that can be permission-checked. That is a poor fit for a domain that has no private key at all — a recipient set you encrypt to, where someone else holds the key. Deployments where writing a file is awkward (a distroless image with no shell, an orchestrator with no inline-file primitive) then have to mount a volume to deliver two lines of public key material.

SOPS_MCP_DOMAINS takes the same document inline:

SOPS_MCP_DOMAINS='{"version":1,"domains":{"archive":{"recipients":["age1archive..."]}}}'

YAML works too; JSON is simply the form that survives environments where a multi-line value is inconvenient, and it parses because YAML is a superset of JSON.

version here is the document schema version, not a key version — the same field as in the file form.

A domain defined here may not set keys or key_file and the server refuses to start if one does. An environment variable is visible to docker inspect and /proc/<pid>/environ, and carries none of the ownership and mode checks a file gets, so it is the wrong place for a private key.

The two sources are merged, so a deployment can keep its key-holding domains in a file and its public ones inline. A domain name defined by more than one source is fatal rather than silently resolved — the same rule that already applies when a file defines default alongside the v1 environment variables.

Which domain does a tool use?

In order: the domain argument, then the domain recorded in the file's _meta_unencrypted block, then default. Set SOPS_MCP_REQUIRE_DOMAIN=1 to remove the last step and force every call to be explicit.

The recorded name is a hint. The check is the recipient list in the file's own SOPS envelope, which is why a mislabelled file is refused rather than re-keyed.

Recipient rotation

Adding or removing a recipient is a two-step operation:

  1. Edit the domain's recipient list and restart the server.

  2. Run sops_rekey on each affected file, naming the domain.

Until a file is rekeyed, mutations on it are refused with a message pointing here. sops_list_secrets reports the mismatch too, and needs no private key to do it.

sops_rekey will not move a file between domains. A file that records a domain can only be rekeyed onto that same domain's current recipient list; naming a different one is refused. This matters when two domains share a private key, where decryption would otherwise succeed and quietly drop the recipients the other domain does not have. Moving secrets between domains is deliberately manual: decrypt with the sops CLI, then sops_create_secrets into the new domain.

Files this server will not touch

SOPS can protect a file in ways this server cannot reproduce, because it always re-encrypts to a flat age recipient list:

  • Another master key alongside age (pgp, kms, gcp_kms, azure_kv, hc_vault). Re-encrypting would drop that holder.

  • Shamir key groups (key_groups with a shamir_threshold). Re-encrypting would flatten an n-of-m threshold into a list any single holder could open.

  • mac_only_encrypted. It leaves the plaintext metadata block unauthenticated, so a file's recorded domain and each secret's source could be rewritten by anyone who can edit the file.

Both are silent downgrades, so every mutation refuses these files and sops_list_secrets flags them. Manage them with the sops CLI.

Running mixed versions

A file this version writes stays readable and writable by sops-mcp 0.10.1 and earlier. The older version does not know about the domain field, so it drops it the next time it mutates the file, leaving a file with no recorded domain. That resolves to default here, and the recipient check still protects it either way. Re-record the domain by passing domain explicitly on any later call, or with sops_rekey.

What domains are not

Domains separate key sets, not callers. On the SSE transport a single API token reaches every domain the server holds. If you need two parties that must not read each other's secrets, run a server process each.

Security

  • No filesystem access — The server never reads from or writes to the client filesystem. All content is passed as text parameters and returned as text.

  • No private key for normal use — Encryption uses only the public key. The private key is needed only for mutation tools.

  • No plaintext in responses — Generated secret values are never returned. Derived values are returned because they're meant to be pasted into config files (e.g. an Authelia PBKDF2 hash) — don't derive values you don't intend to publish.

  • Secure temp files — Used only for sops CLI invocation. Created with 0600 permissions, overwritten with zeros before deletion, cleanup guaranteed by finally block.

  • OS-level entropy — Secret generation uses Python's secrets module (backed by /dev/urandom).

  • stdio transport by default — No network exposure; runs as a client child process.

  • Input validation — Key names must match ^[A-Z][A-Z0-9_]*$.

Encrypted file format

DB_PASSWORD: ENC[AES256_GCM,data:...,tag:...,type:str]
SMTP_USER: ENC[AES256_GCM,data:...,tag:...,type:str]
DB_PASSWORD_HASH: ENC[AES256_GCM,data:...,tag:...,type:str]
_meta_unencrypted:
    version: 1
    domain: default
    secrets:
        DB_PASSWORD:
            source: generated
            description: Database password
            generation:
                length: 32
                charset: alphanumeric
            last_rotated: "2026-04-21T15:30:00Z"
        SMTP_USER:
            source: external
            description: SMTP username
        DB_PASSWORD_HASH:
            source: derived
            derivation:
                transform: sha256_hex
                from: DB_PASSWORD
            last_rotated: "2026-04-21T15:30:00Z"
sops:
    age:
        - recipient: age1...
          enc: |
            -----BEGIN AGE ENCRYPTED FILE-----
            ...
    unencrypted_suffix: _unencrypted

Secret values are AES-256-GCM encrypted. The _meta_unencrypted block is stored in plaintext (using SOPS's unencrypted_suffix feature) so metadata is readable without decryption.

Every sops call this server makes is pinned to an empty --config, so a .sops.yaml in the directory the server was started from cannot change how files are encrypted. Recipients come from the domain, and nothing else.

The sops: block above is abridged. A real file also carries lastmodified, a mac, a version, and empty lists for the master-key types this server does not use (pgp, kms, gcp_kms, azure_kv, hc_vault). The MAC covers unencrypted values too, so an edited _meta_unencrypted block fails to decrypt. If any of those other key lists is non-empty, this server refuses to mutate the file — it encrypts to age alone and would otherwise drop that key holder silently.

Do not reshape the output

The encrypted content this server returns must be committed exactly as it comes back. SOPS binds each value's AES-GCM tag to that value's path in the document, so moving a value to a different key — nesting a root-level DB_PASSWORD under stringData: to build a Kubernetes Secret manifest, say — invalidates the tag even though the ENC[...] string itself is untouched:

$ sops decrypt secrets.enc.yaml
Error decrypting tree: Error walking tree: Could not decrypt value: Could not decrypt with AES_GCM: cipher: message authentication failed

The value is unrecoverable at that point. Editing the plaintext parts by hand fails the same way, for a different reason: the MAC covers unencrypted values, so an edited _meta_unencrypted block gives MAC mismatch. Reindenting, reordering keys or reflowing the YAML is safe — SOPS parses the document, so only key paths and values matter.

If you need secrets in some other document shape, build that shape from the flat file at deploy time (Kustomize's secretGenerator, helm-secrets, sops-secrets-operator) rather than reshaping the encrypted file.

Why these tools and not others

Per-key read (decrypt-one-secret): intentionally absent. Returning plaintext over the MCP boundary would give the model access to secret material during tool calls — an accidental exfiltration vector. If you need a plaintext value, run sops decrypt yourself with the age private key.

Per-key in-place update for generated/derived secrets: intentionally absent. sops_rotate_generated is the one path that changes those values, so rotations leave an audit trail (last_rotated timestamp) and cascade cleanly to derived secrets.

.sops.yaml creation rules: out of scope. The server has no view of your filesystem, so path-based creation rules cannot apply. Recipients come from key domains instead, and sops_rekey covers the updatekeys workflow.

Tenant isolation on the SSE transport: not yet. Domains separate key sets, not callers. A single SOPS_MCP_API_TOKEN grants every domain the server holds, so do not put two mutually distrusting parties behind one SSE server. Run one process each.

Other source types (imported, templated): out of scope. Those are orchestration concerns — fetch values from Vault or compose URLs in your deployment templating layer, then pass the result here as an external secret.

Supply chain integrity

The Docker build is hardened with three layers of verification, enforced by a CI gate.

Base image digest pinning

The Dockerfile builds on cgr.dev/chainguard/python (Wolfi), pinned by SHA-256 digest (@sha256:...) so Docker always pulls the exact image that was audited rather than whatever the tag currently points to. Two stages are pinned separately: :latest-dev for the build stage, which has apk, a shell and a build toolchain, and :latest for the runtime stage, which is distroless — no shell, no package manager. Both digests and their cosign signature status are tracked in base-images.lock.json.

Update the base image (when upstream publishes security patches):

pip install requests  # one-time; cosign on PATH is needed for the signature checks
python3 lib/pin_base_images.py

Signed binaries from Wolfi

The sops and age binaries are installed with apk from Wolfi's package repository, whose index is signed by Chainguard. They are pinned transitively through the base image digest, so re-pinning the base image also re-pins these binaries.

Python dependency hash pinning

Runtime dependencies are installed from requirements.lock.txt with pip install --require-hashes, which rejects any package whose content doesn't match the recorded SHA-256 hashes. This prevents dependency hijacking and typosquatting.

Update dependencies after editing requirements.in:

pip install pip-tools  # one-time
lib/compile_requirements.sh

CI verification

The supply-chain.yml workflow runs lib/verify_requirements.py, lib/verify_base_images.py and lib/verify_version.py, then audits the lockfile for known advisories. It runs on every pull request to main, and on pushes to main that touch the Dockerfile, a lockfile, pyproject.toml, server.json or lib/. It checks that lockfiles are well-formed, that every Dockerfile FROM line is digest-pinned, that server.json matches the version in pyproject.toml, and that no pinned dependency has a published vulnerability.

The audit is the same gate the publish workflow runs, so a lockfile that would block a release now blocks the pull request instead. Because it queries live advisory data, an unrelated pull request can start failing when a new advisory lands against a pinned dependency. That is the intended trade-off; the fix is to refresh the lockfile.

Note that pip-compile keeps existing pins unless told otherwise, so regenerating after an advisory needs the upgrade flag:

lib/compile_requirements.sh --upgrade

Verifying a published release

Every container image published to GHCR is signed by this repo's publish workflow using keyless cosign via sigstore, and ships with SLSA v1.0 build provenance and an SPDX SBOM attached as OCI artifacts. Every commit on main and every release tag is SSH-signed. You can verify all of this without holding any long-lived key material — the signatures are anchored in sigstore's public transparency log.

Install cosign: https://docs.sigstore.dev/system_config/installation

Verify the image signature. A successful verification proves the image was built by this repository's publish.yml workflow, not just that its digest matches a reference someone sent you.

cosign verify \
  --certificate-identity-regexp 'https://github.com/privacyplaybook/sops-mcp/\.github/workflows/publish\.yml@.*' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  ghcr.io/privacyplaybook/sops-mcp:<tag>

Inspect the attestations.

# List all artifacts attached to the image
cosign tree ghcr.io/privacyplaybook/sops-mcp:<tag>

# Download the SBOM (SPDX JSON)
cosign download sbom ghcr.io/privacyplaybook/sops-mcp:<tag>

# Verify the SLSA build provenance
cosign verify-attestation --type slsaprovenance1 \
  --certificate-identity-regexp 'https://github.com/privacyplaybook/sops-mcp/\.github/workflows/publish\.yml@.*' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  ghcr.io/privacyplaybook/sops-mcp:<tag>

Verify git tags and commits. The simplest check is the green Verified badge on the github.com commits and tags pages.

Development

python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
pytest tests/ -v  # unit tests plus end-to-end sops round-trips
ruff check src/ tests/

Example: integrating with a CI/CD deployment pipeline

A common pattern: use this server to produce secrets.enc.yaml files committed to your infrastructure repo, then decrypt them in CI and inject the plaintext values as environment variables to a container orchestrator (Portainer, Kubernetes, Nomad).

  1. Generate an age keypair once. Give the private key to your CI as a secret (e.g. SOPS_AGE_KEY), the public key to Claude Code as SOPS_MCP_AGE_PUBLIC_KEY.

  2. Ask the model to produce a secrets.enc.yaml with sops_create_secrets.

  3. Commit the encrypted file.

  4. In your deploy workflow, run sops decrypt secrets.enc.yaml > .env (or equivalent) and pass the result to your orchestrator.

  5. When secrets need rotating, ask the model to run sops_rotate_generated or sops_update_external; commit and redeploy.

The _meta_unencrypted block lets your tools filter out the metadata (keys starting with _) when pushing values to an orchestrator, so metadata never leaks into environment variables.

License

Apache-2.0. See LICENSE.

Available Tools

11 tools
sops_add_metadataA

Add _meta_unencrypted metadata to an existing SOPS-encrypted file that lacks it. Decrypts the file, adds metadata, and re-encrypts preserving original plaintext values. Requires a private key for the target domain.

ParametersJSON Schema
NameRequiredDescriptionDefault
domainNoName of the key domain to encrypt to / decrypt with. Optional. Defaults to the domain recorded in the file's _meta_unencrypted block, and failing that to 'default'. Call sops_list_domains to see what this server has configured.
secret_metadataYesMapping of key names to metadata. Each value has 'source' ('generated', 'external', or 'derived') and optional 'description'. For 'derived', also provide 'transform' and 'from'.
encrypted_contentYesContents of a secrets.enc.yaml file

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full load and does well: it discloses the decrypt–modify–re-encrypt pipeline, that original plaintext values are preserved, and that a private key for the target domain is required. It omits edge-case behavior such as what happens when the file already contains metadata, keeping it just below a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three tight sentences: purpose first, mechanics second, prerequisite last. No redundancy and every sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a mutation tool with no annotations and no output schema, the description covers the operation, preservation guarantee, and key requirement. It would be fully complete with a note on what is returned (re-encrypted content) and behavior when metadata already exists.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents domain, secret_metadata, and encrypted_content in detail, including enum values and nested structure. The description adds only the private-key requirement for the domain parameter, which is useful but marginal; baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (add _meta_unencrypted metadata) and resource (existing SOPS-encrypted file), with a clear precondition ('that lacks it') and the transformation performed. This distinguishes it from siblings like sops_add_secrets and sops_update_external, which operate on different content.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The phrase 'existing SOPS-encrypted file that lacks it' implies the situation in which this tool applies, and the private-key requirement gives a prerequisite. However, no alternative tool is named and no explicit when-not guidance (e.g., what to use if metadata already exists) is provided.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sops_add_secretsA

Add new secrets to an existing SOPS-encrypted file. Decrypts the file, merges in new secrets, and re-encrypts — preserving all existing values and metadata. Rejects keys that already exist in the file. Supports generated, external, and derived sources. Requires a private key for the target domain.

ParametersJSON Schema
NameRequiredDescriptionDefault
domainNoName of the key domain to encrypt to / decrypt with. Optional. Defaults to the domain recorded in the file's _meta_unencrypted block, and failing that to 'default'. Call sops_list_domains to see what this server has configured.
secretsYesNew secrets to add
encrypted_contentYesContents of an existing secrets.enc.yaml file

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does well: it discloses the decrypt/merge/re-encrypt cycle, that existing values and metadata are preserved, that duplicate keys are rejected, and that a private key is required for auth. It omits atomicity/partial-failure behavior and does not say what the call returns, which matters for a mutation with no output schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four short sentences, front-loaded with the action and effect, then constraints and auth. No filler, and each sentence adds a distinct fact (effect, preservation guarantee, rejection rule, key requirement).

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 3-parameter mutation tool with no annotations and no output schema, the description covers purpose, side effects, preservation semantics, and auth requirements. The main remaining gap is the return value — an agent chaining this into a later call has to guess whether new encrypted content comes back.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description adds meaning beyond it by summarizing the three source modes (generated/external/derived) at the top level and by stating the private-key requirement for the domain parameter, which the schema's domain text does not mention. It does not explain the nested secret fields, but those are fully documented in the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Add new secrets to an existing SOPS-encrypted file') and immediately enumerates the mechanism (decrypt, merge, re-encrypt). The word 'existing' and the 'rejects keys that already exist' clause distinguish it from the sibling sops_create_secrets without needing either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives clear preconditions (file must exist, keys must be new, a private key for the target domain is required) and points at sops_list_domains for domain discovery. It never names the sibling an agent should use instead for keys that already exist (e.g. sops_update_external), so routing guidance is implied rather than explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sops_create_oidc_secretA

Convenience tool: create an Authelia-compatible OIDC client secret. Generates KEY_NAME as a 64-char alphanumeric 'generated' secret AND KEY_NAME_HASH as a 'derived' PBKDF2-SHA512 hash of it, stored together in a new encrypted file. The hash is returned in the response for pasting into Authelia's configuration.yml. Equivalent to calling sops_create_secrets with one generated and one derived (pbkdf2_sha512_authelia) entry.

ParametersJSON Schema
NameRequiredDescriptionDefault
domainNoName of the key domain to encrypt to / decrypt with. Optional. Defaults to the domain recorded in the file's _meta_unencrypted block, and failing that to 'default'. Call sops_list_domains to see what this server has configured.
key_nameYesBase name for the plaintext secret (e.g. GRAFANA_OIDC_CLIENT_SECRET). The hash will be stored as KEY_NAME_HASH. Must match ^[A-Z][A-Z0-9_]*$.
descriptionNoHuman-readable note (e.g. 'OIDC client secret for Grafana')

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does well: it discloses the secret's format (64-char alphanumeric), the hash algorithm (PBKDF2-SHA512), that both are stored together in a new encrypted file, and that the hash is returned in the response for pasting into Authelia config. It omits auth/permission requirements and whether an existing file/key is overwritten.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The first sentence front-loads the purpose, and subsequent sentences add concrete, non-redundant detail. It is somewhat dense but every sentence earns its place; no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with no output schema and no annotations, the description adequately covers what is created, how it is stored, and what is returned (the hash). Minor gaps remain around overwrite behavior and required permissions, but the call can be made correctly from what is provided.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds meaning beyond the schema by spelling out the KEY_NAME/KEY_NAME_HASH naming relationship and the returned hash. It does not add syntax detail for domain or description, but those are already documented in the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('create an Authelia-compatible OIDC client secret') and enumerates exactly what gets produced (KEY_NAME generated secret plus KEY_NAME_HASH derived hash). It also distinguishes itself from sibling sops_create_secrets by framing itself as a convenience wrapper.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly routes the agent by stating it is 'Equivalent to calling sops_create_secrets with one generated and one derived (pbkdf2_sha512_authelia) entry', which tells the agent when to prefer this shortcut versus the general tool. It does not state when NOT to use it, but the convenience framing makes the boundary clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sops_create_secretsA

Generate and encrypt secrets as SOPS YAML. Returns encrypted content for the client to write to disk. Supports three sources: 'generated' (cryptographic randomness), 'external' (user-provided values), and 'derived' (computed from another key in the same file via a transform such as pbkdf2_sha512_authelia). Derived secret plaintexts are returned in the response so they can be copied into config files.

ParametersJSON Schema
NameRequiredDescriptionDefault
domainNoName of the key domain to encrypt to / decrypt with. Optional. Defaults to the domain recorded in the file's _meta_unencrypted block, and failing that to 'default'. Call sops_list_domains to see what this server has configured.
secretsYesList of secrets to create

TDQS

A3.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does real work: it discloses that encrypted content is returned for the client to write (the tool does not touch disk itself) and that derived secret plaintexts are exposed in the response. It omits auth/permission requirements and whether existing keys are overwritten, but the key behavioral traits are surfaced.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four sentences, front-loaded with the purpose and the disk-write caveat, followed by source enumeration and the plaintext-return note. Each sentence carries information, though the source list partially duplicates the schema enums.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description correctly describes what is returned (encrypted content plus derived plaintexts). Given a rich input schema and no annotations, it covers the essential behaviors an agent needs, though it could say more about failure modes or overwrite semantics.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents every field including domain and the nested source/transform/length options. The prose restates the three source kinds but adds no syntax or format detail beyond the schema, so the baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description gives a specific verb+resource ('Generate and encrypt secrets as SOPS YAML') and elaborates the three source modes, so the agent knows exactly what the tool produces. It does not, however, name or contrast any sibling such as sops_add_secrets or sops_update_external, leaving the create-vs-add boundary to inference.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It enumerates the supported sources but never states when to choose this tool over the several near-neighbors (sops_add_secrets, sops_update_external, sops_rotate_generated). No prerequisites, no exclusions, no routing guidance are provided.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sops_delete_secretsA

Delete one or more keys from an existing SOPS-encrypted file. Removes both the encrypted value and the _meta_unencrypted entry. Rejects deletion of keys that other derived secrets depend on unless those dependents are also in the delete list. Requires a private key for the domain.

ParametersJSON Schema
NameRequiredDescriptionDefault
domainNoName of the key domain to encrypt to / decrypt with. Optional. Defaults to the domain recorded in the file's _meta_unencrypted block, and failing that to 'default'. Call sops_list_domains to see what this server has configured.
key_namesYesKeys to delete
encrypted_contentYesContents of an existing secrets.enc.yaml file

TDQS

A3.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does well: it discloses that both the encrypted value and the _meta_unencrypted entry are removed, that dependent derived secrets block deletion unless included, and that a private key for the domain is required. It stops short of stating whether the operation is irreversible or what is returned on partial failure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four compact sentences, front-loaded with the core action and followed by the destructive and dependency semantics. No filler, though the dependency sentence is slightly dense.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a mutation tool with no annotations and no output schema, the description covers the destructive effect, dependency rejection rule, and authentication requirement. It lacks only return-value or failure-mode detail, which is a minor gap given the schema's completeness.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so domain, key_names, and encrypted_content are already well documented, and the schema itself points to sops_list_domains. The description adds only the implicit tie between the private-key requirement and the domain parameter, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (delete) and resource (keys in an existing SOPS-encrypted file), which is clearly distinct from sibling operations like add, create, rename, and rekey. It doesn't explicitly name a sibling to disambiguate against, but the action is unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no statement of when to use this tool versus alternatives such as sops_add_secrets or sops_rename_secret, nor any prerequisite context beyond the key requirement. Usage must be inferred from the verb.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sops_list_domainsA

List the key domains this server is configured with: their names, age recipients (public keys), how many private keys the server holds for each, and whether the domain can decrypt or only encrypt. Never returns private key material.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and delivers meaningful behavioral context: it discloses exactly what the response contains and asserts the security-relevant guarantee that private key material is never returned. It stops short of stating auth requirements or explicitly labeling the operation as non-mutating.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single sentence that front-loads the core purpose and appends the return-field breakdown and the no-private-key guarantee. Every clause adds information; there is no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, so the description must (and largely does) explain the return shape, covering domain names, recipients, key counts, and capability. Only minor gaps remain, such as permission prerequisites for an operator wondering whether they are allowed to call it.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so there is nothing for the description to clarify; the baseline for a no-arg tool is 4. No parameter-level ambiguity is introduced.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (List) and resource (key domains this server is configured with), then enumerates exactly what is returned: names, age recipients, private-key counts, and encrypt-vs-decrypt capability. The resource is clearly distinct from sibling list tools such as sops_list_secrets, so an agent can tell them apart without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description never says when to call this versus alternatives (e.g., sops_list_secrets or sops_list_domains-style discovery before rotate/rekey operations), nor does it state any prerequisites. Usage is only weakly implied by the read-only verb.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sops_list_secretsA

List key names and metadata from a SOPS-encrypted file. No decryption needed — reads key names from encrypted YAML and metadata from the _meta_unencrypted block.

ParametersJSON Schema
NameRequiredDescriptionDefault
domainNoName of the key domain to encrypt to / decrypt with. Optional. Defaults to the domain recorded in the file's _meta_unencrypted block, and failing that to 'default'. Call sops_list_domains to see what this server has configured.
encrypted_contentYesContents of a secrets.enc.yaml file

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It does disclose one genuinely useful behavior — no decryption is required and key names come from encrypted YAML while metadata comes from the _meta_unencrypted block. It says nothing about permissions, error behavior on malformed files, or how the optional domain affects the read.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two tight sentences, front-loaded with the operation and followed by the key distinction (no decryption). Every clause carries information; nothing is padding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple two-parameter read tool with no output schema, the description adequately conveys what is returned (key names and metadata) and the mechanism. Minor gaps around the domain parameter's effect and failure modes remain, but nothing critical is missing for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so both parameters are already documented in the schema, including the domain default fallback chain and the pointer to sops_list_domains. The description adds no parameter-level meaning beyond what the schema provides, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: 'List key names and metadata from a SOPS-encrypted file.' An agent can distinguish this from mutating siblings (sops_add_secrets, sops_delete_secrets) and from sops_list_domains by the resource named. It does not explicitly name which sibling is the alternative, so it falls short of a 5.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied rather than stated: the 'No decryption needed' clause signals this is the inspection path when you only need to see what keys exist. However, no explicit when-to-use, prerequisites, or named alternatives to other list/inspect siblings are given.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sops_rekeyA

Re-encrypt a SOPS file onto the current recipient list of the named domain, updating its recorded domain. This is the tool to run after a domain's recipients change, and the way to clear the 'recipients do not match' refusal from the other mutation tools. It changes who can read the file, so 'domain' is required rather than inferred. Requires a private key for that domain.

ParametersJSON Schema
NameRequiredDescriptionDefault
domainYesRequired. The domain whose current recipient list the file is re-encrypted to. This changes who can read the file, so it is never inferred from the file or defaulted. Call sops_list_domains to see the options.
encrypted_contentYesContents of a secrets.enc.yaml file

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does well: it discloses the mutation's security consequence ('changes who can read the file'), the auth prerequisite ('Requires a private key for that domain'), and the non-inferable nature of 'domain'. It omits return format and whether prior ciphertext remains valid, so it is not exhaustive.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three tight sentences, front-loaded with the core action and followed by when-to-use and prerequisites. Slight redundancy with the domain parameter description, but no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 2-parameter tool with no output schema and no annotations, the description covers purpose, timing, security impact, and prerequisites. The only gap is what the call returns (e.g., new encrypted content vs. a status), which an agent might want given the absence of an output schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3. The description reinforces why 'domain' must be explicit and points to sops_list_domains for options, but that same guidance already lives in the schema's property description, so it adds little beyond the structured field.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource+mechanism: 'Re-encrypt a SOPS file onto the current recipient list of the named domain.' It also names the side effect (updating the recorded domain), which differentiates it from sibling mutation tools like sops_rotate_generated or sops_add_secrets.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives a clear trigger ('after a domain's recipients change') and a concrete diagnostic condition ('recipients do not match' refusal from the other mutation tools). It stops short of naming a specific sibling alternative, but the routing context is strong enough for the agent to pick this tool over its siblings.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sops_rename_secretA

Rename a key in an existing SOPS-encrypted file. Preserves the value, source type, and metadata. Updates 'from' references in any derived secrets that depend on the renamed key. Requires a private key for the target domain.

ParametersJSON Schema
NameRequiredDescriptionDefault
domainNoName of the key domain to encrypt to / decrypt with. Optional. Defaults to the domain recorded in the file's _meta_unencrypted block, and failing that to 'default'. Call sops_list_domains to see what this server has configured.
new_nameYesNew key name (must match ^[A-Z][A-Z0-9_]*$ and not collide with an existing key)
old_nameYesCurrent key name
encrypted_contentYesContents of an existing secrets.enc.yaml file

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations to lean on, the description carries the full burden and does reasonably well: it discloses an auth requirement (private key for the domain), preservation of value/source-type/metadata, and the non-obvious side effect of rewriting 'from' references in dependent secrets. It stops short of stating failure behavior, idempotency, or whether the operation can be undone.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three short sentences, zero filler, and the core operation is front-loaded before the preservation and dependency-update details. Every clause earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For an unannotated mutation tool with four parameters and no output schema, the description covers purpose, prerequisites, preserved state, and downstream side effects. It omits return-value/result behavior and error conditions (e.g., name collision handling), which the schema only partially covers.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already documents domain, old_name, new_name, and encrypted_content thoroughly, making 3 the baseline. The description adds marginal meaning — 'Requires a private key for the target domain' ties the domain parameter to auth, and the preservation clause hints at what old_name vs new_name carry over — but contributes no syntax or format detail beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource ('Rename a key in an existing SOPS-encrypted file') and then enumerates the distinct semantics — value/type/metadata preservation and propagation to derived secrets — which cleanly separates it from siblings like sops_add_secrets, sops_delete_secrets, and sops_rotate_generated.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied (rename a key when its name must change) and one prerequisite is given — a private key for the target domain. However, it never states when to prefer this over alternatives such as delete+add, nor does it name related tools like sops_list_domains or sops_create_secrets, leaving routing to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sops_rotate_generatedA

Re-generate 'generated' secrets with new random values while preserving 'external' secrets. Requires a private key for the target domain.

ParametersJSON Schema
NameRequiredDescriptionDefault
domainNoName of the key domain to encrypt to / decrypt with. Optional. Defaults to the domain recorded in the file's _meta_unencrypted block, and failing that to 'default'. Call sops_list_domains to see what this server has configured.
encrypted_contentYesContents of an existing secrets.enc.yaml file

TDQS

A3.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It does disclose two useful behavioral facts: external secrets are preserved, and a private key for the target domain is required. However, it does not say whether the caller must supply the encrypted content back or where the rotated result is returned, and repeats a domain hint already present in the schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences, front-loaded with the action and the key distinguishing behavior (preserving external secrets). Every sentence earns its place with no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a mutation tool with no annotations and no output schema, the description should explain what the operation produces or returns and any side effects. It covers the auth requirement and preservation behavior but leaves the return/side-effect picture incomplete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so both parameters are already documented in the schema, including the domain defaulting logic. The description adds only the implicit requirement of a valid private key for the domain, so the baseline of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb ('re-generate') and resource ('generated' secrets), and importantly distinguishes the target from 'external' secrets in the same sentence. It does not explicitly contrast with the sibling sops_rekey, which is the nearest alternative, so it falls short of a 5.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies the scenario (rotating random secrets) but never states when to choose this over sops_rekey or any other sibling, nor any exclusions or prerequisites beyond the key requirement. Usage is implied rather than explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sops_update_externalA

Replace the value of an 'external' secret (e.g. after the user rotated an upstream API key). Rejects attempts to update 'generated' or 'derived' secrets — use sops_rotate_generated for those. Recomputes any derived secrets that reference this key. Requires a private key for the domain.

ParametersJSON Schema
NameRequiredDescriptionDefault
valueYesNew plaintext value
domainNoName of the key domain to encrypt to / decrypt with. Optional. Defaults to the domain recorded in the file's _meta_unencrypted block, and failing that to 'default'. Call sops_list_domains to see what this server has configured.
key_nameYesKey to update
encrypted_contentYesContents of an existing secrets.enc.yaml file

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does well: it discloses a prerequisite ('Requires a private key for the domain'), a rejection rule, and an important side effect ('Recomputes any derived secrets that reference this key'). It leaves the mutation semantics and return shape implicit, which prevents a 5, but this is unusually rich for an unannotated mutation tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, zero filler, and the core action is front-loaded ahead of the constraints and side effects. Each sentence carries a distinct obligation (action, exclusion/alternative, side effect + prerequisite).

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For an unannotated mutation tool with no output schema, the description covers usage, restrictions, side effects, and auth requirements adequately; return-value detail is absent but arguably not needed. The main residual gap is that it never states the operation is a write/mutation explicitly, relying on 'Replace' to imply it.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so both the value, key_name, domain, and encrypted_content semantics are already documented in the schema, including the domain defaulting logic and the sops_list_domains pointer. The description adds only the contextual framing that value is a rotated upstream key, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Replace the value of an external secret') and scopes it tightly to the 'external' secret class, naming the alternative tool for the other classes. An agent can distinguish it from sops_rotate_generated and sops_add_secrets without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit triggering condition ('after the user rotated an upstream API key') and an explicit exclusion with the redirect ('Rejects attempts to update generated or derived secrets — use sops_rotate_generated for those'). When-to-use, when-not-to-use, and the alternative are all present.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 11 tool updatesv0.11.0
    • Changedsops_add_metadata1 field changed
      • addedInput schema / properties / domain
        Added value: +{
        +  "description": "Name of the key domain to encrypt to / decrypt with. Optional. Defaults to the domain recorded in the file's _meta_unencrypted block, and failing that to 'default'. Call sops_list_domains to see what this server has configured.",
        +  "type": "string"
        +}
    • Changedsops_add_secrets1 field changed
      • addedInput schema / properties / domain
        Added value: +{
        +  "description": "Name of the key domain to encrypt to / decrypt with. Optional. Defaults to the domain recorded in the file's _meta_unencrypted block, and failing that to 'default'. Call sops_list_domains to see what this server has configured.",
        +  "type": "string"
        +}
    • Changedsops_create_oidc_secret1 field changed
      • addedInput schema / properties / domain
        Added value: +{
        +  "description": "Name of the key domain to encrypt to / decrypt with. Optional. Defaults to the domain recorded in the file's _meta_unencrypted block, and failing that to 'default'. Call sops_list_domains to see what this server has configured.",
        +  "type": "string"
        +}
    • Changedsops_create_secrets1 field changed
      • addedInput schema / properties / domain
        Added value: +{
        +  "description": "Name of the key domain to encrypt to / decrypt with. Optional. Defaults to the domain recorded in the file's _meta_unencrypted block, and failing that to 'default'. Call sops_list_domains to see what this server has configured.",
        +  "type": "string"
        +}
    • Changedsops_delete_secrets1 field changed
      • addedInput schema / properties / domain
        Added value: +{
        +  "description": "Name of the key domain to encrypt to / decrypt with. Optional. Defaults to the domain recorded in the file's _meta_unencrypted block, and failing that to 'default'. Call sops_list_domains to see what this server has configured.",
        +  "type": "string"
        +}
    • Addedsops_list_domains
    • Changedsops_list_secrets1 field changed
      • addedInput schema / properties / domain
        Added value: +{
        +  "description": "Name of the key domain to encrypt to / decrypt with. Optional. Defaults to the domain recorded in the file's _meta_unencrypted block, and failing that to 'default'. Call sops_list_domains to see what this server has configured.",
        +  "type": "string"
        +}
    • Addedsops_rekey
    • Changedsops_rename_secret1 field changed
      • addedInput schema / properties / domain
        Added value: +{
        +  "description": "Name of the key domain to encrypt to / decrypt with. Optional. Defaults to the domain recorded in the file's _meta_unencrypted block, and failing that to 'default'. Call sops_list_domains to see what this server has configured.",
        +  "type": "string"
        +}
    • Changedsops_rotate_generated1 field changed
      • addedInput schema / properties / domain
        Added value: +{
        +  "description": "Name of the key domain to encrypt to / decrypt with. Optional. Defaults to the domain recorded in the file's _meta_unencrypted block, and failing that to 'default'. Call sops_list_domains to see what this server has configured.",
        +  "type": "string"
        +}
    • Changedsops_update_external1 field changed
      • addedInput schema / properties / domain
        Added value: +{
        +  "description": "Name of the key domain to encrypt to / decrypt with. Optional. Defaults to the domain recorded in the file's _meta_unencrypted block, and failing that to 'default'. Call sops_list_domains to see what this server has configured.",
        +  "type": "string"
        +}
  2. 9 tool updatesv0.10.0
    • First observedsops_add_metadata
    • First observedsops_add_secrets
    • First observedsops_create_oidc_secret
    • First observedsops_create_secrets
    • First observedsops_delete_secrets
    • First observedsops_list_secrets
    • First observedsops_rename_secret
    • First observedsops_rotate_generated
    • First observedsops_update_external

TDQS

A3.9/5.0

Scored across 11 tools

Disambiguation4/5

Most tools have clearly distinct roles (update_external vs rotate_generated vs add_secrets are well-differentiated by their descriptions). The main overlap is between sops_create_secrets and sops_add_secrets, and especially sops_create_oidc_secret which is explicitly a convenience wrapper over create_secrets, but the descriptions make the boundaries clear enough to choose correctly.

Naming Consistency4/5

All tools use a consistent sops_verb_noun snake_case pattern (sops_create_secrets, sops_delete_secrets, sops_list_domains). Minor deviations: sops_update_external drops the 'secrets' noun and sops_rename_secret uses singular 'secret' whereas peers use plural, but the convention is still predictable.

Tool Count5/5

11 tools is well-scoped for a SOPS secret lifecycle server, with each mutation, read, and maintenance operation earning its place. No redundant or filler tools.

Completeness4/5

Covers creation, addition, deletion, renaming, rotation, metadata, rekeying, domain listing, and external update — a solid lifecycle surface. The notable gap is a tool to decrypt/read plaintext values of existing secrets (list_secrets only exposes key names), which could force workarounds.

Maintenance

ActivityMaintained
ResponsivenessSlow

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    MCP server that lets AI agents call APIs without ever seeing the credentials, using a local encrypted vault and per-secret allowlist policies for HTTP requests and subprocess environment variables.
    1
    AGPL 3.0
  • A
    license
    Not graded
    quality
    C
    maintenance
    Secrets management MCP server that injects credentials into API requests for AI agents, enforcing policies and logging all activity without exposing raw keys.
    42 npm
    30
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    MCP server enabling AI agents to use secrets (API keys, tokens) via encrypted vault, executing HTTP/shell/SSH actions server-side while never exposing secret values to the AI.
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Encrypted secrets management MCP server for AI agents, enabling secure storage, retrieval, rotation, and auditing of API keys and credentials with AES-128 encryption.
    MIT