/ examples / mcp_server / mcp_auth_example.py
mcp_auth_example.py
  1  #!/usr/bin/env python3
  2  """
  3  MCP Auth Example
  4  
  5  Demonstrates the OAuth 2.1 and OIDC Discovery per MCP 2025-11-25 specification.
  6  
  7  Features:
  8  - OAuth 2.1 with PKCE
  9  - OpenID Connect Discovery
 10  - API Key authentication
 11  - Scope management
 12  
 13  Usage:
 14      python mcp_auth_example.py
 15  """
 16  
 17  import asyncio
 18  from praisonai.mcp_server.auth.oauth import OAuthConfig, OAuthManager
 19  from praisonai.mcp_server.auth.oidc import OIDCDiscovery
 20  from praisonai.mcp_server.auth.api_key import APIKeyAuth
 21  from praisonai.mcp_server.auth.scopes import ScopeManager
 22  
 23  
 24  async def main():
 25      print("=" * 60)
 26      print("MCP Auth Example (2025-11-25 Specification)")
 27      print("=" * 60)
 28      
 29      # 1. OIDC Discovery
 30      print("\n1. OpenID Connect Discovery")
 31      print("-" * 40)
 32      
 33      discovery = OIDCDiscovery()
 34      
 35      # Discover Google's OIDC configuration
 36      issuer = "https://accounts.google.com"
 37      print(f"   Discovering OIDC config from: {issuer}")
 38      
 39      config = await discovery.discover(issuer)
 40      if config:
 41          print(f"   ✓ Issuer: {config.issuer}")
 42          print(f"   ✓ Authorization endpoint: {config.authorization_endpoint}")
 43          print(f"   ✓ Token endpoint: {config.token_endpoint}")
 44          print(f"   ✓ JWKS URI: {config.jwks_uri}")
 45      else:
 46          print("   ✗ Discovery failed")
 47      
 48      # 2. OAuth 2.1 Configuration
 49      print("\n2. OAuth 2.1 Configuration")
 50      print("-" * 40)
 51      
 52      oauth_config = OAuthConfig(
 53          authorization_endpoint="https://auth.example.com/authorize",
 54          token_endpoint="https://auth.example.com/token",
 55          client_id="my-mcp-client",
 56          default_scopes=["openid", "profile", "tools:read"],
 57          use_pkce=True,  # PKCE is required for OAuth 2.1
 58      )
 59      
 60      oauth = OAuthManager(oauth_config)
 61      
 62      # Create authorization URL with PKCE
 63      auth_url, auth_request = oauth.create_authorization_url(
 64          scopes=["openid", "profile", "tools:read", "tools:call"],
 65      )
 66      
 67      print(f"   Authorization URL: {auth_url[:80]}...")
 68      print(f"   State: {auth_request.state}")
 69      print(f"   PKCE code verifier: {auth_request.code_verifier[:20]}...")
 70      print(f"   PKCE code challenge: {auth_request.code_challenge[:20]}...")
 71      
 72      # 3. API Key Authentication
 73      print("\n3. API Key Authentication")
 74      print("-" * 40)
 75      
 76      api_key_auth = APIKeyAuth(allow_env_key=False)
 77      
 78      # Generate a new API key
 79      raw_key, api_key = api_key_auth.generate_key(
 80          name="demo-key",
 81          scopes=["tools:read", "tools:call", "resources:read"],
 82      )
 83      
 84      print(f"   Generated key: {raw_key[:20]}...")
 85      print(f"   Key ID: {api_key.key_id}")
 86      print(f"   Key name: {api_key.name}")
 87      print(f"   Scopes: {api_key.scopes}")
 88      
 89      # Validate the key
 90      is_valid, validated_key = api_key_auth.validate(raw_key)
 91      print(f"\n   Validation result: {'✓ Valid' if is_valid else '✗ Invalid'}")
 92      
 93      # Validate via header
 94      is_valid_header, _ = api_key_auth.validate_header(f"Bearer {raw_key}")
 95      print(f"   Header validation: {'✓ Valid' if is_valid_header else '✗ Invalid'}")
 96      
 97      # 4. Scope Management
 98      print("\n4. Scope Management")
 99      print("-" * 40)
100      
101      scope_manager = ScopeManager()
102      
103      # Check if required scopes are granted
104      required = ["tools:read"]
105      granted = ["tools:call"]  # tools:call implies tools:read
106      
107      is_valid, challenge = scope_manager.validate_scopes(required, granted)
108      print(f"   Required: {required}")
109      print(f"   Granted: {granted}")
110      print(f"   Valid: {'✓' if is_valid else '✗'}")
111      
112      # Expand scopes (show implied scopes)
113      expanded = scope_manager.expand_scopes(["tools:call"])
114      print(f"\n   Expanded 'tools:call': {expanded}")
115      
116      # Admin scope expands to all
117      admin_expanded = scope_manager.expand_scopes(["admin"])
118      print(f"   Expanded 'admin': {list(admin_expanded)[:5]}...")
119      
120      # 5. WWW-Authenticate Challenge
121      print("\n5. WWW-Authenticate Challenge")
122      print("-" * 40)
123      
124      challenge_header = oauth.create_www_authenticate_challenge(
125          required_scopes=["admin"],
126          error="insufficient_scope",
127          error_description="Admin access required",
128      )
129      print(f"   Header: {challenge_header}")
130      
131      print("\n" + "=" * 60)
132      print("Auth Example Complete!")
133      print("=" * 60)
134  
135  
136  if __name__ == "__main__":
137      asyncio.run(main())