OAuth 2.1 for MCP Servers: An End-to-End Guide A developer has published an end-to-end guide detailing how OAuth 2.1 can secure remote Model Context Protocol (MCP) servers, walking through how an AI client discovers a login provider, identifies itself via Client ID Metadata Document (CIMD), obtains a resource-bound token, and calls protected MCP tools. The guide uses a worked example of a company exposing customer operations through a remote MCP server, showing how a 401 challenge with a WWW-Authenticate header points clients to Protected Resource Metadata defined by RFC 9728, which lists the trusted authorization servers and supported scopes. It emphasizes that the MCP server validates tokens from a separate authorization server rather than signing users in itself, and that OAuth is not mandatory for local stdio or public read-only deployments. Table of contents How an AI client discovers a login provider, identifies itself with CIMD, obtains a resource-bound token, and calls protected MCP tools. Imagine a company has a database and exposes a few operations through a remote MCP server: list customers get customer update customer An employee asks an AI application to retrieve a customer record. The application is the MCP host ; inside it, an MCP client connects to the server. The server is available at https://mcp.example.com/mcp and protects customer data. A separate identity provider, such as the company’s Keycloak service or a hosted provider, signs the employee in and issues OAuth tokens. The goal is to let the employee’s client use only the permitted tools, while the MCP server can verify who is calling, what access was granted, and whether the token was issued for this server. The simple mental model: MCP describes the operation being requested. OAuth provides a verifiable access pass for the caller. The client carries the pass in the HTTP request; the server checks it before executing the operation. | Party | Responsibility | Example | |---|---|---| | MCP client | Sends MCP requests and obtains/attaches OAuth tokens | AI application or IDE | | MCP server / resource server | Exposes tools and verifies incoming tokens | https://mcp.example.com/mcp | | Authorization server AS | Signs users in and issues tokens | https://login.example.net | The authorization server and MCP server are different roles, even when one vendor hosts both. In the common arrangement, the MCP server does not sign the user in and does not mint the access token . It publishes information about which authorization server it trusts, then validates tokens from that issuer. OAuth is not mandatory for every MCP deployment. A local stdio server, a public read-only service, or an application with another trust model may not use this remote OAuth flow. This article concerns a protected remote MCP server over HTTP . This is a learning diagram, not a claim that every network exchange uses the same HTTP endpoint or that every host exposes each step to the user. A real MCP client usually performs the discovery and token flow automatically. The client wants to call get customer . The MCP body might look like this in a sessionless protocol version: POST /mcp HTTP/1.1 Host: mcp.example.com Content-Type: application/json MCP-Protocol-Version: 2026-07-28 Mcp-Method: tools/call Mcp-Name: get customer { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "get customer", "arguments": { "customer id": "cust 123" }, " meta": { "io.modelcontextprotocol/protocolVersion": "2026-07-28", "io.modelcontextprotocol/clientCapabilities": {} } } } If there is no valid bearer token, the server rejects the HTTP request before the tool runs: HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer resource metadata="https://mcp.example.com/.well-known/oauth-protected-resource/mcp", scope="customers:read" A 401 means the request has not presented acceptable authentication credentials. The WWW-Authenticate challenge tells the client how to begin authorization. It can point at PRM and identify the scope needed for access. The actual MCP protocol revision matters. Older revisions use initialize and session identifiers; the 2026-07-28 revision has no initialize handshake or Mcp-Session-Id . OAuth is independent of that lifecycle: a protected HTTP request still carries an access token in either era. The client fetches the URL named in the challenge. The document is Protected Resource Metadata PRM , defined by RFC 9728 https://datatracker.ietf.org/doc/html/rfc9728 . A simplified example: { "resource": "https://mcp.example.com/mcp", "authorization servers": "https://login.example.net" , "scopes supported": "customers:read", "customers:write" , "bearer methods supported": "header" } PRM describes the protected resource—in this example, the MCP endpoint. It says which authorization server or servers can issue acceptable tokens and may list supported scopes. It does not authenticate the user or issue a token. Where does PRM come from? The MCP server operator configures and serves it as a public JSON document at the appropriate well-known URL. It must be reachable without an access token so an unauthenticated client can discover how to authenticate. The URL is derived from the protected resource URL according to RFC 9728; for a resource whose path is /mcp , current implementations commonly use a path-aware URL such as /.well-known/oauth-protected-resource/mcp . Where does the client keep it? The client fetches the document over HTTPS and reads it to continue discovery. It may cache public metadata according to its implementation and HTTP caching rules. It is not an access token, a secret, or MCP meta . The MCP server is the source of its own PRM because it is the resource whose accepted issuer and canonical identifier the client needs to learn. Treating arbitrary metadata as trusted without validating its origin and values would be unsafe. PRM names an authorization-server issuer. The client fetches that issuer’s Authorization Server Metadata ASM document, using RFC 8414 https://datatracker.ietf.org/doc/html/rfc8414 or OpenID Connect Discovery where applicable. { "issuer": "https://login.example.net", "authorization endpoint": "https://login.example.net/authorize", "token endpoint": "https://login.example.net/token", "jwks uri": "https://login.example.net/.well-known/jwks.json", "code challenge methods supported": "S256" , "client id metadata document supported": true, "scopes supported": "customers:read", "customers:write" } ASM describes the authorization server : where authorization and token requests go, and which OAuth features it supports. It is a separate document because the same authorization server can serve many resource servers. The client checks that the metadata’s issuer matches the issuer it intended to contact. This helps prevent it from mixing up authorization servers. The jwks uri is a public-key location; it is commonly used by the MCP server to obtain keys for verifying signed tokens. The client reads the metadata; the resource server uses the keys to validate tokens. Before the authorization server can process the login flow, it needs an OAuth client identifier : which software is asking to access the user’s data? This identifies the application, not the human user. MCP’s 2026-07-28 client-registration guidance gives a selection order: See the MCP client registration specification https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2026-07-28/basic/authorization/client-registration.mdx . With CIMD, the client uses a stable HTTPS URL as its client id . That URL serves a JSON document describing the client. For example, a company might publish: { "client id": "https://assistant.example.com/oauth/client-metadata.json", "client name": "Example AI Assistant", "client uri": "https://assistant.example.com", "redirect uris": "http://127.0.0.1:53682/callback" , "grant types": "authorization code", "refresh token" , "response types": "code" , "token endpoint auth method": "none" } This is illustrative; clients must follow the exact fields and validation requirements in the current CIMD specification. In particular, the client id must be an HTTPS URL with a path, and its value must match the URL where the metadata is published exactly. The authorization server fetches the document, checks it, and validates that the requested redirect URI is one the document permits. The authorization server advertises CIMD support in ASM, for example: { "client id metadata document supported": true } If supported, the client can send this URL as its client id . The AS obtains the client’s metadata from that URL instead of requiring a new registration record for every client/authorization-server pairing. MCP clients often encounter authorization servers they have never seen before. Traditional pre-registration requires someone to create a client record in advance. DCR solved the onboarding problem by allowing clients to POST their metadata to a registration endpoint and receive a newly created client ID. But DCR requires the AS to operate a registration endpoint and manage registrations created dynamically. CIMD changes where the client identity comes from: the client publishes its metadata at a stable HTTPS address it controls, and the AS retrieves that document when needed. The identifier can be reused with multiple authorization servers that support CIMD, without registering the client separately at each one. This reduces registration state and makes client identity more directly tied to control of a web domain. CIMD is not proof that the user is safe , nor is it a replacement for user authentication, PKCE, scopes, or token validation. It identifies and describes the OAuth client. The authorization server still needs to validate the document and redirect URI; the user still signs in; and the MCP server still checks the resulting token. DCR is deprecated in the current MCP specification, not instantly removed. It remains a compatibility path when an authorization server does not support CIMD. The video’s ScaleKit walkthrough is useful for understanding the DCR-based onboarding it demonstrates, but its statement that DCR is the default should not be repeated as current MCP guidance. Current MCP registration guidance https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2026-07-28/basic/authorization/client-registration.mdx The client starts an OAuth Authorization Code flow. Before opening the browser, it generates a high-entropy random secret called the code verifier and keeps it locally. It derives a code challenge using SHA-256 and Base64URL encoding: code verifier = random secret stored temporarily by the client code challenge = BASE64URL SHA256 code verifier The client sends the challenge to the authorization endpoint, not the verifier. The verifier is held back until the client redeems the authorization code. A simplified authorization request might contain: response type=code client id=https://assistant.example.com/oauth/client-metadata.json redirect uri=http://127.0.0.1:53682/callback code challenge=