MCP OAuth guide

MCP OAuth: How MCP Server Authentication Works, Step by Step

Remote MCP servers sign users in with OAuth 2.1. This guide follows one real connection request by request, using MCPBackend's production endpoints, then covers client setup, API keys versus OAuth, and the errors you are most likely to hit.

Updated October 27, 2026

The short answer

The MCP authorization specification treats a remote MCP server as an OAuth 2.1 resource server. The client (Claude, Cursor, Claude Code and others) gets an access token from an authorization server and sends it as Authorization: Bearer …on every request. Everything else in MCP auth is about letting a client do that with nothing but the server's URL:

  • Discovery. A 401 response points to protected resource metadata (RFC 9728), which names the authorization server, whose own metadata (RFC 8414) lists its endpoints.
  • Registration. The client registers itself through dynamic client registration (RFC 7591), so nobody has to create an OAuth app by hand.
  • Sign-in. The user approves access in a browser with the authorization code grant and PKCE. Passwords and codes never pass through the AI tool.
  • Audience. The token is issued for one resource (RFC 8707), so a token for one MCP server cannot be replayed against another.

Local servers that run over stdio do not use any of this. They inherit your machine's user and normally read secrets from environment variables. OAuth matters once a server is reached by URL and acts on someone's account.

The flow, request by request

These are the requests a client makes the first time it connects to MCPBackend's server at https://mcp.mcpbackend.com/mcp. The responses are copied from production; you can repeat the first three with curl.

1. The first call fails with 401 and a pointer

POST https://mcp.mcpbackend.com/mcp            (no Authorization header)

HTTP/2 401
www-authenticate: Bearer realm="OAuth",
  resource_metadata="https://mcp.mcpbackend.com/.well-known/oauth-protected-resource/mcp",
  error="invalid_token", error_description="Missing or invalid access token"

2. Protected resource metadata names the authorization server

GET https://mcp.mcpbackend.com/.well-known/oauth-protected-resource/mcp

{
  "resource": "https://mcp.mcpbackend.com/mcp",
  "authorization_servers": ["https://mcp.mcpbackend.com"],
  "scopes_supported": ["mcp:tools"],
  "bearer_methods_supported": ["header"]
}

3. Authorization server metadata lists the endpoints

GET https://mcp.mcpbackend.com/.well-known/oauth-authorization-server

{
  "issuer": "https://mcp.mcpbackend.com",
  "authorization_endpoint": "https://mcp.mcpbackend.com/authorize",
  "token_endpoint": "https://mcp.mcpbackend.com/token",
  "registration_endpoint": "https://mcp.mcpbackend.com/register",
  "scopes_supported": ["mcp:tools"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post", "none"]
}

Two details matter here. Only S256 PKCE is accepted (plain PKCE and the implicit flow are switched off), and public clients can use token_endpoint_auth_method: "none", which is what desktop and CLI clients normally do.

4. The client registers itself

POST https://mcp.mcpbackend.com/register
{
  "client_name": "Example MCP client",
  "redirect_uris": ["http://127.0.0.1:33418/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none"
}
→ 201 { "client_id": "…", ... }

The client keeps that client_id. On MCPBackend a registration lasts 90 days; after that the client registers again.

5. You approve access in the browser

GET https://mcp.mcpbackend.com/authorize
  ?response_type=code
  &client_id=…
  &redirect_uri=http://127.0.0.1:33418/callback
  &scope=mcp:tools
  &resource=https://mcp.mcpbackend.com/mcp
  &code_challenge=<base64url(sha256(verifier))>
  &code_challenge_method=S256
  &state=<random>

The server validates the request and redirects you to the consent page in the MCPBackend dashboard. If you are signed in you see the client's name, your account email and the requested access, and you click Connect or Deny. If you are not signed in, you sign in with Google or a four-digit code sent to your email and come straight back to the same consent screen. The pending request is single-use and expires after 10 minutes. Approving sends the browser to the client's redirect_uri with a one-time code; denying sends error=access_denied.

6. The client exchanges the code for tokens

POST https://mcp.mcpbackend.com/token
grant_type=authorization_code&code=…&client_id=…
&redirect_uri=http://127.0.0.1:33418/callback&code_verifier=<original verifier>

→ { "access_token": "…", "token_type": "bearer", "expires_in": 3600,
    "refresh_token": "…", "scope": "mcp:tools" }

7. Every MCP request carries the token

POST https://mcp.mcpbackend.com/mcp
Authorization: Bearer <access_token>
Content-Type: application/json

{"jsonrpc":"2.0","id":1,"method":"tools/list"}

The access token lasts one hour. Before it runs out the client calls /token with grant_type=refresh_token; refresh tokens last 28 days, so you only see the consent screen again if a client sits unused for about a month. The single mcp:tools scope lets the agent use the MCP tools as your account, with the same plan limits and permissions you have in the dashboard.

What an MCP client needs to support

When a server's sign-in fails in one client but works in another, it is usually one of these capabilities.

CapabilityWhy it mattersMCPBackend expects
Streamable HTTP transportRemote servers are reached by URL, not started as a processPOST to /mcp
Resource and authorization server discoveryFinds the endpoints from the 401 aloneRFC 9728 and RFC 8414 metadata
Dynamic client registrationNo hand-made OAuth app or client secretPOST /register
Authorization code with PKCEProtects the code on its way back to the clientS256 only
A redirect URI it can receiveLoopback port for desktop or CLI apps, hosted callback for web appsAny URI the client registered
Token storage and refreshAvoids a consent screen every hour1-hour access, 28-day refresh

Connect Claude Code, Cursor and Claude

Each client only needs the server URL; the OAuth steps above happen on first use.

Claude Code:

claude mcp add --transport http mcpbackend https://mcp.mcpbackend.com/mcp

Then run /mcp inside Claude Code and choose to authenticate the server. A browser tab opens on the consent page.

Cursor, in ~/.cursor/mcp.json or a project's .cursor/mcp.json:

{
  "mcpServers": {
    "mcpbackend": { "url": "https://mcp.mcpbackend.com/mcp" }
  }
}

Claude and Claude Desktop: Settings → Connectors → Add custom connector, paste the URL and click Connect. Other clients that implement the MCP authorization spec should follow the same flow, but we only document the three above; check your client's own docs for where remote servers go. The MCP config generator writes the snippet for several clients, and Connect MCP and the Agent Skill covers the rest of the setup.

Ask the agent to list its tools after connecting. If list_projects and create_table appear, discovery, registration, consent and token exchange all worked.

API key vs OAuth for MCP servers

Plenty of MCP servers accept a static API key in a header instead of OAuth. Both work; they suit different situations.

Static API keyOAuth 2.1
SetupCreate a key, paste it into the client configAdd the URL, approve once in the browser
Who it identifiesWhoever holds the keyA signed-in user and a named client
LifetimeUntil someone revokes itShort access token plus refresh token
Where the secret livesConfig files, env vars, sometimes gitThe client's token store
ScopeWhatever the key was created withScopes the user approved
Good forCI jobs, server-to-server calls, local toolsHosted servers used from AI clients

MCPBackend uses both, for different surfaces. The MCP server accepts OAuth access tokens only, so you never paste a credential into Claude or Cursor. The REST data API your finished app calls uses project API keys (per-table permissions, shown once at creation) for servers and scripts, and end-user tokens from app signup and login for browsers. A project API key sent to /mcp is rejected with the same 401 as no token at all. The dashboard login versus app login guide explains why those identities stay separate.

Common MCP OAuth errors and fixes

What you seeWhat it meansFix
401 invalid_token: Missing or invalid access tokenNo token, an expired one, or a credential that is not an MCP OAuth token (such as an API key)Re-authenticate from the client; do not paste keys into the MCP config
400 Invalid authorization requestThe /authorize URL is malformed or lacks S256 PKCEUpdate the client; plain PKCE and implicit flow are not supported
400 Unknown OAuth clientThe client ID was never registered here or its registration expiredRemove the server from the client and add it again so it re-registers
This authorization request expired or was already usedThe consent page sat open over 10 minutes, or was submitted twiceStart the connection again from the client
Your dashboard session expired. Sign in again.The dashboard sign-in behind the consent page lapsedSign in again; you return to the same consent screen
error=access_denied at the redirectDeny was clicked on the consent pageReconnect and click Connect
invalid_grant at /tokenCode reused, PKCE verifier mismatch, or refresh token older than 28 daysRun the sign-in again from the client

Two longer troubleshooting guides cover the cases that are hardest to spot: MCP OAuth keeps returning to consent and MCP returns unauthorized after a successful connection. The MCP guides hub has the rest.

Adding OAuth to your own MCP server

If you are building a remote MCP server rather than connecting to one, you need an authorization server that issues tokens for it. The usual options are an OAuth provider library running next to your server or an existing identity provider that supports dynamic client registration. MCPBackend runs Cloudflare's workers-oauth-provider library in the same Worker as its MCP endpoint, and hands the browser to its dashboard for sign-in and consent.

Whichever you choose, check four things before you ship: a 401 with a resource_metadatalink, metadata at both well-known URLs, S256-only PKCE, and tokens bound to your server's resource URL. If your actual goal is a database, auth and an API for an app you are building with an AI editor, you may not need your own server at all; see the remote MCP server guide and the MCP database guide.

Frequently asked questions

How does MCP authentication work?+

A remote MCP server acts as an OAuth 2.1 resource server. When a client calls it without a token, the server answers 401 with a link to its protected resource metadata. The client reads that, finds the authorization server, registers itself, sends you to a browser sign-in and consent page, exchanges the returned code for tokens using PKCE, and then sends the access token as a Bearer header on every MCP request.

Does MCP require OAuth?+

Not always. The MCP authorization spec applies to HTTP transports and is optional. Local stdio servers usually read credentials from environment variables instead. Remote servers that act on a user's account should use OAuth so the user never pastes a long-lived secret into an AI client.

What is dynamic client registration in MCP?+

Dynamic client registration (RFC 7591) lets an MCP client create its own OAuth client ID by POSTing its name and redirect URIs to the server's registration endpoint. It means you can add a server by URL alone, without creating an OAuth app by hand. MCPBackend's registration endpoint is https://mcp.mcpbackend.com/register.

Can I use an API key instead of OAuth for an MCP server?+

Some servers accept a static API key in a header, which is simpler but means a long-lived secret sits in a config file. MCPBackend's MCP server accepts OAuth access tokens only. Its project API keys are for the data API that your app calls, not for the MCP endpoint.

How long do MCP OAuth tokens last?+

It depends on the server. On MCPBackend an access token lasts one hour and a refresh token 28 days, so a client used at least once every four weeks stays connected without asking you to sign in again. Client registrations expire after 90 days.

Why does my MCP client keep asking me to authenticate?+

Usually the client cannot store or use its refresh token, the refresh token has expired, or the client registration was removed. Remove the server from the client, add it again and complete the consent screen once. If the loop continues, check that the client supports PKCE with S256 and the authorization code grant.

Build the backend, skip the setup

Give your AI editora real backend

Start with a free project. Database, REST API, auth, permissions, webhooks, and MCP are included.

Related guides