KavachOS is open source. Cloud launching soon.
kavachOS

02/DEEP DIVE

MCP OAuth 2.1 explainedfor every MCP server in 2026

The four RFCs that define MCP auth, why they matter, and a flow diagram you can read on a Tuesday.

GD

Gagan Deep Singh

Founder, GLINCKER

Published

April 12, 202610 min read

By the end of 2026, Claude Desktop will refuse to connect to an unauthenticated MCP server. Anthropic published that requirement in the MCP spec update last quarter. If you are shipping an MCP tool today without OAuth 2.1, you are on a countdown.

The problem is that the spec references four different RFCs without much explanation of how they connect. Teams read one, implement it in isolation, then wonder why clients reject their metadata endpoint. I want to walk through all four in the order they actually matter at runtime.

RFC 8414

Authorization server metadata

Advertises endpoints, supported grant types, and PKCE methods

RFC 9728

Protected resource metadata

Tells clients which authorization server governs a resource

RFC 7591

Dynamic client registration

Lets clients register at runtime without a developer portal

PKCE S256

Code challenge for public clients

Prevents authorization code interception on native and CLI apps


01

Why MCP needs OAuth at all

MCP is a protocol for AI models to call tools. Those tools do real things: read your calendar, write to a database, send emails. A tool server that accepts any request from any caller is a significant attack surface. The MCP spec chose OAuth 2.1 because it is the only widely supported delegated authorization standard with an active ecosystem of client libraries.

OAuth 2.1 is not a new protocol. It is RFC 6749 with the deprecated grant types (implicit, resource owner password) removed and PKCE made mandatory. If you have shipped OAuth 2.0 before, most of what you know still applies. The additions are what the MCP spec layers on top.


02

RFC 8414: authorization server metadata

RFC 8414 defines a well-known endpoint at/.well-known/oauth-authorization-server where an authorization server publishes a JSON document describing itself. The document lists the token endpoint, the authorization endpoint, the supported scopes, and the PKCE challenge methods it accepts.

An MCP client hits this endpoint before doing anything else. It learns where to redirect the user, what grant types are available, and whether dynamic registration is supported. A minimal compliant response looks like this:

json/.well-known/oauth-authorization-server
{
  "issuer": "https://auth.example.com",
  "authorization_endpoint": "https://auth.example.com/authorize",
  "token_endpoint": "https://auth.example.com/token",
  "registration_endpoint": "https://auth.example.com/clients",
  "scopes_supported": ["read", "write", "admin"],
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code"],
  "code_challenge_methods_supported": ["S256"]
}

The registration_endpoint field is optional in the RFC but required by the MCP spec. If you omit it, clients that rely on dynamic registration cannot self-register and the flow stalls. That is the first footgun I see in the wild.


03

RFC 9728: protected resource metadata

RFC 9728 is newer and less well known. It defines a well-known endpoint on the resource server itself at /.well-known/oauth-protected-resource. This document answers a different question: given a resource endpoint, which authorization server should I trust?

Before RFC 9728, a client had to know the authorization server out of band. You hard-coded it somewhere, or the user told you, or you relied on a convention. RFC 9728 makes this discoverable. Your MCP tool server publishes its own well-known document pointing at the authorization server it trusts:

json/.well-known/oauth-protected-resource
{
  "resource": "https://tools.example.com",
  "authorization_servers": [
    "https://auth.example.com"
  ],
  "scopes_supported": ["read", "write"],
  "bearer_methods_supported": ["header"]
}

A compliant MCP client discovers your tool server URL, fetches the protected resource metadata, finds the authorization server, then fetches the authorization server metadata via RFC 8414. The discovery chain is self-contained. No out-of-band configuration needed.


04

RFC 7591: dynamic client registration

Traditional OAuth flows require a developer to create an application in a portal, get a client ID, and ship that ID with their code. That model breaks in a world where MCP clients can be AI agents that do not have a pre-registered identity with your server.

RFC 7591 solves this with a registration endpoint. A client posts its metadata, including redirect URIs and requested scopes, and receives a fresh client ID in return. No portal needed. The MCP spec mandates that compliant servers support this endpoint.

httpPOST /clients (RFC 7591 request)
POST /clients HTTP/1.1
Host: auth.example.com
Content-Type: application/json

{
  "client_name": "My MCP Agent",
  "redirect_uris": ["https://agent.example.com/callback"],
  "grant_types": ["authorization_code"],
  "scope": "read write",
  "token_endpoint_auth_method": "none"
}

// Response:
{
  "client_id": "s6BhdRkqt3",
  "client_secret_expires_at": 0,
  "redirect_uris": ["https://agent.example.com/callback"],
  "grant_types": ["authorization_code"]
}

Note token_endpoint_auth_method: "none". Public clients, meaning clients that cannot keep a secret, declare themselves here. A CLI tool, a desktop application, or an AI agent running on user hardware is a public client. The server cannot trust a secret that ships inside a binary.


05

PKCE S256: why the code challenge matters

PKCE stands for Proof Key for Code Exchange. The problem it solves: in the authorization code flow, the browser or app receives an authorization code and exchanges it for a token. On mobile and desktop, malicious apps can register the same redirect URI and intercept that code before your app can.

PKCE closes that window. Before starting the flow, your app generates a random secret called the code verifier. It then hashes that secret with SHA-256 to produce the code challenge. The challenge travels with the authorization request. The verifier travels with the token exchange. Only the entity that started the flow knows the verifier.

typescriptpkce.ts
import { createHash, randomBytes } from "crypto";

function generatePkce() {
  // 1. Generate a random 32-byte verifier
  const verifier = randomBytes(32).toString("base64url");

  // 2. SHA-256 hash it to get the challenge
  const challenge = createHash("sha256")
    .update(verifier)
    .digest("base64url");

  return { verifier, challenge };
}

// Authorization request includes:
// &code_challenge=<challenge>
// &code_challenge_method=S256

// Token exchange includes:
// code_verifier=<verifier>

OAuth 2.1 makes S256 the only supported method. The plain method from the original PKCE RFC is gone. If your server still accepts code_challenge_method=plain, strip it. Clients that only send S256 will fail your token exchange if you also accept plain and mis-route the request.


06

The full flow, narrated

Put the four pieces together and the flow looks like this:

  1. 1The MCP client discovers the tool server URL from user configuration or an MCP registry.
  2. 2It fetches GET /.well-known/oauth-protected-resource on the tool server (RFC 9728) and reads the authorization_servers field.
  3. 3It fetches GET /.well-known/oauth-authorization-server on that authorization server (RFC 8414) and discovers the registration and authorization endpoints.
  4. 4If it has no client_id for this server, it POSTs to the registration endpoint (RFC 7591) and receives one.
  5. 5It generates a PKCE verifier and challenge, then redirects the user to the authorization endpoint with the challenge attached.
  6. 6The user authenticates and approves the requested scopes. The server issues an authorization code.
  7. 7The client exchanges the code for tokens using the code verifier. The server verifies that SHA-256(verifier) matches the stored challenge.
  8. 8The client attaches the access token to every tool call as a Bearer token in the Authorization header.

07

Common footguns

I have reviewed a few MCP server implementations over the past few months. The same mistakes come up repeatedly.

The first is skipping RFC 9728 entirely. Teams implement RFC 8414 on their authorization server and stop there. But without the protected resource document on the tool server itself, clients cannot discover the authorization server automatically. You end up requiring clients to hard-code your auth server URL, which breaks when you migrate infrastructure.

The second is serving RFC 8414 metadata without a registration_endpoint field. As noted earlier, the MCP spec requires it. Clients built on the MCP SDK check for this field on startup and log an error if it is absent.

The third is token validation on the resource server. Your tool server receives a Bearer token. You need to verify it against the authorization server that issued it, check the audience claim (the aud field should be your tool server URI), and check the scope. Many implementations skip the audience check. An attacker who obtains a valid token for a different resource server can replay it against yours.


08

Write it yourself vs. use kavachOS

These are the honest trade-offs I see when teams evaluate both paths. Neither is wrong for every situation.

Write it yourself
  • You own every detail of the RFC implementation.
  • Dynamic client registration is 200+ lines of validation logic before you touch token issuance.
  • RFC 9728 metadata and PKCE verification need tests with clients that behave badly on purpose.
  • Any bug in your token validation is your incident response at 2 AM.
kavachOS
  • All four RFC endpoints ship with a single API key.
  • Dynamic client registration, PKCE S256 validation, and audience checking are handled before your code runs.
  • Audit trail on every token issuance and rejection, with agent-level attribution.
  • You add validateMcpToken() to your route handler and ship.

The full integration is in the MCP OAuth guide. It covers dynamic client registration, the full request and response format, and live examples.

Topics

  • #MCP OAuth 2.1
  • #Model Context Protocol
  • #RFC 9728
  • #RFC 8414
  • #RFC 7591
  • #PKCE S256
  • #MCP authentication

Keep going in the docs

Read next

Share this post

Get started

MCP OAuth 2.1 in minutes, not weeks

All four RFC endpoints, dynamic client registration, and audience validation. Free up to 1,000 MAU.