Skip to main content

OAuth 2.1 Integration Guide for Claude MCP Clients

This guide explains how to integrate your MCP servers with Claude Desktop and other AI agents using OAuth 2.1 with PKCE.

Overview

The platform provides a complete OAuth 2.1 authorization server that enables secure, user-authenticated access to MCP servers. This is the recommended method for AI agents like Claude Desktop to access your deployed APIs.

Why OAuth 2.1?

  • User Consent: Users explicitly authorize AI agents to access their MCP servers
  • Security: PKCE (Proof Key for Code Exchange) prevents authorization code interception
  • Standard Protocol: Works with any OAuth 2.1 compatible client
  • Token Management: Automatic token expiration and refresh handling
  • Multi-Device: Same credentials work across devices

Quick Start

Step 1: Register an OAuth Client

You have two options to register a client:
  1. Navigate to your deployment in the dashboard
  2. Find the “OAuth 2.1 Client” section
  3. Select your platform: Claude Desktop, Claude.ai (Web), or Both
  4. Click “Set Up OAuth”
  5. The system will automatically register the correct redirect URIs for your selected platform
  6. Note: No client secret is required as we use PKCE for enhanced security.

Option B: Use the API

curl -X POST \
  'https://your-project.supabase.co/functions/v1/mcp-oauth-server/register' \
  -H 'Content-Type: application/json' \
  -d '{
    "client_name": "My AI Agent",
    "redirect_uris": ["http://localhost:3000/callback"],
    "scope": "openid email"
  }'
Response:
{
  "client_id": "mcp_abc123...",
  "client_secret": "secret_xyz789...",
  "client_name": "My AI Agent",
  "redirect_uris": ["http://localhost:3000/callback"],
  "token_endpoint_auth_method": "client_secret_post"
}

Step 2: Configure Claude Desktop or Claude.ai

IMPORTANT: Remote MCP servers with OAuth must be configured via Settings > Connectors, not the configuration file.

For Claude Desktop:

  1. Open Claude Desktop
  2. Go to Settings > Connectors
  3. Click “Add custom connector”
  4. Fill in the following information:
    • Name: Tydli - [your-deployment-slug]
    • MCP Server URL: https://your-project.supabase.co/functions/v1/mcp-router/[your-deployment-slug]
    • OAuth Client ID: (your client ID from Step 1)
    • Authorization URL: https://your-project.supabase.co/functions/v1/mcp-oauth-server/authorize
    • Token URL: https://your-project.supabase.co/functions/v1/mcp-oauth-server/token
  5. Click Connect

For Claude.ai (Web):

  1. Open https://claude.ai/settings/connectors
  2. Click “Add custom connector”
  3. Fill in the same information as above
  4. Click Connect
Note: Claude Desktop and Claude.ai handle redirect URIs automatically. Make sure your OAuth client is registered with the correct redirect URIs for your platform (see “Redirect URI Configuration” section below).

Step 3: Authorize in Claude

  1. Restart Claude Desktop
  2. Claude will automatically detect the new MCP server
  3. You’ll be prompted to authorize access in your browser
  4. Sign in with your account credentials (email must be verified)
  5. Review the authorization request on the consent page
  6. Click “Approve” to grant Claude access
  7. Claude can now access your MCP server!

Redirect URI Configuration by Client Type

Understanding redirect URIs is critical for OAuth integration. Different AI agents handle redirects differently:

Claude Desktop

Recommended URIs (register all):
http://127.0.0.1:6277/callback
http://127.0.0.1:6278/callback
http://127.0.0.1:6279/callback
Claude Desktop handles OAuth flows automatically when configured via Settings > Connectors:
  • Opens your browser for authorization automatically
  • Listens for the OAuth callback on local ports (6277-6279 range)
  • Exchanges the authorization code for tokens internally
  • Manages token refresh automatically
Important Notes:
  • Remote MCP servers with OAuth must be configured via Settings > Connectors
  • Do NOT use claude_desktop_config.json for remote OAuth servers
  • Register multiple redirect URIs (ports 6277-6279) for maximum compatibility
  • Claude Desktop handles all redirect URI logic automatically
Setup Steps:
  1. Open Claude Desktop → Settings → Connectors
  2. Click “Add custom connector”
  3. Enter your MCP server URL and OAuth client ID
  4. Claude Desktop will prompt for authorization in your browser

Claude Web (claude.ai)

Note: Web integration requires partnership with Anthropic for Custom Connectors. Claude’s web application handles OAuth on Anthropic’s servers. When integrated:
  • Redirect URIs are managed by Anthropic’s infrastructure
  • Users authorize through Anthropic’s OAuth consent flow
  • You’ll need to work with Anthropic to set up the integration
  • Typical redirect URI format: https://claude.ai/oauth/callback
For web integration inquiries, contact Anthropic’s partnership team.

Custom Web Applications

For your own web application integrating with MCP servers: Development:
http://localhost:3000/callback
http://localhost:8080/oauth/callback
Production:
https://yourdomain.com/oauth/callback
https://app.yourdomain.com/api/oauth/callback
Requirements:
  • HTTPS required for all non-localhost URIs
  • Must be an exact match (no wildcards)
  • Maximum 10 redirect URIs per OAuth client
  • Each URI must be ≤500 characters
Implementation Example:
// Your callback endpoint
app.get('/oauth/callback', async (req, res) => {
  const { code, state } = req.query;
  
  // Exchange code for tokens
  const response = await fetch(tokenEndpoint, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      code,
      redirect_uri: 'https://yourdomain.com/oauth/callback',
      client_id: CLIENT_ID,
      client_secret: CLIENT_SECRET,
      code_verifier: storedVerifier, // From PKCE flow
    })
  });
  
  const tokens = await response.json();
  // Store tokens securely and redirect user
});

Other MCP Clients

For generic MCP clients or custom implementations:
  • Follow the OAuth 2.1 with PKCE specification
  • Use http://localhost or http://127.0.0.1 with any available port for local testing
  • Implement a callback handler that listens on your redirect URI
  • Exchange the authorization code using PKCE code_verifier

Mobile-Responsive Authorization Flow

The OAuth server provides a fully mobile-responsive authorization flow:

Login Page

  • Clean, modern design that works on all devices
  • Email and password authentication
  • Clear error messages
  • Automatic focus on email field
  • Secure password input
  • Shows which application is requesting access
  • Lists requested permissions
  • User email displayed
  • Clear “Approve” and “Deny” buttons
  • Warning about trusting applications
Both pages are optimized for:
  • Smartphones (320px+)
  • Tablets (768px+)
  • Desktop (1024px+)

OAuth Flow Diagram

┌─────────────┐                                  ┌──────────────┐
│   Claude    │                                  │   User's     │
│   Desktop   │                                  │   Browser    │
└──────┬──────┘                                  └──────┬───────┘
       │                                                │
       │ 1. Initiate Authorization                     │
       ├──────────────────────────────────────────────>│
       │   GET /authorize?client_id=...                │
       │   &code_challenge=...                         │
       │                                                │
       │                           2. Not authenticated │
       │                           ┌────────────────────┤
       │                           │ Show Login Page    │
       │                           └────────────────────>
       │                                                │
       │                           3. User logs in      │
       │                           <────────────────────┤
       │                           POST /login          │
       │                                                │
       │                           4. Show Consent Page│
       │                           ┌────────────────────┤
       │                           │                    │
       │                           5. User approves     │
       │                           <────────────────────┤
       │                           POST /authorize      │
       │                                                │
       │ 6. Redirect with code                         │
       │<──────────────────────────────────────────────┤
       │   ?code=abc123&state=...                      │
       │                                                │
       │ 7. Exchange code for token                    │
       ├──────────────────────────────────────────────>│
       │   POST /token                                  │
       │   {code, code_verifier}                        │
       │                                                │
       │ 8. Return access token                        │
       │<──────────────────────────────────────────────┤
       │   {access_token, expires_in}                   │
       │                                                │
       │ 9. Call MCP server with token                 │
       ├──────────────────────────────────────────────>│
       │   Authorization: Bearer mcp_access_...         │
       │                                                │

Protected Resource Metadata (RFC 9728)

Tydli implements RFC 9728 OAuth 2.0 Protected Resource Metadata for automatic service discovery. This allows OAuth clients to discover authorization server endpoints and supported authentication methods without manual configuration.

Discovery Endpoint

Endpoint: GET /.well-known/oauth-protected-resource Base URL: Your MCP server URL (e.g., https://your-project.supabase.co/functions/v1/mcp-router/your-slug)

Example Request

curl https://your-project.supabase.co/functions/v1/mcp-router/your-deployment-slug/.well-known/oauth-protected-resource

Example Response

{
  "resource": "https://your-project.supabase.co/functions/v1/mcp-router/your-slug",
  "authorization_servers": [
    "https://your-project.supabase.co/functions/v1/mcp-oauth-server"
  ],
  "bearer_methods_supported": ["header"],
  "resource_documentation": "https://tydli.io/docs",
  "scopes_supported": ["openid", "email", "mcp.tools.read", "mcp.tools.execute"]
}

Response Fields

FieldDescription
resourceThe protected resource identifier (your MCP server URL)
authorization_serversArray of OAuth 2.0 authorization server URLs
bearer_methods_supportedSupported token transmission methods (header, body, query)
resource_documentationURL to resource documentation
scopes_supportedArray of OAuth scopes this resource accepts

Use Cases

1. Client Discovery OAuth clients can automatically discover where to send authorization requests:
// Fetch protected resource metadata
const metadata = await fetch(`${mcpServerUrl}/.well-known/oauth-protected-resource`);
const { authorization_servers } = await metadata.json();

// Automatically use the correct authorization server
const authServer = authorization_servers[0];
2. Security Validation Clients verify tokens match the intended resource (prevents confused deputy attacks):
// Token should have audience claim matching resource URL
const tokenAudience = decodedToken.aud; // From JWT
const expectedResource = metadata.resource;

if (tokenAudience !== expectedResource) {
  throw new Error('Token not intended for this resource');
}
3. Scope Discovery Clients learn which scopes are available:
const { scopes_supported } = metadata;
// Request appropriate scopes during authorization
const requestedScopes = scopes_supported.join(' ');

MCP Specification Compliance

REQUIRED Features Implemented:
  • RFC 9728 Protected Resource Metadata endpoint
  • RFC 8414 Authorization Server Metadata (/.well-known/oauth-authorization-server)
  • RFC 8707 Resource Indicators (audience binding)
  • OAuth 2.1 with PKCE (S256 mandatory)
  • WWW-Authenticate header on 401 responses
  • Token audience validation
SHOULD Features Implemented:
  • Dynamic Client Registration (RFC 7591)
  • State parameter validation (CSRF protection)
  • Refresh token rotation for public clients
Security Guarantees:
  • Tokens bound to specific MCP servers (no token passthrough)
  • Authorization codes expire in 10 minutes
  • Access tokens expire in 1 hour
  • Refresh tokens expire in 90 days
  • HTTPS enforced (except localhost)
  • Exact redirect URI matching

Authorization Server Metadata

For completeness, the authorization server also exposes metadata: Endpoint: GET /.well-known/oauth-authorization-server
curl https://your-project.supabase.co/functions/v1/mcp-oauth-server/.well-known/oauth-authorization-server
Response:
{
  "issuer": "https://your-project.supabase.co/functions/v1/mcp-oauth-server",
  "authorization_endpoint": "https://your-project.supabase.co/functions/v1/mcp-oauth-server/authorize",
  "token_endpoint": "https://your-project.supabase.co/functions/v1/mcp-oauth-server/token",
  "revocation_endpoint": "https://your-project.supabase.co/functions/v1/mcp-oauth-server/revoke",
  "userinfo_endpoint": "https://your-project.supabase.co/functions/v1/mcp-oauth-server/userinfo",
  "registration_endpoint": "https://your-project.supabase.co/functions/v1/mcp-oauth-server/register",
  "jwks_uri": "https://your-project.supabase.co/functions/v1/mcp-oauth-server/.well-known/jwks.json",
  "scopes_supported": ["openid", "email", "mcp.tools.read", "mcp.tools.execute"],
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["client_secret_post", "client_secret_basic", "none"]
}
This metadata enables OAuth clients to discover all endpoints automatically, supporting the OAuth 2.0 Dynamic Discovery specification.

Security Best Practices

For OAuth Clients

  1. Store Client Secrets Securely
    • Never commit client secrets to version control
    • Use environment variables or secure credential storage
    • Rotate secrets if compromised
  2. Use PKCE Correctly
    • Generate cryptographically secure code verifiers (43-128 chars)
    • Use S256 challenge method (SHA-256)
    • Never reuse code verifiers
  3. Validate Redirect URIs
    • Use exact URI matches (no wildcards)
    • Use HTTPS in production (HTTP only for localhost)
    • Validate state parameter to prevent CSRF

For Users

  1. Only Authorize Trusted Applications
    • Review what permissions are being requested
    • Check the application name and redirect URI
    • Deny access if unsure
  2. Keep Credentials Secure
    • Use strong passwords
    • Don’t share your account credentials
    • Log out when finished
  3. Monitor Access
    • Review authorized applications regularly
    • Revoke access for unused applications
    • Check deployment logs for suspicious activity

Rate Limits

The OAuth server enforces rate limits to prevent abuse:
EndpointLimitWindow
/register20 requestsPer hour per IP
/authorize20 requestsPer hour per IP
/token10 requestsPer minute per client
MCP server access uses user-specific rate limits:
  • Free Plan: 20 requests/hour, 1000 requests/month
  • Pro Plan: Higher limits (check your plan details)

Troubleshooting

”Invalid client_id”

  • Verify your client ID is correct
  • Check if the client was registered successfully
  • Ensure you’re using the correct OAuth server URL

”Invalid redirect_uri”

  • Redirect URI must exactly match registered URI
  • Check for trailing slashes
  • Verify HTTP vs HTTPS protocol

”Invalid code_verifier”

  • Code verifier must match the code challenge
  • Ensure you’re using S256 hashing
  • Don’t reuse code verifiers

”Access token expired”

  • Access tokens expire after 1 hour
  • Use refresh token to get new access token (see “Using Refresh Tokens” below)
  • Client should automatically request new token
  • Check client’s token refresh implementation

”Rate limit exceeded”

  • Wait for the specified retry period
  • Reduce request frequency
  • Consider upgrading your plan for higher limits

Using Refresh Tokens

Refresh tokens allow clients to obtain new access tokens without requiring user re-authentication, providing a seamless experience for long-running applications.

Token Lifetimes

  • Access Token: 1 hour (3600 seconds)
  • Refresh Token: 90 days (7,776,000 seconds)

How It Works

  1. During initial authorization (via /token with authorization_code grant), you receive both:
    • access_token (short-lived, 1 hour)
    • refresh_token (long-lived, 90 days)
  2. When the access token expires, use the refresh token to get a new access token
  3. The refresh token remains valid and can be reused multiple times (until it expires or is revoked)
Most OAuth clients (like Claude Desktop when configured via Settings > Connectors) handle token refresh automatically:
  • Client detects access token expiration (via 401 response or checking expires_in)
  • Client sends refresh_token grant request to /token endpoint
  • Server validates refresh token and returns new access token
  • Client continues without user intervention
No code changes needed - the OAuth client handles this transparently!

Manual Refresh Example

If you’re implementing your own client, here’s how to manually refresh: Request:
curl -X POST \
  'https://your-project.supabase.co/functions/v1/mcp-oauth-server/token' \
  -H 'Content-Type: application/json' \
  -d '{
    "grant_type": "refresh_token",
    "refresh_token": "mcp_refresh_abc123...",
    "client_id": "mcp_xyz789..."
  }'
Response:
{
  "access_token": "mcp_access_new123...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "openid email"
}
Note: The refresh token itself is not returned again - it remains the same and can be reused.

Implementation Example (JavaScript)

async function refreshAccessToken(refreshToken, clientId) {
  const response = await fetch(
    'https://your-project.supabase.co/functions/v1/mcp-oauth-server/token',
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        grant_type: 'refresh_token',
        refresh_token: refreshToken,
        client_id: clientId
      })
    }
  );

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Token refresh failed: ${error.error_description}`);
  }

  const tokens = await response.json();
  
  // Store new access token
  // Keep using the same refresh token for future refreshes
  return {
    accessToken: tokens.access_token,
    expiresIn: tokens.expires_in,
    refreshToken: refreshToken // Same refresh token
  };
}

// Usage in your application
try {
  const newTokens = await refreshAccessToken(
    storedRefreshToken,
    'your-client-id'
  );
  
  // Update stored access token
  localStorage.setItem('access_token', newTokens.accessToken);
  
  // Retry failed request with new token
  const response = await callMCPServer(newTokens.accessToken);
} catch (error) {
  // Refresh failed - user needs to re-authorize
  console.error('Token refresh failed:', error);
  redirectToLogin();
}

Best Practices

  1. Store Securely: Refresh tokens are long-lived and powerful - store them encrypted at rest
  2. Refresh Proactively: Refresh before expiration (e.g., when 90% of token lifetime has passed)
  3. Handle Failures: If refresh fails, prompt user to re-authenticate
  4. Revoke on Logout: Always revoke refresh tokens when user logs out
  5. One Refresh Token Per Client: Each device/client should have its own refresh token
  6. Monitor Usage: Check deployment logs for suspicious refresh patterns

Error Handling

Common refresh token errors:
ErrorReasonSolution
invalid_grantRefresh token expired, revoked, or invalidRe-authenticate user
invalid_clientClient ID doesn’t matchVerify client credentials
invalid_requestMissing required parametersCheck request format

Revoking Refresh Tokens

To revoke a refresh token (e.g., on logout):
curl -X POST \
  'https://your-project.supabase.co/functions/v1/mcp-oauth-server/revoke' \
  -H 'Content-Type: application/json' \
  -d '{
    "token": "mcp_refresh_abc123...",
    "token_type_hint": "refresh_token"
  }'
Response: 200 OK (no body per OAuth 2.1 spec) After revocation:
  • Refresh token becomes invalid immediately
  • Associated access tokens remain valid until expiration
  • User must re-authorize to get new tokens

Security Considerations

  1. Never expose refresh tokens in URLs - always use request body
  2. Validate client_id on refresh - ensures token isn’t stolen across clients
  3. Rate limit refresh requests - current limit is 10/minute per client
  4. Monitor for abuse - unusual refresh patterns may indicate token theft
  5. Rotate on suspicious activity - revoke and require re-auth if compromise suspected

Advanced Configuration

Custom Scopes

Request specific scopes during authorization:
GET /authorize?client_id=...&scope=openid+email+profile
Supported scopes:
  • openid: OpenID Connect authentication
  • email: Access to user email
  • profile: Access to user profile information

Token Introspection

Check token validity programmatically:
const { data, error } = await supabase
  .from('oauth_access_tokens')
  .select('user_id, expires_at, revoked')
  .eq('access_token', token)
  .single();

if (!data || data.revoked || new Date(data.expires_at) < new Date()) {
  // Token invalid or expired
}

Revoking Access

Revoke an access token:
const { error } = await supabase
  .from('oauth_access_tokens')
  .update({ revoked: true })
  .eq('access_token', token);

Testing Your Integration

1. Test Client Registration

curl -X POST \
  'https://your-project.supabase.co/functions/v1/mcp-oauth-server/register' \
  -H 'Content-Type: application/json' \
  -d '{
    "client_name": "Test Client",
    "redirect_uris": ["http://localhost:3000/callback"]
  }'

2. Test Authorization Flow

Open in browser:
https://your-project.supabase.co/functions/v1/mcp-oauth-server/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=http://localhost:3000/callback&code_challenge=CHALLENGE&code_challenge_method=S256

3. Test Token Exchange

curl -X POST \
  'https://your-project.supabase.co/functions/v1/mcp-oauth-server/token' \
  -H 'Content-Type: application/json' \
  -d '{
    "grant_type": "authorization_code",
    "code": "YOUR_AUTH_CODE",
    "redirect_uri": "http://localhost:3000/callback",
    "client_id": "YOUR_CLIENT_ID",
    "code_verifier": "YOUR_CODE_VERIFIER"
  }'

4. Test MCP Server Access

curl -X GET \
  'https://your-project.supabase.co/functions/v1/mcp-router/your-slug' \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'

Migration from JWT to OAuth

If you’re currently using JWT authentication, here’s how to migrate:
  1. Create OAuth Client: Register a new client for your application
  2. Update Configuration: Switch from JWT to OAuth in your client config
  3. Test Authorization: Verify the OAuth flow works correctly
  4. Update Documentation: Update any integration docs or scripts
  5. Deprecate JWT: Remove JWT-based access after migration complete

Support

Need help with OAuth integration?

Examples

Python Example

from requests_oauthlib import OAuth2Session
from oauthlib.oauth2 import BackendApplicationClient

# OAuth configuration
client_id = "your-client-id"
client_secret = "your-client-secret"
authorize_url = "https://your-project.supabase.co/functions/v1/mcp-oauth-server/authorize"
token_url = "https://your-project.supabase.co/functions/v1/mcp-oauth-server/token"
redirect_uri = "http://localhost:3000/callback"

# Initialize OAuth session
oauth = OAuth2Session(client_id, redirect_uri=redirect_uri)

# Get authorization URL
authorization_url, state = oauth.authorization_url(authorize_url)
print(f"Please authorize: {authorization_url}")

# After user authorizes, exchange code for token
code = input("Enter authorization code: ")
token = oauth.fetch_token(
    token_url,
    code=code,
    client_secret=client_secret
)

# Use token to access MCP server
mcp_url = "https://your-project.supabase.co/functions/v1/mcp-router/your-slug"
response = oauth.get(mcp_url)
print(response.json())

Node.js Example

const { AuthorizationCode } = require('simple-oauth2');

const config = {
  client: {
    id: 'your-client-id',
    secret: 'your-client-secret'
  },
  auth: {
    tokenHost: 'https://your-project.supabase.co',
    tokenPath: '/functions/v1/mcp-oauth-server/token',
    authorizePath: '/functions/v1/mcp-oauth-server/authorize'
  }
};

const client = new AuthorizationCode(config);

// Generate authorization URL
const authorizationUri = client.authorizeURL({
  redirect_uri: 'http://localhost:3000/callback',
  scope: 'openid email',
  state: '12345'
});

console.log('Authorize here:', authorizationUri);

// Exchange code for token (after user authorizes)
const tokenParams = {
  code: 'authorization-code',
  redirect_uri: 'http://localhost:3000/callback'
};

const accessToken = await client.getToken(tokenParams);

// Use token to access MCP server
const response = await fetch(
  'https://your-project.supabase.co/functions/v1/mcp-router/your-slug',
  {
    headers: {
      'Authorization': `Bearer ${accessToken.token.access_token}`
    }
  }
);

console.log(await response.json());

Conclusion

OAuth 2.1 with PKCE provides secure, user-consented access to your MCP servers. The implementation is fully compliant with modern OAuth standards and works seamlessly with Claude Desktop and other AI agents. For more information, see: