On this page
MCP Authentication and MCP OAuth: How Sign-In Works (Nov 2026)

MCP authentication works two ways: remote MCP servers sign you in with OAuth 2.1, and local (stdio) servers read a key from the environment. If your client keeps asking you to sign in again, the server usually issues no refresh token, so every expired access token means a fresh sign-in. Prefer servers with scoped OAuth over all-access keys.

Sign-in trouble is a steady theme on Reddit. Recent threads include r/laravel's "PSA: Lock down your MCP OAuth registrations" (20+ comments), r/mcp's "Why OAuth isn't enough", a GitLab MCP sign-in failing in Visual Studio 2026 with "Client ID is not available", and "ClickUp MCP keeps disconnecting" on r/hermesagent.

How MCP OAuth works: the authorization flow step by step

The rules are in the Authorization section of the current MCP specification (2026-07-28), with the details split into Authorization Server Discovery, Client Registration and Security Considerations. The short version, quoting the spec where it matters:

It's optional, and only for HTTP. "Authorization is OPTIONAL for MCP implementations." Servers using stdio "SHOULD NOT follow this specification, and instead retrieve credentials from the environment." That is why local servers ask for an API key in env, and remote ones open a browser.

The MCP server is an OAuth resource server. "A protected MCP server acts as an OAuth 2.1 resource server", and your client acts as the OAuth client. The sign-in page belongs to an authorization server, which can be the same service or a separate one.

Discovery starts with a 401. "MCP servers MUST implement OAuth 2.0 Protected Resource Metadata (RFC9728)." When you connect without a token, the server answers 401 Unauthorized, usually with a WWW-Authenticate header pointing to a metadata document. If the header is missing, the client tries /.well-known/oauth-protected-resource instead. That document names the authorization server.

The client needs a client ID. For clients that support every option, the spec's priority order is: a pre-registered client ID, then a Client ID Metadata Document (an HTTPS URL that describes the client), then Dynamic Client Registration (DCR) "as a fallback". If none works, the client should prompt you to enter the details.

PKCE is required. "MCP clients MUST implement PKCE", using S256, and must refuse to continue if the authorization server's metadata doesn't list code_challenge_methods_supported.

Tokens are tied to one server. Clients must send a resource parameter naming the MCP server, and "MCP servers MUST validate that access tokens were issued specifically for them as the intended audience." A server "MUST NOT pass through the token it received from the MCP client" to other APIs. This stops one server's token being replayed against another.

Every request carries the token. The client sends Authorization: Bearer <token> on every HTTP request, never in the URL. An invalid or expired token gets a 401.

Scopes can grow when needed. If a token lacks a permission, the server should answer 403 with error="insufficient_scope" and the scopes it needs, and the client should run a "step-up" sign-in for those scopes.

What changed in the 2026-07-28 spec

The previous version of this guide quoted the 2025-11-25 spec. The core flow above is unchanged. These are the differences that matter for sign-in:

Change What it means
Dynamic Client Registration is deprecated "New implementations should use Client ID Metadata Documents instead." DCR stays for backwards compatibility. Expect fewer "register your app" errors as clients and providers move to metadata documents
New refresh-token rules Clients "MUST NOT assume refresh tokens will be issued" and MAY add offline_access when the authorization server lists it. MCP servers SHOULD NOT put offline_access in their own scope lists
Issuer checks (RFC 9207) Authorization servers SHOULD return an iss value with the code, and clients MUST check it before redeeming the code. Metadata whose issuer doesn't match the URL it came from MUST be rejected. This blocks "mix-up" attacks
Credentials bound to one authorization server Clients key stored client credentials by issuer and MUST re-register if the server moves to a different authorization server
application_type in DCR Desktop and CLI clients SHOULD register as "native", which avoids redirect-URI rejections from OpenID Connect providers
Step-up keeps old scopes When re-authorizing, the client asks for the union of its earlier scopes and the new ones. Servers SHOULD name every scope an operation needs in one challenge
No sessions MCP is now stateless: the initialize handshake and the Mcp-Session-Id header are gone. Each request stands alone, and the token on it is what tells the server who is calling

Full list: the spec's changelog.

MCP server authentication: how to add OAuth to your own server

If you're building a remote MCP server, this is the minimum the spec asks of the server side. A local stdio server doesn't need any of it: read a key from the environment, as in keeping API keys out of your MCP config.

  1. Use an existing authorization server. Your company identity provider, or the API provider whose data the server reads, issues the tokens. Writing your own login and token service is rarely worth the risk.

  2. Publish Protected Resource Metadata at /.well-known/oauth-protected-resource (or at that path plus your MCP endpoint path), with authorization_servers listing at least one authorization server.

  3. Answer unauthenticated requests with 401 and a WWW-Authenticate header carrying resource_metadata and, ideally, the scope needed:

    HTTP/1.1 401 Unauthorized
    WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
                             scope="files:read"
    
  4. Validate every token on every request: signature or introspection, expiry, and an audience that names your server. "MCP servers MUST NOT accept or transit any other tokens."

  5. Never forward the client's token. If your server calls another API, it gets its own token for that API.

  6. Keep scopes small. Ask for read scopes up front and return 403 insufficient_scope with the extra scope when a tool needs write access. Leave offline_access out of your scope lists; refresh tokens are the authorization server's call.

  7. Check the authorization server's settings: PKCE with S256 advertised, exact redirect-URI matching, Client ID Metadata Document support (DCR only if you need older clients), and an iss value in authorization responses.

  8. If you keep DCR open, rate-limit it. That's the point of the r/laravel PSA: an open registration endpoint with no limit lets anyone create client registrations.

  9. Proxying a third-party API with one static client ID? The spec requires per-client user consent before forwarding to the third party's authorization server (the "confused deputy" rule).

Test it with the MCP Inspector: connect once with no credentials and you should get "auth required" (CLI exit code 3), then run the full OAuth flow and watch the 401 and the Authorization header in the Network tab. If many servers need the same company sign-in, an MCP gateway can handle it centrally. The rest of the server-side checklist is in how to create an MCP server, securely.

Need OAuth added to your MCP server, or a hardened server built from scratch? Tell us what it connects to.

Why your client keeps asking you to sign in

Cause What you see Fix
No refresh token issued A new sign-in every time the access token expires Ask for offline_access if the provider lists it (Claude Code adds it automatically; in Cursor add it to auth.scopes)
Refresh token rejected or rotated away A "re-authenticate" notice after working for a while Sign in again from the client's MCP menu
Client can't register (no DCR, no metadata document support) Errors like "does not support dynamic client registration" or "Client ID is not available" Register an OAuth app with the provider and give the client a static client ID
Redirect URI mismatch Browser shows an error after you approve Use the callback URL the provider has registered for your client
A static Authorization header is configured Connection fails instead of opening a browser Remove the header to use OAuth, or fix the token
Sign-in per client and per machine Signed in on your laptop, not in CI or a cloud agent Sign in once in each environment

Claude Code

From Claude Code's MCP docs (more commands in our Claude Code MCP setup guide):

  • "Authentication tokens are stored securely and refreshed automatically." When a stored token gets a 401, Claude Code refreshes it, reconnects and retries once. If the refresh token itself is rejected, you get a notice; open /mcp and choose Re-authenticate.
  • If the authorization server lists offline_access, Claude Code adds it so the token can be refreshed without a new browser sign-in.
  • Sign in from your shell instead of a session. Over SSH, add --no-browser to get a URL to open on your own machine:
claude mcp login sentry
  • Headless runs (claude -p, the Agent SDK) can't run the browser flow. Sign in from an interactive session or with claude mcp login first.
  • Claude Code discovers servers that use a Client ID Metadata Document on its own. If the server supports neither that nor DCR, register an app with the provider, then add the server with your client ID (the secret is prompted for and stored in the system keychain or a credentials file, not in your config):
claude mcp add --transport http \
  --client-id your-client-id --client-secret --callback-port 8080 \
  my-server https://mcp.example.com/mcp

Use http://localhost:PORT/callback as the redirect URI in the provider's form, with the same port.

  • To limit what you grant, pin scopes with oauth.scopes in the server's config. If a tool later fails with "needs additional permissions", add the named scope there and authenticate again.

Cursor

From Cursor's MCP docs:

  • Cursor uses the authorization code flow with PKCE and "renews an expired access token with the refresh token the server issued." If the server returns an expiring token with no refresh token, you sign in again when it expires.
  • If your provider only issues refresh tokens for a scope such as offline_access, add that scope to auth.scopes in mcp.json.
  • For servers without DCR, set a static CLIENT_ID (and CLIENT_SECRET if the provider uses confidential clients) in the server's auth block, using environment variables rather than pasted values.
  • Cursor's desktop callback is http://localhost:8787/callback.
  • Cloud Agents keep their own sign-ins, separate from your machine's. Signing out locally doesn't sign them out.

For other clients, check their docs for refresh-token support before blaming the server.

OAuth vs API key vs personal access token

Authentication is the most heavily weighted of the four checks in our trust grade. What matters is less the method and more how much a leaked credential could do.

Method How it works Typical risk Example from the directory
OAuth with real scopes or grants Browser sign-in, you approve specific access Lowest: a token covers only what you granted Stripe (A): you grant each account or sandbox. Slack (A): per-tool scopes plus admin approval
OAuth with all-or-nothing access Browser sign-in, but one grant covers everything you can do Medium: no key on disk, but the agent can do anything you can Notion (B): full Notion permissions, no scopes
Personal access token (PAT) You create a token and paste it into config Medium to high: lives on disk, often broad, often long-lived GitHub (A): hosted use in Claude Code and Cursor means pasting a PAT; its OAuth asks for every scope by default
API key A static key in env or a header Highest if it acts for the whole account Most local servers. See keeping API keys out of your MCP config
None No sign-in Fine for public read-only data; risky for anything else Microsoft Learn (public docs)

Safety note: watch for "full user permissions"

OAuth feels safer because there is no key to leak, but many hosted servers grant the agent everything your account can do. Examples from our listings:

  • Notion: OAuth acts with your full Notion permissions; there are no scopes or read-only mode for OAuth connections.
  • Figma: one scope, mcp:connect, covers read and write tools alike.
  • Asana: OAuth without scopes.
  • Dropbox: full Dropbox access unless you register your own app.

What to do about it:

  1. Use a separate account or workspace for agent work where the vendor allows it.
  2. Prefer read-only options when they exist, for example the read-only endpoints of GitHub and Buildkite, or the read-only grant on Cloudflare.
  3. Keep tool approval on for anything that writes.
  4. Revoke when you're done. In Claude Code, claude mcp logout <name> revokes tokens where the provider supports it. Removing the server alone deletes the local tokens but doesn't revoke them.

Before you connect a new server at all, run the 5-minute security checklist.

Frequently asked questions

What is MCP OAuth?

It's the sign-in flow the MCP spec defines for remote servers: OAuth 2.1 with PKCE, where the MCP server is the resource server and your AI app is the OAuth client. The server points the client to its authorization server, you approve access in the browser, and the client sends the resulting token on every request.

What is the difference between MCP authentication and MCP authorization?

Authentication is signing in: proving who you are. Authorization is what that sign-in grants. The MCP spec covers both in its "Authorization" section: you sign in on the authorization server's page, and the client gets an access token that allows specific access (scopes) to one MCP server. So when people say "MCP authentication", the spec's answer is the OAuth authorization flow above.

Does MCP require authentication?

No. The spec makes authorization optional, and says local stdio servers should take credentials from the environment instead. Remote servers that protect user data are expected to use the OAuth flow the spec defines. A remote server that lists its tools with no credentials at all can be used by anyone who finds the URL.

How do I add authentication to my MCP server?

Publish Protected Resource Metadata, return 401 with a WWW-Authenticate header to unauthenticated requests, let an existing authorization server issue tokens, and check each token's audience on every request. The MCP server authentication section above has the full list.

Why does my MCP server keep asking me to log in?

Most often the server issues an expiring access token without a refresh token, so the client can't renew it silently. Adding offline_access can fix it if the provider lists that scope (Cursor: auth.scopes; Claude Code adds it itself). Otherwise, the provider has to change.

Is Dynamic Client Registration deprecated in MCP?

Yes, as of the 2026-07-28 spec. Client ID Metadata Documents replace it: the client's ID is an HTTPS URL that describes it, so nothing has to be registered in advance. DCR still works with providers that haven't moved. If a provider supports neither, register an app yourself and give the client its client ID (--client-id in Claude Code, auth.CLIENT_ID in Cursor).

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

Yes, but outside the spec's OAuth flow. Local stdio servers are meant to read credentials such as API keys from the environment. Many remote servers also accept a key or token in a static Authorization header; in Claude Code, a configured header replaces the browser sign-in. Keep keys out of the config file itself, as shown in keeping API keys out of your MCP config.

Is a personal access token safer than OAuth?

Usually not. A PAT sits in a file, rarely expires, and is often created with broad permissions. A scoped OAuth grant that the client refreshes and stores securely is the better default. If you must use a PAT, give it the fewest permissions and an expiry date.

Why is OAuth "not enough" for agents?

The r/mcp thread makes a fair point: a token says who granted access, not which agent is acting. Logs show your account either way. Separate accounts for agents, narrow grants and tool approval close most of that gap today.

More: Is this MCP server safe to install? · MCP gateway · MCP security best practices · Browse the directory