How OAuth works with MCP, explained without the jargon

OAuth specs are long, and most of them were written for people who already know OAuth. This page is not that. It is the version of “how does OAuth work with MCP” you can read once and actually understand.

Who this is for

You are building or maintaining an MCP server. You have heard it should use OAuth instead of a static API key, but you have never implemented OAuth yourself, and the spec documents make your eyes glaze over. That is normal. OAuth 2.1 is a small number of ideas wrapped in a lot of precise, defensive language. Once you see the ideas laid out plainly, the spec stops being scary — it just becomes the fine print.

The four actors

Every OAuth flow has the same four characters. Once you can name them, the rest of the flow is just them passing messages back and forth.

  • The MCP client — the thing that wants to use your tool. In practice this is often an AI assistant like Claude, acting on behalf of a human user.
  • The MCP server — your server. It exposes tools (functions the AI can call) and it is the thing you are trying to protect from random, unauthenticated access.
  • The authorization server — the piece that checks who is asking, gets a human’s permission, and hands out tokens. This is the role mcpauth plays. It is a separate job from the MCP server itself, which is exactly why it is usually easier to hand it off than to build it.
  • The resource — the actual data or capability behind your tools: a database, a file store, a third-party API, whatever your MCP server wraps. The resource is what everyone is ultimately trying to protect.

The flow, in plain English

Here is the whole handshake, start to finish. No step here is optional or MCP-specific trivia — this is the actual sequence a client and server go through.

  1. Discovery. The client hits a well-known URL on your MCP server and gets redirected to your authorization server’s discovery document — a JSON file at /.well-known/oauth-authorization-server that lists every endpoint it needs. The client never has to be told these URLs by a human; it just knows the base URL and looks the rest up.
  2. Dynamic registration. The client registers itself with the authorization server by calling POST /api/oauth/register. No human fills out a form or copies a client ID into a config file — the client does this automatically, in code, the first time it sees your server.
  3. A human authorizes access. The client sends the user to GET /oauth/authorize in a browser. The person signs in and sees a consent screen — “this app wants to access this” — and clicks approve or deny. This is the one step in the whole flow that a human actually has to do.
  4. A token is issued. Once the human approves, the authorization server hands back a code, and the client exchanges it for an access token by calling POST /api/oauth/token. That token is now proof that a specific user approved this specific client for this specific access.
  5. The client calls the MCP server. Every request to your MCP server now includes that token as a bearer token — sent in the request, no re-login needed until it expires.
  6. The server validates it. Your MCP server checks the token is real, unexpired, and covers the right scope before it lets the request through to your tools. This is the check that turns “anyone can call this” into “only authorized callers can call this.”

Where mcpauth fits

mcpauth is the authorization server in that flow — steps 1 through 4. It runs discovery, dynamic registration, the consent screen, and token issuing for you, so you don’t write any of that from scratch. Your MCP server still does step 6, validating incoming tokens, but you don’t hand-roll that either: the mcpauth SDK wraps the official @modelcontextprotocol/sdk’s bearer-auth middleware, so one line of Express middleware covers it.

Concretely, that line looks like this:

app.use("/mcp", mcpAuth({ registrationSecret: process.env.MCPAUTH_SECRET }));

Requests without a valid token get rejected with a spec-correct 401 before they ever reach your tool handlers. Everything above — discovery, registration, consent, tokens, validation — is the part of MCP’s spec that almost no OAuth provider actually supports, which is why mcpauth exists as a separate, drop-in piece rather than something you’re expected to bolt onto an existing identity platform.

A short glossary

Bearer token
A string that proves access, no other credentials needed — whoever “bears” it can use it, the same way a hotel key card works for whoever is holding it.
PKCE
A one-time secret the client generates before it starts the login flow, so that only the client that started the flow can finish it. It stops an attacker from hijacking someone else’s authorization code. It is mandatory in OAuth 2.1, not optional.
Scope
A label for what a token is allowed to do — like “read-only” versus “read and write.” A token can be valid without being allowed to do everything.
DCR
Short for Dynamic Client Registration. It is how a client registers itself with an authorization server automatically, in code, instead of a human manually creating an app entry in a dashboard first.
Introspection
A way for a server to ask the authorization server “is this token still good?” directly, instead of only trusting what the token itself claims.

Ready to see it running against a real MCP server?