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

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
}

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

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