Securing MCP Server connection credentials with SOPS
When using coding assistants like OpenCode, Claude, Codex, or others it can be useful to connect to external systems using the Model Context Protocol, or MCP.
MCP servers usually require authentication and sometimes this is the form of API keys or username/password combinations.
When defining connections in a configuration file these usually end up hardcoded. In the OpenCode configuration format this would be:
{
"mcp": {
"my-local-mcp": {
"type": "local",
"command": ["docker", "run", "--rm", "-i", "-e", "MY_SECRET_TOKEN", "my-mcp-server-image:latest"],
"environment": {
"MY_SECRET_TOKEN": "admin"
}
}
}
}
This has the disadvantage of storing the token in plain text format. There is the possibility of referencing environment variables instead using the {env:MY_SECRET_TOKEN} format , but this puts the burden on the user to set those correctly and many times they end up being stored in profile files or .envrc files managed by direnv which are also stored in plain text format and have the same disadvantages.
A more robust alternative is to use SOPS to store the secrets in an encrypted format and then decrypt them on-the fly for the MCP server. This way the credentials are not available to all programs that have access to the configuration file and only exposed on demand when the MCP server is launched. Evenmore, the credentials can be safely committed to version control without the risk of exposing them in plain text format.
SOPS is a very flexibile tool and can be used with a variety of encryption systems, such as age, GPG, Hashicorp Vault, AWS KMS, Azure Key Vault, and Google Cloud KMS. Please reference the SOPS documentation for setting it up. If undecided, I would recommend starting with age and then moving to a more complex system if needed.
Once age is set up, you can create an encrypted file with the credentials using the following command:
# env file is present alongside the configuration file
$ sops .env
This will open up a text editor where you can define the credentials. Replace the existing sample data based on your requirements, e.g.
MY_SECRET_TOKEN=admin
Once you close the editor you can check what is exactly is stored on disk.
$ cat .env
MY_SECRET_TOKEN=ENC[AES256_GCM,data:...=.....,tag:....==,type:str]
sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\n.....\n-----END AGE ENCRYPTED FILE-----\n
sops_lastmodified=2026-03-27T14:51:51Z
sops_mac=ENC[AES256_GCM,data:....]
sops_unencrypted_suffix=_unencrypted
sops_version=3.12.2
We don’t need to go into the details but the important part is that the credentials are stored in an encrypted format and cannot be read without the appropriate decryption key.
To access the stored data programatically we will wrap access to the credentials with sops exec-file which will decrypt the file and make the contents available as a temporary file. A quick test shows us how this works:
$ sops exec-file '.env' 'ls {}; cat {}'
/tmp/.sops643234936/tmp-file
MY_SECRET_TOKEN=admin
To use the encrypted credentials you need to wrap your startup command with sops exec-file. The configuration file then becomes:
{
"mcp": {
"my-local-mcp": {
"type": "local",
"command": ["sops", "exec-file", ".env", "docker run --rm -i ---env-file {} my-mcp-server-image:latest"]
}
}
}
In this setup the credentials are not stored in plain text and are only decrypted on demand when the MCP server is launched. They are are also not available as environment variables to other processes, which adds an extra layer of security.