MCP server security checklist: secrets, isolation, auth
An MCP server is an API that an AI calls with credentials you gave it. That combination — machine-driven calls, real secrets, natural-language control — has its own failure modes. Here is the checklist we hold our own platform to.
Updated July 2026
1. Transport and authentication
- TLS everywhere. A remote MCP server speaks Streamable HTTP over HTTPS at a stable URL — no plaintext fallback, no self-signed shortcuts in production.
- Every request authenticated before it reaches tool code. OAuth 2.1 with PKCE, Dynamic Client Registration and audience-bound tokens (RFC 8707) is the MCP standard — the details are in our plain-English OAuth explainer. The important checklist item: an unauthenticated request must be rejected by the gateway, not by convention inside your tool logic.
- Verify audience, not just signature. A token minted for someone else's server must not work on yours. Signature + expiry + audience — all three, every call.
2. Secret storage and injection
- Encrypted at rest. API keys your server needs (Notion tokens, database DSNs, Stripe keys) belong in a store that is encrypted with a real scheme — AES-256-GCM, not base64, not "it's an internal database".
- Injected at runtime, never baked in. Secrets should enter the process as environment variables at container start — never committed into the generated code, the image, or the template you share.
- Never echoed back. No tool response, error message, build log or debug output should ever contain a secret value. Test this deliberately: ask the AI client to print its configuration and confirm the secret does not appear.
- Scoped upstream credentials. If the upstream API offers read-only or resource-scoped keys, use them. The MCP server can only leak the power you gave it.
3. Isolation between servers
If you run more than one MCP server — or host servers for more than one user — the blast radius question dominates: what does a compromised or simply buggy server reach?
- One server, one container. Process-level separation is not enough when different trust domains share a host. Each server should run in its own container with its own secrets, so a bug in one cannot read another's environment.
- No host mounts, no docker socket. A tool-serving container has no business seeing the host filesystem or the container runtime.
- Resource limits. Memory and CPU caps per container turn a runaway loop into a restart instead of a host outage.
4. Least-privilege tool design
The cheapest security control in MCP is deciding what tools exist at all. The AI will eventually call every tool you expose, with every argument shape it can think of.
- Read-only by default. Ship
searchandgettools first; add write tools only when the use case demands them, and separately. - Narrow arguments. A
query_orders(customer_id)tool is auditable; arun_sql(sql)tool is an incident report with a delay timer. - Validate inputs inside the tool. Tool schemas are hints to the model, not enforcement. Your handler must validate as if the input came from the public internet — because via the model, it did.
5. Logging without leaking
- Log that a tool was called, its latency and result status — the metrics you need for operations.
- Be deliberate about logging arguments and results: they routinely contain customer data and occasionally contain secrets a model pasted into a query. Redact or hash by default.
- Keep an audit trail of who connected which client and when — OAuth gives you the identity for free; keep it attached to the call records.
6. The prompt-injection surface
Two MCP-specific angles most checklists miss. First: tool descriptions are instructions to the model. If you install a third-party MCP server, its tool descriptions enter your AI's context — a malicious description can tell the model to exfiltrate data through another tool's arguments. Install servers the way you install browser extensions: from sources you trust, reading what they expose.
Second: tool results are untrusted input to the model. If your tool returns content from the outside world (web pages, tickets, emails), that content can contain instructions the model may follow. You cannot fully solve this server-side, but you can avoid amplifying it: return structured data instead of raw HTML where possible, and never grant one server both broad read and broad write power — that pairing is what turns an injected instruction into an actual exfiltration.
What a checklist can't fix
How BuildMyMCPServer maps to this list
For transparency about our own posture: generated servers run one-per-container behind an OAuth 2.1 authorization server; customer secrets are AES-256-GCM encrypted at rest and injected as environment variables at runtime, never logged or echoed back; and every server's TypeScript source can be exported for review. Templates carry the spec and code, never the author's credentials — you add your own on fork.
Skip the boilerplate — describe your tool, get a hosted MCP server.
BuildMyMCPServer generates the TypeScript server, wraps it in OAuth 2.1 and deploys it to a public Streamable HTTP URL for Claude, Cursor and ChatGPT. Free tier, source export, no lock-in.
Start building →