MCP Gatekeeper 访问控制网关

为 AI 编码智能体(如 Claude Code)提供基于 DUO 双重认证的分层访问控制,通过零常驻访问令牌代理安全接入 OpenBao/HashiCorp Vault,确保机密信息必须经过人工审批后才能被读取或写入。

  • by adamksmith
  • Python
  • 本地部署
  • 更新于
在 GitHub 查看 adamksmith/mcp-gatekeeper

使用说明

MCP Gatekeeper

Zero-standing-access token broker for AI coding agents. Sits between Claude Code (or any MCP client) and OpenBao / HashiCorp Vault, ensuring no secrets are accessible without explicit human approval via a DUO push to your phone.

The Problem

MCP servers inherit whatever access the host process has. Connect Claude Code to your infrastructure and it gets the same credentials you do — permanently, silently, for the entire session. There's no consent model, no escalation path, no timeout. The current MCP ecosystem gives you two options: full access or no access.

MCP Gatekeeper adds a third option: earned access — tiered, time-bounded, and re-approved at every step up.

How It Works

MCP Gatekeeper implements a three-tier access model where every tier transition requires a DUO push notification to your phone. The AI agent starts with nothing and must ask for what it needs.

┌─────────────────┐   DUO push    ┌─────────────────┐   DUO push    ┌─────────────────┐
│   No Access  │──────────────▶│   Read-Only  │──────────────▶│  Read-Write  │
│   (default)  │  authenticate │   (4hr TTL)  │   escalate   │  (15min TTL) │
└─────────────────┘               └─────────────────┘               └─────────────────┘
        ▲                              ▲  │                              │
        │           logout             │  │ auto-renews on expiry        │
        │◀─────────────────────────────┘  │ (DUO push)                   │
        │                                 │                              │
        │              logout             │  expires (hard) / deescalate │
        │◀────────────────────────────────│◀─────────────────────────────┘
                                          │     drops to RO, not No Access

Consent Model

Session-scoped consent (RO): "Yes, I'm working." Auto-renews transparently when the 4-hour TTL expires. You approved a work session — reads shouldn't interrupt your flow.

Task-scoped consent (RW): "Yes, do this dangerous thing." 15-minute TTL, non-renewable — the RW token never auto-renews (enforced in code); when it expires the agent drops back to read-only and must explicitly re-escalate. Every write task requires a conscious approval decision.

Scope: Consent and token state live in a single server process and are global to it. Gatekeeper is single-operator, single-process by design — one process holds one shared RO/RW state; it does not isolate authorization per session or per principal. Don't front it with a proxy that multiplexes multiple clients into one process — they would share a tier.

Token Hierarchy

No standing access to your secrets. There is exactly one permanent credential — the bootstrap token — and its only power is to initiate an MFA challenge. Three tokens, three policies, each with only enough access to reach the next step:

  1. Bootstrap token — Injected by Vault Secrets Operator (VSO). Can only read secret/data/claude/ro-login and secret/data/claude/rw-login. Effectively inert without your phone.
  2. RO token — Obtained via claude-ro userpass login + DUO push. Can read secrets within scoped paths.
  3. RW token — Obtained via claude-rw userpass login + second DUO push. Can read and write secrets within scoped paths.

Even if the bootstrap token is compromised, an attacker can only request authentication. The DUO push goes to your phone. A compromised token becomes an alert system, not a silent exfiltration vector.

Tools

MCP Gatekeeper exposes eight tools to the AI agent:

Tool Requires Description
authenticate Nothing Trigger DUO push to obtain RO token (4hr). Reads auto-trigger this, so explicit use is optional.
escalate RO Trigger second DUO push to obtain RW token (15min). Required before any write.
deescalate RW Revoke the RW token immediately and drop back to RO. Good practice after completing write tasks.
logout RO+ Revoke all tokens and drop to no_access. Use at session end for full disconnection.
read_secret RO+ Read a KV v2 secret. Uses RW token if held (bypasses RO deny rules), otherwise auto-renews RO.
write_secret RW Write a KV v2 secret. No auto-renewal — explicit escalation required.
list_secrets RO+ List secret keys at a path. Uses RW token if held, otherwise auto-renews RO.
token_status Nothing Report current tier and remaining TTL for held tokens.

Prerequisites

  • OpenBao or HashiCorp Vault with KV v2 secrets engine
  • DUO MFA configured as an auth method enforcement on the Vault instance
  • Two userpass accounts (claude-ro, claude-rw) with DUO MFA enforcement
  • Three policies scoping each token tier's access
  • Vault Secrets Operator (optional, for Kubernetes bootstrap token injection)

Configuration

Two environment variables:

Variable Description
VAULT_ADDR OpenBao/Vault API address (e.g., https://vault.example.com)
VAULT_TOKEN Bootstrap token — minimal policy, can only read userpass credentials

Deployment

Kubernetes (recommended)

Deploy as a pod in a dedicated AI namespace with the bootstrap token injected via VSO:

docker build -t mcp-gatekeeper:latest .
# Push to your registry, deploy with your K8s manifests

The Dockerfile runs as a non-root user (mcpuser) and uses a multi-stage build with uv for fast installs.

Local Development

export VAULT_ADDR="https://vault.example.com"
export VAULT_TOKEN="hvs.bootstrap-token-here"
python -m mcp_gatekeeper

Design Principles

Invisible, not forbidden. Don't give the agent the option to be dumb. If Claude Code can see VSO pods, it will try to extract secrets from them instead of using the API. Hide infrastructure entirely rather than returning 403s that invite retry loops.

Every failure mode defaults to no access. DUO timeout → no token. Token expiry → no access. Network partition → nothing executes. Pod crash → session ends. There is no state where a failure grants more access than was intended.

DUO lives server-side. The 2FA gate is in the MCP server infrastructure, not the agent's prompt or context window. The agent can't see it, reason about it, or engineer around it. From its perspective: call the tool, either get a token or don't.

Design for compromise. Gatekeeper trusts its caller — it expects to sit behind an authenticating transport (e.g., an OIDC proxy) — so assume that transport is breached. What's the blast radius? Even then, a compromised caller can only request tokens; DUO push approval still goes to your physical device. The architecture treats every layer as already breached and asks "what's the worst that can happen?"

Session state as authorization context. Most MCP servers are stateless — same input, same output, every time. Gatekeeper maintains a server-side state machine that changes the behavior of every tool call: the same read_secret to the same path returns data or 403 depending on accumulated session consent, not on input parameters. DUO approvals are the mutations to that state. Unlike MCP's in-band elicitation — which flows back through the agent and is therefore manipulable — those approvals happen out-of-band, on a channel the agent can't see or influence.

Project Structure

mcp-gatekeeper/
├── src/mcp_gatekeeper/
│   ├── __init__.py
│   ├── __main__.py          # Entrypoint, env config, FastMCP server init
│   ├── tools.py              # Eight MCP tool definitions
│   └── vault_client.py       # OpenBao HTTP client, token lifecycle, DUO flows
├── Dockerfile                # Multi-stage build (uv + python:3.13-slim)
├── fastmcp.json              # FastMCP server configuration
└── pyproject.toml            # Python 3.13, fastmcp + httpx

License

Apache-2.0 — See LICENSE for details.