secret-vault-mcp

secret-vault-mcp

Local, encrypted password vault with AES-256-GCM and PBKDF2, exposing MCP tools for secure credential management, password generation, and search.

Category
访问服务器

README

secret-vault-mcp

Local, encrypted password vault exposed as a Model Context Protocol (MCP) server. Secrets live on disk in AES-256-GCM ciphertext, unlocked by a master password derived via PBKDF2-HMAC-SHA256 (600,000 iterations). The master password is never written, never logged, and never transmitted.

Designed to be consumed by any MCP client (opencode, Claude Desktop, Cursor, etc.) via stdio transport.

Why

  • Local-first: no network, no cloud, no telemetry.
  • Strong crypto: AES-256-GCM with fresh 12-byte nonce per write; PBKDF2 with OWASP-recommended 600k iterations.
  • MCP-native: all 8 vault operations are first-class tools the LLM can call.
  • Auditable: ~87% test coverage, small surface, no exotic dependencies.

Tools

Tool Description Inputs
add_secret Add a new entry name, username, password, url?, notes?
get_secret Fetch an entry; password revealed only when reveal_password=true name, reveal_password=false
list_secrets List all entries without exposing passwords
update_secret Update one or more fields of an existing entry name, username?, password?, url?, notes?
delete_secret Remove an entry by name name
generate_password Generate a strong random password length=20, upper=true, lower=true, digits=true, symbols=true, exclude_ambiguous=false
search_secrets Case-insensitive substring search across name/url/username/notes query
vault_status Vault metadata: entry count, last access timestamp, version

All tools return JSON. Errors are returned as {"error": "..."} without stack traces or internal state.

Examples

Once registered in your MCP client, the 8 tools are available to the LLM. You invoke them with natural language — the client translates into MCP tools/call.

In opencode / Claude Desktop / Cursor (natural language):

# Store a secret
> add a secret called github with username felipe and password xK9!mP2qR8vN4wL7

# List everything (no passwords exposed)
> show me all my saved credentials

# Retrieve one (without exposing the password)
> get the github entry

# Retrieve and reveal
> get the github entry and show me the password

# Generate a strong password
> generate a 40-character password with symbols, no ambiguous characters

# Search across all fields
> find any entry that mentions "opencode"

# Update (rotate a password)
> rotate the password for the github entry to a new generated one

# Delete
> delete the entry called old-mailbox

# Status
> how many entries are in the vault and when was it last accessed?

Equivalent raw tool calls (what the client sends over JSON-RPC):

{ "method": "tools/call", "params": { "name": "add_secret",
  "arguments": { "name": "github", "username": "felipe", "password": "xK9!mP2qR8vN4wL7" } } }

{ "method": "tools/call", "params": { "name": "list_secrets" } }

{ "method": "tools/call", "params": { "name": "get_secret",
  "arguments": { "name": "github", "reveal_password": true } } }

{ "method": "tools/call", "params": { "name": "generate_password",
  "arguments": { "length": 40, "symbols": true, "exclude_ambiguous": true } } }

{ "method": "tools/call", "params": { "name": "search_secrets",
  "arguments": { "query": "opencode" } } }

{ "method": "tools/call", "params": { "name": "update_secret",
  "arguments": { "name": "github", "password": "<new-generated>" } } }

{ "method": "tools/call", "params": { "name": "delete_secret",
  "arguments": { "name": "old-mailbox" } } }

{ "method": "tools/call", "params": { "name": "vault_status" } }

Calling from a Python script (using the official mcp client):

import asyncio, os
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

async def main():
    params = StdioServerParameters(
        command="/home/felipe/infra/secret-vault-mcp/.venv/bin/python3",
        args=["-u", "/home/felipe/infra/secret-vault-mcp/server.py"],
        env={**os.environ, "VAULT_MASTER_PASSWORD": "your-long-passphrase"},
    )
    async with stdio_client(params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()

            await session.call_tool("add_secret", {
                "name": "github", "username": "felipe", "password": "xK9!mP2qR8vN4wL7",
                "url": "https://github.com",
            })

            result = await session.call_tool("get_secret", {
                "name": "github", "reveal_password": True,
            })
            print(result.content[0].text)

asyncio.run(main())

Typical workflows:

# Onboarding: create a few entries
> store these credentials: github/felipe/<gen-32>, aws-root/admin/<gen-40>, redis-local/redis/<gen-24>

# Daily use: look up a password to paste into another tool
> get the aws-root entry and reveal the password

# Rotation: replace a weak/old password
> generate a 32-character password, then update the github entry with it

# Audit: what do I have stored?
> list all entries and group them by URL domain

# Cleanup: remove obsolete entries
> delete entries named "test-1", "test-2", and "demo"

Security Model

  • Cipher: AES-256-GCM, 12-byte random nonce per encryption operation.
  • KDF: PBKDF2-HMAC-SHA256, 600,000 iterations, 16-byte random salt.
  • Storage layout (XDG Base Directory):
    • vault.enc — ciphertext, mode 0600
    • vault.salt — salt, mode 0600
    • vault.lockflock for inter-process serialization
  • Concurrency: flock (inter-process) + threading.RLock (intra-process re-entrant).
  • Atomic writes: temp file + os.replace — no partial writes.
  • Master password: required at server start via VAULT_MASTER_PASSWORD; never logged, never printed, never cached to disk.
  • Passwords in responses: list_secrets always returns password="". get_secret returns password="" unless reveal_password=true.

⚠️ Threat model: protects against at-rest disclosure of the vault files (e.g. disk theft, backup leakage). Does not protect against a compromised runtime or a malicious MCP client that calls reveal_password=true on your behalf. Treat the master password as the root of trust.

Setup

cd ~/infra/secret-vault-mcp
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
cp .env.example .env
# edit .env and set VAULT_MASTER_PASSWORD

Run

# via the MCP stdio transport (used by opencode/Claude Desktop/Cursor)
python server.py

# or with the master password inline
VAULT_MASTER_PASSWORD="my-long-passphrase" python server.py

Tests

pytest                                        # 47 tests
pytest --cov=auth --cov=client --cov=models --cov=server

Current coverage: ~87% (target ≥80%).

File Layout (Runtime)

$XDG_DATA_HOME/secret-vault-mcp/   (default: ~/.local/share/secret-vault-mcp/)
  ├── vault.enc      # AES-256-GCM ciphertext
  ├── vault.salt     # 16-byte salt (0600)
  └── vault.lock     # flock file

Override the directory with XDG_DATA_HOME.

Project Layout

secret-vault-mcp/
  ├── auth.py            # VaultConfig + constants (KDF, cipher, paths)
  ├── client.py          # VaultClient (encrypt/decrypt, CRUD, lock, search, generate)
  ├── models.py          # Pydantic models (SecretEntry, SecretUpdate, VaultState)
  ├── server.py          # FastMCP server + tool definitions
  ├── tests/
  │   ├── conftest.py
  │   ├── test_auth.py
  │   ├── test_client.py
  │   └── test_server.py
  ├── pyproject.toml
  ├── .env.example
  ├── .gitignore
  ├── LICENSE
  ├── .github/workflows/ci.yml
  └── README.md

Register in opencode

Add the following to ~/.config/opencode/opencode.jsonc:

{
  "mcp": {
    "secret-vault": {
      "type": "stdio",
      "command": "/home/felipe/infra/secret-vault-mcp/.venv/bin/python3",
      "args": ["-u", "/home/felipe/infra/secret-vault-mcp/server.py"],
      "env": {
        "VAULT_MASTER_PASSWORD": "your-long-passphrase-here"
      },
      "enabled": true
    }
  }
}

🔐 Keep VAULT_MASTER_PASSWORD out of version control. Consider using a secret loader (1Password CLI, pass, system keyring) to inject it at startup.

CI

GitHub Actions runs ruff + pytest on every push/PR and creates a GitHub release on push to main using ncipollo/release-action.

License

MIT © 2026 Felipe Moura (@n8nfelipe)

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选