Background
vault-mcp-server currently only authenticates to Vault using a static token — either VAULT_TOKEN env var or X-Vault-Token per-request. In gateway-fronted deployments (e.g., Toolhive, or any reverse proxy that terminates user OIDC and forwards Authorization: Bearer <JWT> to the MCP server), this means:
- Every user reaches Vault as the same service-account identity, so Vault audit logs cannot attribute requests to real users.
- The static token must be granted the union of all users' privileges, breaking least-privilege.
- Per-user RBAC at the Vault layer (via group-driven policies) is impossible — authz can only be enforced at the gateway.
Proposal
Support Vault's JWT auth method as an alternative authentication mode. When configured, the server would:
- Read the JWT from the incoming request (e.g.,
Authorization: Bearer <JWT> header forwarded by the gateway).
- Exchange it via
POST /v1/auth/<jwt-mount>/login with role=<configured-role> and jwt=<incoming-jwt> to obtain a short-lived, user-scoped Vault token.
- Use that token (
X-Vault-Token) for the actual Vault API call(s) on behalf of the request.
- Optionally cache the resulting Vault token in-memory keyed by the JWT's
sub+iat (or jti) for the token's TTL, to avoid a /login round-trip per request.
This is the pattern Vault Agent, vault-secrets-operator, and most modern Vault clients already implement.
Configuration sketch
New env vars / flags (all optional; static-token mode remains the default):
| Variable |
Purpose |
VAULT_AUTH_METHOD |
token (default) or jwt |
VAULT_AUTH_JWT_PATH |
Mount path of the JWT auth method (default jwt) |
VAULT_AUTH_JWT_ROLE |
Vault role to authenticate against |
VAULT_AUTH_JWT_HEADER |
Header to read the JWT from (default Authorization) |
VAULT_AUTH_JWT_CACHE_TTL |
Optional cap on per-user token cache (default = role's token_ttl) |
When VAULT_AUTH_METHOD=jwt, the existing VAULT_TOKEN / X-Vault-Token paths are bypassed for that request.
Why this matters
In my organization's deployment (Vault behind Toolhive + Keycloak):
- Vault already has the JWT auth method wired to Keycloak with external-group → policy mappings (
/mcp-admin, /vault-kv-admins, /mcp-readonly), so per-user policy enforcement is fully working at the Vault layer for any client that can present the JWT.
- The Toolhive gateway already exchanges the user's Keycloak token to
aud=mcp-vault and forwards it as Authorization: Bearer to the backend.
- The only missing link is
vault-mcp-server doing the JWT-for-Vault-token exchange — without it, the per-user trust chain ends at the gateway and collapses into a shared service account at Vault.
Acceptance criteria
References
Background
vault-mcp-servercurrently only authenticates to Vault using a static token — eitherVAULT_TOKENenv var orX-Vault-Tokenper-request. In gateway-fronted deployments (e.g., Toolhive, or any reverse proxy that terminates user OIDC and forwardsAuthorization: Bearer <JWT>to the MCP server), this means:Proposal
Support Vault's JWT auth method as an alternative authentication mode. When configured, the server would:
Authorization: Bearer <JWT>header forwarded by the gateway).POST /v1/auth/<jwt-mount>/loginwithrole=<configured-role>andjwt=<incoming-jwt>to obtain a short-lived, user-scoped Vault token.X-Vault-Token) for the actual Vault API call(s) on behalf of the request.sub+iat(orjti) for the token's TTL, to avoid a/loginround-trip per request.This is the pattern Vault Agent, vault-secrets-operator, and most modern Vault clients already implement.
Configuration sketch
New env vars / flags (all optional; static-token mode remains the default):
VAULT_AUTH_METHODtoken(default) orjwtVAULT_AUTH_JWT_PATHjwt)VAULT_AUTH_JWT_ROLEVAULT_AUTH_JWT_HEADERAuthorization)VAULT_AUTH_JWT_CACHE_TTLtoken_ttl)When
VAULT_AUTH_METHOD=jwt, the existingVAULT_TOKEN/X-Vault-Tokenpaths are bypassed for that request.Why this matters
In my organization's deployment (Vault behind Toolhive + Keycloak):
/mcp-admin,/vault-kv-admins,/mcp-readonly), so per-user policy enforcement is fully working at the Vault layer for any client that can present the JWT.aud=mcp-vaultand forwards it asAuthorization: Bearerto the backend.vault-mcp-serverdoing the JWT-for-Vault-token exchange — without it, the per-user trust chain ends at the gateway and collapses into a shared service account at Vault.Acceptance criteria
/loginexchange with optional in-memory caching keyed by JWT identityReferences