How MCP clients discover and use OAuth authentication with your server.
When you enable OAuth on your MCP server, clients need to know:
- How to authenticate - OAuth provider details
- Where to get tokens - Authorization endpoints
- How to send tokens - Authorization header format
This library provides automatic discovery via OAuth 2.0 metadata endpoints.
sequenceDiagram
participant C as MCP Client
participant S as Your MCP Server
participant P as OAuth Provider
Note over C: User adds server to client
C->>S: GET /.well-known/oauth-authorization-server
S->>C: OAuth metadata (issuer, endpoints, etc.)
Note over C: Client auto-configures OAuth
C->>P: OAuth flow (authorization code)
P->>C: Access token
C->>S: POST /mcp + Bearer token
S->>C: Authenticated tool response
Clients that support auto-discovery:
- Claude Desktop (native OAuth)
- Claude Code (native OAuth)
- MCP Inspector (browser OAuth)
Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"my-server": {
"url": "https://your-server.com/mcp"
}
}
}That's it! Claude Desktop will:
- Fetch
https://your-server.com/.well-known/oauth-authorization-server - Discover OAuth issuer and endpoints
- Guide user through OAuth flow
- Store and manage tokens automatically
For clients without auto-discovery:
{
"mcpServers": {
"my-server": {
"url": "https://your-server.com/mcp",
"headers": {
"Authorization": "Bearer YOUR_TOKEN_HERE"
}
}
}
}How to get token:
- HMAC: Generate using
jwt.NewWithClaims()(see HMAC Guide) - OIDC: Use OAuth provider's token endpoint or admin tools
For simple clients that can't do OAuth:
{
"mcpServers": {
"my-server": {
"url": "https://your-server.com/mcp",
"oauth": {
"authorizationUrl": "https://your-server.com/oauth/authorize",
"tokenUrl": "https://your-server.com/oauth/token"
}
}
}
}Client can now use your server's OAuth endpoints instead of going directly to the provider.
Your server automatically exposes (when using WithOAuth()):
GET https://your-server.com/.well-known/oauth-authorization-serverReturns:
{
"issuer": "https://your-server.com",
"authorization_endpoint": "https://your-server.com/oauth/authorize",
"token_endpoint": "https://your-server.com/oauth/token",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code"],
"code_challenge_methods_supported": ["plain", "S256"]
}GET https://your-server.com/.well-known/openid-configurationReturns similar metadata with OIDC-specific fields.
GET https://your-server.com/.well-known/oauth-protected-resourceTells clients this is an OAuth-protected resource.
Server config (mark3labs SDK):
import "github.com/tuannvm/oauth-mcp-proxy/mark3labs"
_, oauthOption, _ := mark3labs.WithOAuth(mux, &oauth.Config{
Provider: "okta",
Issuer: "https://company.okta.com",
Audience: "api://my-server",
})
mcpServer := server.NewMCPServer("Server", "1.0.0", oauthOption)Server config (official SDK):
import mcpoauth "github.com/tuannvm/oauth-mcp-proxy/mcp"
mcpServer := mcp.NewServer(&mcp.Implementation{...}, nil)
_, handler, _ := mcpoauth.WithOAuth(mux, &oauth.Config{
Provider: "okta",
Issuer: "https://company.okta.com",
Audience: "api://my-server",
}, mcpServer)Client discovers:
- Metadata endpoints return Okta URLs
- Client authenticates directly with Okta
- Client sends Okta token to your server
- Your server validates token against Okta
Client config (auto-discovery):
{
"mcpServers": {
"my-server": {
"url": "https://your-server.com/mcp"
}
}
}Client fetches metadata, sees Okta issuer, handles OAuth with Okta directly.
Server config (mark3labs SDK):
import "github.com/tuannvm/oauth-mcp-proxy/mark3labs"
_, oauthOption, _ := mark3labs.WithOAuth(mux, &oauth.Config{
Provider: "okta",
ClientID: "...",
ClientSecret: "...",
ServerURL: "https://your-server.com",
RedirectURIs: "https://your-server.com/oauth/callback",
})
mcpServer := server.NewMCPServer("Server", "1.0.0", oauthOption)Server config (official SDK):
import mcpoauth "github.com/tuannvm/oauth-mcp-proxy/mcp"
mcpServer := mcp.NewServer(&mcp.Implementation{...}, nil)
_, handler, _ := mcpoauth.WithOAuth(mux, &oauth.Config{
Provider: "okta",
ClientID: "...",
ClientSecret: "...",
ServerURL: "https://your-server.com",
RedirectURIs: "https://your-server.com/oauth/callback",
}, mcpServer)Client discovers:
- Metadata endpoints return YOUR server URLs (not Okta)
- Client authenticates through your server
- Your server proxies to Okta
- Client sends token from your server
Client config (auto-discovery):
{
"mcpServers": {
"my-server": {
"url": "https://your-server.com/mcp"
}
}
}Client fetches metadata, sees your server as issuer, does OAuth flow through your server.
# OAuth provider
export OAUTH_PROVIDER=okta
export OAUTH_ISSUER=https://company.okta.com
export OAUTH_AUDIENCE=api://my-server
# Proxy mode (if needed)
export OAUTH_CLIENT_ID=your-client-id
export OAUTH_CLIENT_SECRET=your-client-secret
export OAUTH_SERVER_URL=https://your-server.com
export OAUTH_REDIRECT_URIS=https://your-server.com/oauth/callback
# HMAC (if using)
export JWT_SECRET=your-32-byte-secret# values.yaml
oauth:
enabled: true
mode: native # or proxy
provider: okta
redirectURIs: "" # For proxy mode
oidc:
issuer: https://company.okta.com
audience: api://my-server
clientId: "" # For proxy mode
clientSecret: "" # For proxy mode (stored in Secret)services:
mcp-server:
image: your-mcp-server:latest
environment:
OAUTH_PROVIDER: okta
OAUTH_ISSUER: https://company.okta.com
OAUTH_AUDIENCE: api://my-server
env_file:
- .env.secrets # Contains OAUTH_CLIENT_SECRET, JWT_SECRET# Check OAuth discovery
curl https://your-server.com/.well-known/oauth-authorization-server | jq
# Verify issuer matches expected provider
jq '.issuer' # Should be your provider (native) or your server (proxy)# For HMAC - generate test token
# See examples/mark3labs/simple/ or examples/official/simple/ for token generation
# For OIDC - get token from provider (see examples/README.md for Okta setup)
# Test with curl
curl -X POST https://your-server.com/mcp \
-H "Authorization: Bearer <token>" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Add server to Claude Desktop and verify:
- OAuth flow initiates automatically
- No manual token configuration needed
- Authentication works end-to-end
Check:
curl https://your-server.com/.well-known/oauth-authorization-server
# Should return 200 with JSON metadataIf 404, verify WithOAuth() was called and server is running.
Check:
- Client is sending
Authorization: Bearer <token>header - Token is valid (not expired)
- Token's
issandaudmatch server config
Debug: Enable verbose logging in client if available.
Native mode:
- Check client can reach OAuth provider directly
- Verify provider's redirect URIs include client's callback
Proxy mode:
- Check client can reach your server's /oauth endpoints
- Verify your server's redirect URIs configured in provider
Location:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json
Config:
{
"mcpServers": {
"my-oauth-server": {
"url": "https://mcp-server.example.com/mcp"
}
}
}Claude Desktop auto-discovers OAuth via metadata endpoints.
With auto-discovery:
{
"mcpServers": {
"my-server": {
"url": "https://your-server.com/mcp"
}
}
}With manual token:
{
"mcpServers": {
"my-server": {
"url": "https://your-server.com/mcp",
"headers": {
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
}
}- Configuration Guide - Server-side OAuth configuration
- Provider Guides - OAuth provider setup
- Troubleshooting - Common client issues