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

ConcernOwner
Transport securityServer operator
Authentication and audience validationServer implementer
Token lifecycleServer implementer and authorization server
What a tool doesServer implementer
Tool description accuracyServer implementer. Unverifiable by anyone else
Which servers an agent hasYou
What credential each server getsYou
Filesystem scopeYou
Approval policyYou
The capability combinationYou, and nothing else is looking
Prompt injection resistanceNobody. 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

Was this page helpful?