MCP Tools Reference
Complete API reference for all MCP tools provided by the secretctl MCP server.
Overview
The secretctl MCP server implements a security-first design where AI agents never receive plaintext secrets. This follows the "AI-Safe Access" architecture, aligned with 1Password's "Access Without Exposure" philosophy.
Available Tools:
| Tool | Description |
|---|---|
secret_list | List secret keys with metadata (no values) |
secret_exists | Check if a secret exists with metadata |
secret_get_masked | Get masked secret value (e.g., ****WXYZ) |
secret_run | Execute command with secrets as environment variables |
secret_list_fields | List field names for multi-field secrets (no values) |
secret_get_field | Get non-sensitive field values only |
secret_run_with_bindings | Execute with predefined environment bindings |
secret_list
List all secret keys with metadata. Returns key names, tags, expiration, and flags for notes/URL presence. Does NOT return secret values.
Input Schema
{
"tag": "string (optional)",
"expiring_within": "string (optional)"
}
| Field | Type | Required | Description |
|---|---|---|---|
tag | string | No | Filter by tag |
expiring_within | string | No | Filter by expiration (e.g., 7d, 30d) |
Output Schema
{
"secrets": [
{
"key": "string",
"field_count": "number",
"tags": ["string"],
"expires_at": "string (RFC 3339, optional)",
"has_notes": "boolean",
"has_url": "boolean",
"created_at": "string (RFC 3339)",
"updated_at": "string (RFC 3339)"
}
]
}
| Field | Type | Description |
|---|---|---|
key | string | Secret key name |
field_count | number | Number of fields (1 for legacy single-value secrets) |
tags | array | Tags associated with the secret |
expires_at | string | Expiration date in RFC 3339 format (optional) |
has_notes | boolean | Whether the secret has notes |
has_url | boolean | Whether the secret has a URL |
created_at | string | Creation timestamp in RFC 3339 format |
updated_at | string | Last update timestamp in RFC 3339 format |
Examples
List all secrets:
// Input
{}
// Output
{
"secrets": [
{
"key": "AWS_ACCESS_KEY",
"field_count": 1,
"tags": ["aws", "prod"],
"has_notes": false,
"has_url": true,
"created_at": "2025-01-15T10:30:00Z",
"updated_at": "2025-01-15T10:30:00Z"
},
{
"key": "db/prod",
"field_count": 5,
"tags": ["db", "prod"],
"expires_at": "2025-06-15T00:00:00Z",
"has_notes": true,
"has_url": false,
"created_at": "2025-01-10T08:00:00Z",
"updated_at": "2025-01-10T08:00:00Z"
}
]
}
Filter by tag:
// Input
{
"tag": "prod"
}
// Output
{
"secrets": [/* secrets with "prod" tag */]
}
Filter by expiration:
// Input
{
"expiring_within": "30d"
}
// Output
{
"secrets": [/* secrets expiring within 30 days */]
}
secret_exists
Check if a secret key exists and return its metadata. Does NOT return the secret value.
Input Schema
{
"key": "string"
}
| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | The secret key to check |
Output Schema
{
"exists": "boolean",
"key": "string",
"tags": ["string"],
"expires_at": "string (RFC 3339, optional)",
"has_notes": "boolean",
"has_url": "boolean",
"created_at": "string (RFC 3339, optional)",
"updated_at": "string (RFC 3339, optional)"
}
Examples
Check existing secret:
// Input
{
"key": "API_KEY"
}
// Output
{
"exists": true,
"key": "API_KEY",
"tags": ["api", "prod"],
"has_notes": true,
"has_url": true,
"created_at": "2025-01-15T10:30:00Z",
"updated_at": "2025-01-15T10:30:00Z"
}
Check non-existent secret:
// Input
{
"key": "NONEXISTENT_KEY"
}
// Output
{
"exists": false,
"key": "NONEXISTENT_KEY",
"tags": null,
"has_notes": false,
"has_url": false
}
secret_get_masked
Get a masked version of a secret value (e.g., ****WXYZ). Useful for verifying secret format without exposing the actual value.
Input Schema
{
"key": "string"
}
| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | The secret key to retrieve |
Output Schema
{
"key": "string",
"masked_value": "string",
"value_length": "integer",
"field_count": "integer",
"fields": {
"field_name": {
"value": "string",
"sensitive": "boolean",
"value_length": "integer"
}
}
}
| Field | Type | Description |
|---|---|---|
key | string | The secret key |
masked_value | string | Masked primary value (single-value secrets only; empty for multi-field) |
value_length | integer | Length of the primary value (single-value secrets only; 0 for multi-field) |
field_count | integer | Number of fields in this secret |
fields | object | Map of field names to masked field info (multi-field secrets only) |
Note: For multi-field secrets,
masked_valueandvalue_lengthare empty/0. Use thefieldsmap to access individual field values.
Masking Behavior
The masking algorithm:
- Shows the last 4 characters for secrets 9+ characters
- Shows the last 2 characters for secrets 5-8 characters
- Shows only asterisks for secrets 1-4 characters
- Non-sensitive fields are shown in full (not masked)
| Secret Length | Masked Output |
|---|---|
| 1-4 chars | **** (all asterisks) |
| 5-8 chars | ******YZ (last 2 visible) |
| 9+ chars | *****WXYZ (last 4 visible) |
Multi-field secrets:
fieldsmap contains all fields with their masked/full values- Sensitive fields are masked according to the algorithm above
- Non-sensitive fields show the complete value
Examples
Get masked value (single-field secret):
// Input
{
"key": "API_KEY"
}
// Output (assuming API_KEY = "sk-abc123xyz789")
{
"key": "API_KEY",
"masked_value": "**********z789",
"value_length": 14,
"field_count": 1
}
Short secret:
// Input
{
"key": "PIN"
}
// Output (assuming PIN = "1234")
{
"key": "PIN",
"masked_value": "****",
"value_length": 4,
"field_count": 1
}
Multi-field secret (e.g., database credential):
// Input
{
"key": "db/postgres"
}
// Output (multi-field secret with username, password, host)
{
"key": "db/postgres",
"masked_value": "",
"value_length": 0,
"field_count": 3,
"fields": {
"username": {
"value": "dbadmin",
"sensitive": false,
"value_length": 7
},
"password": {
"value": "***********5678",
"sensitive": true,
"value_length": 16
},
"host": {
"value": "db.example.com",
"sensitive": false,
"value_length": 14
}
}
}
secret_run
Execute a command with specified secrets injected as environment variables. Output is automatically sanitized to prevent secret leakage. Requires policy approval.
Input Schema
{
"keys": ["string"],
"command": "string",
"args": ["string"],
"timeout": "string (optional)",
"env_prefix": "string (optional)",
"env": "string (optional)"
}
| Field | Type | Required | Description |
|---|---|---|---|
keys | string[] | Yes | Secret keys to inject (glob patterns supported) |
command | string | Yes | Command to execute |
args | string[] | No | Command arguments |
timeout | string | No | Execution timeout (e.g., 30s, 5m). Default: 5m |
env_prefix | string | No | Prefix for environment variable names |
env | string | No | Environment alias (e.g., dev, staging, prod) |
Output Schema
{
"exit_code": "integer",
"stdout": "string",
"stderr": "string",
"duration_ms": "integer",
"sanitized": "boolean"
}
| Field | Type | Description |
|---|---|---|
exit_code | integer | Command exit code (0 = success) |
stdout | string | Standard output (sanitized) |
stderr | string | Standard error (sanitized) |
duration_ms | integer | Execution duration in milliseconds |
sanitized | boolean | Whether output was sanitized |
Key Pattern Syntax
The keys field supports glob patterns:
| Pattern | Matches |
|---|---|
API_KEY | Exact match for API_KEY |
aws/* | All keys under aws/ (single level) |
db/* | All keys under db/ (single level) |
Environment Variable Naming
Secret keys are transformed to environment variable names:
/is replaced with_-is replaced with_- Names are converted to UPPERCASE
| Secret Key | Environment Variable |
|---|---|
aws/access_key | AWS_ACCESS_KEY |
db-password | DB_PASSWORD |
api/prod/key | API_PROD_KEY |
Output Sanitization
All command output is scanned for secret values. Any matches are replaced with [REDACTED:key]:
Original: "Connected to database with password secret123"
Sanitized: "Connected to database with password [REDACTED:DB_PASSWORD]"
Examples
Run with single secret:
// Input
{
"keys": ["API_KEY"],
"command": "curl",
"args": ["-H", "Authorization: Bearer $API_KEY", "https://api.example.com"]
}
// Output
{
"exit_code": 0,
"stdout": "{\"status\": \"ok\"}",
"stderr": "",
"duration_ms": 245,
"sanitized": false
}
Run with wildcard pattern:
// Input
{
"keys": ["aws/*"],
"command": "aws",
"args": ["s3", "ls"]
}
// Output
{
"exit_code": 0,
"stdout": "2025-01-15 10:30:00 my-bucket\n",
"stderr": "",
"duration_ms": 1250,
"sanitized": false
}
Run with environment alias:
// Input
{
"keys": ["db/*"],
"command": "./deploy.sh",
"env": "prod"
}
// Output
{
"exit_code": 0,
"stdout": "Deployment complete",
"stderr": "",
"duration_ms": 5000,
"sanitized": false
}
Run with prefix:
// Input
{
"keys": ["API_KEY"],
"command": "./app",
"env_prefix": "MYAPP_"
}
// Environment variables:
// MYAPP_API_KEY=<value>
// Output
{
"exit_code": 0,
"stdout": "Application started",
"stderr": "",
"duration_ms": 100,
"sanitized": false
}
secret_list_fields
List all field names and metadata for a multi-field secret. Returns field names, sensitivity flags, hints, and aliases. Does NOT return field values.
Input Schema
{
"key": "string"
}
| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | The secret key to list fields for |
Output Schema
{
"key": "string",
"fields": [
{
"name": "string",
"sensitive": "boolean",
"hint": "string (optional)",
"kind": "string (optional)",
"aliases": ["string"]
}
]
}
Examples
List fields for a database secret:
// Input
{
"key": "database/production"
}
// Output
{
"key": "database/production",
"fields": [
{
"name": "host",
"sensitive": false,
"hint": "Database hostname"
},
{
"name": "port",
"sensitive": false,
"hint": "Database port"
},
{
"name": "username",
"sensitive": false,
"hint": "Database username"
},
{
"name": "password",
"sensitive": true,
"hint": "Database password"
}
]
}
secret_get_field
Get a specific field value from a multi-field secret. Only non-sensitive fields can be retrieved (AI-Safe Access policy). Sensitive fields will be rejected.
Input Schema
{
"key": "string",
"field": "string"
}
| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | The secret key |
field | string | Yes | The field name to retrieve |
Output Schema
{
"key": "string",
"field": "string",
"value": "string",
"sensitive": "boolean"
}
Examples
Get non-sensitive field:
// Input
{
"key": "database/production",
"field": "host"
}
// Output
{
"key": "database/production",
"field": "host",
"value": "db.example.com",
"sensitive": false
}
Attempt to get sensitive field (rejected):
// Input
{
"key": "database/production",
"field": "password"
}
// Error Response
{
"error": "field 'password' is marked as sensitive and cannot be retrieved via MCP (AI-Safe Access policy)"
}
secret_run_with_bindings
Execute a command with environment variables injected based on the secret's predefined bindings. Each binding maps an environment variable name to a field. Requires policy approval.
Input Schema
{
"key": "string",
"command": "string",
"args": ["string"],
"timeout": "string (optional)"
}
| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | Secret key with bindings defined |
command | string | Yes | Command to execute |
args | string[] | No | Command arguments |
timeout | string | No | Execution timeout (e.g., 30s, 5m). Default: 5m |
Output Schema
{
"exit_code": "integer",
"stdout": "string",
"stderr": "string",
"sanitized": "boolean"
}
How Bindings Work
When a secret is created with bindings, each binding maps an environment variable name to a field:
secretctl set database/production \
--field host=db.example.com \
--field port=5432 \
--field password \
--binding PGHOST=host \
--binding PGPORT=port \
--binding PGPASSWORD=password
When secret_run_with_bindings is called with this secret, the following environment variables are set:
PGHOST=db.example.comPGPORT=5432PGPASSWORD=<password value>
Examples
Run PostgreSQL command:
// Input
{
"key": "database/production",
"command": "psql",
"args": ["-c", "SELECT 1"]
}
// Output
{
"exit_code": 0,
"stdout": " ?column? \n----------\n 1\n(1 row)\n",
"stderr": "",
"sanitized": true
}
Policy Configuration
The secret_run and secret_run_with_bindings tools require policy approval. Create ~/.secretctl/mcp-policy.yaml:
version: 1
default_action: deny
# Commands that are always blocked (security)
# - env, printenv, set, export, cat /proc/*/environ
# User-defined denied commands
denied_commands: []
# Allowed commands (required when default_action is deny)
allowed_commands:
- aws
- gcloud
- kubectl
- curl
- ./deploy.sh
# Environment aliases for key transformation
env_aliases:
dev:
- pattern: "db/*"
target: "dev/db/*"
prod:
- pattern: "db/*"
target: "prod/db/*"
Policy Evaluation Order
- Default denied commands (always blocked):
env,printenv,set,export - User-defined
denied_commands: Explicitly blocked commands - User-defined
allowed_commands: Explicitly allowed commands default_action: Fallback action (allowordeny)
Security Requirements
The policy file must meet these requirements:
- File permissions:
0600(owner read/write only) - No symlinks (direct file only)
- Owned by current user
Security Design
AI-Safe Access Architecture
The secretctl MCP server follows the "AI-Safe Access" security model:
| Tool | Plaintext Access | Purpose |
|---|---|---|
secret_list | No | List keys and metadata only |
secret_exists | No | Check existence and metadata |
secret_get_masked | No | Verify format without exposure |
secret_run | No* | Inject via environment variables |
secret_list_fields | No | List field names and metadata only |
secret_get_field | No** | Non-sensitive fields only |
secret_run_with_bindings | No* | Inject via predefined bindings |
* Secrets are injected as environment variables into the child process. The AI agent never sees the plaintext values.
** Only fields marked as sensitive: false can be retrieved. Sensitive fields are rejected with an error.
Why No secret_get?
A secret_get tool that returns plaintext values is intentionally not implemented. This design choice aligns with:
- 1Password: Explicitly refuses to expose raw credentials via MCP
- HashiCorp Vault: "Raw secrets never exposed" policy
- Industry best practice: Minimize secret exposure surface
Output Sanitization
The secret_run tool automatically sanitizes command output to prevent accidental secret leakage:
- Exact string matching for secret values
- Replacement with
[REDACTED:key]placeholder - Applied to both stdout and stderr
Limitations:
- Base64 or hex-encoded secrets are not detected
- Partial matches are not detected
- Obfuscated or transformed values are not detected
Error Handling
Common Errors
| Error | Description |
|---|---|
secret not found | The requested secret key does not exist |
policy not found | No MCP policy file exists |
command not allowed | Command blocked by policy |
timeout exceeded | Command execution exceeded timeout |
Error Response Format
{
"error": {
"code": -32000,
"message": "secret not found: API_KEY"
}
}
Integration Example
Configure in Claude Code (~/.claude.json):
{
"mcpServers": {
"secretctl": {
"command": "/path/to/secretctl",
"args": ["mcp-server"],
"env": {
"SECRETCTL_PASSWORD": "your-master-password"
}
}
}
}
See MCP Integration Guide for detailed setup instructions.