Control agent access

How MCP authorization works (OAuth 2.1)

A remote MCP server has to know who is calling and what they may do. The MCP spec uses OAuth 2.1 for that, plus a few rules that tie each token to one server.

By
Dan Flanagan
Updated
October 15, 2026
Checked against
the MCP specification (modelcontextprotocol.io), current protocol version 2026-07-28, October 2026
Reading time
3 min

New to MCP? What is an MCP server? covers the basics first.

A local MCP server doesn't need any of this. Your AI app starts it as a process and hands it an API key from the environment. A remote server sits at a URL that many people and agents can reach, so it has to check who each caller is, every time.

The MCP specification (protocol version 2026-07-28) uses OAuth 2.1 for this instead of inventing something new, and adds a few standards on top. It applies only to servers reached over HTTP. The spec says stdio servers "SHOULD NOT follow this specification, and instead retrieve credentials from the environment."

The flow

Say an agent wants to read tickets from a hosted issue tracker at mcp.example-tracker.com.

  1. The client calls without a token and gets a 401. The WWW-Authenticate header points to the server's protected resource metadata (RFC 9728), a small JSON document naming the authorization server it trusts.
  2. The client looks up that authorization server. Its metadata (RFC 8414), or an OpenID Connect discovery document, lists the login and token endpoints.
  3. The user signs in with PKCE. The client sends a hash of a random secret with the login request, then the secret itself when exchanging the code for a token. Someone who intercepts the code can't use it without the secret. The spec requires PKCE on every flow.
  4. The token is tied to one server. The client includes resource=https://mcp.example-tracker.com in both requests, using resource indicators (RFC 8707), and the authorization server writes that URL into the token's audience.
  5. The server checks the audience on every call. A token issued for some other API is rejected with a 401, even if it's validly signed and the user is allowed to use that other API.

No passing tokens along

If the server needs to call the tracker's real API, it uses its own credential for that. It never forwards the token the client sent. The spec puts it plainly: servers "MUST NOT accept any tokens that were not explicitly issued for the MCP server."

The spec's security best practices call the forwarding mistake token passthrough. It defeats the downstream API's own checks, and a token leaked at one hop works anywhere that hop is trusted. The confused deputy problem covers the attacker's side of this.

What the spec leaves to you

Check the audience on every request, rather than just once at login. Treat a server's discovery metadata as untrusted input too: the best-practices page warns that a malicious server can point its metadata at internal addresses, and recommends blocking private IP ranges when fetching OAuth URLs.

The spec also doesn't decide what a valid token may do. An authorization server can still hand out scopes that are far too broad. For local stdio servers, the key in the environment still needs narrowing, as covered in least privilege for AI agents.

How Statio handles it

People sign in to Statio through WorkOS using the authorization code flow with PKCE, and Statio then issues its own signed tokens instead of passing the identity provider's tokens around. Agents aren't people signing in, so each one is its own principal with short-lived tokens Statio issues. When Statio connects to a vendor's remote MCP server, it's an OAuth client in the flow above, identifying itself with a client metadata document or registering dynamically where the vendor allows it. Once a token is valid, per-tool permissions decide what it can call.

Questions

No. The spec says stdio servers should read credentials from their environment. They still need narrow credentials, just not this flow.

It writes the server's URL into the token's audience, so the token only works on that one server.