Executive Summary & Operational Protocol: As enterprise organizations adopt Anthropic Model Context Protocol (MCP) to connect AI agents with internal PostgreSQL databases, GitHub repositories, and Jira workspaces, security teams face unprecedented vulnerability vectors. Unprotected MCP servers allow indirect prompt injections to execute arbitrary database queries or exfiltrate private credentials. This playbook details the zero-trust architecture for securing MCP deployments using OIDC JWT identity passthrough, OAuth2 granular scope controls, gVisor container isolation, and Sidecar Regex Prompt Inspection.
Executive Takeaways#
- The MCP Threat Surface: Direct tool execution bypasses traditional UI security controls. An attacker injecting malicious instructions into a database field can trick an MCP client into executing arbitrary shell commands.
- Zero Token Impersonation: MCP servers must never run with static superuser database credentials. All requests must forward the end-user OpenID Connect (OIDC) JWT token to enforce Role-Based Access Control (RBAC).
- Ephemeral Sandbox Environments: Execute file system and code execution tools inside transient gVisor-sandboxed Docker containers with read-only root filesystems and restricted egress networking.
- Strict Schema Filtering: Validate input payloads against rigid JSON-RPC schemas before passing parameters to underlying Python/Node.js tool execution logic.
01. The MCP Threat Surface#
In a standard Model Context Protocol integration, the host application (MCP Client) connects to external tools (MCP Servers) over stdio or Streamable HTTP.
02. Top 3 MCP Vulnerability Vectors & Mitigations#
1. Indirect Prompt Injection#
- Vector: Malicious text stored in a CRM or document triggers unintended MCP tool execution.
- Mitigation: Human-in-the-Loop (HITL) approval gates for all mutating tools (
DELETE,UPDATE,POST).
2. Static Credential Escalation#
- Vector: MCP server runs as a database
postgressuperuser, granting agents access to unauthorized tenant data. - Mitigation: Dynamic User-Scoped Row-Level Security (RLS) connection pools driven by verified OIDC JWT claims.
3. Container Escape & File Exfiltration#
- Vector: An agent executing bash commands reads local environment variables and SSH keys.
- Mitigation: Running MCP tool executors inside gVisor (runsc) sandboxes with read-only root filesystems.
03. Production Code: Python MCP Server with OIDC Token Inspection#
import os, jwt
from fastapi import FastAPI, Depends, HTTPException, Security
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from mcp.server.fastmcp import FastMCP
# Initialize FastMCP Server with Security Rules
mcp = FastMCP("Enterprise-Secure-Postgres-MCP")
security = HTTPBearer()
OIDC_ISSUER = os.getenv("OIDC_ISSUER", "https://auth.enterprise.com/realms/main")
OIDC_AUDIENCE = "mcp-db-gateway"
JWKS_CLIENT = jwt.PyJWKClient(f"{OIDC_ISSUER}/protocol/openid-connect/certs")
def verify_oidc_jwt(credentials: HTTPAuthorizationCredentials = Security(security)) -> dict:
token = credentials.credentials
try:
signing_key = JWKS_CLIENT.get_signing_key_from_jwt(token)
payload = jwt.decode(
token,
signing_key.key,
algorithms=["RS256"],
audience=OIDC_AUDIENCE,
issuer=OIDC_ISSUER
)
return payload
except Exception as e:
raise HTTPException(status_code=401, detail=f"OIDC Token Verification Failed: {str(e)}")
@mcp.tool()
def execute_user_query(sql_query: str, user_claims: dict = Depends(verify_oidc_jwt)) -> str:
user_id = user_claims.get("sub")
user_roles = user_claims.get("realm_access", {}).get("roles", [])
# Enforce Read-Only Rule for Non-Admin Users
if "db_admin" not in user_roles and not sql_query.strip().upper().startswith("SELECT"):
raise HTTPException(status_code=403, detail="Mutating SQL queries require 'db_admin' role claim.")
return f"Query executed under user identity {user_id} with verified scope."
