Provides secure secrets management through HashiCorp Vault, enabling agent-scoped secret storage, retrieval, and management with KV v2 operations, optional Transit encryption/decryption, and per-agent namespacing with authentication via API keys, JWT, or mTLS
Vault MCP Bridge
Overview
FastAPI MCP-compatible server that manages agent-scoped secrets and crypto via HashiCorp Vault.
Auth: API Key, JWT (HS256 and RS256/JWKS), and mTLS via reverse-proxy headers.
Per-agent namespacing in KV v2; Transit support (encrypt/decrypt/sign/verify/rewrap/random).
Optional per-request child token issuance bound to per-agent policies; simple per-agent rate limiting.
Prometheus metrics at
/metricsand optional OpenTelemetry tracing.
Quickstart
Python env:
python3 -m venv .venv && source .venv/bin/activatepython -m pip install -r requirements.txt
Start local Vault (dev) for testing:
cd local-vault && docker compose up -d && cd ..This provisions KV v2 at
secret/, a Transit key, and example policies. Seelocal-vault/README.md.
Run server:
Easiest:
python main.py(env:HOST=0.0.0.0 PORT=8089 RELOAD=true LOG_LEVEL=debug)Or:
python -m uvicorn main:app --reloadOr factory:
python -m uvicorn vault_mcp.app:create_app --factory --reload
Helper:
scripts/run_dev_jwt.shstarts the app with Vault + JWT defaults (no manual env export).Sanity checks:
curl http://127.0.0.1:8089/healthzbash scripts/smoke.sh(expects local Vault and default dev auth)
Vault access (required before running the server):
Set credentials so the app can authenticate to Vault, e.g. for the bundled dev compose stack:
export VAULT_ADDR=http://127.0.0.1:8200 export VAULT_TOKEN=root # replace with your own token or AppRoleWithout these env vars (or the AppRole equivalents)
/readyzwill return 503 and observability/secret routes will be unauthorized.To avoid re-exporting each run you can
source config/dev-jwt.env(new sample file) before launching, or usescripts/run_dev_jwt.shwhich applies the same defaults automatically.
Optional admin UI:
python3 -m venv .ui-venv && source .ui-venv/bin/activatepip install -r ui/requirements.txtstreamlit run ui/streamlit_app.pyUse the Manage Subjects & Keys page to manage
config/users.json, rotate credentials, and sync profiles to the sidebar.JWT helpers in the UI require
JWT_HS256_SECRETto be set before launch (e.g.,source config/dev-jwt.envor runscripts/run_dev_jwt.sh).Use AWS KMS Operations to supply temporary AWS credentials (if needed) and call encrypt/decrypt, data-key, sign, and verify APIs.
JWT quickstart (HS256):
Install helper deps:
pip install 'python-jose[cryptography]'Generate a token with the bundled script (adjust subject/scopes as needed):
- python scripts/gen_jwt.py \ --secret dev-secret \ --issuer mcp-auth \ --audience mcp-agents \ --sub agent_api \ --scopes read,write,delete,list \ --ttl 600
Use the printed value as the
Authorization: Bearer <token>header when calling the API or configuring the Streamlit console.
Features
KV v2 secret CRUD with per-agent prefixes and safe pathing.
Transit: encrypt/decrypt, sign/verify, rewrap, and random bytes (base64/hex).
AWS KMS (optional): encrypt/decrypt, generate data keys, and sign/verify with native AWS keys.
Database: dynamic credentials issuance and lease management.
SSH: OTP credential and SSH certificate signing.
Auth: API Key, JWT (HS256 or RS256 via JWKS), mTLS via headers.
Child tokens per request (optional); per-agent in-memory rate limiting.
MCP: JSON-RPC over HTTP at
POST /mcp/rpc(withGET /mcp/ssekeepalive channel) and stdio transport viascripts/mcp_stdio.py.Streamlit operations hub: Vault Operations covers Secrets, Transit, Database, SSH, and MCP tools, while a dedicated AWS KMS page lets you enter temporary AWS credentials and invoke encrypt/decrypt, data-key, and signing APIs.
Streamlit agent admin: create multiple AI agent profiles, toggle LLM usage, assign credentials (linked user/API key/JWT), define tasks, capture a
secrets_backendplan (Vault/KMS/Hybrid), and monitor progress/status.MCP lifecycle:
initialize,tools/list,resources/list,prompts/list,tools/call,shutdown. Protocol version:2025-06-18.Tools exposed (with required scopes):
KV:
kv.read(read, supportsversion),kv.write(write),kv.list(list),kv.delete(delete),kv.undelete(write),kv.destroy(write)Transit:
transit.encrypt(write),transit.decrypt(read),transit.sign(write),transit.verify(read),transit.rewrap(write),transit.random(read)DB:
db.issue_creds(write),db.renew(write),db.revoke(write)SSH:
ssh.otp(write),ssh.sign(write)KMS:
kms.encrypt(write),kms.decrypt(read),kms.generate_data_key(write),kms.sign(write),kms.verify(read)
Observability endpoints:
/observability/summary(Vault/API status + in-flight requests) and/observability/logs/{requests|responses|server}(tail JSON logs, read scope required).Metrics at
/metrics; optional OpenTelemetry via OTLP HTTP exporter.
Resources
Scheme
kv://{subject}/{path}with optional?version=N.resources/list: advertiseskv://{subject}/(KV root) for the authenticated subject.resources/get:kv://{subject}/foo/barreturns{ data, version }(JSON) for that KV path.kv://{subject}/foo/(trailing slash) returns{ keys: [...] }listing under that prefix.Cross-subject access is forbidden.
Prompts
prompts/list: returns prompt specs forkv_readandkv_writewith input schemas.prompts/get:kv_read: returns examplemessagesand asuggested_toolcall forkv.read.kv_write: returns examplemessagesand asuggested_toolcall forkv.write.
Configuration (env)
Vault:
VAULT_ADDR(default: http://localhost:8200)VAULT_NAMESPACE(Enterprise only)VAULT_TOKENorVAULT_ROLE_ID+VAULT_SECRET_IDKV_MOUNT(default: secret)DEFAULT_PREFIX(default: mcp)
Config file (optional, no .env needed):
Set
APP_CONFIG_FILEto a JSON/TOML/YAML file path. Example defaults auto-detected from CWD:config.toml,config.json,config.yaml.Environment variables always override file values.
Note:
.envfiles are not auto-loaded anymore.Precedence: runtime args (where applicable) → environment variables →
APP_CONFIG_FILE/auto-detected config → built-in defaults.
Auth enable flags:
AUTH_API_KEY_ENABLED(default: true)AUTH_JWT_ENABLED(default: true)AUTH_MTLS_ENABLED(default: false)CLI helper:
scripts/manage_user.py create <subject>writes metadata toconfig/users.jsonand prints policy/API key export commands.UI helper: in Streamlit, check “Generate JWT token” to issue a token during user creation. Generation failures show inline errors and the user entry is not written.
API Keys:
API_KEYS_JSONJSON map:{ "<api-key>": "<agent-id>" }
JWT:
Common:
JWT_ISSUER(default: mcp-auth),JWT_AUDIENCE(default: mcp-agents)HS256:
JWT_HS256_SECRETRS256/JWKS:
JWT_JWKS_URLorJWT_JWKS_FILE,JWT_JWKS_CACHE_SECONDS(default: 300),JWT_REQUIRE_KID(default: false)Validation toggles:
JWT_VALIDATE_ISSUER(default: true),JWT_VALIDATE_AUDIENCE(default: true)Helper:
python scripts/gen_jwt.py --secret <JWT_HS256_SECRET> --sub <agent>for quick dev tokens (see Quickstart example above).Metadata persisted per user:
jwt_created_at,jwt_expires_at,jwt_ttl_seconds. The Streamlit Current users grid surfaces status, timestamps, TTL seconds, and offers CSV export.
mTLS via proxy headers:
MTLS_IDENTITY_HEADER(default: x-ssl-client-s-dn)MTLS_VERIFY_HEADER(default: x-ssl-client-verify)MTLS_SUBJECT_CN_PREFIX(default: CN=)
Child token issuance:
CHILD_TOKEN_ENABLED(default: false)CHILD_TOKEN_TTL(default: 90s)CHILD_TOKEN_POLICY_PREFIX(default: mcp-agent-)
Rate limiting:
RATE_LIMIT_ENABLED(default: true)RATE_LIMIT_REQUESTS(default: 60)RATE_LIMIT_WINDOW_SECONDS(default: 60)
Server behavior:
HOST,PORT,RELOAD,LOG_LEVELforpython main.pyLogs directory:
LOG_DIR(default:./logs)EXPOSE_REST_ROUTES(default: true) — when false, disables REST feature routers (/secrets,/transit,/db,/ssh,/whoami) for MCP‑only deployments.AWS KMS (optional):
AWS_KMS_ENABLED(default: false)AWS_REGION(required when enabling KMS unless provided by IAM role/default profile)AWS_KMS_DEFAULT_KEY_ID(optional convenience default for encrypt/data-key/sign requests)AWS_KMS_ENDPOINT(optional; point to LocalStack or custom endpoint)AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_SESSION_TOKEN(optional; falls back to standard AWS credential resolution)
Observability:
Prometheus at
/metrics(always enabled)OpenTelemetry:
OTEL_EXPORTER_OTLP_ENDPOINT,OTEL_SERVICE_NAME(optional)
Auth Modes
API Key: send
X-API-Key: <key>. Map keys to agents viaAPI_KEYS_JSON.JWT: send
Authorization: Bearer <token>withsuband optionalscopes.mTLS: terminate TLS at proxy and pass DN via
X-SSL-Client-S-DN; CN is used as subject.
Agent Path Namespace
Secrets live under
{KV_MOUNT}/data/{DEFAULT_PREFIX}/{subject}/...(KV v2). The server enforces safe relative paths within the agent prefix.
Child Token Issuance
If
CHILD_TOKEN_ENABLED=true, a child token is minted per request with policy{CHILD_TOKEN_POLICY_PREFIX}{subject}and TTLCHILD_TOKEN_TTL.Ensure the policy exists and the parent token can create child tokens.
Policy
Generate HCL for an agent:
python scripts/gen_policy.py --agent alice --mount secret --prefix mcp > alice.hclSuggested policy name:
mcp-agent-alice
The policy grants CRUD/list on
data/{prefix}/{agent}/*, list/read onmetadata/{prefix}/{agent}/*, and versioned ops on delete/undelete/destroy.Apply with the Vault CLI:
vault policy write mcp-agent-alice alice.hcl
Endpoints
KV v2
PUT
/secrets/{path}— write (scope: write)GET
/secrets/{path}— read (scope: read) [queryversionoptional]DELETE
/secrets/{path}— delete latest version (scope: delete)GET
/secrets?prefix=...— list keys (scope: list)POST
/secrets/{path}:undelete— body{ "versions": [1,2] }(scope: write)POST
/secrets/{path}:destroy— body{ "versions": [1,2] }(scope: write)
Transit
POST
/transit/encrypt—{ "key": "k", "plaintext": "<b64>" }(scope: write)POST
/transit/decrypt—{ "key": "k", "ciphertext": "..." }(scope: read)POST
/transit/sign—{ key, input, hash_algorithm?, signature_algorithm? }(scope: write)POST
/transit/verify—{ key, input, signature, hash_algorithm? }(scope: read)POST
/transit/rewrap—{ key, ciphertext }(scope: write)GET
/transit/random?bytes=32&format=base64|hex(scope: read)
KMS endpoints are always exposed for development but return 503 unless
AWS_KMS_ENABLED=truePOST
/kms/encrypt—{ plaintext: <b64>, key_id?, encryption_context?, grant_tokens? }(scope: write)POST
/kms/decrypt—{ ciphertext: <b64>, encryption_context?, grant_tokens? }(scope: read)POST
/kms/data-key—{ key_id?, key_spec? | number_of_bytes?, encryption_context?, grant_tokens? }(scope: write)POST
/kms/sign—{ key_id?, message? | message_digest?, signing_algorithm, message_type?, grant_tokens? }(scope: write)POST
/kms/verify—{ key_id?, signature: <b64>, message? | message_digest?, signing_algorithm, message_type?, grant_tokens? }(scope: read)
Database
POST
/db/creds/{role}— issue dynamic DB creds (scope: write)POST
/db/renew—{ lease_id, increment? }(scope: write)POST
/db/revoke—{ lease_id }(scope: write)
SSH
POST
/ssh/otp—{ role, ip, username, port? }(scope: write)POST
/ssh/sign—{ role, public_key, cert_type?, valid_principals?, ttl? }(scope: write)
Health/Debug/Metrics
GET
/healthz,/livez,/readyz,/whoami,/echo-headers,/metrics/readyzreturns a JSON body detailing Vault (ok/detail) and, when enabled, AWS KMS readiness. Expect HTTP 503 with explanatory messages when Vault credentials are missing or KMS is misconfigured.
MCP
Mounted at
/mcpwhenfastapi-mcpis available
API Docs
Swagger UI:
http://127.0.0.1:8089/docsReDoc:
http://127.0.0.1:8089/redocOpenAPI:
http://127.0.0.1:8089/openapi.json
MCP Usage
HTTP JSON-RPC:
POST /mcp/rpcwith JSON-RPC 2.0 messages; authenticate same as REST (API key / JWT / mTLS).SSE channel:
GET /mcp/sseprovides periodic keepalives for server→client messaging (placeholder; extend as needed).stdio:
SUBJECT=agentA python scripts/mcp_stdio.pyand write newline-delimited JSON-RPC messages to stdin.Initialize result includes
protocolVersion: 2025-06-18, basic capabilities, and lists tools/resources/prompts.SSE events: server emits
tool.completedevents with{type, tool, subject, ts}; keepalives every 15s.
MCP Inspector
An official, interactive UI for MCP servers (Swagger-like, but for MCP) that discovers tools/resources/prompts and lets you call them live.
Connect via HTTP:
RPC URL:
http://127.0.0.1:8089/mcp/rpcSSE URL (optional):
http://127.0.0.1:8089/mcp/sseAuth headers: add
X-API-Key: dev-api-key(orAuthorization: Bearer <JWT>) in the Inspector’s connection settings.If connecting from the hosted Inspector (HTTPS) to your local HTTP server, enable CORS:
export CORS_ALLOW_ORIGINS=https://inspector.modelcontextprotocol.ioConsider exposing your server via HTTPS (e.g., ngrok) to avoid mixed-content blocking.
Or connect via stdio:
Command:
SUBJECT=agent_api python scripts/mcp_stdio.pyInspector will spawn the process and speak JSON-RPC over stdio.
Once connected, Inspector should list the available tools:
kv.read,kv.write,kv.list,kv.delete,kv.undelete,kv.destroy.Current state: Resources/Prompts are empty; SSE sends keepalives only.
Troubleshooting Inspector
ModuleNotFoundError: vault_mcpwhen running stdio: ensure you run from repo root, or useSUBJECT=agent_api PYTHONPATH=$(pwd) python scripts/mcp_stdio.py. The script now auto-adds repo root tosys.path.CORS errors in the browser: set
CORS_ALLOW_ORIGINS=https://inspector.modelcontextprotocol.io(comma separate multiple origins) and restart the server.Mixed content blocked: use an HTTPS tunnel to your local server (e.g.,
ngrok http 8089) and switch Inspector URLs tohttps.
Prometheus & OpenTelemetry
Prometheus endpoint:
GET /metrics(text). Quick check:curl -s http://127.0.0.1:8089/metrics | head.Metrics include
http_requests_totalandhttp_request_duration_secondswith labelsmethod,route,status.Additional telemetry:
http_requests_with_correlation_totalcounts requests that include or receive a correlation ID. Every HTTP response returnsX-Correlation-Id; if OTEL tracing is enabled,X-Trace-Idis also emitted.OpenTelemetry tracing (optional): set
OTEL_EXPORTER_OTLP_ENDPOINT(e.g.,http://localhost:4318/v1/traces) andOTEL_SERVICE_NAME(default:vault-mcp).Structured logs: JSON files under
./logs/(requests.log,responses.log,server.log). Tail withtail -f logs/requests.log.
Logging Details
Format: newline-delimited JSON. Core fields:
ts,lvl,msg,loggerplus context inextra.Request logs (
vault_mcp.request): includerequest_id,client,method,path,status,duration_ms.Response/event logs (
vault_mcp.response): per-endpoint keys, e.g.,kv_put|kv_get|kv_delete:subject,path,keys,version,request_idkv_list:subject,prefix,count,request_idtransit_*:subject,key, size/validity hints,request_iddb_*andssh_*: high-level descriptors (e.g.,role,lease_id_suffix,ip,user), never secret values
Request ID: responses include
X-Request-Id; it is echoed in logs for correlation.Example request log line:
{ "ts": "2024-01-01T10:00:00", "lvl": "info", "msg": "request", "logger": "vault_mcp.request", "request_id": "...", "client": "127.0.0.1", "method": "GET", "path": "/secrets", "status": 200, "duration_ms": 12 }
Examples (curl)
Write then read a secret (API key
dev-keyfor agentagent_api):curl -X PUT -H 'X-API-Key: dev-key' -H 'Content-Type: application/json' \ -d '{"data": {"foo":"bar"}}' http://127.0.0.1:8089/secrets/configs/democurl -H 'X-API-Key: dev-key' http://127.0.0.1:8089/secrets/configs/demo
Random bytes from Transit (hex):
curl -H 'X-API-Key: dev-key' 'http://127.0.0.1:8089/transit/random?bytes=16&format=hex'
RS256/JWKS quick test:
See
local-vault/jwks/README.mdfor generating keys, running JWKS, and testing.
Local Dev Helpers
Start server with sensible dev env:
bash scripts/run_dev.shEnable all auth regardless of current env:
bash scripts/run_all_auth.shSmoke test (server + local Vault):
bash scripts/smoke.shAuth tests by agent:
API key (agent_api):
bash scripts/test_agent_api.shJWT HS256 (agent_jwt):
bash scripts/test_agent_jwt.shJWT RS256/JWKS (agent_jwt):
bash scripts/test_agent_jwt_rs256.shmTLS headers (agent_mtls):
bash scripts/test_agent_mtls.sh
End-to-End and Example Agents
One-shot E2E (server + MCP HTTP + stdio + optional REST):
LOG_LEVEL=DEBUG bash scripts/e2e_local.sh
Example MCP agents (HTTP JSON-RPC):
API key:
bash scripts/run_example_agent.shJWT:
bash scripts/run_example_agent.sh --jwt 'YOUR_JWT'mTLS headers:
bash scripts/run_example_agent.sh --mtlsNo‑LLM direct client (no model provider):
bash scripts/run_example_agent.sh --no-llm
Examples
LangChain agent that wraps these endpoints as tools:
See
examples/langchain_agent/README.mdandexamples/langchain_agent/agent.py
Testing
Run tests:
pytestPytest overview:
tests/test_health.py: Verifies basic liveness endpoints —GET /healthzandGET /livezreturnok: true.tests/test_auth_and_kv.py: Exercises API‑key auth and KV v2 CRUD.Uses a mocked hvac KV client to avoid real Vault.
Flow:
PUT /secrets/configs/demowrites data,GETreads it back,DELETEremoves it, subsequentGETreturns 404.Also checks
GET /whoamireturns the expected subject forX-API-Key.
tests/test_transit_random.py: Tests Transit random byte generation endpoint with deterministic mock.Monkeypatches
client_for_principalto return a stub wheregenerate_random_bytesis predictable.Validates both
format=hexand defaultbase64responses forGET /transit/random.
tests/test_health_ready.py: Covers/readyzfor authenticated, unauthenticated, Vault error, and generic error cases.tests/test_kv_extras.py: CoversGET /secrets?prefix=...list and version ops (:undelete,:destroy).tests/test_transit_endpoints.py: Covers/transit/encrypt|decrypt|sign|verify|rewrapwith a transit stub.tests/test_db_and_ssh_routes.py: Covers/db/creds|renew|revokeand/ssh/otp|signwith stubs.tests/test_auth_modes.py: HS256 JWT/whoami(valid and bad aud), mTLS header success/fail.tests/test_auth_jwt_rs256_local.py: Local RS256: generates RSA + JWKS and validates/whoamivia monkeypatched JWKS.tests/test_rate_limit_and_metrics.py: Verifies/metricsand rate limiting on/transit/random(429 on third call).tests/test_security_path_and_scopes.py: Path sanitization and 403 when scopes are insufficient.tests/test_app_exception_handlers.py: Maps VaultForbidden-> 403 andVaultError-> 502 JSON responses.(If you add MCP client tests) exercise
POST /mcp/rpcforinitialize,tools/list, andtools/callwith a JWT or API key.
Run subsets
Keyword filter:
pytest -k transitCoverage detail:
pytest -q --cov=vault_mcp --cov-report=term-missing
Security Notes
Use TLS end-to-end; for mTLS, terminate at a trusted proxy and pass identity headers.
Avoid logging secret values; the app uses structured logging with response metadata only.
Prefer JWT or mTLS in production; reserve API keys for development.
Enable Vault audit devices and keep token TTLs minimal.
Troubleshooting
Import errors (e.g., fastapi not found): ensure you use the same Python interpreter that installed deps.
python -m uvicorn main:app --reload
Uvicorn targets: use
<module>:<attribute>— e.g.,main:app.Change port/host:
python -m uvicorn main:app --reload --port 8090 --host 0.0.0.0Increase logs: add
--log-level debug --access-log
This server cannot be installed
remote-capable server
The server can be hosted and run remotely because it primarily relies on remote services or has no dependency on the local environment.
Enables secure management of agent-scoped secrets in HashiCorp Vault through MCP protocol. Provides per-agent namespacing, multiple authentication methods (API key, JWT, mTLS), and optional encryption/decryption capabilities with built-in rate limiting.
Related MCP Servers
- -security-license-qualityProvides an MCP server that allows AI assistants to interact with Obsidian vaults, enabling reading/writing notes, managing metadata, searching content, and working with daily notes.Last updated -28MIT License
- -security-license-qualityA proof-of-concept server that securely retrieves credentials from 1Password vaults and provides them to AI agents via Model Context Protocol (MCP), enabling AI assistants to use stored credentials for tasks like automated logins.
- -security-license-qualityAn MCP (Multi-Agent Conversation Protocol) server that enables interaction with Google's Managed Service for Microsoft Active Directory through its OpenAPI, allowing users to manage identity resources through natural language.Last updated -
- -security-license-qualityAn MCP (Multi-Agent Conversation Protocol) Server that provides access to Google Cloud's Dataproc Metastore API, enabling AI agents to manage and interact with Hive metastore services through natural language.Last updated -