Complete guide for configuring and using EntraID (Azure AD) authentication with the getset-pox-mcp MCP server.
- Overview
- Architecture
- Setup Guide
- Configuration
- Authentication Flows
- Security Best Practices
- Troubleshooting
- API Reference
The getset-pox-mcp server supports Microsoft EntraID (formerly Azure AD) authentication for secure access to Microsoft Graph API and other Azure resources.
- OAuth2 Authentication: Industry-standard OAuth2/OpenID Connect
- Multiple Auth Modes: Application (daemon) and Delegated (user) authentication
- Token Management: Automatic token refresh and secure caching
- MSAL Integration: Uses Microsoft Authentication Library (MSAL) for Python
- Secure by Default: Follows Microsoft security best practices
Enable authentication when your MCP tools need to:
- Access Microsoft Graph API
- Call Azure services on behalf of the server
- Perform operations requiring specific permissions
- Access user-specific data (delegated mode)
┌─────────────────┐
│ MCP Client │
│ (Claude/IDE) │
└────────┬────────┘
│
│ MCP Protocol
│
▼
┌─────────────────────────────────────┐
│ MCP Server (getset-pox-mcp) │
│ ┌──────────────────────────────┐ │
│ │ Authentication Middleware │ │
│ └──────────┬───────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────┐ │
│ │ EntraID Auth Provider │ │
│ │ (MSAL + Token Manager) │ │
│ └──────────┬───────────────────┘ │
│ │ │
└─────────────┼────────────────────────┘
│
│ OAuth2/OIDC
│
▼
┌────────────────────┐
│ Microsoft EntraID │
│ (Azure AD) │
└────────┬───────────┘
│
│ Access Token
│
▼
┌────────────────────┐
│ Microsoft Graph │
│ Other Azure APIs │
└────────────────────┘
- AuthConfig: Configuration management for auth settings
- TokenManager: Token storage, validation, and refresh
- EntraIDAuthProvider: OAuth2 flows and MSAL integration
- AuthMiddleware: Server integration and request handling
- Python 3.8 or higher
- Azure subscription (free tier works)
- Azure AD tenant
- Administrator access to Azure Portal
- Go to Azure Portal
- Navigate to Azure Active Directory → App Registrations
- Click + New registration
- Fill in the details:
- Name:
getset-pox-mcp(or your preferred name) - Supported account types: Select based on your needs
- Redirect URI: Leave blank for application mode
- Name:
- Click Register
- Note down:
- Application (client) ID
- Directory (tenant) ID
- In your app registration, go to Certificates & secrets
- Click + New client secret
- Add description:
MCP Server Secret - Select expiry period (recommended: 6-12 months)
- Click Add
- Copy the secret value immediately (you won't see it again!)
- Go to API permissions
- Click + Add a permission
- Select Microsoft Graph → Application permissions
- Add required permissions, for example:
User.Read.AllGroup.Read.AllDirectory.Read.All
- Click Add permissions
- Click Grant admin consent (requires admin)
- Confirm the consent
- Go to API permissions
- Click + Add a permission
- Select Microsoft Graph → Delegated permissions
- Add required permissions, for example:
User.ReadGroup.Read.All
- Click Add permissions
- Admin consent may be required for some permissions
# Install MSAL library
pip install msal>=1.24.0
# Or reinstall all requirements
pip install -r requirements.txt-
Copy the authentication template:
cp getset_pox_mcp/authentication/.env.auth.example .env
-
Edit
.envwith your values:# Required ENTRA_TENANT_ID=your-tenant-id-here ENTRA_CLIENT_ID=your-client-id-here ENTRA_CLIENT_SECRET=your-client-secret-here # Enable authentication ENTRA_ENABLE_AUTH=true # Set mode ENTRA_AUTH_MODE=application
-
Verify
.envis in.gitignore(it should be by default)
# Run the server
python -m getset_pox_mcp.server
# Check logs for:
# "Authentication: enabled=True, mode=application"
# "Server authentication successful"| Variable | Required | Default | Description |
|---|---|---|---|
ENTRA_TENANT_ID |
Yes | - | Azure AD tenant ID |
ENTRA_CLIENT_ID |
Yes | - | Application (client) ID |
ENTRA_CLIENT_SECRET |
Yes* | - | Client secret (*required for app mode) |
ENTRA_ENABLE_AUTH |
No | false |
Enable authentication |
ENTRA_AUTH_MODE |
No | application |
application or delegated |
ENTRA_SCOPES |
No | https://graph.microsoft.com/.default |
Comma-separated scopes |
ENTRA_AUTHORITY |
No | Auto | Authority URL |
ENTRA_REDIRECT_URI |
No | http://localhost:8000/callback |
Redirect URI (delegated mode) |
ENTRA_TOKEN_CACHE_PATH |
No | - | Token cache file path |
from getset_pox_mcp.authentication import AuthConfig, EntraIDAuthProvider
# Load from environment
config = AuthConfig.from_env()
# Or create manually
config = AuthConfig(
tenant_id="your-tenant-id",
client_id="your-client-id",
client_secret="your-secret",
enable_auth=True,
auth_mode="application",
scopes=["https://graph.microsoft.com/.default"]
)
# Initialize provider
provider = EntraIDAuthProvider(config)
# Get access token
token = await provider.get_access_token()Best for server-to-server authentication without user interaction.
Flow:
- Server starts
- Requests token from EntraID using client credentials
- Receives access token
- Uses token for Microsoft Graph API calls
- Token auto-refreshes when expired
Use Cases:
- Background processing
- Scheduled tasks
- Service accounts
- Reading organization data
Configuration:
ENTRA_AUTH_MODE=application
ENTRA_SCOPES=https://graph.microsoft.com/.defaultFor operations on behalf of a specific user.
Flow:
- Server starts
- Displays device code and URL
- User visits URL and enters code
- User authenticates and grants consent
- Server receives access token and refresh token
- Token auto-refreshes using refresh token
Use Cases:
- User-specific data access
- Operations requiring user consent
- Interactive workflows
Configuration:
ENTRA_AUTH_MODE=delegated
ENTRA_SCOPES=User.Read,Group.Read.AllDO:
- ✅ Use environment variables for secrets
- ✅ Use Azure Key Vault in production
- ✅ Use Managed Identity when running in Azure
- ✅ Rotate secrets every 6-12 months
- ✅ Set appropriate secret expiry dates
DON'T:
- ❌ Hardcode secrets in code
- ❌ Commit
.envfiles to git - ❌ Share secrets in plain text
- ❌ Use long-lived secrets unnecessarily
Principle of Least Privilege:
- Only request permissions you actually need
- Prefer delegated permissions when possible
- Use specific scopes instead of broad permissions
- Regularly audit and remove unused permissions
Example:
# BAD: Requesting everything
ENTRA_SCOPES=https://graph.microsoft.com/.default
# GOOD: Specific permissions only
ENTRA_SCOPES=User.Read.All,Group.Read.AllBest Practices:
- Tokens are sensitive - never log token values
- Use secure file permissions for token cache
- Clear tokens on error or logout
- Validate tokens before use
- Monitor token usage for anomalies
- Use HTTPS for all production endpoints
- Implement rate limiting
- Monitor for suspicious authentication patterns
- Use firewall rules to restrict access
- Enable Azure AD Conditional Access policies
try:
token = await auth_provider.get_access_token()
if not token:
# Handle auth failure gracefully
logger.error("Authentication failed")
# Don't expose details to client
except Exception as e:
# Log error but don't expose details
logger.error(f"Auth error: {type(e).__name__}")
# Clear potentially corrupted tokens
auth_provider.clear_cache()- Enable Azure AD sign-in logs
- Monitor authentication failures
- Set up alerts for suspicious activity
- Regularly review granted permissions
- Track token usage patterns
Cause: Client secret is incorrect or expired
Solution:
- Generate new client secret in Azure Portal
- Update
ENTRA_CLIENT_SECRETin.env - Restart server
Cause: Permissions not granted or consent not provided
Solution:
- Go to Azure Portal → App Registrations → API Permissions
- Click "Grant admin consent"
- Wait 5-10 minutes for propagation
Cause: Wrong tenant ID or user not in tenant
Solution:
- Verify
ENTRA_TENANT_IDis correct - Ensure user exists in the tenant
- Check supported account types in app registration
Cause: ENTRA_ENABLE_AUTH not set to true
Solution:
# In .env file
ENTRA_ENABLE_AUTH=trueCause: Token refresh failed or credentials invalid
Solution:
- Clear token cache: Delete
.token_cachefile - Verify credentials are still valid
- Check client secret hasn't expired
- Restart server to re-authenticate
-
Enable debug logging:
LOG_LEVEL=DEBUG python -m getset_pox_mcp.server
-
Check authentication status:
from getset_pox_mcp.authentication.middleware import get_auth_middleware auth_middleware = get_auth_middleware() status = auth_middleware.get_auth_status() print(status)
-
Test token acquisition:
from getset_pox_mcp.authentication import AuthConfig, EntraIDAuthProvider config = AuthConfig.from_env() provider = EntraIDAuthProvider(config) token = await provider.get_access_token() print(f"Token obtained: {token is not None}")
-
Verify permissions: Use the
check_token_permissionsMCP tool to test permissions
Configuration class for authentication settings.
class AuthConfig:
def __init__(
self,
tenant_id: str,
client_id: str,
client_secret: Optional[str] = None,
authority: Optional[str] = None,
scopes: List[str] = ["https://graph.microsoft.com/.default"],
redirect_uri: str = "http://localhost:8000/callback",
enable_auth: bool = False,
auth_mode: str = "application",
token_cache_path: Optional[str] = None
):
...
@classmethod
def from_env(cls) -> "AuthConfig":
"""Load configuration from environment variables."""
...
def validate(self) -> bool:
"""Validate the configuration."""
...OAuth2 authentication provider.
class EntraIDAuthProvider:
def __init__(self, config: AuthConfig):
...
async def get_access_token(self, force_refresh: bool = False) -> Optional[str]:
"""Get a valid access token."""
...
def clear_cache(self) -> None:
"""Clear all cached tokens."""
...
def get_auth_status(self) -> Dict[str, Any]:
"""Get current authentication status."""
...Middleware for server integration.
class AuthMiddleware:
def __init__(self, auth_config: Optional[AuthConfig] = None):
...
async def authenticate_server(self) -> bool:
"""Authenticate during server startup."""
...
async def get_valid_token(self) -> Optional[str]:
"""Get a valid access token."""
...
def require_auth(self) -> Callable:
"""Decorator to require authentication."""
...from getset_pox_mcp.authentication.middleware import get_auth_middleware
# Get middleware instance
auth_middleware = get_auth_middleware()
# Authenticate server
await auth_middleware.authenticate_server()
# Get token for API call
token = await auth_middleware.get_valid_token()
# Use decorator to protect functions
@auth_middleware.require_auth()
async def protected_function():
# This function requires authentication
pass- Microsoft Identity Platform Documentation
- MSAL Python Documentation
- Microsoft Graph API Reference
- Azure AD App Registration Guide
- OAuth 2.0 Authorization Framework
For issues and questions:
- Check this documentation
- Review server logs with
LOG_LEVEL=DEBUG - Check Azure AD sign-in logs in Azure Portal
- Open an issue on GitHub with:
- Error messages (sanitized, no secrets!)
- Server logs
- Configuration (without secrets!)
- Steps to reproduce
Last Updated: 2025-11-16