Integrating a custom Model Context Protocol (MCP) server with Claude Desktop or Claude Code requires more than simply adding a login screen. Claude expects a specific OAuth-based handshake so it can discover your authorization server, register itself as a client, send the user through approval, and then reconnect with a valid access token.
This post focuses on what you need to build to make that connector work end to end. Instead of walking through framework-specific code, it breaks the flow into the concrete endpoints, behaviors, and security checks required for a compliant OAuth 2.1 integration.
High-Level OAuth 2.1 Flow
The implementation utilizes OAuth 2.1 with PKCE (Proof Key for Code Exchange) to secure the connection between Claude and the MCP server.
sequenceDiagram
participant C as Claude Desktop
participant M as MCP Server
participant U as User Browser
Note over C,M: 1. Connection Initialization
C->>M: POST /api/mcp (No Token)
M-->>C: 401 Unauthorized + WWW-Authenticate Header
Note over C,M: 2. Discovery
C->>M: GET /.well-known/oauth-protected-resource
M-->>C: Returns Authorization Server URI
C->>M: GET /.well-known/oauth-authorization-server
M-->>C: Returns OAuth Endpoints & Capabilities
Note over C,M: 3. Dynamic Registration
C->>M: POST /api/oauth/register (Client Details)
M-->>C: Returns dynamically generated client_id
Note over C,U: 4. User Authorization (PKCE)
C->>U: Opens Browser (client_id, code_challenge, S256)
U->>M: GET /api/oauth/authorize
Note right of M: User Logs In & Approves
M-->>U: Redirects to Claude Callback with Authorization Code
U->>C: Delivers Code via localhost callback
Note over C,M: 5. Token Exchange
C->>M: POST /api/oauth/token (Code + code_verifier)
Note right of M: Verifies code_verifier against code_challenge
M-->>C: Returns Access Token (No client_secret needed)
Note over C,M: 6. Authenticated Connection
C->>M: POST /api/mcp (Bearer Token)
M-->>C: 200 OK (Connection Established)
What You Need to Implement
1. Discovery Mechanisms (RFC 8414 & RFC 9728)
Discovery is what allows Claude to understand how your MCP server wants authentication to work. Without it, the connector cannot automatically find the correct authorization endpoints.
You need two metadata endpoints:
/.well-known/oauth-protected-resourceThis tells Claude that your MCP endpoint is protected and identifies which authorization server should be used for that resource./.well-known/oauth-authorization-serverThis publishes your OAuth server capabilities, including the authorization endpoint, token endpoint, registration endpoint, supported grant types, supported response types, and PKCE support.
The important part is not just returning metadata, but returning metadata that matches the actual behavior of your server. If you advertise PKCE with S256, your token exchange must enforce it. If you expose dynamic registration, that endpoint must actually provision usable public clients.
2. MCP Endpoint Authentication Handshake
Your MCP endpoint should reject unauthenticated requests in a way that starts the OAuth discovery flow instead of failing silently.
When Claude calls the MCP endpoint without a valid bearer token, return 401 Unauthorized and include a WWW-Authenticate header that points to your protected resource metadata document. That response is the signal Claude uses to begin discovery.
If you skip this step and only return a generic unauthorized response, Claude has no standard way to know where your authorization server lives.
3. Dynamic Client Registration (RFC 7591)
Dynamic client registration is what makes the connector feel native. Instead of asking users to create OAuth credentials by hand, Claude can register itself automatically as a public client.
At this stage, your registration endpoint should:
- Accept client metadata from Claude.
- Create a public client record without requiring a client secret.
- Store enough registration context to audit and control abuse.
- Return a client identifier that can be used in the authorization flow.
Because this endpoint creates new clients on demand, it needs abuse controls. In practice that means rate limiting, basic request validation, and cleanup for stale dynamic registrations that were created but never linked to a real user authorization.
4. User Authorization & Code Generation
The authorization endpoint is where the user actually logs in and approves access for Claude.
This part should do four things reliably:
- Authenticate the user in your application.
- Validate the incoming authorization request, including redirect URI, scope, response type, and PKCE parameters.
- Show or process user consent if your product requires it.
- Issue a short-lived authorization code tied to the user, client, redirect URI, and PKCE challenge.
If you support dynamic clients, this is also the point where the newly registered client becomes associated with the authenticated user or tenant context that will actually own the connection.
5. PKCE Implementation (OAuth 2.1 Mandate)
PKCE is not optional here. Claude acts as a public client, so your connector should require the authorization request to include a code challenge and challenge method, and your token endpoint must verify the code verifier before issuing tokens.
From an implementation perspective, that means storing the PKCE challenge alongside the authorization code and refusing to redeem the code unless the verifier matches the original challenge. Supporting S256 is the expected baseline.
This is the control that prevents intercepted authorization codes from being replayed by another party.
6. Token Exchange for Public Clients
Once Claude receives the authorization code through its callback, it exchanges that code at your token endpoint.
Since this is a public-client flow, you should not expect a client secret. Instead, the security of the exchange comes from strict validation of:
- the authorization code
- the client identifier
- the redirect URI
- the PKCE verifier
- token lifetime and one-time code usage
If all checks pass, issue the access token Claude will use when calling your MCP endpoint again. If you support refresh tokens, apply the same level of care to rotation, expiry, and revocation.
7. Authenticated Connection
After the token exchange succeeds, Claude reconnects to the MCP endpoint with the access token as a bearer token.
At that point, your MCP server needs ordinary protected-resource behavior:
- extract the bearer token
- validate it
- resolve the user or tenant context behind it
- reject expired or invalid tokens
- allow the MCP request only when the token is valid for the requested resource and scope
This final step is where the entire connector becomes usable from the Claude side. If the earlier OAuth flow is correct but token validation is inconsistent, the integration will still feel broken.
Implementation Checklist
If you want this to work reliably with Claude Desktop or Claude Code, your server should provide:
- A protected MCP endpoint that returns
401with a discovery hint when no token is present. - Protected resource metadata at
/.well-known/oauth-protected-resource. - Authorization server metadata at
/.well-known/oauth-authorization-server. - A dynamic client registration endpoint for public clients.
- An authorization endpoint that validates redirect URI, scope, and PKCE inputs.
- A token endpoint that enforces one-time code redemption and PKCE verification.
- Access token validation on the MCP endpoint itself.
- Operational safeguards such as rate limiting, short-lived codes, cleanup of stale registrations, and audit logging.
Conclusion
The core idea is straightforward: Claude needs a standards-compliant way to discover your OAuth server, register as a public client, guide the user through consent, exchange the authorization code with PKCE, and return to the MCP endpoint with a valid bearer token.
Once you implement those pieces consistently, the experience becomes close to a one-click connector instead of a manual authentication workaround. That is what makes an MCP server feel like a real Claude integration rather than just an API with login attached.
