# API keys or OAuth for your MCP server

- Published: 2026-08-13
- Tag: EDUCATION
- URL: https://authplane.ai/blog/mcp-api-keys-vs-oauth/
- Excerpt: When a static API key is fine for an MCP server, the four things it can't do, and what the MCP spec actually recommends once you go remote.

---

*When a static key is fine, the exact point where it stops being fine, and what the spec wants once you cross it.*

## TL;DR

- A static API key is fine for a local, single-user MCP server that nothing else can reach. The spec even endorses it for STDIO servers.
- Go remote and the spec expects OAuth 2.1 (a SHOULD, not a MUST). Add a second agent or move near regulated data and the four gaps below turn real on their own, whatever the spec says.
- The move off a key is a middleware swap, not a rewrite.

You've got a key in a `.env` file and someone just told you that's a problem. Maybe it is. Maybe it isn't. You're in good company either way: when Astrix Security analyzed more than 5,200 open-source MCP servers for its [State of MCP Server Security 2025](https://astrix.security/learn/blog/state-of-mcp-server-security-2025/) report, published that October, 53% were running on long-lived static keys or personal access tokens and only 8.5% used OAuth. The study read repository READMEs rather than code, so treat the split as directional.

This is about the exact line where a key stops being fine, so you can check which side of it you're on instead of taking anyone's word for it.

## When an API key is fine for an MCP server

Local server. One user. One agent. No agent handing work to another. Nothing regulated. Not reachable from the internet. Tick all of those and a static key is a reasonable call, and most of OAuth would be machinery you never touch.

That's not a setup for a gotcha. It's true, and it's why the next part lands: the problems with keys are real, but they're conditional, and the condition is the moment you step outside that box.

## Four things an API key can't do that OAuth can

Once your server is shared, remote, multi-agent, or near regulated data, a key hits four walls. None are fixable while it stays a key.

### It never expires on its own

A leaked key is good until a human notices and rotates it, which might be never. An OAuth access token ages out on a timer you set, typically minutes, so a leak has a bounded blast radius instead of a door left open for months.

### It can't scope down

There's one key, so the agent that reads your issues carries the same credential as the one that can delete the repo. OAuth scopes let you hand each agent only what it needs, and a compromised reader stays a reader.

### It carries no identity

A key proves someone holds the key. It can't tell your server which user is behind a call, or which agent is acting for whom. Row-level security, per-user limits, tenant isolation: all of it needs an identity the key doesn't carry.

### It can't recover

Revoke a key and the agent has nothing to fall back on. Calls start returning 401 mid-workflow, and the agent has no way to ask for a new credential, someone has to notice and hand it one. OAuth clients handle this themselves: the refresh flow lets a client get a fresh token without human intervention, and when rotation is enabled a replayed refresh token is a signal you can alert on rather than a silent reuse.

## Does the MCP spec require OAuth?

Less than people assume, and the honest answer is more useful than the scary one. The spec says authorization is OPTIONAL. For STDIO servers it goes further: implementations SHOULD NOT follow the authorization spec at all, and should take credentials from the environment instead, which is the spec explicitly endorsing the key in your `.env` for local servers. For HTTP transports it says implementations SHOULD conform to the authorization spec, and that spec is built on OAuth 2.1 with PKCE. SHOULD is not MUST: you can deviate if you have a reason and understand what you're giving up.

So a static key on a remote server isn't a spec violation. It's a documented deviation from what the spec recommends, and the thing you give up is bigger than compliance. MCP clients discover how to authenticate through Protected Resource Metadata and the `WWW-Authenticate` challenge. A static key takes part in none of that. Any agent that isn't yours can't connect without you provisioning a secret out of band, one client at a time. That's what usually forces the move, well before anyone raises the spec.

The 2026-07-28 revision tightened the model around OAuth rather than the requirement to use it: issuer validation under RFC 9207, client credentials bound to the server that issued them, and Dynamic Client Registration formally deprecated in favor of Client ID Metadata Documents. See the [authorization section of the spec](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization).

## How to migrate from API keys to OAuth

Moving from an API key to OAuth is a smaller code change than it sounds. Right now your server checks one thing on each request: does it carry the correct secret key? With OAuth it checks a token instead: is the token valid, was it issued for this server, and does it grant the permission this call needs? That check runs in the same place it does today, the middleware that sits in front of your request handlers. You're changing what gets verified, not how requests move through your server.

**Before:** `if request.header("x-api-key") != API_KEY: reject()`

**After:** the SDK fetches your authorization server's JWKS, verifies the token's signature, checks the audience and required scope, and hands your handler the identity. The AuthPlane SDKs for Go, TypeScript, and Python do this behind one call, and also serve the Protected Resource Metadata endpoint your clients use to discover where to authenticate.

## API keys vs OAuth: a side-by-side comparison

The middle column is roughly what you get from an OAuth 2.1 server. The right column is what AuthPlane adds on top.

{% table %}
- Capability
- Static API key
- OAuth 2.1 (any compliant server)
- With AuthPlane
---
- Expiry
- Never, until rotated by hand
- Short-lived access tokens
- Short-lived, configurable
---
- Scoping
- All or nothing
- Per-scope, as granular as you define
- Per agent, per tool
---
- Identity
- Holder only
- End user (`sub`) and client
- Plus agent identity and delegation chain on agent-mediated calls
---
- Revocation
- Silent 401s, no recovery
- Revocation endpoint and refresh flow
- Refresh rotation with replay detection and family revocation
---
- Audit trail
- None
- Whatever your AS records
- Issuance and consent logged by default
---
- Remote MCP, spec-aligned
- Deviates from the spec's SHOULD
- Yes
- Yes
{% /table %}

## API keys or OAuth: which should you use?

Keep the key if you're local and solo. The day you go remote, the spec's recommendation and your clients' discovery flow both point the same way. Add a second agent or get near regulated data and those four gaps turn real regardless. The move is a middleware swap, not a rewrite. If you want SDKs that handle the validation end for you, they're here: [github.com/AuthPlane](https://github.com/AuthPlane).

## Sources

*Primary and official sources, accessed August 2026.*

- **MCP Authorization specification**, 2026-07-28 (current). modelcontextprotocol.io/specification/2026-07-28/basic/authorization
- **Astrix Security**, State of MCP Server Security 2025 (published October 2025). astrix.security/learn/blog/state-of-mcp-server-security-2025
- **RFC 7636**, Proof Key for Code Exchange (PKCE). datatracker.ietf.org/doc/html/rfc7636