Skip to content

MCP authentication: OAuth 2.1 for remote MCP servers

On this page

MCP server authentication follows OAuth 2.1 when a remote server requires sign-in, and that flow is what people usually mean by MCP OAuth. The client calls the server, gets a 401 that points to the server's metadata, finds the authorization server, identifies itself, sends the person through a browser sign-in, and receives an access token issued for that one MCP server. Some remote servers accept anonymous calls or a static API key instead, and local servers that run over stdio read credentials from their environment.

This guide was checked against the MCP authorization specification, revision 2026-07-28, on 30 October 2026.

Which MCP servers need authentication?

Authorization is optional in MCP. A public documentation server may accept anonymous calls, while servers that read or change your accounts usually require sign-in. The specification splits by transport:

  • Streamable HTTP (remote) servers should follow the OAuth-based authorization specification.
  • stdio (local) servers should not. The client starts them as a process and passes credentials through environment variables, such as a GITHUB_TOKEN in the server's config entry.

For local servers, the question is where that environment variable comes from. A token pasted into a project's config file is easy to commit by accident.

Remote MCP servers explains the transports and how to add a remote server in each AI app.

Authentication only establishes who the client is acting for. What that client may then do is authorization, and it happens on every request: the MCP server checks the token's scopes, and the upstream account's own permissions still apply. Signing in doesn't grant access to every tool. Per-agent permissions shows how Toolstile handles that.

The MCP OAuth flow, step by step

1. The first request gets a 401

The client sends an MCP request without a token. A protected server answers 401 Unauthorized with a WWW-Authenticate header. This is the real response from Toolstile's endpoint:

Shell
curl -i -X POST https://mcp.toolstile.com/mcp
http
HTTP/2 401
www-authenticate: Bearer resource_metadata="https://mcp.toolstile.com/.well-known/oauth-protected-resource/mcp", scope="mcp:access"

resource_metadata points to the server's metadata document, and scope names the scopes the client should ask for.

2. The client reads the protected resource metadata

Every MCP server that uses authorization must publish OAuth 2.0 Protected Resource Metadata (RFC 9728). Clients use the URL from the WWW-Authenticate header when there is one. Otherwise they try the well-known locations: first the path-specific one (/.well-known/oauth-protected-resource/mcp for a server at /mcp), then the root.

JSON
{
  "resource": "https://mcp.example.com/mcp",
  "authorization_servers": ["https://auth.example.com"],
  "scopes_supported": ["files:read", "files:write"]
}

Scope names are defined by each server; mcp:access in step 1 is Toolstile's. The authorization_servers list must name at least one authorization server. The MCP server and its authorization server can be one service or two; many providers put the MCP server in front of an identity provider they already run.

3. The client discovers the authorization server

Next the client fetches the authorization server's metadata, through OAuth Authorization Server Metadata (RFC 8414) or OpenID Connect discovery. Clients must support both, and try the well-known locations in a set order. For an issuer with a path, such as https://auth.example.com/tenant1, that's /.well-known/oauth-authorization-server/tenant1, then /.well-known/openid-configuration/tenant1, then /tenant1/.well-known/openid-configuration. Appending .well-known after the path for OAuth metadata is a common bug. The document lists the authorization and token endpoints, the supported PKCE methods and how clients can register.

The issuer in the document must be identical to the issuer the client used to build the URL. If they differ, the client must not use the metadata.

4. The client gets a client ID

An OAuth client needs a client ID before it can start sign-in. MCP clients often meet servers they have never seen before, so arranging an ID with every provider in advance doesn't scale. The specification sets a priority order:

  1. Pre-registered credentials, when the client already has a client ID for this authorization server.
  2. Client ID Metadata Documents (CIMD), when the authorization server advertises client_id_metadata_document_supported. The client ID is an HTTPS URL with a path, and the JSON document at that URL describes the client. The authorization server fetches it and checks redirect URIs against it, so one client ID works with every server that supports CIMD.
  3. Dynamic Client Registration (RFC 7591), when the server has a registration_endpoint. The client posts its metadata and receives a client ID. Revision 2026-07-28 deprecates this in favor of CIMD; it stays available for authorization servers that don't support CIMD yet.
  4. Asking the user to paste client details, as a last resort.

A Client ID Metadata Document is small. This is the one Toolstile uses when it signs in to upstream MCP servers:

JSON
{
  "client_id": "https://app.toolstile.com/oauth/client-metadata.json",
  "client_name": "Toolstile",
  "redirect_uris": ["https://app.toolstile.com/oauth/upstream/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none"
}

The document must include client_id, client_name and redirect_uris, and client_id must exactly match the URL the document is served from.

When sign-in fails at this step, read the provider's error first. Common causes are a provider that offers neither CIMD nor dynamic registration, a redirect URI the provider doesn't accept, a provider policy that only admits approved clients, or a dynamic registration without application_type. OpenID providers default that to web and then reject a localhost redirect, so desktop and CLI clients should send "native". The fix is usually an OAuth app registered with the provider. If the MCP server also accepts an access token, that's another way in.

5. The person signs in and approves access

The client opens the authorization endpoint in a browser, with PKCE (RFC 7636). Clients must use the S256 method when they can, and must refuse to continue if the authorization server's metadata lists no code_challenge_methods_supported.

The request carries a resource parameter (RFC 8707) set to the MCP server's canonical URI, such as https://mcp.example.com/mcp. Clients send it in both the authorization and token requests, whether or not the authorization server supports it, so the token is issued for that server.

Before redirecting, the client records the authorization server's issuer. When the browser returns, the client compares the iss parameter (RFC 9207) with that issuer, exactly as strings. A mismatch means the response is rejected. If the metadata sets authorization_response_iss_parameter_supported: true and iss is missing, the response is rejected too. These checks stop mix-up attacks, where a malicious authorization server tricks the client into sending a code to the wrong place. PKCE alone doesn't prevent those.

6. The client exchanges the code and calls the server

The client redeems the code at the token endpoint and receives an access token, often with a refresh token. From then on, every HTTP request to the MCP server carries Authorization: Bearer <token>. Tokens never go in the URL query string.

Token audience, scopes and refresh tokens

Tokens are issued for one server

An MCP server must check that each access token was issued for it as the audience, and reject tokens that weren't. Clients must not send an MCP server a token from any other authorization server.

The same rule applies when an MCP server calls another API. It must use a separate token issued for that API, and must not forward the token it received from the client. Forwarding is called token passthrough. The specification forbids it because it skips controls that depend on the token's audience, such as rate limits and validation, makes downstream logs show the wrong caller, and lets a token stolen at one service work at others. MCP security risks covers it alongside the other attacks.

Scopes: start small and step up

For the first sign-in, a client requests the scopes named in the 401 challenge, or every scope in scopes_supported if the challenge named none. Servers are encouraged to keep that initial set minimal.

If a later call needs more, the server answers 403 with error="insufficient_scope" and the scopes the operation needs. The client then sends the person through sign-in again for the union of the old and new scopes. A server can start with read access this way and ask for write access only when an agent first tries to write.

Refresh tokens

Authorization servers should issue short-lived access tokens, and must rotate refresh tokens for public clients. Clients keep refresh tokens confidential and can't assume they'll get one. When a refresh token expires or is revoked, the person signs in again. In Claude Code, the server shows as needing authentication until you sign in again with /mcp.

Credentials are bound to an authorization server

Clients keep separate credentials and tokens for each authorization server, and never reuse a client ID from one server at another. If a provider moves to a new authorization server, a client that registered dynamically must register again. A client with pre-registered credentials should show an error until someone registers a new OAuth app. CIMD client IDs are portable, so they need neither, which is one reason the specification now prefers them.

API keys, client credentials and enterprise sign-in

Some remote MCP servers skip OAuth and accept a provider API key or personal access token in the Authorization header. It's simpler and suits headless jobs. The cost is a token that often lasts for months, may carry more of the account's permissions than the job needs, and sits on every machine that uses it, so rotating it means updating each copy. If a server offers both, use OAuth for people and keep static tokens for scripts, with the narrowest scopes and shortest expiry the provider allows.

Two official MCP authorization extensions cover cases the core flow doesn't. OAuth client credentials lets a service, such as a CI job, get a token without a person signing in. Enterprise-managed authorization lets an organization's identity provider decide which MCP servers its people can reach. Both only apply when the client and the server support them.

Adding OAuth to your own MCP server

If you're building a remote MCP server, the server side of the flow comes down to a few jobs:

  1. Publish protected resource metadata that names your authorization server, and serve it at the well-known path for your MCP endpoint.
  2. Answer unauthenticated requests with 401 and a WWW-Authenticate header carrying resource_metadata and the initial scope.
  3. Validate every token: signature or introspection, expiry, and an audience that matches your server's canonical URI.
  4. Return 403 with error="insufficient_scope" when a valid token lacks a scope the operation needs.
  5. Use a separate credential for any API your server calls. Never forward the client's token.

Use an existing authorization server or identity provider rather than writing one. Check that it supports PKCE with S256, the resource parameter, and CIMD or dynamic registration, or MCP clients won't be able to register. Building an MCP server in the official documentation covers the SDKs.

Authentication through an MCP gateway

A gateway looks like an MCP server to your client and like an MCP client to each upstream server, so it authenticates separately on each side. Your AI app runs the flow above once, against the gateway, and gets a token that only works at the gateway. Separately, someone completes each upstream provider's sign-in, or supplies an API key, once per connection, and the gateway keeps that credential. When the gateway forwards a call, it uses the connection's credential, never the agent's token. The authentication layers section of the gateway guide explains how the upstream account's permissions limit both.

Because the agent's token only works at the gateway, revoking one agent doesn't touch the upstream sign-in, and replacing an upstream account doesn't force every app to sign in again.

How Toolstile handles MCP authentication

On each side of the gateway:

  • AI app to Toolstile. The 401 in step 1 points to Toolstile's protected resource metadata. Its authorization server supports Client ID Metadata Documents and dynamic registration, accepts only S256 PKCE, and includes iss in authorization responses. After sign-in, the browser asks which workspace the app should use, and the new agent starts with no access. The client setup guides show the flow in Claude Code, Codex, Cursor and Claude Desktop.
  • Toolstile to upstream servers. When you connect a server with browser sign-in, Toolstile identifies itself with the Client ID Metadata Document shown in step 4, or registers dynamically where the provider supports only that. Providers that need a registered OAuth app can use client settings from your administrator or ones you enter, and you can also connect with an access token. Upstream tokens are encrypted at rest and refreshed by Toolstile. If a refresh is refused, the connection is marked as needing reconnection.
  • Scripts. Apps that can't run a browser sign-in can use an agent token with an expiry you choose. It works only at Toolstile, so it never exposes upstream credentials.

Getting started with a first connection walks through the setup, connecting a server covers when to choose OAuth or a bearer token, and security and data handling describes how credentials are stored.

MCP authentication checklist

  • Use OAuth for people and static tokens only for headless jobs.
  • Keep local server credentials out of files that get committed.
  • When a sign-in fails at registration, read the provider's error and check its registration options before debugging the client.
  • Check that each server validates the token audience, and that nothing forwards a client's token upstream.
  • Ask for the smallest scopes that work, and let step-up handle the rest.
  • Expect refresh tokens to expire, and know who can sign in again for each shared connection.
  • Revoke access at the layer that issued it: the gateway for agents, the provider for upstream accounts.

To control what each authenticated agent can do next, read the MCP allowlist guide.