Part 1 ended on a single structural gap: when AI agents reach tools over the Model Context Protocol (MCP), there is no one place to ask “should this specific call happen, right now?” The calls originate everywhere, authenticated by different mechanisms, landing on servers with no shared policy. This piece is the design I reached for to build that one place — no code (that’s Part 3), just the architecture and the trust boundaries.

Start from the framing that motivates the whole thing: when an AI agent calls a tool, a non-deterministic process is reaching out to a server it was handed, with credentials it was given, to take an action nobody reviewed at that exact moment. The agent is the new insider. And the dominant security answer in this space is classification — scan the server, label it safe or unsafe, allow or block it once, up front. I think classification is the wrong load-bearing primitive. The design here is built around the opposite one.

In one picture

No matter how many tools, servers, or identity providers exist, the agent talks to exactly one thing, at one address, one port. It doesn’t get a credential per server and a config file per client — it gets one endpoint. Every call goes through that one door, gets checked, and only then reaches a backend. The diagram below walks the same gateway through the situations that actually come up — one IdP, two IdPs, a service account, per-user credentials, and onboarding a new server:

That’s the whole user experience: one endpoint to configure, every call mediated behind it, and the agent only ever sees the tools it’s allowed to use. Simplicity on the outside is exactly what makes the control possible on the inside.

Why classifying servers can’t be the control

Static classification asks “is this MCP server trustworthy?” and answers once. That breaks for three reasons, and each is a design constraint:

  1. Trust is per-call, not per-server. A filesystem server is fine reading a temp dir and catastrophic reading ~/.ssh. The server didn’t change between those two calls — the arguments did. A label assigned at registration can’t see arguments that arrive at call time.
  2. The agent is the threat surface. Even a perfectly benign server is dangerous if a prompt-injected agent drives it. You cannot classify your way out of an adversary who controls the caller.
  3. Credentials leak through the gap. If the agent holds the downstream secret, every tool it can reach is a credential it can exfiltrate. Classification never touches this.

So classification isn’t worthless — servers are still quarantined and inspected when they register. But it can’t be the load-bearing control. That has to sit in the path of every call and decide there, with the arguments, the identity, and a policy in front of it. Mediate, don’t classify.

The gateway isn’t the new part

A reverse proxy in front of MCP servers — one entry point doing identity, routing, and policy — is an established pattern, and several teams already ship it:

  • AnthropicMCP Tunnels connect managed agents to private MCP servers over an outbound, encrypted tunnel. (Notably, Tunnels deliberately don’t decide who can call which tool — that governance is left to a separate layer, which is exactly the gap this piece is about.)
  • DockerMCP Gateway, an open-source proxy that runs each server in an isolated container and manages credentials and access control.
  • MicrosoftMCP Gateway, a reverse proxy with session-aware routing and lifecycle management in Kubernetes.
  • Solo.ioagentgateway, a data plane proxying many backends through one endpoint, with runtime policy against tool poisoning and rug-pulls.
  • Kong — a clean explainer of the pattern.

So the design question isn’t “build a gateway” — it’s how. Two axes are where the interesting decisions live. Coverage of cases: most implementations stop at identity + a tool ACL + audit, the commoditized layer; the hard part is argument-aware per-call policy, credentials brokered fully out of the agent’s hands, and the content-trust problem where the tool result is the attack (Part 4). Integration ergonomics: onboarding a server shouldn’t require the server to implement its own auth — it should register and declare, and the gateway supplies identity, brokers credentials, and curates exposure. An integration tax is how gateways die in practice: if onboarding is painful, teams route around the gateway and the chokepoint becomes a fiction.

This isn’t “more features than Docker or Solo” — they do plenty well, several at a maturity this design doesn’t claim. It’s a bet on where the decision and the burden should sit.

One call, six checks, one door

Every tool call passes through a fixed pipeline before anything reaches a backend. Each stage is a gate that can stop the call, and each carries its own reason to exist — one for security, one for the people integrating against it. The schema below reads both:

identity → RBAC → quarantine → policy → credentials → audit every tool call · in order · deny-by-default · fail-closed
  1. 1
    Identity ⊥ agent ↔ gateway
    Established at the edge (mTLS / OIDC + PKCE), honoured only if it proves it came through the edge.
    security the caller can't forge a subject; absent proof it's treated as unauthenticated — fail-closed.
    ux the agent authenticates once, to one endpoint — no per-server login.
  2. 2
    Authorization (RBAC)
    A subject is bound to a role; a role grants a set of tools. The cheap, static cut.
    security rejects "no business touching this server" before any expensive logic runs.
    ux access is described once, per role — not wired into every server.
  3. 3
    Quarantine + supply-chain
    Registered servers are quarantined, then re-scanned on a schedule (SBOM / CVE) and expected to ship signed, provenance-checked builds.
    security "good standing" means a recent pass — a server clean on day one can carry a known CVE a week later.
    ux a gate condition, not a blocker for normal calls; vetting is the operator's, not the caller's, concern.
  4. 4
    Policy ⊥ gateway ↔ policy
    An external engine (OPA/Rego) decides over subject + tool + arguments. Deny-by-default, fail-closed.
    security decided by code the agent can't influence, over inputs it can't forge; if the engine is down the call is denied.
    ux policy is one versioned artifact to reason about, not N server configs.
  5. 5
    Credential brokering ⊥ gateway ↔ secrets
    The agent never holds the secret; the gateway injects it just-in-time, isolated per identity. The server declares a mode (none / service / per-user / OAuth).
    security compromise the agent and you get its identity — not a wallet of downstream secrets.
    ux integrate a server by declaring a credential mode — no auth code in the server.
  6. 6
    Audit
    Every decision — allow or deny — emits an event. Raw arguments aren't stored, only keyed (HMAC) digests.
    security forensics + non-repudiation without turning the audit store into a second copy of the data.
    ux one place to answer "who did what, and why was it allowed?"
network isolation underneath — backends reachable only through the gateway. If a call can reach a backend without passing these six, the design has failed.

Two things are worth pulling out of that. Policy is argument-aware — it decides over the arguments, not just “is this agent allowed this tool”, which is what classification can never see. And credential brokering is where security and UX stop fighting: taking the secret out of the agent’s hands is the security win, and letting a server declare a mode instead of writing auth is the UX win — the same design move buys both.

And the boundary that makes Check 1 sound: network isolation. Backends sit on pairwise-isolated networks with no route to each other or the internet — reachable by the gateway and nothing else. That contains a compromised backend and removes any path to forge an identity around the edge. The whole thesis is one invariant: if you can reach a backend without passing through the pipeline, the design has failed — and that’s the first thing worth attacking.

Profiles: least privilege and a smaller context

Once every client meets one decision point, a new question has a single place to be answered: what tools does this identity even get to see? The answer is a per-identity profile — a curated subset of the catalog, enabled and disabled per identity, with every change written to an append-only audit trail. It pays off twice:

  • Security — least privilege by surface. A tool that isn’t in your profile isn’t advertised to your agent, so a clean agent never reaches for it. (To be precise about what’s wired today: profiles filter what the agent sees; turning profile state into a hard deny at the policy engine — so a prompt-injected agent can’t invoke a hidden tool by name either — is the roadmap step. Hiding isn’t yet blocking.)
  • Context — less bloat, a sharper agent. Every tool definition costs tokens and dilutes attention. Point an agent at forty-seven tools and it burns budget reading past the forty-one it’ll never use. A profile hands it six definitions, not forty-seven: smaller prompt, lower cost, better tool selection. Debloating the context falls out of the same gate that enforces least privilege.

What this is, and the honest cost

This is not the most feature-complete MCP gateway. Multi-IDP, audit dashboards, self-service onboarding, server catalogues — increasingly commoditized, and several commercial products do them well. This is one architectural argument expressed as a system: AI-agent tool security belongs at runtime, in the path of every call, with the agent treated as untrusted and credentials kept out of its hands.

And the honest cost of one door: centralizing every decision also centralizes every target. The agent’s blast radius shrinks precisely because the gateway’s grows — own the gateway and you own every identity and every brokered secret. That’s a deliberate trade, the same one a secrets manager makes, and it only holds if the gateway is hardened to match: per-identity key derivation, the secret store on its own boundary (HSM-backed is the goal), and the gateway treated as the highest-value asset in the system. I’d rather name that trade than let “one door” imply there’s no concentrated risk.

What comes next

Part 3 — building it. The mechanisms behind each control, the cryptographic constructions this piece waved at, and the things that broke when design met reality — where the trade-offs above turned out to be harder, or easier, than they look on paper.

Part 4 — the content problem. The gap a gateway can’t close alone: a faithfully authenticated, authorized, audited call that the agent only made because a malicious tool result told it to. That’s the signed trust-envelope idea — provenance borrowed from signed media, re-targeted at MCP results, so a session that touched untrusted content is stopped structurally from driving a high-integrity action.


I write about cyber security — purple teaming, detection, secure architecture, and the open-source tools I build. Views my own.