Secure MCP Servers for Production with 5 Principles
Design MCP servers for agents using 5 principles to shrink attack surface and block OASP top 10 threats; deploy remotely via HTTP with OAuth 2.1, preferring CIMD over DCR for dynamic client auth.
Agent-Human Differences Expose Security Risks
Agents differ from humans in discovery, iteration, and context handling, each casting a security shadow. Agents enumerate every MCP tool and read all descriptions on connect, creating token-expensive surfaces for tool poisoning via hidden instructions in docs (OASP MCP top 10 #3). Iteration broadcasts full conversation history per retry, risking data leakage of sensitive prior tool outputs. Limited ~200k token context forces loading all data upfront, enabling context injection/oversharing of PII, credentials, or internals (OASP #10). Cure by curating minimal tools exposing least data—fewer tools mean less attack surface.
5 Principles Unite MCP Design and Security
Apply product engineering to MCP: good design preempts OAuth needs and blocks OASP top 10.
- Shrink attack surface: Consolidate fine-grained ops (e.g., no delete-user tool if only order-check needed) into coarse outcome-focused tools. Yields one permission check, audit log, auth point per door—fewer locks to manage.
- Constrain inputs at schema: Accept top-level primitives/enums or flat dicts; use Pydantic for strictness. Rejects nested free-form payloads to block command injection via unconstrained strings passed to shells/queries/APIs.
- Defend via documentation: Write complete, unambiguous tool docs to crowd out poisoning from attacker-controlled neighbor servers shadowing yours.
- Return minimal data: Strip PII/credentials/system details from responses—agents don't need them for tasks, preventing prompt injection exfiltration from context.
- Minimize blast radius: Scope perms at tool/resource level (use MCP readonly annotation); convert read tools to resources. Remove unneeded tools to eliminate vectors—agents trust whatever you expose, so enforce trust.
Cross Production Chasm with OAuth 2.1 Flows
Local stdio MCP suits solo dev (walled garden, API keys in env/config) but fails production: Stack Lock tests show 20/22 requests fail at 20 concurrent connections. Switch to remote HTTP transport for scaling, multi-client, governance—but hits 'security cliff' needing auth/TLS/rate limits instantly.
API keys (long-lived, unscoped, shared) create confused deputy risks: MCP passes unverified keys to upstream APIs, compromising all if leaked.
Dynamic Client Registration (DCR): Client self-registers via /register for client ID; uses PKCE for auth code flow with SSO/consent, yielding scoped JWT access token. MCP validates, exchanges for session token (RFC 8693) to call APIs without passthrough. Solves pre-reg but proliferates registrations (non-portable across OS), vulnerable to phishing (anyone POSTs /register), trusts self-asserted metadata.
Client ID Metadata Document (CIMD, preferred since Nov 2025): Client exposes public HTTPS URL with metadata (proves control, binds redirect URIs). Auth server fetches during /authorize, auto-registers verifiable clients. Harder for attackers; no registration DB bloat; selective allow/deny.
Enterprise Additions Beyond Scopes
Tool/resource-level RBAC (not session), data masking (hide PII like emails/phones pre-agent), interaction logging (agent/tool/params/response for EU AI Act), end-to-end tracing (client request to response) for governance/observability—like distributed systems but for autonomous decisions.