Skip to content
Contextely
Academy6 min readBy The Contextely Team

MCP Authentication Patterns, and Where Each One Leaks

Four mcp authentication patterns in real use today, what each one actually proves, and the specific way each one fails once an agent is holding the token.

A pile of old padlocks and keys of different shapes, standing in for the range of mcp authentication patterns in use and how differently each one actually locks

Photo: Goszton on Pexels

Key takeaways

Ask five different MCP deployments how they handle mcp authentication and you will get four different answers, because the spec leaves more of this open than most people assume. Some of that variation is by design, matched to where the server actually runs. Some of it is teams reaching for whatever was fastest to wire up, and not noticing that the pattern they picked leaks in a specific, predictable way once real data is behind it.

The four patterns actually in use

Environment credentials for STDIO servers. A server launched as a local subprocess reads its credentials from the environment it was started in, the same way any command-line tool does. The MCP authorization specification explicitly says implementations using STDIO transport SHOULD NOT attempt to layer its OAuth flow on top, because the process already inherits the trust boundary of whoever launched it.

A static bearer token. An HTTP-based server checks one header against one stored value. Anyone holding the token gets in, and once in, every call runs with the same access as the token itself. This is the fastest pattern to build and the one most likely to be mistaken for something stronger than it is, because "bearer token" sounds like infrastructure even when the implementation behind it is a single shared secret.

An API key that resolves to a member. Structurally similar to a bearer token in transport, the difference is what happens on the server after the header is read: the key is looked up, resolved to a specific person with a role and a set of grants, and everything downstream runs against that identity rather than against an undifferentiated "the app."

Full OAuth 2.1 with PKCE. The pattern the spec actually documents for HTTP transports: a client obtains a short-lived, scoped token through an authorization code flow, proving both that it holds a valid client registration and that a resource owner consented to the specific access requested.

Which mcp authentication pattern is most secure?

OAuth 2.1 is the strongest of the four at the question it actually answers: proving who is opening a session, without a long-lived shared secret sitting in a config file somewhere. But "most secure" depends on what you are defending against. A leaked static bearer token and a leaked OAuth access token both grant access for as long as they remain valid; the meaningful difference is that an OAuth token is typically short-lived and scoped, so the blast radius of a leak is smaller and self-limiting, where a static token often is not.

Where each one actually leaks

Pattern What it proves What leaks, and how
STDIO environment credentials The process is running under whatever account launched it Anything with local access to the environment or process list; the trust boundary is the machine, not the protocol
Static bearer token Someone holds the token The token itself, in full, for as long as it is valid; every caller behind it is indistinguishable
API key resolved to a member This specific person's credential is in use Still the credential itself, but a leak is attributable to one person, and revocation does not affect anyone else's key
OAuth 2.1 with PKCE A specific client, with a specific consented scope, for a limited time A stolen access token, but typically short-lived; the authorization code and refresh token exchange are harder to intercept than a static value

Table 1: what each authentication pattern actually guarantees, and the specific thing that goes wrong when it is compromised.

"MCP clients MUST implement Resource Indicators for OAuth 2.0 as defined in RFC 8707 to explicitly specify the target resource for which the token is being requested."
Model Context Protocol specification, Authorization

That requirement exists precisely to stop a token issued for one MCP server being replayed against another, which is the specific failure mode that makes a bearer token dangerous once an agent is juggling several tool connections at once. Naming the resource explicitly closes a hole that a generic OAuth implementation, not written with MCP's multi-server reality in mind, would otherwise leave open.

The gap every pattern shares

Every one of these four proves something about the connection. None of them, on their own, decides what a given call is allowed to return. That is a second, separate design choice, made by whoever operates the server, not answered by the choice of authentication pattern. A server can run flawless OAuth and still respond to every authenticated caller identically, because nothing after the handshake resolves the caller down to a specific person with specific scopes.

Does adding mcp oauth remove the need for a permissions layer?

No. It changes what a compromised credential is worth (a short-lived scoped token instead of a permanent shared secret) but it does not add per-caller entitlement on the data a tool call returns. Those are additive, not substitutable: a server can and often should run OAuth for the connection and a separate entitlement check for every call, because they defend against different things.

Practical guidance for choosing a pattern

What a resolved API key actually looks like in a client config

The third pattern, an API key that resolves to a specific member rather than an undifferentiated application, is worth showing concretely rather than describing in the abstract, because the difference from a static bearer token is entirely in what happens on the server, not in the shape of the request itself:

{
  "mcpServers": {
    "contextely": {
      "url": "https://your-deployment/api/mcp",
      "headers": { "Authorization": "Bearer ctx_sk_..." }
    }
  }
}

At the wire level that is indistinguishable from a static bearer token. The header is the same shape either way. What differs is entirely server-side: whether that value is checked against a single stored secret, or looked up against a table of members, each with their own role and their own explicit grants. Two servers can accept the exact same request format and diverge completely in what a caller can subsequently retrieve, which is precisely why "we use bearer tokens" is not, on its own, a meaningful answer to how a system authenticates.

For how that second question gets answered once a caller is authenticated, mcp authorization covers the per-person entitlement layer this post deliberately leaves out. The MCP pillar page states the same distinction in the context of the whole product, /docs/mcp has the actual connection config for the API-key pattern described above, and /security documents how a credential is stored and rotated once you have one.

Frequently asked questions

Is mcp oauth the most secure mcp authentication pattern?

It is the strongest pattern for proving who is opening a session, because it avoids long-lived shared secrets and supports short-lived, scoped tokens with PKCE. It is not, by itself, a solution to what that session may then retrieve. A server that implements OAuth flawlessly and then answers every authenticated call identically has solved authentication and left authorization untouched.

Why is stdio exempt from the mcp authorization specification?

Because a STDIO server runs as a local subprocess of the client, inheriting the same trust boundary as any other program the user chooses to run on their own machine. The spec's authorization flow is built for HTTP-based transports crossing a network boundary, where the server cannot otherwise know who is calling.

What is the actual risk of a static bearer token for mcp server authentication?

Not that it is weak cryptographically, but that it is usually undifferentiated. One token typically grants the same access to everyone who holds it, so a leaked token is a leaked identity for the whole team, not for one person, and revoking it means rotating it for everyone at once.

Does rotating an api key improve mcp server security on its own?

It limits how long a leaked key stays useful, which matters, but it does not change what the key was allowed to do while valid. Rotation without per-caller scoping still leaves every holder of the current key with identical access.

Free for 500 retrievals a month, and self-hostable with no limits.