Building Secure MCP Servers: Best Practices for Engineering Teams
MCP Servers Are Production Infrastructure. Treat Them That Way.
The moment a client connects, your MCP server is live infrastructure. Not a prototype. Not a sandbox. The execution layer between an LLM's intent and your backend systems carries the same blast radius as any privileged API endpoint, and a larger attack surface, because the caller is non-deterministic and user-influenced in ways your existing threat models were not designed for.
Traditional API security assumes a human or a predictable automated system on the other end. MCP breaks that assumption. LLM output is injection-prone by design: users craft inputs that manipulate model behavior, and that manipulation arrives at your server looking like a legitimate tool call with well-formed JSON arguments. Input validation is still necessary. It stopped being sufficient the day you put a language model in the call path.
Architecture and Trust Boundaries
An MCP server has four components: transport layer (stdio, HTTP/SSE, WebSocket), tool definitions with handlers, resource endpoints, and prompt templates. Clients call tools by name with JSON-structured arguments. The server executes and returns structured responses.
The LLM is not inside your trust boundary. It lives in the client layer, next to the user. Every LLM-generated tool argument deserves the same treatment as a query parameter from an anonymous HTTP client, because functionally that is what it is.
Input validation, authorization enforcement, output filtering, and audit logging belong to the server. The client owns nothing you care about from a security standpoint. Transport choice and tool scoping decisions made now are expensive to reverse, so get them right before you ship.
Vulnerabilities Worth Prioritizing
Prompt injection via tool calls. Attacker-controlled content enters the LLM context, shapes the model's output, and arrives at your server as an apparently legitimate tool call. Schema validation at the server boundary is your defense. It works regardless of how the malicious content got into the context.
Command injection. Tools that pass arguments to shell commands, subprocesses, or query builders without sanitization are exploitable. Parameterize everything. String interpolation of LLM-supplied values into executable contexts is a vulnerability, not a shortcut.
Over-permissioned tools. A tool with read-write access on a read-only use case widens blast radius for free. Scope every tool to the minimum permissions its declared function requires, not the maximum that makes development convenient.
Tool poisoning in dynamic discovery. Dynamic tool registration without an allowlist means an attacker registers a plausible-sounding tool, clients call it, and credentials or data go somewhere they should not. The allowlist is not optional. Dynamic discovery without one is a high-severity misconfiguration.
Credential and PII leakage. Tools that return upstream API responses verbatim will eventually expose tokens, keys, or personal data. Filter sensitive fields in the response pipeline before anything leaves the server. This is an output pipeline problem, not a logging problem.
Unbounded resource consumption. An unthrottled client hammering an expensive tool endpoint will exhaust infrastructure. Rate limiting belongs at the transport layer, enforced per client identity and per tool, not bolted on after the first incident.
Cross-client data leakage. In multi-tenant deployments, session context not strictly isolated between clients is a vulnerability by default. Enforce per-session scoping at the handler level. Shared state requires explicit justification and explicit isolation controls.
Authentication and Authorization
For HTTP-based deployments, OAuth 2.0 with short-lived bearer tokens is the standard. API keys work for service-to-service scenarios with a 90-day rotation ceiling, automated where the infrastructure supports it. Mutual TLS adds transport-layer identity on top of application-layer auth and earns its operational overhead on high-sensitivity deployments.
RBAC handles static role assignments cleanly. Read-only roles get read tools; write roles get write tools. Layer ABAC where your access model requires dynamic policy evaluation based on tenant, time, or data sensitivity. Most deployments need both: RBAC as the foundation and ABAC for the edge cases RBAC cannot express.
Tool chaining is where privilege models break in genuinely interesting ways. A sequence of individually authorized calls can produce an outcome that no single call was authorized to produce. Model your tool call graphs. Flag combinations that, in aggregate, exceed the permissions of the initiating session. Standard API security ignores this because deterministic callers do not chain tools opportunistically. LLM-driven clients do.
Validate token signature, expiry, issuer, and audience on every request. Expired tokens get rejected, not extended. Downstream service credentials belong in a secrets manager, not environment variables on shared infrastructure.
Data Validation and Sanitization
Every tool parameter gets validated against a JSON Schema at the server boundary before any handler logic runs. Type, format, range, and length are all enforced in the schema. Non-conforming inputs get a 400-class rejection. Do not coerce or sanitize malformed inputs; reject them and let the caller deal with it.
SQL goes through parameterized queries. Shell arguments go through explicit argument arrays. File paths get canonicalized and checked against an allowlist of permitted directories. These are not MCP-specific controls. They are standard secure coding practices applied to a context where adversarially crafted inputs are the expected case, not the edge case.
On the output side, set and enforce maximum response payload sizes. Strip credentials, internal stack traces, and PII before returning anything to the client. LLM-supplied values do not touch eval(), exec(), or any dynamic execution path.
Transport Security
TLS 1.3 preferred, TLS 1.2 as the floor. Automate certificate rotation with 90-day certs and ACME-based renewal; manual rotation is operational risk that accumulates silently until it fails at the worst moment.
Stdio-based servers running locally get OS-level process isolation. Restrict which users can invoke the server process. The transport is the process boundary; protect it accordingly.
MCP server ports on networked deployments are internal endpoints. They sit behind a network boundary and are reachable only by authorized clients via firewall rules or service mesh policy, not exposed directly.
WebSocket connections get the same authentication and transport security treatment as HTTP. Validate tokens on the initial upgrade request and on reconnect. Sessions that persist across authentication state changes get invalidated and re-authenticated; a WebSocket connection does not outlive its credential validity.
Monitoring and Incident Response
Log every tool invocation: caller identity, tool name, a parameter summary (hashed or truncated for sensitive fields), and response status. Raw secrets and unredacted PII do not appear in logs.
Anomaly signals worth instrumenting: call frequency spikes on expensive tools, unexpected tool combinations that suggest chaining exploitation, repeated authentication failures, and authorization denials clustering around a specific client identity or tool. The chaining pattern in particular will not surface in standard SIEM rules without explicit configuration; build the detection before you need it.
MCP server logs feed into the same SIEM pipeline as your production API logs, on the same schedule, with the same alert thresholds.
Write incident playbooks for MCP-specific scenarios before you need them. A compromised tool deployment needs a tested rollback path: version your tool definitions, maintain rollback artifacts, and run the rollback procedure in a drill before an incident forces it live. A malicious tool registration needs immediate deregistration capability; verify it exists and works before you discover otherwise under pressure.
Penetration tests against MCP endpoints run on the same cadence as your other production services. Prompt injection and tool chaining exploits fall outside standard API pen test scope. Specify them explicitly in your test briefs or they will not be tested.
Where to Start
The risk profile is a privileged API surface plus the additional attack vectors that come from LLM-generated, user-influenced inputs. The controls are not new disciplines. Input validation, least privilege, secrets management, transport security, and observability applied to a different execution model.
Security ownership spans engineering, security, and AI platform teams. Gaps at the boundaries between those teams are where real incidents originate.
Start with input validation, tool permission scoping, authentication enforcement, and logging. Those four close the highest-severity exposures. Then work through the rest.