Skip to main content
Glama
Yormede
by Yormede

safe-key-mcp

An MCP server for safe secret access by AI agents. Secrets are stored encrypted. AI agents see only names and descriptions, never values. All actions are executed server-side. Outputs are scrubbed before being returned.


Context — Why this project exists

Today, AI coding agents (Claude, GPT, Codex, etc.) increasingly need to interact with authenticated services: GitHub APIs, cloud providers, databases, SSH servers, email, etc.

The current solutions are problematic:

Approach

Problem

Paste tokens in the chat

Token ends up in conversation history, logs, and LLM provider servers

Environment variables

AI can echo $TOKEN and see the value

.env files

AI can cat .env

1Password MCP

Proprietary, paid, limited to 1Password users

The fundamental issue: every existing approach eventually exposes the secret value to the AI as plaintext text — which gets sent to inference servers, stored in logs, and leaked in conversation history.

The real threat model

The AI is not the enemy. The real risks are:

  • Accidental leaks in logs, debug output, error messages

  • Prompt injection from a malicious website extracting secrets

  • Conversation history readable by developers or stored on LLM servers

  • Verbose tool output that echoes back headers, URLs, or environment variables

We don't need military-grade sandboxing. We need secrets to never appear in the AI's context window.


Related MCP server: Janee

How it works

┌─────────────────────────────────────┐
│        safe-key-mcp server          │
│                                     │
│  vault.enc (AES-256-GCM encrypted) │
│  ┌─────────────────────────────┐    │
│  │ github_token = ghp_xxx      │    │
│  │ api_key      = sk-xxxxx     │    │
│  │ ssh_key_ovh  = -----BEGIN…  │    │
│  └─────────────────────────────┘    │
│                                     │
│  MCP Tools:                         │
│  • list_secrets() → names only      │
│  • http_request(secret, url, …)     │
│  • shell_exec(secret, template)     │
│  • ssh_exec(secret, host, cmd)      │
│                                     │
│  Output sanitizer:                  │
│  Scrubs all known secret values     │
│  from every response before sending │
└──────────────┬──────────────────────┘
               │
         [names only ↑]  [scrubbed output ↓]
               │
┌──────────────┴──────────────────────┐
│            AI Agent                  │
│  "Use github_token to GET /repos…"  │
│                                      │
│  Never sees: ghp_xxx                 │
│  Never sees: sk-xxxxx                │
│  Never sees: -----BEGIN…             │
└──────────────────────────────────────┘

Encryption

  • AES-256-GCM authenticated encryption

  • Master password derived via PBKDF2-HMAC-SHA256 (600,000 iterations)

  • Each save uses a fresh random salt (16 bytes) and nonce (12 bytes)

  • Vault file is a single binary blob — not human-readable

Sanitizer

The output sanitizer is a defence-in-depth measure. It replaces any occurrence of a known secret value with [REDACTED] in all tool responses. This catches:

  • Error messages that echo back a URL containing a token

  • Verbose curl/HTTP output that includes headers

  • Shell command output that accidentally prints environment variables

It is not a security boundary against a determined attacker with shell access.


MCP Tools

Tool

What the AI sends

What the AI receives

list_secrets

nothing

names + descriptions (no values)

http_request

secret_name, url, method, auth_style

HTTP response with values scrubbed

shell_exec

secret_name, command template with {SECRET}

stdout/stderr with values scrubbed

ssh_exec

secret_name, host, username, command

stdout/stderr with values scrubbed

http_request auth styles

  • bearerAuthorization: Bearer <value>

  • basic_user — HTTP Basic Auth (secret as username)

  • basic_password — HTTP Basic Auth (secret as password)

  • header — Custom header (specify header_name)

  • query_param — URL query parameter (specify param_name)

shell_exec template

The command must contain exactly one {SECRET} placeholder:

git clone https://user:{SECRET}@github.com/org/repo.git

The server replaces {SECRET} with the actual value, runs the command, and scrubs the output.


Quick start

Prerequisites

  • Python 3.10+

  • pip install -e ".[dev]"

Add secrets (CLI)

export SAFE_KEY_MASTER_PASSWORD="your-strong-password"

# Interactive (password prompt, value never shown)
python -m safe_key_mcp add github_token -d "GitHub Personal Access Token" -t "ci,github"
python -m safe_key_mcp add openai_key -d "OpenAI API key" -t "llm"

# List (values never shown)
python -m safe_key_mcp list

Run the server

export SAFE_KEY_MASTER_PASSWORD="your-strong-password"
python -m safe_key_mcp serve --host 127.0.0.1 --port 8500

Docker

cp .env.example .env
# Edit SAFE_KEY_MASTER_PASSWORD in .env

docker compose up -d
# Server available at http://localhost:8500/sse

Connect to your AI agent

Add to your MCP client config (e.g. OpenCode, Claude Desktop):

{
  "mcpServers": {
    "safe-key": {
      "url": "http://localhost:8500/sse"
    }
  }
}

Project structure

src/safe_key_mcp/
├── __init__.py       # Package docstring
├── __main__.py       # CLI: serve / add / list / delete
├── config.py         # Configuration via environment variables
├── vault.py          # AES-256-GCM encrypted storage
├── sanitizer.py      # Output scrubbing
└── server.py         # MCP server with 4 tools
tests/
├── conftest.py
├── test_vault.py     # 8 tests — encryption, persistence, auth
├── test_sanitizer.py # 6 tests — scrubbing, edge cases
└── test_server.py    # 3 tests — tool integration

Environment variables

Variable

Default

Description

SAFE_KEY_MASTER_PASSWORD

(required)

Master password for vault encryption

SAFE_KEY_VAULT_PATH

./data/vault.enc

Path to the encrypted vault file

SAFE_KEY_HOST

0.0.0.0

SSE server host

SAFE_KEY_PORT

8500

SSE server port


Status

This project is under active development and has not been tested in production.

  • Core vault encryption: working (17/17 tests passing)

  • MCP tools: implemented, not yet tested with real services

  • Docker deployment: ready, not yet deployed

  • Sanitizer: functional, basic pattern matching

What's next — evolution toward a Service Gateway

During development, we realized that this "vault + secret names" approach still has a conceptual flaw: the AI knows that secrets exist. It manipulates secret names, chooses auth styles, and builds authenticated requests — it's just one abstraction layer away from the values.

The next evolution of this project will be a Service Gateway where:

  • The AI doesn't know secrets exist at all

  • It just calls services: service_call("github", "GET", "/repos/owner/repo")

  • Auth is resolved entirely server-side by config mapping

  • The AI's mental model is "use this service", not "use this secret"

This is a fundamentally different approach — closer to how a browser handles cookies than how a developer handles API keys. That work will happen in a separate repository.


License

MIT

Maintenance

ActivitySlowing
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    MCP server that lets AI agents call APIs without ever seeing the credentials, using a local encrypted vault and per-secret allowlist policies for HTTP requests and subprocess environment variables.
    1
    AGPL 3.0
  • A
    license
    Not graded
    quality
    C
    maintenance
    Secrets management MCP server that injects credentials into API requests for AI agents, enforcing policies and logging all activity without exposing raw keys.
    54
    30
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Encrypted secrets management MCP server for AI agents, enabling secure storage, retrieval, rotation, and auditing of API keys and credentials with AES-128 encryption.
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    MCP server for end-to-end encrypted secret storage and retrieval, enabling LLM agents to fetch secrets by name via one-time URLs while keeping plaintext out of model context and logs.
    1
    -