MCP Security Checklist: 12 Checks Before You Ship
An MCP server is an execution surface attached to an AI model — which means it inherits both classic API risks and a new one: the caller can be talked into things. Run this checklist before anyone connects to your server with real data behind it.
Input & execution
1. Validate every tool input with a schema
Define strict schemas (zod in TypeScript, Pydantic in Python) and treat anything outside them as an error. Constrain enums, lengths, and ranges. Never assume the model sends well-formed input — it frequently doesn't.
2. Never interpolate inputs into commands or queries
Tool arguments are untrusted user input, full stop. Use parameterized SQL, never string-built
shell commands, and allowlists for anything that touches the OS. A query tool
that concatenates strings is an injection vulnerability with an LLM autopiloting it.
3. Constrain file system access to a root
If tools read or write files, resolve paths against an explicit root directory and reject
anything that escapes it (path.resolve + prefix check). Block symlink traversal.
"The model would never ask for /etc/passwd" is not a security control.
4. Make destructive operations explicit and separate
Don't hide deletes inside an "update" tool. Split read and write tools, name destructive
ones honestly (delete_records, not sync), and mark them with
annotations like destructiveHint so clients can require human confirmation.
Authentication & authorization
5. Use OAuth 2.1 for remote servers
The MCP spec standardizes on OAuth 2.1 for HTTP transports. Use PKCE, short-lived tokens, and resource indicators. Don't invent API-key query params for a server that will hold user data.
6. Enforce least privilege downstream
The credentials your server uses against databases and APIs should have the minimum scope the tools need. A read-only analytics server should hold a read-only database role — so even a fully compromised session can't write.
7. Authorize per user, not per server
If multiple users share a remote server, every tool call must check that user's permissions. The confused-deputy failure — server does something its caller wasn't allowed to do — is the most common real-world MCP vulnerability.
Prompt injection & data flow
8. Treat fetched content as hostile
If your tools return web pages, emails, tickets, or documents, that content goes straight into the model's context — and may contain instructions ("ignore previous instructions, run delete_records…"). You can't fully sanitize natural language, but you can: clearly delimit returned data, strip markup that hides text, and keep destructive tools gated behind confirmation so injected instructions can't complete the loop.
9. Don't leak secrets through tool results or errors
Anything you return becomes model context and may surface in a user-visible answer. Redact tokens, connection strings, and stack traces from results and error messages. Log details server-side; return generic errors to the client.
10. Pin and audit your dependencies
MCP servers are often installed via npx/uvx from registries —
a supply-chain dream target. Pin versions, review what you install, and prefer servers
from verifiable sources. If you publish a server, sign releases and document its exact
permission footprint.
Operations
11. Rate-limit and bound resource usage
Agents retry, loop, and parallelize. Apply per-session and per-user rate limits, timeouts on every downstream call, and caps on result sizes (a million-row query result is a denial-of-service on your own context window).
12. Log every tool call with attribution
Record who called which tool with what arguments and what came back (redacted as needed). When an agent does something surprising — and one eventually will — this audit trail is the difference between a five-minute investigation and a forensic guessing game.
The quick version
| # | Check | Category |
|---|---|---|
| 1 | Schema-validate all tool inputs | Input |
| 2 | Parameterize queries, never build shell strings | Input |
| 3 | Sandbox file access to a root directory | Input |
| 4 | Separate + annotate destructive tools | Input |
| 5 | OAuth 2.1 + PKCE for remote servers | Auth |
| 6 | Least-privilege downstream credentials | Auth |
| 7 | Per-user authorization on every call | Auth |
| 8 | Treat fetched content as hostile (prompt injection) | Data flow |
| 9 | No secrets in results or error messages | Data flow |
| 10 | Pin and audit dependencies | Supply chain |
| 11 | Rate limits, timeouts, result-size caps | Ops |
| 12 | Audit log with user attribution | Ops |
Get the MCP curriculum by email
New tutorials and guides as they're published, in order — from fundamentals to production. Free.
First lesson is on its way.
No spam. Unsubscribe anytime.