An MCP server sits between a non-deterministic AI client and your real data. That is powerful, and also exactly as dangerous as it sounds. Below is a practical checklist for shipping an MCP server you can defend in a security review.
1. Use OAuth 2.1 with PKCE for anything multi-user
The MCP spec recommends OAuth 2.1 for remote servers that hold user data, and so do we. Every request carries an access token; your server validates it, extracts the user identity, and scopes everything downstream to that identity. PKCE protects the authorisation code from interception on the client side. Skip this only for strictly single-tenant internal tools — and even then, API keys with scopes beat no auth at all.
2. Design tools around least privilege
An LLM will call every tool you expose. Assume it. Do not ship a generic 'execute SQL' tool when three narrow tools would do. Every tool should have a typed JSON schema, a minimal parameter set, and a description that tells the model exactly when to call it and when not to.
- Prefer read-only tools by default; gate writes behind explicit user consent.
- Never accept raw shell commands, raw SQL, or raw filesystem paths as tool arguments.
- Scope list/search tools to the authenticated user's resources. Never let a tool argument select a tenant.
- For dangerous actions (delete, pay, email), require a confirmation flow, not a single call.
3. Validate every input at the boundary
Model-generated tool arguments are user input with a fancy hat on. Validate types, lengths, and enum membership with a schema library (Zod, Pydantic, JSON Schema) before touching your business logic. Reject anything that does not fit, and return a clear error so the model can correct itself.
4. Rate-limit per credential, not per IP
A single user running an agent loop can fire hundreds of tool calls per minute. Rate-limit per API key or per OAuth token so one runaway loop does not degrade service for everyone. Return HTTP 429 with a Retry-After header the client can respect.
5. Defend against prompt injection in tool outputs
This is the MCP-specific gotcha. If a tool returns text that will be re-inserted into the model's context, that text can contain instructions aimed at the model rather than the human. A tool that reads a web page, an email, or a customer support ticket is a prompt-injection vector. Mitigations:
- Sanitise or quarantine tool output that originated from untrusted sources.
- Never auto-execute a second tool based solely on the contents of the first tool's output — require a fresh model reasoning step.
- Do not render tool output into privileged contexts (system prompts, policy documents) without review.
6. Log every tool call, redact sensitive fields
You want to be able to answer 'which tools did this user invoke in the last hour, and with what arguments?' A ring buffer per credential is enough to start. Redact obvious secrets — API keys, tokens, personal data — before persisting.
7. Publish a clear security surface
Document which tools exist, what they do, what scopes they require, and how a user revokes access. Expose a /.well-known/security.txt or similar contact endpoint. Ship a tested revocation flow; do not wait for the first incident to find out yours does not work.
Quick checklist
- OAuth 2.1 with PKCE (or scoped API keys for single-tenant tools)
- Typed JSON schemas on every tool argument
- Least-privilege tool surface, read-only by default
- Per-credential rate limits with Retry-After
- Prompt-injection-aware output handling
- Audit logs with secret redaction
- Published revocation and incident contact
How 3meel's MCP server applies this
3meel's MCP endpoint supports OAuth 2.1 and scoped API keys, enforces per-key rate limits (60 requests per minute), ships narrow typed tools for queries and memory, and never uses uploaded documents for model training. All inputs are validated at the boundary; audit logs live behind your account.
Start with a free account, exercise the MCP surface, and review the security docs before wiring it into a production agent.
Start free