docs/features/keyring-integration.md
MCPProxy integrates with your operating system's native credential store to securely manage secrets such as API keys, tokens, and passwords. Instead of storing sensitive values in plaintext configuration files, you reference them using the ${keyring:name} syntax, and MCPProxy resolves them at runtime from the system keyring.
Storing secrets directly in mcp_config.json is convenient but risky:
MCPProxy's keyring integration solves this by:
MCPProxy uses the zalando/go-keyring library (v0.2.6), which supports:
| Platform | Backend | Notes |
|---|---|---|
| macOS | Keychain (via Security framework) | Secrets stored under the "mcpproxy" service in Keychain Access |
| Linux | Secret Service API (libsecret) | Requires a running secret service (GNOME Keyring, KWallet, etc.) |
| Windows | Windows Credential Manager | Secrets visible in Control Panel > Credential Manager |
All secrets are stored under the service name mcpproxy.
MCPProxy supports two types of secret references that can be used anywhere in your configuration where a string value is expected:
| Syntax | Provider | Description |
|---|---|---|
${keyring:name} | OS Keyring | Resolves from macOS Keychain, Linux Secret Service, or Windows Credential Manager |
${env:VAR_NAME} | Environment | Resolves from environment variables |
References can be used in:
env field)args field)headers field)Note: The
urlfield does not support secret references. If your server URL contains credentials, use environment variables or headers instead.
{
"mcpServers": [
{
"name": "github-mcp",
"url": "https://api.github.com/mcp",
"protocol": "http",
"headers": {
"Authorization": "Bearer ${keyring:github-token}"
},
"enabled": true
},
{
"name": "my-stdio-server",
"command": "python",
"args": ["-m", "my_server"],
"protocol": "stdio",
"env": {
"API_KEY": "${keyring:my-api-key}",
"DATABASE_URL": "${env:DATABASE_URL}"
},
"enabled": true
}
]
}
MCPProxy provides a secrets command group for managing keyring entries.
# Interactive (prompts for value)
mcpproxy secrets set my-api-key
# Inline value
mcpproxy secrets set my-api-key "sk-abc123..."
# From environment variable
mcpproxy secrets set my-api-key --from-env=MY_API_KEY
# From stdin
echo "secret-value" | mcpproxy secrets set my-api-key --from-stdin
| Flag | Default | Description |
|---|---|---|
--type | keyring | Secret provider type |
--from-env | Read value from an environment variable | |
--from-stdin | false | Read value from stdin |
On success, the command prints the reference syntax to use in your configuration:
Secret 'my-api-key' stored successfully in keyring
Use in config: ${keyring:my-api-key}
# Masked output (default)
mcpproxy secrets get my-api-key
# Output: my-api-key: sk-****23
# Unmasked output
mcpproxy secrets get my-api-key --masked=false
# Output: my-api-key: sk-abc123...
| Flag | Default | Description |
|---|---|---|
--type | keyring | Secret provider type |
--masked | true | Mask the secret value in output |
mcpproxy secrets del my-api-key
| Flag | Default | Description |
|---|---|---|
--type | keyring | Secret provider type |
# List keyring secrets only (default)
mcpproxy secrets list
# List from all providers (keyring + env)
mcpproxy secrets list --all
# JSON output
mcpproxy secrets list -o json
# YAML output
mcpproxy secrets list -o yaml
Example table output:
NAME TYPE
github-token keyring
my-api-key keyring
db-password keyring
The migrate command analyzes your configuration for hardcoded values that look like secrets and suggests migrating them to the keyring:
# Dry run -- show what would be migrated
mcpproxy secrets migrate --dry-run
# Interactive migration
mcpproxy secrets migrate
# Auto-approve all migrations
mcpproxy secrets migrate --auto-approve
Example output:
Found 2 potential secrets for migration:
1. Field: Servers[0].Env.API_KEY
Current value: sk-a****23
Suggested ref: ${keyring:env_api_key}
Confidence: 85.0%
2. Field: Servers[1].Headers.Authorization
Current value: Bea****en
Suggested ref: ${keyring:headers_authorization}
Confidence: 72.0%
The detection engine considers:
| Flag | Default | Description |
|---|---|---|
--dry-run | false | Show candidates without making changes |
--auto-approve | false | Automatically approve all migrations |
--from | plaintext | Source type |
--to | keyring | Target type |
MCPProxy's Web UI includes a dedicated Secrets & Environment Variables page accessible from the sidebar navigation. This page provides a visual interface for managing all secret references in your configuration.
The page displays four summary statistics:
${keyring:...} syntax${env:...} syntaxFilter secrets by category using the filter buttons:
${keyring:...} references${env:...} referencesA search bar allows filtering by name or reference string.
Each keyring secret card shows:
${keyring:my-api-key})For environment variables, a How to Set button provides platform-specific instructions.
The modal for adding or updating a secret includes:
${keyring:name} syntax to useWhen a secret is added or updated through the Web UI, MCPProxy automatically:
The Migration Candidates section at the bottom of the page shows plaintext values in your configuration that appear to be secrets. Each candidate shows:
${keyring:...} referenceClick Store in Keychain to get CLI instructions for migrating a specific candidate.
The following REST API endpoints are available for programmatic secret management:
POST /api/v1/secrets
{
"name": "my-api-key",
"value": "sk-abc123...",
"type": "keyring"
}
Response:
{
"message": "Secret 'my-api-key' stored successfully in keyring",
"name": "my-api-key",
"type": "keyring",
"reference": "${keyring:my-api-key}"
}
DELETE /api/v1/secrets/{name}?type=keyring
GET /api/v1/secrets/config
Returns all secret and environment variable references found in the current configuration, along with their resolution status.
GET /api/v1/secrets/refs
POST /api/v1/secrets/migrate
All endpoints require API key authentication via X-API-Key header or ?apikey= query parameter.
Resolver with two default providers: keyring and env${type:name} references:
env map values)args array values)headers map values)When a secret is stored or deleted (via CLI, Web UI, or API), MCPProxy:
secrets.changed event on the internal event bus${keyring:secret-name}env or args contain the changed secret reference (note: headers are resolved at connection time but do not trigger auto-restart)This means you can update a secret without manually restarting MCPProxy or individual servers.
Resolved secret values are automatically registered with the log sanitizer. Any log line containing a resolved secret value will have it masked (e.g., sk-a***23). This prevents accidental secret leakage in log files.
Masking formats: MCPProxy uses two masking functions with slightly different notation. CLI and API output uses
MaskSecretValuewith four asterisks (e.g.,sk-****23). Log sanitization usesmaskValuewith three asterisks (e.g.,sk-a***23). Both show the first few and last two characters of the original value.
Since OS keyring APIs do not provide a "list all entries" function, MCPProxy maintains an internal registry entry (stored in the keyring itself under the key _mcpproxy_secret_registry). This registry is a newline-separated list of secret names, automatically updated when secrets are added or removed.
The mcpproxy doctor command and the diagnostics API check for missing secrets. If a server's configuration references a ${keyring:...} or ${env:...} value that cannot be resolved, it appears in the diagnostics as a missing secret with the server name and reference string.
The recommended workflow for using keyring secrets:
Step 1: Add the secret reference to your configuration
{
"mcpServers": [
{
"name": "my-server",
"command": "my-tool",
"protocol": "stdio",
"env": {
"API_KEY": "${keyring:my-api-key}"
}
}
]
}
Step 2: The secret appears as "Missing" in Web UI or diagnostics
mcpproxy doctor
# Shows: Missing secret 'my-api-key' referenced by server 'my-server'
Step 3: Store the actual value
mcpproxy secrets set my-api-key
# Enter secret value: ****
Or use the Web UI: click Add Value next to the missing secret.
Step 4: Server automatically restarts with the resolved secret
MCPProxy detects the change and restarts my-server with the resolved environment variable.
Symptoms: Error message "keyring is not available on this system" or "provider for keyring is not available."
macOS: Keychain Access should work out of the box. If MCPProxy runs as a background daemon (e.g., via launchd), ensure it has access to the login keychain. You may need to unlock the keychain first:
security unlock-keychain ~/Library/Keychains/login.keychain-db
Linux: Ensure a Secret Service provider is running:
# Check if secret service is available
dbus-send --session --dest=org.freedesktop.secrets \
--type=method_call --print-reply \
/org/freedesktop/secrets org.freedesktop.DBus.Peer.Ping
Common providers: GNOME Keyring (gnome-keyring-daemon), KDE Wallet (kwalletd5).
For headless Linux servers without a desktop environment, consider using environment variable references (${env:...}) instead.
Windows: Windows Credential Manager should work without additional setup.
Symptoms: Server fails to connect or uses the literal string ${keyring:name} instead of the resolved value.
Verify the secret exists:
mcpproxy secrets get my-api-key
Check the exact name matches (case-sensitive):
mcpproxy secrets list
Check MCPProxy logs for resolution errors:
mcpproxy serve --log-level=debug
Look for log entries containing "Failed to resolve secret" or "CRITICAL: Failed to resolve secret."
On macOS, the first time MCPProxy accesses the keychain, you may see a system dialog asking to allow access. Click Always Allow to prevent repeated prompts. If you accidentally clicked Deny, you can fix this in:
mcpproxy to the allowed applications listmacOS: Open Keychain Access.app and search for "mcpproxy" to see all stored entries.
Linux:
Use secret-tool (part of libsecret):
secret-tool search service mcpproxy
Windows: Open Control Panel > Credential Manager > Windows Credentials and look for entries with "mcpproxy" in the name.
If updating a secret does not trigger a server restart:
${keyring:my-api-key} requires a secret named my-api-key)${keyring:name} references are storedsecrets list and secrets/refs endpoints return only names and typessecrets/config endpoint checks resolution status without exposing values