Skip to main content

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:

ToolDescription
secret_listList secret keys with metadata (no values)
secret_existsCheck if a secret exists with metadata
secret_get_maskedGet masked secret value (e.g., ****WXYZ)
secret_runExecute command with secrets as environment variables
secret_list_fieldsList field names for multi-field secrets (no values)
secret_get_fieldGet non-sensitive field values only
secret_run_with_bindingsExecute with predefined environment bindings
security_scoreGet vault security health score and recommendations

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)"
}
FieldTypeRequiredDescription
tagstringNoFilter by tag
expiring_withinstringNoFilter 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)"
}
]
}
FieldTypeDescription
keystringSecret key name
field_countnumberNumber of fields (1 for legacy single-value secrets)
tagsarrayTags associated with the secret
expires_atstringExpiration date in RFC 3339 format (optional)
has_notesbooleanWhether the secret has notes
has_urlbooleanWhether the secret has a URL
created_atstringCreation timestamp in RFC 3339 format
updated_atstringLast 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"
}
FieldTypeRequiredDescription
keystringYesThe 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"
}
FieldTypeRequiredDescription
keystringYesThe 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"
}
}
}
FieldTypeDescription
keystringThe secret key
masked_valuestringMasked primary value (single-value secrets only; empty for multi-field)
value_lengthintegerLength of the primary value (single-value secrets only; 0 for multi-field)
field_countintegerNumber of fields in this secret
fieldsobjectMap of field names to masked field info (multi-field secrets only)

Note: For multi-field secrets, masked_value and value_length are empty/0. Use the fields map 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 LengthMasked Output
1-4 chars**** (all asterisks)
5-8 chars******YZ (last 2 visible)
9+ chars*****WXYZ (last 4 visible)

Multi-field secrets:

  • fields map 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)"
}
FieldTypeRequiredDescription
keysstring[]YesSecret keys to inject (glob patterns supported)
commandstringYesCommand to execute
argsstring[]NoCommand arguments
timeoutstringNoExecution timeout (e.g., 30s, 5m). Default: 5m
env_prefixstringNoPrefix for environment variable names
envstringNoEnvironment alias (e.g., dev, staging, prod)

Output Schema​

{
"exit_code": "integer",
"stdout": "string",
"stderr": "string",
"duration_ms": "integer",
"sanitized": "boolean"
}
FieldTypeDescription
exit_codeintegerCommand exit code (0 = success)
stdoutstringStandard output (sanitized)
stderrstringStandard error (sanitized)
duration_msintegerExecution duration in milliseconds
sanitizedbooleanWhether output was sanitized

Key Pattern Syntax​

The keys field supports glob patterns:

PatternMatches
API_KEYExact 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 KeyEnvironment Variable
aws/access_keyAWS_ACCESS_KEY
db-passwordDB_PASSWORD
api/prod/keyAPI_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"
}
FieldTypeRequiredDescription
keystringYesThe 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"
}
FieldTypeRequiredDescription
keystringYesThe secret key
fieldstringYesThe 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)"
}
FieldTypeRequiredDescription
keystringYesSecret key with bindings defined
commandstringYesCommand to execute
argsstring[]NoCommand arguments
timeoutstringNoExecution 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.com
  • PGPORT=5432
  • PGPASSWORD=<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
}

security_score​

Get the security health score of your vault including password strength, duplicate detection, and expiration status. Returns a score from 0-100 with issue details and suggestions.

Input Schema​

{
"include_keys": "boolean (optional)"
}
FieldTypeRequiredDescription
include_keysbooleanNoWhether to include secret keys in issue details (default: false)

Output Schema​

{
"overall_score": "integer (0-100)",
"components": {
"strength": "integer (0-25)",
"uniqueness": "integer (0-25)",
"expiration": "integer (0-25)",
"coverage": "integer (0-25)"
},
"issues_count": {
"duplicates": "integer",
"weak": "integer",
"expiring": "integer",
"expired": "integer"
},
"top_issues": [
{
"type": "string",
"severity": "string",
"count": "integer",
"description": "string",
"secret_keys": ["string"]
}
],
"suggestions": ["string"],
"limited": "boolean"
}
FieldTypeDescription
overall_scoreintegerTotal security score (0-100)
componentsobjectScore breakdown by category (each 0-25)
components.strengthintegerPassword strength score
components.uniquenessintegerPassword uniqueness score
components.expirationintegerExpiration compliance score
components.coverageintegerField coverage score (Phase 3, currently always 25)
issues_countobjectCount of each issue type
top_issuesarrayIssues with details (Free: weak/duplicate limited to 3 each)
suggestionsarrayActionable recommendations
limitedbooleanTrue if results were limited (Free edition)

Issue Types​

TypeSeverityDescription
weakwarningPassword has insufficient strength
duplicatewarningMultiple secrets share the same password
expiringwarningSecret expires within warning period
expiredcriticalSecret has already expired

Examples​

Get security score:

// Input
{}

// Output
{
"overall_score": 85,
"components": {
"strength": 20,
"uniqueness": 25,
"expiration": 15,
"coverage": 25
},
"issues_count": {
"duplicates": 0,
"weak": 2,
"expiring": 1,
"expired": 0
},
"top_issues": [
{
"type": "weak",
"severity": "warning",
"description": "Password has insufficient strength"
},
{
"type": "expiring",
"severity": "warning",
"description": "Secret expires in 5 days"
}
],
"suggestions": [
"Update weak passwords with stronger alternatives (14+ characters)",
"Plan to renew expiring credentials before they expire"
],
"limited": false
}

Get security score with secret keys:

// Input
{
"include_keys": true
}

// Output
{
"overall_score": 85,
"components": {
"strength": 20,
"uniqueness": 25,
"expiration": 15,
"coverage": 25
},
"issues_count": {
"duplicates": 0,
"weak": 2,
"expiring": 1,
"expired": 0
},
"top_issues": [
{
"type": "weak",
"severity": "warning",
"description": "Password has insufficient strength",
"secret_keys": ["github-token", "api/legacy"]
},
{
"type": "expiring",
"severity": "warning",
"description": "Secret expires in 5 days",
"secret_keys": ["aws/temp-token"]
}
],
"suggestions": [
"Update weak passwords with stronger alternatives (14+ characters)",
"Plan to renew expiring credentials before they expire"
],
"limited": false
}

Free vs Team Edition​

FeatureFreeTeam
Security score✅✅
Issue counts✅✅
Weak password issues3 maxUnlimited
Duplicate password issues3 maxUnlimited
Expiring/expired issues✅✅
Secret keys in issues✅✅
Team-wide dashboard❌✅

When limited: true, some weak or duplicate password issues were omitted due to Free edition limits. Upgrade to Team edition for complete visibility.


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​

  1. Default denied commands (always blocked): env, printenv, set, export
  2. User-defined denied_commands: Explicitly blocked commands
  3. User-defined allowed_commands: Explicitly allowed commands
  4. default_action: Fallback action (allow or deny)

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:

ToolPlaintext AccessPurpose
secret_listNoList keys and metadata only
secret_existsNoCheck existence and metadata
secret_get_maskedNoVerify format without exposure
secret_runNo*Inject via environment variables
secret_list_fieldsNoList field names and metadata only
secret_get_fieldNo**Non-sensitive fields only
secret_run_with_bindingsNo*Inject via predefined bindings
security_scoreNoGet security metrics and recommendations

* 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​

ErrorDescription
secret not foundThe requested secret key does not exist
policy not foundNo MCP policy file exists
command not allowedCommand blocked by policy
timeout exceededCommand 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.