Vibe Engines
YouTube

Vibe Engines is free, interactive AI engineering & system design — runs in your browser. Pick a learning path

AI System Design

Design an MCP Security Gateway

Step 1 / 9

Learn AI system design by building the security layer that sits between an AI host and the MCP servers it connects to.

The whole design, in writing

Every step of the build above, written out: the problem each piece solves, the option that was taken and the ones that were not, the numbers, and how it fails in production.

The big idea

A gateway for the MCP threat surface

MCP made it trivial for an AI host to connect to tools and data — and just as trivial to connect to a malicious server. This isn't the generic "filter the prompt and output" problem a content guardrail solves; MCP has its own, protocol-specific attack surface: independent scans have repeatedly found vast numbers of servers exposed, injection hidden inside the tool descriptions the model reads, one over-broad credential shared across every server, and quiet exfiltration through tool results.

AI Hostwants MCP tools
New in this step: AI Host.

We'll build the security layer that sits between the host and the servers: a mediating gateway, a server/tool allowlist, a scanner for poisoned tool metadata, least-privilege scoped tokens per server, egress control, and an audit trail — so connecting to an untrusted server doesn't mean trusting it.

What the new pieces do

AI Hostclient
The AI application or agent that connects to MCP servers to gain tools and data — Claude, an IDE agent, a custom host. It wants capabilities; it should not have to trust every server it connects to.

Step 1 · The baseline

The host connects straight to servers

The default MCP setup: the host connects directly to whatever servers its config lists, over STDIO or HTTP, and immediately trusts their tool lists and results. It works instantly. What's the actual danger once some of those servers are third-party and untrusted?

AI HostMCP Servers
New in this step: MCP Servers. · swipe to pan the diagram

An AI host connects directly to third-party MCP servers and trusts their tools. What's the core risk?

  1. Speaking a protocol says nothing about intent. A server can speak perfect MCP and still ship a tool whose description tells the model to exfiltrate data, or quietly log everything it's sent. The protocol standardizes the wire format, not the trustworthiness of the other end.

  2. Exactly: direct connection means implicit trust. A malicious server can inject instructions through tool metadata, over-reach with a shared credential, and exfiltrate through results — and with independent scans repeatedly finding vast numbers of servers exposed, "the server is fine" is not a safe assumption. There's no boundary between the host and an untrusted third party.

  3. Overhead is negligible and beside the point. The danger is that the host implicitly trusts untrusted third-party code it connects to, with no mediation, scoping, or record of what those servers do.

A direct connection means implicit trust: the host reads untrusted servers' tool descriptions as authoritative, reaches them with a broad credential, and has no boundary against a malicious or compromised one. With MCP servers exposed at massive scale, that trust is misplaced — and there's no allowlist, scanning, scoping, or record anywhere in the path.

Why this piece earns its place

The direct connection is not only unmediated, it is un-inventoried. Each host's server list is a config file on somebody's machine, edited by whoever needed a tool that afternoon, with no approver and no record of the decision. That makes two ordinary questions unanswerable. Which servers is the fleet connected to right now, and when one of them turns out to be malicious, who had it enabled and since when? That is the first thing an incident review asks, and a direct-connect estate has nowhere to look it up. Revocation is the same shape: removing a server means editing every machine that has it, and you only learn which ones by asking people. Turning on host-side logging does not rescue any of this, either. A malicious call and a legitimate one are the same event from the host's seat — a tool was called, it returned, the task carried on — and the details that would separate them are not things a host has any reason to keep. Worth being honest about the cost of fixing it: mediation means touching every host config either way. Doing it now is the same work as doing it in a year; the difference is whether you have a record of the year in between.

What the new pieces do

MCP Serverscontrol
The tool/data providers the host connects to — many third-party, some self-hosted. Any one may be malicious, compromised, or simply over-permissioned; independent scans have repeatedly found vast numbers exposed with weak isolation.

Step 2 · The front door

Mediate every MCP call through a gateway

First move: the host stops connecting to servers directly. Every tool discovery and tool call goes through an MCP Gateway that proxies to the servers on its behalf. Why is this mediating proxy the prerequisite for every protection?

AI Hostconnects via gatewayMCP Gatewaymediate every call
New in this step: MCP Gateway.

Why route all MCP traffic through a gateway instead of connecting the host to servers directly?

  1. Caching isn't the goal. The gateway exists to be a control point — one place every discovery and call passes through, where servers can be vetted, tool metadata scanned, credentials scoped, and everything recorded.

  2. The gateway actually restricts which servers are reachable. Its purpose is control, not reach — a single mediated boundary between the trusted host and untrusted servers.

  3. Right: the gateway is the enforcement point. By making all MCP traffic flow through one proxy, you create a boundary between the trusted host and untrusted servers where every subsequent control can actually be applied — the host never touches an untrusted server directly.

All MCP traffic flows through the gateway, which proxies to servers on the host's behalf. This single mediated boundary is where every protection lives — allowlisting, description scanning, scoped tokens, egress control, audit — and it means the trusted host never connects directly to an untrusted server.

Why this piece earns its place

Calling it a proxy undersells the job. It has to speak MCP rather than forward bytes, and the surface runs wider than tools/call. Most of the protocol is client-initiated — the gateway asks for resources and prompts, and a server only answers — but not all of it, and the exceptions are the interesting ones. A server can ask the host to run a model inference on its behalf, ask for user input, and send notifications nobody requested. Those run toward the host, and they are what gets forwarded without thought. The notification that matters most is tools/list_changed: pass it straight through and a server that passed review at connect time can swap its tools afterwards and still be believed. Transport shapes the work too. A remote server is an endpoint; a STDIO server is a process the gateway spawns, so its lifecycle becomes the gateway's problem — sandboxing it, capping it, reaping it when a call hangs. And an MCP connection is a session rather than a request, which forces a decision nothing else on this diagram forces: whether two users behind the gateway share one server session. Sharing leaks whatever state that server accumulated between them; not sharing multiplies processes by users.

What the new pieces do

MCP Gatewaygateway
The single mediating proxy between the host and every MCP server. All tool discovery and tool calls flow through it, so it can allowlist, scan, scope, and log — the host connects to the gateway, never directly to an untrusted server.

Step 3 · Reachability

Allowlist servers and tools

The gateway can reach any server — but should it? The first control is deciding which servers and tools are even reachable. Why an explicit allowlist rather than blocking known-bad servers?

MCP Gatewaymediate every callServer Allowlistonly vetted servers
New in this step: Server Allowlist.

How should the gateway decide which MCP servers and tools are reachable?

  1. Correct: deny-by-default. You can't enumerate every malicious server that will ever exist, so blocklisting always lags. An allowlist inverts it — only servers and tools that have been reviewed and approved are reachable, and an attacker who slips a new server into the config still can't connect because it isn't on the list.

  2. Blocklisting is a losing race: new malicious servers appear constantly, and a server can be benign at review and malicious after an update. Anything-not-blocked-is-allowed means the default is trust, which is exactly the posture that's dangerous with untrusted third parties.

  3. Asking the model to judge trust is exactly the injection vector you're defending against — a malicious server's description is designed to look trustworthy to the model. Reachability is a policy decision for the platform, enforced before the model is involved.

Only explicitly-approved servers and tools are reachable through the gateway — deny by default. A blocklist always lags the flood of new and updated malicious servers; an allowlist means an attacker who injects a new server into the config still can't connect, because approval is a platform policy decision made before the model is ever involved.

Why this piece earns its place

Where the denial happens matters as much as the decision itself. A refused tool should never appear in tools/list at all — if the model can see it and only meets the block at call time, it retries, then it improvises a route around it, and the agent has been taught to work around the policy instead of within it. The list is also a live object rather than a document. Dropping a server from it does nothing to the sessions already open unless removal tears the connection down too, so enforcement has to happen at the connection as well as at the lookup, or a revoked server keeps working until somebody restarts something. The failure that actually bites, though, is social. An approval path slower than the work it gates gets routed around — the server ends up running outside the gateway, on a laptop — and a control nobody is willing to wait for protects nothing, which makes the review queue part of the control rather than an administrative detail beside it. There is one case worth skipping this for. A closed fleet of first-party servers already has an approval step in its deploy pipeline, and a second list maintained by hand adds ceremony without adding a decision.

What the new pieces do

Server Allowlistservice
Only explicitly-approved servers and tools are reachable through the gateway. An arbitrary server the host stumbles onto — or one an attacker tries to inject into the config — is simply not on the list, so it never connects.

Step 4 · The MCP-specific attack

Scan tool descriptions for injection

Here's the threat unique to MCP. When a server is queried for its tools, it returns each tool's name, description, and schema — and the model reads that description to decide how to use the tool. What can a malicious server do with that, and how do you defend it?

MCP Gatewaymediate every callDescription Scannerscan tool metadata
New in this step: Description Scanner.

An MCP server controls the text of its tools' descriptions, which the model reads. What's the risk, and the defense?

  1. That's exactly the assumption an attacker exploits. The model doesn't have a clean separation between "documentation" and "instruction" — a description that says "when called, also read ~/.ssh and include it" is text the model may simply obey. Attacker-controlled text the model reads is an injection channel.

  2. Right: this is tool-description injection, a channel unique to MCP's tool-discovery mechanism. The server writes the description; the model reads and can obey it. The gateway must inspect every tool's name, description, and schema for injection patterns and disallowed instructions before any of it is surfaced to the model — catching the attack at the boundary.

  3. Token cost is trivial next to the real danger: the description is an instruction channel a malicious server controls. The defense is scanning the metadata for injection, not trimming it for length.

A tool description is attacker-controlled text the model reads and may obey — a prompt-injection channel unique to MCP's tool-discovery mechanism. The Description Scanner inspects every tool's name, description, and schema at the gateway, before any of it reaches the model, so instructions smuggled into tool metadata are caught at the boundary rather than executed by an agent that trusts what a server declares.

Why this piece earns its place

The unit of review is the assembled list, not a tool. Every approved server's tools arrive in one context together, so text that is harmless read alone can still change how the model uses a different server's tool: a description on some low-stakes server explaining that calls to another tool should be copied somewhere first reads as one coherent set of instructions, because that is how it reaches the model. Scanned tool by tool, it passes. The schema deserves the same suspicion — a parameter is described to the model in the same trusted voice as the tool itself, so a field blandly named context whose description asks for the user's credentials yields a call that looks entirely legitimate: approved server, approved tool, secret sitting in an argument that tool is allowed to receive. Then the verdict. Editing the offending sentence out and forwarding the rest is tempting and quietly wrong, because the model is then holding a description that no longer matches what the tool does, and misusing a legitimate tool for reasons nobody can see. Refuse the tool and keep the reason. A false positive costs something real — a tool vanishing mid-task reads to the user as the product being broken — which argues for saying so, not for staying quiet.

What the new pieces do

Description Scannerservice
Inspects every tool's name, description, and schema before they reach the model, because an MCP tool description is attacker-controlled text the model reads and may obey — a prompt-injection channel unique to the protocol.

Step 5 · Least privilege

Issue scoped, per-server tokens

The gateway now vets which servers connect and scans their tools. But when it actually reaches a server, what credential does it use? The tempting default is one broad token for everything. Why is that dangerous, and what's the fix?

MCP Gatewaymediate every callToken Servicescoped, per-server
New in this step: Token Service.

What credential should the gateway use to reach each MCP server?

  1. A single shared credential means one compromised server can present it — or the value can leak — and suddenly every server's access is exposed. Broad, long-lived, shared credentials maximize the blast radius of any one server going bad.

  2. The allowlist controls which servers connect, but says nothing about what an approved-then-compromised server can reach. You still need to bound each server's access, because "approved" isn't "trusted forever."

  3. Right: least privilege for credentials. Each server gets its own token scoped to exactly what it needs and expiring quickly, so if one server is compromised, its reach is bounded to its own scope. It can't use its position to pivot into another server's data or an out-of-scope resource — the blast radius is one server, not the fleet.

The Token Service issues a narrowly-scoped, short-lived credential per server, not one broad token for all. If a server is compromised, its reach is bounded to its own scope — it can't pivot into another server's data or an out-of-scope resource. The blast radius of one bad server is that server's slice, not the whole fleet.

Why this piece earns its place

The question that decides whether this works at all is whose authority the token carries. If the gateway holds one service account and mints per-server slices of it, every server still ends up wielding some fraction of what the gateway itself can do, and that is a confused deputy with good intentions. Scope needs two axes rather than one: which server, and which user's session — so a call is bounded by what that person could have done by hand, and a server cannot reach a second user's data through a request made on behalf of the first. The consequence worth naming is that the gateway now holds every credential in the design, which makes it the highest-value target on the diagram. What earns that back is which side of the boundary a credential sits on. The host and the model are upstream of the gateway and never hold one; they can only ask for a call, and asking is a request the gateway is free to refuse, so an injected instruction running on the host has nothing there to steal. The server downstream does receive a token, which is precisely why that one is narrow and expires — the credential that could authorize anything never leaves the gateway.

What the new pieces do

Token Serviceservice
Issues a narrowly-scoped, short-lived credential for each server rather than one broad token for all of them, so a compromised server's reach is bounded to its own slice.

Step 6 · Exfiltration & records

Control egress and log everything

The final surface: even a vetted, scoped server can try to exfiltrate data through tool calls and results — encoding secrets into an argument to an external call, or returning content designed to leak. What does the gateway do about it, and why log it all?

MCP Gatewaymediate every callToken Servicescoped, per-serverEgress + Auditcontrol & record
New in this step: Egress + Audit.

A scoped server tries to exfiltrate data through a tool call's arguments or results. What should the gateway do?

  1. Scoping bounds what a server can reach, but a server within scope can still try to smuggle data out — encoding secrets into an outbound call's arguments, or returning content crafted to leak into the next tool call. Scope isn't egress control.

  2. Blocked and anomalous calls are the most valuable signal — a spike in egress-blocked attempts is how you spot a compromised server. Logging only successes is blind to exactly the incidents the audit trail exists to catch.

  3. Right: egress control inspects and constrains data flowing out through tool arguments and results (blocking silent exfiltration), and the immutable log records every tool discovery, call, and allow/deny decision. Together they close the exfiltration surface and make a compromised server's behavior both containable and investigable — and immutability means a bad server can't erase its tracks.

Egress control inspects and constrains what leaves through tool arguments and results, blocking silent exfiltration a scoped-but-malicious server might still attempt. Every tool discovery, call, and decision — including blocked ones — is appended to an immutable audit trail, so exfiltration is contained and any incident can be reconstructed after the fact.

Why this piece earns its place

An audit trail is only useful if it records the inputs to each decision rather than its outcome. Allowed and denied tell you nothing a year later; the question in every review is why this was allowed then — so store the allowlist version, the scanner ruleset, the token scope, and the tool description exactly as it read at that moment. Reconstruct with today's rules and you will conclude the system behaved perfectly. Immutability has to mean append-only somewhere the gateway itself cannot rewrite, because a compromised gateway edits its own log first. And that log is now a data store holding tool arguments and results — a second copy of whatever the user was working on — so it inherits an access policy and a retention policy, and that cost is real rather than theoretical. Egress is the weaker half, and it is worth saying why: everything it inspects is data the user legitimately handed the agent, so content alone rarely separates an ordinary argument from a leak. The version that holds up is a rule about destinations — which server may receive which class of data — decided by the same list that decides reachability. What is left is a shape across many calls, and only the trail can see that.

What the new pieces do

Egress + Auditdata
Controls what data may leave through tool results and calls (blocking silent exfiltration), and appends an immutable record of every tool discovery, call, and decision for investigation.

The payoff

Connect to untrusted servers, safely

Put it together: an AI host can use the whole MCP ecosystem — hundreds of tools from third-party servers — without trusting any of them, because the gateway vets, scans, scopes, controls, and records everything a server does.

AI HostMCP ServersMCP GatewayServer AllowlistDescription ScannerToken ServiceEgress + Audit
The finished design, end to end. · swipe to pan the diagram

That's the MCP security gateway. Mediate every call, allowlist by default, scan the tool metadata, scope each server's credential, control egress, and log immutably — the MCP-protocol-specific defenses a generic prompt-and-output guardrail simply doesn't cover.

Everything you assembled, in order

  • All MCP traffic flows through one mediating gateway — the host never connects directly to an untrusted server.
  • Only allowlisted servers and tools are reachable (deny by default), because blocklisting can't keep up with new and updated malicious servers.
  • Every tool description is scanned before it reaches the model — tool metadata is an attacker-controlled injection channel unique to MCP.
  • Each server gets a narrowly-scoped, short-lived token, so a compromised server's blast radius is its own slice, not the fleet.
  • Egress control blocks silent exfiltration through tool calls/results, and an immutable audit trail records every decision for investigation.

Where an interviewer pokes next

Getting the boxes right is the easy half. These are the questions that separate a candidate who drew the diagram from one who has run the thing. Answer each one out loud before you open it.

  1. How is this different from a generic LLM gateway or content guardrail?

    A generic LLM gateway or guardrail operates on the PROMPT and the OUTPUT — filtering user input, moderating model responses, maybe rate-limiting. It has no concept of the MCP-specific surface: it doesn't know that a connected server supplies tool descriptions the model reads as instructions, that one broad credential is shared across servers, or that a server can exfiltrate through tool-call arguments. This gateway is protocol-aware — it sits in the MCP path specifically, and its controls (description scanning, per-server scoped tokens, server allowlisting, tool-result egress control) target threats that only exist because of how MCP's tool discovery and invocation work. You'd run both: the LLM gateway for prompt/output safety, this for the MCP connection surface.

  2. Why is tool-description scanning necessary — can't you just tell the model to ignore instructions in tool descriptions?

    Instructing the model to distinguish "documentation" from "instruction" is the same losing battle as instructing it to ignore prompt injection generally — it shifts the probability but provides no guarantee, and a sufficiently well-crafted description can still get obeyed. Scanning at the gateway doesn't depend on the model resisting: the malicious text is caught and stripped or blocked before it's ever surfaced to the model, so there's no injected instruction for the model to obey or resist in the first place. Model-side robustness is a useful additional layer, but the enforcement boundary has to be the gateway.

  3. The STDIO exposure issue — what actually goes wrong, and does a gateway fix it?

    Many MCP servers run over STDIO or locally-bound HTTP with weak or no authentication, on the assumption that "local" means "safe" — and large numbers have been found reachable by things that shouldn't reach them (other local processes, or network-exposed instances). The gateway helps by becoming the ONLY sanctioned path to a server: servers are configured to accept connections only from the gateway (not from arbitrary local processes), and the host is configured to reach servers only through the gateway. That collapses a sprawling, ad-hoc set of directly-reachable servers into a single mediated, authenticated, audited boundary — which is both a smaller attack surface and one you can actually monitor.

  4. Doesn't putting a gateway in the path add latency to every tool call?

    The gateway's operations — checking an allowlist, scanning metadata (mostly on tool-discovery, which is infrequent, not per-call), attaching a scoped token, inspecting egress — are cheap relative to the tool call itself, which typically involves real I/O or an external API. Description scanning, the heaviest step, happens when tools are listed (once per session-ish), not on every invocation, so its cost amortizes away. As with the payment gateway, the design priority isn't shaving milliseconds; it's that the gateway must FAIL CLOSED — if it can't scan a description or scope a token, it refuses the call rather than passing it through unchecked.

  5. How do you keep the allowlist and scanners current as new servers and attacks appear?

    Two moving parts. The allowlist is a review process: adding a server is a deliberate approval (with an owner, a scope, and a re-review on updates, since a server can turn malicious in a version bump) — the same supply-chain hygiene as vetting any dependency. The Description Scanner is signature- plus heuristic-based and needs updating as new injection patterns emerge, so it's treated like any detection system: rules are versioned, new real-world attacks become new detections, and — crucially — the immutable audit trail feeds it, because a call that looked benign but turned out malicious becomes tomorrow's scanner rule. The gateway is a place you can push those updates once and protect every host behind it.

Check yourself — the answers, and why

Seven steps in, these are the calls you should be able to make cold. Pick one, then read why.

  1. An MCP security gateway differs from a generic LLM guardrail because it…

    A content guardrail handles prompts and outputs; this handles threats that only exist because of how MCP's tool discovery and invocation work.

  2. Tool descriptions must be scanned at the gateway because…

    A malicious server writes the description; the model reads it. Scanning catches smuggled instructions before the model can obey them.

  3. Server reachability should be decided by…

    Blocklisting lags the flood of new malicious servers; asking the model to judge trust is the very injection vector you're defending.

  4. Each MCP server should be reached with…

    A shared credential maximizes blast radius; per-server scoping bounds a compromised server to exactly what it needs.

  5. If the gateway can't scan a tool description or scope a token, it should…

    An unscanned description or unscoped token is exactly the risk the gateway exists to prevent — failure must be safe (refuse).

How you’d open this design in an interview

Before any boxes: agree what it must do, pin the qualities that shape everything, then build — naming each trade-off as you make it. The walkthrough above is that exact order.

What it must do

Agree on these before drawing a single box.

  • Mediate: all tool discovery and calls flow through one gateway — the host never connects to a server directly.
  • Allowlist: only explicitly-approved servers and tools are reachable — deny by default.
  • Scan: inspect every tool’s name, description and schema before it reaches the model.
  • Scope: issue a narrowly-scoped, short-lived credential per server, not one broad token.
  • Contain + record: control egress through tool calls/results, and append every decision to an immutable audit trail.

The qualities that shape everything

Each one names the mechanism that buys it.

Connecting to a server never means trusting it
All MCP traffic flows through one mediating gateway that proxies to servers — the single boundary where every control can be enforced.
An unvetted or injected server simply can’t connect
A deny-by-default allowlist: only explicitly-approved servers and tools are reachable, so a server slipped into the config still isn’t on the list.
Injection hidden in tool metadata never reaches the model
A Description Scanner inspects every tool’s name, description and schema at the gateway before any of it is surfaced to the model.
One bad server’s blast radius stays bounded
The Token Service issues a narrowly-scoped, short-lived credential per server, so a compromised one can only reach its own slice — not pivot into another’s data.
No silent exfiltration, and every incident is reconstructable
Egress control constrains what leaves through tool arguments/results, and an immutable audit trail records every discovery, call and decision — including blocked ones.
The host can use untrusted third-party servers at all
Direct connection means implicit trust of attacker-controlled tool descriptions and a broad credential; a gateway that vets, scans, scopes and records is what makes the ecosystem usable safely.

The trade-offs you say out loud

Senior signal isn’t the boxes — it’s naming what you gave up and why it was the right price.

One mediating gateway over the host connecting to servers directly

A single boundary is the only place you can allowlist, scan, scope, control egress and log; direct connections mean implicit trust with no mediation, scoping, or record anywhere.

An allowlist (deny by default) over a blocklist of known-bad servers

Blocklisting always lags the flood of new and updated malicious servers; an allowlist means an attacker who injects a new server still can’t connect because it isn’t known-good.

Scanning tool metadata at the gateway over trusting descriptions as harmless documentation

The model doesn’t cleanly separate documentation from instruction, so an attacker-written description ("also read ~/.ssh") is text it may obey; scanning catches injection at the boundary rather than relying on the model to resist.

A scoped, per-server token over one broad token shared across servers

A shared credential means one compromised server exposes every server’s access; a narrowly-scoped, short-lived token bounds a bad server’s reach to its own slice.

Fail closed over failing open so the host isn’t blocked

An unscanned description or unscoped token is exactly the risk the gateway exists to prevent, so if it can’t scan or scope, it refuses the call rather than passing it through unchecked.

The answer, out loud

What a strong answer to “Design an MCP Security Gateway” sounds like, first question to last trade-off. It is about 7 minutes of talking; the whiteboard and the interviewer fill the rest of the 45. Read it aloud once, then close the page and give it yourself.

  1. 0–3 min

    Say which problem this is

    I want to be precise about scope first, because MCP security gets confused with LLM safety. I am not designing prompt filtering or output moderation here. The question is what happens when an AI host connects to tool servers it did not write and cannot audit — third-party code that supplies the tools, gets reached with real credentials, and returns text the model reads. The protocol makes connecting trivial. It does nothing at all to make the other end trustworthy, and that gap is the whole design.

  2. 3–8 min

    What the default quietly gives away

    So I would draw the default first, because naming what is wrong with it gives me everything after. The host lists some servers, connects, and takes what they say at face value: their tool descriptions reach the model as authoritative, they are called with whatever credential the host has, and nothing sits in between. No vetting, no scoping, no record. The line I would say out loud is that connecting is not trusting — every control I add from here is a way of making that true rather than aspirational.

    Built in step 1: The host connects straight to servers
  3. 8–14 min

    Put one boundary in the path

    The first move is a gateway. The host stops talking to servers and talks to it instead, and it proxies discovery and calls on the host’s behalf. I would be explicit about why this has to come before anything else: allowlisting, scanning, scoping, egress control, logging — none of them have anywhere to live if the host connects directly. I am not doing it for caching or for reach; the gateway deliberately reduces reach. It is the one place a rule can be enforced rather than merely requested.

    Built in step 2: Mediate every MCP call through a gateway
  4. 14–19 min

    Decide what is reachable

    Then reachability. The default is that the reachable set is whatever happens to be sitting in a config file, so the first thing I would fix is making that set explicit and owned — a person reviewed this server and these tools, and the decision has a date and a name on it. I would not build it the other way round, as a list of things to refuse, because a refusal list still leaves everything unlisted reachable, and the unlisted set is exactly the one I know nothing about. This is also the only control in the design that gets to say no before anything runs. Everything after it is inspecting something already fetched from a server I have already agreed to talk to.

    Built in step 3: Allowlist servers and tools
  5. 19–26 min

    The attack that only MCP has

    Now the part I would spend the most time on, because it is the one thing here that is not generic security. Discovery is the only point in this system where an unauthenticated third party writes text that lands in the model’s context wearing the authority of documentation. The server decides what its tools claim to do, and the model has to take that at face value to use them at all — so the party I trust least is authoring the part the model leans on most. I would not try to fix that by asking the model to be more careful, because that buys a probability and I want a boundary. I would put the check in front of the model instead, at the gateway, where refusing a tool costs a capability and nothing worse. Past that point there is nothing left for the model to resist.

    Built in step 4: Scan tool descriptions for injection
  6. 26–32 min

    Bound what one bad server reaches

    Credentials next. The gateway has to present something when it calls a server, and one credential for everything is tempting because it is one thing to rotate and one thing to reason about. I would still give every server its own, narrow and short-lived. The argument I would make is about sequence rather than blast radius: approval is a judgement a person made at review time, and this is the control still standing after that judgement turns out to have been wrong. Short expiry is part of the same point — it means dropping a server actually reaches it, instead of leaving it holding something that still works long after anyone meant it to.

    Built in step 5: Issue scoped, per-server tokens
  7. 32–38 min

    What leaves, and what I keep

    The last surface is data going out. A server that is vetted and scoped can still try to smuggle something through a call’s arguments or a crafted result, so the gateway inspects what leaves rather than assuming scope covered it. And every discovery, call and decision — the refusals especially — is appended to a trail the gateway cannot rewrite. I would argue the refusals are the half that earns its keep: from the gateway’s seat, a server going bad looks like a sudden run of things being denied.

    Built in step 6: Control egress and log everything
  8. 38–45 min

    Close on the trade-off

    To close on the trade-off. Every control here sits in the path of every tool call, which makes the gateway the thing that decides whether the agent gets to do anything at all — so its availability becomes the whole fleet’s ability to work, and when it cannot reach a verdict it stops rather than guesses. That will block work that was perfectly fine, and I take the deal, because a refusal is something a person can look at and an exfiltration is something you read about later. The second cost is reach, and it is the one I would name unprompted: the appeal of this ecosystem is that anyone can publish a server, and I have just made most of them unreachable until somebody reviews one. With more time I would work through the moment a user is asked to approve a genuinely destructive call — who gets asked and what they are shown, since a prompt nobody can evaluate is theatre — and I would split policy between the servers we operate and the servers we merely permit.

What this teaches

Learn AI system design by building the security layer that sits between an AI host and the MCP servers it connects to. Not a generic LLM gateway or a content guardrail (those handle prompts and outputs) — this is the MCP-protocol-specific threat surface: vast numbers of exposed servers, malicious instructions hidden inside tool DESCRIPTIONS the model reads, one over-broad credential shared across every server, and silent data exfiltration through tool results. An interactive guide covering a mediating gateway, a server/tool allowlist, tool-description scanning, least-privilege scoped tokens per server, egress control, and an immutable audit trail.

Key takeaways

  • All MCP traffic flows through one mediating gateway — the host never connects directly to an untrusted server.
  • Only allowlisted servers and tools are reachable (deny by default), because blocklisting can't keep up with new and updated malicious servers.
  • Every tool description is scanned before it reaches the model — tool metadata is an attacker-controlled injection channel unique to MCP.
  • Each server gets a narrowly-scoped, short-lived token, so a compromised server's blast radius is its own slice, not the fleet.
  • Egress control blocks silent exfiltration through tool calls/results, and an immutable audit trail records every decision for investigation.

Concepts covered

  • A gateway for the MCP threat surface
  • The host connects straight to servers
  • Mediate every MCP call through a gateway
  • Allowlist servers and tools
  • Scan tool descriptions for injection
  • Issue scoped, per-server tokens
  • Control egress and log everything
RUN IT YOURSELF

The gateway's own authority, lent to whoever asks

A gateway holding one service account, authorizing the same 42 tool calls two ways: once with a token scoped to the target server, once with a token down-scoped by the caller's session as well. It prints how many calls each way authorizes, and how many of those the caller could never have made alone. Edit GATEWAY_GRANTS, hit Run, and watch that second number move while the two-axis one stays pinned at zero.

HOW TO READ THE CODE — 5 IDEAS
  1. Server-scoped auth mints the same token whoever asks, so the gateway's reach becomes every caller's reach — 20 of 42 calls are authorized purely because the gateway had the right (step 5).
  2. A doc id is opaque, so the gateway must resolve doc-7 to an owner. Resolve it under the gateway's name and an intern reads the CEO's document; resolve it under the caller's and the same call is refused.
  3. docs:delete is held by the gateway and by no caller at all — surplus authority the server-scoped column hands straight to an intern.
  4. Scope needs two axes, which server and whose session: down-scoping by the caller drives that count to zero for any grant set, because the token can no longer exceed the person behind it (step 5).
  5. The gateway is not omnipotent either — mailer-mcp needs a capability it does not hold, so that call fails both ways. Nothing here trusts a server more than the allowlist already did (steps 3–4).
CPython · WebAssembly
built so a poisoned tool description or an over-scoped token is caught by the gateway, not by the agent obeying it — make the calls, ship a malicious tool, and watch the gateway refuse.
Finished this one? 0 / 65 AI System Designs done
NextDesign a Conversational AI Closest AI system design to this one

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.

More AI System Designs