In his article on the 11 layers of a production-grade MCP server, Fareed Khan builds a fictional but thoroughly worked-out production MCP server, "Atlas-MCP": Postgres Row-Level Security for tenant isolation, OAuth 2.1, a default-deny policy engine, approval gates for destructive operations, a circuit breaker, a Redis token bucket, two-tier caching, observability. It reads like a catalog of everything that gets hard once an agent stops being a toy. Nearly all of it holds up. One caveat made me write this note.
What I'm taking
- Default-deny everywhere. Unknown policy conditions fail closed; an empty outbound allowlist means zero outgoing requests. The right default for agents: prompt injection can't route around what is physically forbidden, as opposed to forbidden by a line in the prompt.
- The approval gate as a control surface. A destructive tool doesn't execute — it creates a pending record with a TTL; a human approves, and both outcomes land in the audit log. The author compares this to confirming edits in Claude Code, and the comparison is exact: confirmation isn't ceremony, it's the control surface.
- Audit with
args_hashinstead of raw arguments — event correlation without hoarding PII. - Structured errors (
code/retryable/hint): "Agents cannot recover from Python tracebacks." Verified on my own agents — they don't.
Where I disagree
The article treats a dedicated MCP server as the mandatory control point and never once compares it with the alternative: the agent calling CLI/API directly. For the article's scenario — multi-tenant SaaS, other people's users, write operations — the choice is right: the MCP server is the one place where RLS, auth and rate limits live together. But I have the opposite production case in front of me: a read-only Kubernetes debugging agent whose author started with an MCP wrapper and deliberately dropped it in favour of Bash + curl + skills. The argument is simple: an MCP tool like query(query: string) types the way of calling, but the request body — the MetricsQL string, the kubectl filter — is still written by the LLM. Typing doesn't remove the main risk. Read/write segregation there came from an allow/deny list on the Bash tool plus GET-only curl — no MCP layer at all.
My own setup works the same way: repeatable procedures live in skills; MCP is wired in only where I need persistent typed access to an external service (a local LLM on a separate machine). The working rule: the layers in the article are properties of the deployment context, not of the protocol. Multi-tenant, write operations, other people's data — most of those layers are mandatory, and they're better off living in one place. A single developer, read-only, a CLI the model already knows from pretraining — a harness is enough: allowlist, GET-only, a confirmation gate.
One more reason to read the article with a pencil: the numbers. "A three-tier tool hierarchy cuts erroneous tool calls by ~40%" rests on an external case study and is essentially unverifiable. As an order of magnitude — maybe; as a benchmark — no.
Bottom line
As a catalog of the hard problems in production agent systems, the article is excellent. Reading it as a checklist for every agent would be the mistake. Layers get switched on by blast radius, not by fashion.