Skip to main content

MCP Server

The env-secrets MCP (Model Context Protocol) server lets AI agents — Claude Code, OpenAI Codex, Gemini CLI, and others — work with AWS Secrets Manager secrets during an agentic session.

Transport: stdio only. The server runs as a subprocess launched by the MCP host. No daemon, no open port.


Security Model

Secret values never enter the agent's context. This is the core design principle.

The MCP server exposes three tools:

ToolWhat it returnsValues exposed to agent?
list_secretsSecret names and metadata❌ No
describe_secretARN, dates, description❌ No
get_commandReady-to-run CLI string❌ No

To read or write a secret value, the agent uses get_command to produce a CLI command, then asks you to run it yourself in your terminal — the value stays entirely outside the agent stream.


Quick Setup

Claude Code

Add to ~/.claude/settings.json (global) or .claude/settings.json (project-level):

{
"mcpServers": {
"env-secrets": {
"command": "npx",
"args": ["-y", "env-secrets", "mcp"],
"env": {
"AWS_REGION": "us-east-1"
}
}
}
}

With a global install (npm install -g env-secrets):

{
"mcpServers": {
"env-secrets": {
"command": "env-secrets-mcp"
}
}
}

Restart Claude Code after saving. The tools list_secrets, describe_secret, and get_command are immediately available in the session.

OpenAI Codex

[[mcp_servers]]
name = "env-secrets"
command = ["npx", "-y", "env-secrets", "mcp"]

[mcp_servers.env]
AWS_REGION = "us-east-1"

With a global install:

[[mcp_servers]]
name = "env-secrets"
command = ["env-secrets-mcp"]

Gemini CLI

{
"mcpServers": {
"env-secrets": {
"command": "npx",
"args": ["-y", "env-secrets", "mcp"],
"env": { "AWS_REGION": "us-east-1" }
}
}
}

Available Tools

list_secrets

Lists secret names in AWS Secrets Manager. Accepts an optional prefix to filter results. Never returns values.

Parameters:

ParameterRequiredDescription
prefixFilter secrets by name prefix
regionAWS region
profileAWS profile name

Example — list all secrets:

{
"name": "list_secrets",
"arguments": {}
}

Response:

[
{
"name": "my-app/prod",
"description": "Production secrets",
"lastChangedDate": "2024-06-01T12:00:00.000Z"
},
{
"name": "my-app/staging",
"description": null,
"lastChangedDate": "2024-05-15T09:30:00.000Z"
}
]

Example — filter by prefix:

{
"name": "list_secrets",
"arguments": {
"prefix": "my-app/",
"region": "us-east-1"
}
}

Response:

[
{
"name": "my-app/prod",
"lastChangedDate": "2024-06-01T12:00:00.000Z"
},
{
"name": "my-app/staging",
"lastChangedDate": "2024-05-15T09:30:00.000Z"
}
]

describe_secret

Returns metadata about a specific secret — ARN, creation date, last-changed date, version IDs, and description. Never returns the secret value.

Parameters:

ParameterRequiredDescription
secret_nameSecret name
regionAWS region
profileAWS profile name

Example:

{
"name": "describe_secret",
"arguments": {
"secret_name": "my-app/prod",
"region": "us-east-1"
}
}

Response:

{
"name": "my-app/prod",
"arn": "arn:aws:secretsmanager:us-east-1:123456789012:secret:my-app/prod-AbCdEf",
"description": "Production credentials for my-app",
"createdDate": "2024-01-01T00:00:00.000Z",
"lastChangedDate": "2024-06-01T12:00:00.000Z",
"lastAccessedDate": "2024-06-10T00:00:00.000Z",
"versionIdsToStages": {
"abc123": ["AWSCURRENT"],
"def456": ["AWSPREVIOUS"]
}
}

get_command

Returns a ready-to-run env-secrets CLI command string for the requested action. No secret values are ever included. The agent hands you the command; you run it in your terminal.

Parameters:

ParameterRequiredDescription
actionOne of get, set, list, describe
secret_nameSecret name (required for get, set, describe; used as prefix for list)
keyKey name (required for set action)
regionAWS region flag value
profileAWS profile flag value

Example — get command to inject secrets into a process:

{
"name": "get_command",
"arguments": {
"action": "get",
"secret_name": "my-app/prod",
"region": "us-east-1"
}
}

Response:

env-secrets aws -s 'my-app/prod' --region 'us-east-1' -- <your-program>

Example — get command to set a key:

{
"name": "get_command",
"arguments": {
"action": "set",
"secret_name": "my-app/prod",
"key": "DATABASE_URL",
"region": "us-east-1"
}
}

Response:

printf 'your-value' | env-secrets aws secret append -n 'my-app/prod' --key 'DATABASE_URL' --value-stdin --region 'us-east-1'

The set command uses --value-stdin so the value is piped in by you and never enters the agent stream. Replace 'your-value' with read -rs to also keep it out of shell history:

read -rs VALUE && printf '%s' "$VALUE" | env-secrets aws secret append -n 'my-app/prod' --key 'DATABASE_URL' --value-stdin

Example — get command to list secrets:

{
"name": "get_command",
"arguments": {
"action": "list",
"secret_name": "my-app/"
}
}

Response:

env-secrets aws secret list --prefix 'my-app/'

Example — get command to describe a secret:

{
"name": "get_command",
"arguments": {
"action": "describe",
"secret_name": "my-app/prod"
}
}

Response:

env-secrets aws secret get -n 'my-app/prod'

All get_command actions at a glance:

ActionReturns
getenv-secrets aws -s 'my-app/prod' -- <your-program>
setprintf 'your-value' | env-secrets aws secret append -n 'my-app/prod' --key 'KEY' --value-stdin
listenv-secrets aws secret list --prefix 'my-app/'
describeenv-secrets aws secret get -n 'my-app/prod'

Example Agent Workflows

Workflow 1 — Discover and inspect secrets before a deployment

Prompt: "List our production secrets and tell me which ones have changed in the last month."

The agent calls list_secrets with prefix prod/, then calls describe_secret for each result to check lastChangedDate. It reports back without ever seeing any values.

Agent → list_secrets({ prefix: "prod/" })
← [{ name: "prod/api", lastChangedDate: "..." }, ...]

Agent → describe_secret({ secret_name: "prod/api" })
← { lastChangedDate: "2024-06-01T...", ... }

Agent → "prod/api was last changed June 1st. prod/db hasn't changed since March."

Workflow 2 — Rotate a secret key

Prompt: "Help me rotate the DATABASE_URL in my-app/prod."

The agent uses get_command to produce the update command. You run it in your terminal — the new value never touches the agent.

Agent → get_command({ action: "set", secret_name: "my-app/prod", key: "DATABASE_URL" })
← "printf 'your-value' | env-secrets aws secret append -n 'my-app/prod' --key 'DATABASE_URL' --value-stdin"

Agent → "Run this command in your terminal and paste in the new DATABASE_URL when prompted:"
printf 'your-value' | env-secrets aws secret append -n 'my-app/prod' --key 'DATABASE_URL' --value-stdin

You → (runs command, types new URL, presses Enter)

For even better security, use read so the value doesn't appear in shell history at all:

read -rs VALUE && printf '%s' "$VALUE" | env-secrets aws secret append -n 'my-app/prod' --key 'DATABASE_URL' --value-stdin

Workflow 3 — Run an app with injected secrets

Prompt: "Give me the command to start the Node.js server with the my-app/prod secrets."

Agent → get_command({ action: "get", secret_name: "my-app/prod", region: "us-east-1" })
← "env-secrets aws -s 'my-app/prod' --region 'us-east-1' -- <your-program>"

Agent → "Replace <your-program> with your start command:"
env-secrets aws -s 'my-app/prod' --region 'us-east-1' -- node server.js

When you run this, secrets are injected as environment variables into the node server.js process only — they never appear in the agent conversation.


Workflow 4 — Audit which secrets exist for a new team member

Prompt: "What secrets does our staging environment use?"

Agent → list_secrets({ prefix: "staging/" })
← [
{ name: "staging/db", lastChangedDate: "..." },
{ name: "staging/api-keys", lastChangedDate: "..." },
{ name: "staging/stripe", lastChangedDate: "..." }
]

Agent → describe_secret({ secret_name: "staging/db" })
← { description: "PostgreSQL connection string for staging", ... }

Agent → "Your staging environment has 3 secrets: staging/db (PostgreSQL connection),
staging/api-keys (last updated 2 weeks ago), and staging/stripe."

AWS Credentials

The MCP server uses the standard AWS credential chain — environment variables, ~/.aws/credentials, IAM roles, instance profiles, etc. No new auth configuration is needed beyond what you already use for the env-secrets CLI.

Set AWS_REGION in the MCP server env block, or pass region per tool call. The profile parameter selects a named AWS profile from ~/.aws/credentials.


Frequently Asked Questions

Why can't the agent read secret values directly?

If values were returned to the agent, they would appear in the conversation transcript, the model's context window, and potentially in logs. By keeping values out of the agent entirely, you get a stronger security boundary — the agent can reason about which secrets exist and how to use them without ever seeing what they contain.

How do I read a secret value myself?

Run env-secrets aws secret value directly in your terminal (not via the agent):

env-secrets aws secret value -n my-app/prod --reveal

Or inject the secret directly into a process without ever displaying the values:

env-secrets aws -s my-app/prod -- printenv DATABASE_URL

What if I need the agent to use a secret value in a command it constructs?

Use get_command with action: "get" to get an inject command, then substitute your actual command:

env-secrets aws -s 'my-app/prod' -- curl -H "Authorization: Bearer $API_KEY" https://api.example.com

The secret is injected as an environment variable into the subprocess — it never needs to appear in the agent's output.

Does this work in CI or non-interactive environments?

Yes. list_secrets, describe_secret, and get_command all work without a TTY. For set operations in CI, use --value-stdin piped from a secrets store or environment variable:

echo "$NEW_API_KEY" | env-secrets aws secret append -n my-app/prod --key API_KEY --value-stdin