The MCP security model
MCP is a well-designed protocol with a clear and narrow security remit. Most of what people expect it to guarantee, it explicitly does not, and understanding exactly where the line falls is the difference between a defensible deployment and a hopeful one.
What MCP is
The Model Context Protocol standardises how an AI application connects to external capabilities. A host (the agent application) runs one or more clients, each connected to a server that exposes:
- tools: actions the model can invoke
- resources: data the model can read
- prompts: templates a user can invoke
Transport is either stdio (a local child process) or streamable HTTP (a remote
service). The protocol is JSON-RPC underneath.
Its success is the reason a security tool for it needs to exist. Before MCP, connecting an agent to a database was an integration project with a design review. Now it is four lines of JSON, and the review did not survive the transition.
Where the trust boundaries are
Five parties, and the boundaries between them are where everything interesting happens.
┌──────────┐ ┌──────────┐ ┌──────────┐
│ User │───────▶│ Host │───────▶│ Server │───────▶ upstream API
└──────────┘ │ (agent) │ └──────────┘
└────▲─────┘
│
┌───────┴────────┐
│ Untrusted │
│ content: │
│ issues, pages, │
│ files, tool │
│ descriptions │
└────────────────┘
- User → Host. The user's instructions. Trusted, but not the only input.
- Untrusted content → Host. The boundary that does not hold. Everything the agent reads arrives in the same context as the user's instructions. See prompt injection.
- Host → Server. Authenticated, if the server implements authorization.
- Server → upstream. Where token passthrough breaks audience restrictions.
- Server → Host, in the response. Tool results and tool descriptions flow back into the model's context, which makes this a content boundary as well as a data one. It is routinely missed.
What the protocol guarantees
MCP, properly implemented, gives you:
A negotiated capability set. The client and server agree on what exists. Tools are declared, not discovered by accident.
An authorization framework. The 2025 specification revisions added OAuth 2.1-based authorization for remote servers, with two requirements that matter: servers must validate the audience of a token, and must not pass tokens through to upstream APIs. See the authorization specification.
Documented security guidance. The security best practices page addresses token passthrough, the confused deputy, session hijacking and proxy misuse with concrete countermeasures.
A consent model in principle. The specification expects hosts to obtain user consent before invoking tools.
That is a genuinely serious security posture for a protocol at this stage of its life.
What it does not do
And here is the line. None of the following is a gap in MCP; each is outside its remit by design. All of them are yours.
It does not constrain what a server can do. MCP describes how a tool is
declared and invoked. It says nothing about what the tool does on the other
side. A tool called get_weather may drop your tables.
It does not verify tool descriptions. Descriptions are free text supplied by the server. Nothing checks them against behaviour. They are also read by the model, which makes them an injection channel. See tools are code.
It does not scope credentials. The credential your server holds is a deployment decision. MCP has no opinion about whether your GitHub token is fine-grained or an org owner's PAT.
It does not sandbox anything. A stdio server is a child process of your
agent with your user's privileges. There is no isolation in the protocol.
It does not compose safely. Five servers on one agent is five capability sets in one context. The protocol has no notion of the combination: no way for a server to say "do not run me alongside anything with network egress", and no host obligation to notice. This is where the lethal trifecta assembles.
It does not mandate lifecycle management. Token expiration and rotation are recommended in guidance and not required by the core specification, which means message replay and stale-session reuse are live concerns in real deployments.
It cannot solve prompt injection. Nothing at the protocol layer can. The instruction and the data arrive in the same context, and that is a property of language models, not of MCP.
The transport question
The two transports have genuinely different security shapes, and conflating them causes bad decisions.
stdio is a local child process. There is no network, so no TLS question
and usually no authentication question. The risks are different in kind:
- The command line is arbitrary code, sourced from a config file.
- The process inherits your environment, including credentials you did not mean to share.
- An unpinned package runner resolves whatever the registry serves at launch.
- There is no isolation. It is your user, on your machine.
This is why Gate refuses to start stdio servers even under --inspect
without a second explicit flag. The command line comes out of the file being
audited, and it runs as you.
Streamable HTTP is a network service. The risks are the familiar ones
(transport security, authentication, audience validation) plus one that is
specific to agents: an attacker who can modify tool results on the wire has a
direct prompt-injection channel. That is why plaintext HTTP to a remote MCP
server is a high finding rather than a note. GATE003.
Configuration as the real boundary
Given all of the above, here is where the actual security boundary sits for a team deploying agents:
The configuration file.
Not the protocol, which does not constrain behaviour. Not the server, which you usually did not write. Not the model, which can be persuaded. The configuration is the only place where somebody in your organisation makes a decision that holds:
- which servers exist
- what credential each one gets
- what filesystem scope each one has
- whether approval is required
- and therefore, what the combination adds up to
Everything Gate does follows from that observation. It is why Gate reads configuration rather than instrumenting runtimes, and why a four-line diff is the unit of analysis.
Where responsibility actually sits
| Concern | Owner |
|---|---|
| Transport security | Server operator |
| Authentication and audience validation | Server implementer |
| Token lifecycle | Server implementer and authorization server |
| What a tool does | Server implementer |
| Tool description accuracy | Server implementer. Unverifiable by anyone else |
| Which servers an agent has | You |
| What credential each server gets | You |
| Filesystem scope | You |
| Approval policy | You |
| The capability combination | You, and nothing else is looking |
| Prompt injection resistance | Nobody. Assume it fails. |
The bottom half of that table is configuration, and the last two rows are the ones with no other owner. That is the gap Gate is for.
npx @usegate/cli scan