The MCP spec is a protocol, not a security framework. Anthropic publishes the message shape, the schema format, the transport. They explicitly leave authentication, authorization, rate-limiting, and auditing to the server author. This is the right call for a protocol; it is also why early MCP servers ship without controls and end up on the wrong end of incident posts.

Twelve controls cover the threat model. In a no-code MCP builder most of them are defaults you don't think about. Hand-rolled, each is a half-day to a week of work. Either way, this is the list to check against before you put the URL anywhere it can be discovered.

Group 1: identity (1-4)

1. Per-tenant tokens, not per-server

One token per server is the failure mode. Every agent that uses the server can read every customer's data. The fix: tokens are bound to a tenant, and tokens are revocable independently. AppElixir generates tokens scoped to a workspace + agent identity. Hand-rolled, the token is a JWT signed by your auth service with tenant_id in the claims and a short TTL.

2. Scope enforcement at the data layer, not the tool layer

The tool reads customer_id from the agent's arguments. The wrong implementation: tool function checks "is customer_id in this tenant?" then queries the DB. The right implementation: the DB query filters on tenant_id = $token_tenant regardless of what customer_id the agent passed. The agent cannot read another tenant's data even if it asks.

3. Token rotation and revocation

Every token has a TTL (24 hours for hot agents, 7-30 days for system agents). Revocation is one button. When a customer leaves, when a developer's laptop is stolen, when you suspect compromise — the token stops working in seconds, not at the next TTL.

4. No anonymous calls

Reject every request without a valid token at the framework level — before tool dispatch, before logging beyond the rejection itself. Don't list available tools to unauthenticated callers; that's free reconnaissance. The error message should be the same 401 whether the token is missing, malformed, or expired.

Group 2: inputs (5-8)

5. Schema validation on every tool call

The MCP spec includes JSON Schema for each tool's inputs. Enforce it. Strict mode: extra fields rejected, type-mismatched fields rejected, missing required fields rejected. The validation runs before the tool function sees the arguments. This catches the agent's typos and the attacker's experiments equally.

6. Parameterized queries

If a tool argument reaches a SQL query, it goes through parameterized binding — never string concatenation. Same rule for shell commands, prompts to internal LLMs, and HTTP requests to backend services. Schema injection is SQL injection wearing a new hat; the defense is identical.

7. PII redaction at the output schema

The output schema specifies what comes back. Default conservative: emails as user***@***.com, no credit card last-4, addresses as city/country only. Full PII requires a higher-privilege token and an explicit include_pii=true argument the schema rejects when the token doesn't allow it. Apply this consistently — the moment one tool leaks raw emails, all of them effectively do, because the agent will quote them in conversation.

8. Deny-by-default for write tools

Read tools are safe by default. Write tools are not. Every write tool has an explicit allowlist of which tokens may call it, and a human-confirmation path when the write is high-impact (refund, delete, change_plan). The default for a new token is "read-only on this server"; opt in to writes per tool, per token.

Group 3: operations (9-12)

9. Per-tool rate limits

A server-wide rate limit is wrong because tools have different cost shapes. KV reads are cheap; vector searches are middling; email sends are expensive and externally costly. Per-tool limits let you cap the right thing. Sensible defaults:

When the limit is hit, return a typed error the agent can reason about, not an HTTP 429 the agent gets confused by.

10. Audit log

Every tool call gets a row: timestamp, agent token (hashed), tenant, tool name, argument JSON, result status, latency. 90-day retention minimum. This is the control that turns "what did the agent do?" from a guess into an answer. It is also the control most missed in early MCP servers — and most regretted in incidents.

11. Tool version pinning

When you change a tool's behavior, version it. get_customer_spend@v1 and get_customer_spend@v2 live side by side; clients pin the version they tested against. Silent behavior changes are how agents start hallucinating new error shapes overnight. Pinning is cheap once the framework supports it; retrofitting is not.

12. Kill switch

One button to disable a tool, one button to disable a token, one button to take the whole server offline. The button needs to work in seconds, from one operator's laptop, without a deploy. The day you need it, you really need it. AppElixir wires this by default; hand-rolled, build it before you ship to anyone you care about.

Three controls that are not on this list (and why)

IP allowlists

Tempting. Wrong. The agent is running on Anthropic's, OpenAI's, or the user's machine — IPs are too diverse to allowlist meaningfully. Authentication via token does the job IP allowlists pretend to do.

Captchas

The caller is a program, not a human. Captchas break legitimate agent calls and add zero security against a real attacker.

"Don't return errors in detail"

Wrong instinct. Detailed errors help the agent recover (correct its arguments and retry). Vague errors waste tokens and increase failed-call ratios. The fix is to return detailed errors about input shape and minimal errors about internal state: "missing required field 'email'" yes; "database connection failed on shard 3" no.

The overlap with broader LLM security

This is the server side. The agent side has its own controls: prompt-injection defense, output filtering, tool-choice auditing, max-iteration caps. Together they form the trust boundary; neither side alone is enough.

The agent-side controls are out of scope for this list, but worth pairing a server-side audit with an agent-side one. About three controls (rate-limit isolation, audit log, kill switch) appear on both lists, because they are bilateral defenses.

What ships in the first iteration

The minimum viable secure MCP server has six of the twelve controls: per-tenant tokens (1), data-layer scoping (2), schema validation (5), parameterized queries (6), per-tool rate limits (9), audit log (10). Ship without those and the server is one curious user away from an incident.

Add the other six (rotation, anonymous-reject, PII redaction, write deny-default, version pinning, kill switch) before the server is exposed to traffic you don't personally trust. On a no-code builder these are checkbox-level changes. Hand-rolled, budget a week.

The honest framing: MCP servers are infrastructure. The security expectations match infrastructure, not prototype quality. Treat them that way from week one.