This guide shows you how to configure the Kubernetes MCP Server to use Microsoft Entra ID (formerly Azure AD) as the OIDC provider.
Entra ID differs from Keycloak in that it only exposes the standard OpenID Connect discovery endpoint (/.well-known/openid-configuration) and does not implement the OAuth Authorization Server Metadata endpoints (/.well-known/oauth-authorization-server).
The MCP server automatically handles this by falling back to openid-configuration when the OAuth-specific endpoints return 404.
- Microsoft Entra ID admin access (Azure Portal)
- Kubernetes cluster configured with Entra ID as the OIDC provider
kubectlCLI with cluster access
- Go to Azure Portal → Microsoft Entra ID → App registrations
- Click New registration
- Fill in:
- Name:
MCP Server(or any name) - Supported account types: "Accounts in this organizational directory only"
- Redirect URI: Leave blank for now
- Name:
- Click Register
From the app's Overview page, copy:
- Application (client) ID →
CLIENT_ID - Directory (tenant) ID →
TENANT_ID
You need one of the following — a client secret, a certificate, or a federated identity credential. If you only need MCP server authentication (no other systems sharing this app registration), certificate-based auth is recommended. If your workload runs in an environment with an external identity provider (e.g., SPIRE), use federated credentials.
Use this if you prefer simplicity or if other systems (e.g., cluster console OIDC login) share this app registration and require a client secret.
- Go to Certificates & secrets (left sidebar)
- Click New client secret
- Add description and expiration
- Click Add
- Copy the Value immediately (only shown once) →
CLIENT_SECRET
Use this for production deployments. No secret to manage — the MCP server authenticates using a signed JWT assertion.
- Generate a certificate (or use your PKI):
openssl req -x509 -newkey rsa:2048 -keyout client.key -out client.crt -days 365 -nodes -subj "/CN=MCP Server" - Go to Certificates & secrets (left sidebar)
- Click the Certificates tab
- Click Upload certificate and select
client.crt - Note the Thumbprint shown after upload — you can use this to verify your config later
Tip: If your cluster already uses a separate app registration with a client secret for console OIDC login, you can create a dedicated app registration for the MCP server using certificate auth only (no secret needed). See Separate App Registration for MCP Server below.
- Go to API permissions (left sidebar)
- Click Add a permission → Microsoft Graph → Delegated permissions
- Add these permissions:
openidprofileemail
- Click Add permissions
- Click Grant admin consent for [your org]
- Go to Token configuration (left sidebar)
- Click Add optional claim
- Select ID token type
- Check these claims:
emailpreferred_username
- Click Add
If you plan to test with MCP Inspector:
- Go to Authentication (left sidebar)
- Under Platform configurations, click Add a platform → Web
- Add redirect URI:
http://localhost:6274/oauth/callback - Click Configure
Create a configuration file (config.toml):
Use this configuration when your Kubernetes cluster accepts Entra ID tokens directly (cluster OIDC is configured with the same Entra ID tenant):
require_oauth = true
oauth_audience = "<CLIENT_ID>"
oauth_scopes = ["openid", "profile", "email"]
# Entra ID uses v2.0 endpoints
authorization_url = "https://login.microsoftonline.com/<TENANT_ID>/v2.0"Replace:
<CLIENT_ID>with your Application (client) ID<TENANT_ID>with your Directory (tenant) ID
Note: When
cluster_auth_modeis not set, the server defaults topassthrough: the Authorization header is forwarded to the cluster when present, and kubeconfig credentials are used when absent. Setcluster_auth_mode = "kubeconfig"to always use kubeconfig credentials regardless of any Authorization header.In
passthroughmode, if token exchange is configured (token_exchange_strategyorsts_audience), the token is exchanged before being passed to the cluster.
Note: Authenticating MCP clients with
require_oauth = truewhile using a shared ServiceAccount for cluster access (cluster_auth_mode = "kubeconfig") is not supported and is rejected at startup — a single ServiceAccount collapses every authenticated user to one cluster identity, breaking per-user audit trails. If your cluster's API server doesn't accept the user's Entra ID tokens directly, use On-Behalf-Of token exchange to exchange them for tokens the cluster accepts while preserving per-user identity.
If your cluster accepts Entra ID tokens and you want to exchange the user's token via the On-Behalf-Of (OBO) flow, use one of the credential options below.
require_oauth = true
oauth_audience = "<CLIENT_ID>"
oauth_scopes = ["openid", "profile", "email"]
authorization_url = "https://login.microsoftonline.com/<TENANT_ID>/v2.0"
# Token exchange configuration (passthrough will use this automatically)
token_exchange_strategy = "entra-obo"
sts_client_id = "<CLIENT_ID>"
sts_client_secret = "<CLIENT_SECRET>"
sts_scopes = ["api://<DOWNSTREAM_API_APP_ID>/.default"]require_oauth = true
oauth_audience = "<CLIENT_ID>"
oauth_scopes = ["openid", "profile", "email"]
authorization_url = "https://login.microsoftonline.com/<TENANT_ID>/v2.0"
# Token exchange with certificate authentication (RFC 7523 JWT Client Assertion)
token_exchange_strategy = "entra-obo"
sts_client_id = "<CLIENT_ID>"
sts_auth_style = "assertion"
sts_client_cert_file = "/path/to/client.crt"
sts_client_key_file = "/path/to/client.key"
sts_scopes = ["api://<DOWNSTREAM_API_APP_ID>/.default"]No client secret is needed when using certificate auth. The MCP server signs a short-lived JWT assertion (5 minutes) using the private key, and Entra ID validates it against the uploaded certificate.
For OBO to work, you need to configure API permissions in Azure:
- Go to your app registration → API permissions
- Click Add a permission → APIs my organization uses
- Select the downstream API app registration
- Add the required delegated permissions
If your MCP server runs in an environment with an external identity provider (e.g., SPIRE, GitHub Actions, or another Kubernetes cluster), you can use workload identity federation instead of managing certificates or secrets. The external IdP issues a JWT that is passed directly to Entra ID as a federated credential.
- Configure a federated identity credential on your app registration:
- Go to Certificates & secrets → Federated credentials → Add credential
- Select the scenario (e.g., "Other issuer")
- Set the Issuer to your external IdP's OIDC issuer URL (e.g.,
https://spire-server.example.com) - Set the Subject identifier to match the
subclaim in the external JWT (e.g.,spiffe://example.com/mcp-server) - Set the Audience to match the
audclaim (typicallyapi://AzureADTokenExchange)
- Ensure the external IdP writes a JWT to a file accessible by the MCP server (e.g., via SPIRE agent, Kubernetes projected volumes, or a sidecar)
require_oauth = true
oauth_audience = "<CLIENT_ID>"
oauth_scopes = ["openid", "profile", "email"]
authorization_url = "https://login.microsoftonline.com/<TENANT_ID>/v2.0"
# Token exchange with federated credential (workload identity federation)
token_exchange_strategy = "entra-obo"
sts_client_id = "<CLIENT_ID>"
sts_auth_style = "federated"
sts_federated_token_file = "/var/run/secrets/tokens/federated-token"
sts_scopes = ["api://<DOWNSTREAM_API_APP_ID>/.default"]The MCP server reads the JWT from sts_federated_token_file on each token request, so token rotation by the external IdP is handled automatically.
./kubernetes-mcp-server --config config.tomlTo test authentication with MCP Inspector:
- Ensure redirect URI is configured (see Step 1)
- Start MCP Inspector:
npx @modelcontextprotocol/inspector@latest $(pwd)/kubernetes-mcp-server --config config.toml - In Authentication section:
- Set Client ID to your
<CLIENT_ID> - Set Scope to
openid profile email
- Set Client ID to your
- Click Connect
- Login with your Entra ID credentials
Entra ID doesn't support RFC 7591 Dynamic Client Registration - clients must be pre-registered in the Azure portal (as shown in Step 1 above).
Add redirect URIs in the Azure portal → Authentication for your MCP clients:
http://localhost:6274/oauth/callback(MCP Inspector default)
The MCP server implements automatic fallback for OIDC providers that don't support all OAuth 2.0 well-known endpoints:
- When a client requests
/.well-known/oauth-authorization-server, the server first tries to proxy the request to Entra ID - Entra ID returns 404 (this endpoint doesn't exist)
- The server automatically falls back to fetching
/.well-known/openid-configuration - The openid-configuration response is returned, which contains all required OAuth metadata
This allows MCP clients to work with Entra ID without any special configuration.
Check that:
- You're using the correct client ID
- The redirect URI matches exactly what's configured in Entra ID
- The client secret is correct (if using client secret auth)
This means the certificate used to sign the JWT assertion doesn't match any certificate uploaded to your app registration.
- Check your certificate's thumbprint:
openssl x509 -in /path/to/client.crt -fingerprint -sha1 -noout
- Go to Azure Portal → App registrations → your app → Certificates & secrets → Certificates
- Compare the thumbprint. If it doesn't match, upload the correct certificate
- Make sure
sts_client_cert_fileandsts_client_key_filepoint to the matching cert/key pair
The redirect URI in your request doesn't match Entra ID configuration:
- Go to Azure Portal → App registrations → your app → Authentication
- Add the exact redirect URI shown in the error message
Ensure your Kubernetes cluster is configured to trust Entra ID tokens:
- The OIDC issuer should be
https://login.microsoftonline.com/{tenant}/v2.0 - The audience should match your client ID or application ID URI
This is expected for oauth-authorization-server and oauth-protected-resource endpoints. The MCP server automatically handles this by falling back to openid-configuration.
| Feature | Keycloak | Entra ID |
|---|---|---|
| oauth-authorization-server endpoint | ✅ Supported | ❌ Not available |
| oauth-protected-resource endpoint | ✅ Supported | ❌ Not available |
| openid-configuration endpoint | ✅ Supported | ✅ Supported |
| Token Exchange (RFC 8693) | ✅ Supported | ❌ Use On-Behalf-Of flow |
| Dynamic Client Registration | ✅ Supported | ❌ Not available |
The MCP server handles these differences automatically through the well-known endpoint fallback mechanism.
| Item | Where to Find |
|---|---|
| Client ID | Azure Portal → App registrations → Overview → Application (client) ID |
| Tenant ID | Azure Portal → App registrations → Overview → Directory (tenant) ID |
| Client Secret | Azure Portal → App registrations → Certificates & secrets → Value column |
| Authorization URL | https://login.microsoftonline.com/<TENANT_ID>/v2.0 |
For the passthrough flow to work, your Kubernetes cluster's API server must be configured to accept Entra ID tokens via OIDC. This is separate from any console or dashboard login configuration your cluster may have.
┌─────────────────────────────────────────────────────────────────────────────┐
│ │
│ ┌──────────┐ ┌─────────────┐ ┌──────────┐ ┌─────────────────┐ │
│ │ User │────▶│ MCP Client │────▶│MCP Server│────▶│ Kubernetes │ │
│ │ │ │ (Inspector) │ │ │ │ Cluster │ │
│ └──────────┘ └─────────────┘ └──────────┘ └─────────────────┘ │
│ │ │ │ │ │
│ │ 1. OAuth │ 2. Bearer │ 3. OBO + │ │
│ │ Login │ Token │ Assertion │ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ Microsoft Entra ID │ │
│ │ │ │
│ │ 1. User authenticates via OAuth 2.0 (authorization code flow) │ │
│ │ 2. MCP Server validates user token │ │
│ │ 3. MCP Server exchanges token using OBO + JWT client assertion │ │
│ │ 4. Cluster validates exchanged token via OIDC │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
- Cluster admin access
- Ability to configure kube-apiserver OIDC flags (managed clusters may expose this differently)
The API server needs the following OIDC flags. How you set these depends on your cluster type:
- kubeadm / self-managed: edit
/etc/kubernetes/manifests/kube-apiserver.yaml - Managed Kubernetes (EKS, AKS, GKE): use the provider's OIDC identity configuration
- Kind / Minikube: pass flags via cluster config
The required flags:
--oidc-issuer-url=https://login.microsoftonline.com/<TENANT_ID>/v2.0
--oidc-client-id=<CLIENT_ID>
--oidc-username-claim=preferred_username
--oidc-groups-claim=groups
Once the API server accepts Entra ID tokens, create RBAC bindings for your users:
# For a specific user
kubectl create clusterrolebinding entra-user-admin \
--clusterrole=cluster-admin \
--user="user@yourdomain.com"
# For a group (requires groups claim configured in Entra ID)
kubectl create clusterrolebinding entra-group-admin \
--clusterrole=cluster-admin \
--group="your-group-object-id"You can test that the API server accepts Entra ID tokens by using kubectl with a token:
kubectl --token="<ENTRA_ID_ACCESS_TOKEN>" get namespacesIf this returns namespaces, your cluster is correctly configured.
# config.toml
log_level = 4
port = "8080"
# OAuth: Users authenticate via Entra ID
require_oauth = true
authorization_url = "https://login.microsoftonline.com/<TENANT_ID>/v2.0"
oauth_audience = "<CLIENT_ID>"
# Pass exchanged token to cluster
cluster_auth_mode = "passthrough"
# Token Exchange: OBO flow with JWT client assertion
token_exchange_strategy = "entra-obo"
sts_client_id = "<CLIENT_ID>"
sts_auth_style = "assertion"
sts_client_cert_file = "/path/to/client.crt"
sts_client_key_file = "/path/to/client.key"
sts_scopes = ["<CLIENT_ID>/.default"]-
MCP Server → Entra ID (OBO Exchange)
- MCP Server authenticates using JWT client assertion (certificate)
- No client_secret needed
- This is what
sts_auth_style = "assertion"configures
-
Cluster → Entra ID (Token Validation)
- The API server validates tokens from Entra ID
- Configured via kube-apiserver OIDC flags
- Uses OIDC discovery to fetch signing keys
Both relationships use the same app registration but serve different purposes.
If your cluster already uses an Entra ID app registration with a client secret for console OIDC login, you may want a separate app registration for the MCP server — especially if you prefer certificate-based auth and don't want to add a certificate to the existing app.
- Your cluster's OIDC app registration is shared with other systems (console, CLI) and uses a client secret
- You want the MCP server to use certificate auth without affecting the existing setup
- You want to scope the MCP server's permissions separately
App Registration A (existing) — used by the cluster for console/CLI OIDC login. Has a client secret. No changes needed.
App Registration B (new) — used by the MCP server for OBO token exchange. Uses certificate auth, no secret required.
- Create a new app registration in Azure (follow Step 1 above)
- Choose Option B: Certificate for credentials — skip the client secret
- On App Registration B, go to Expose an API (left sidebar):
- Click Set next to "Application ID URI" (accept the default
api://<CLIENT_ID_B>) - Click Add a scope → name it (e.g.,
access_as_user) → set "Who can consent" to "Admins and users" → enable it
- Click Set next to "Application ID URI" (accept the default
- On App Registration A (the existing one), go to API permissions:
- Click Add a permission → APIs my organization uses → find App Registration B
- Add the delegated scope you created (e.g.,
access_as_user) - Click Grant admin consent
require_oauth = true
# Use App A's client ID — this is what MCP clients authenticate with
oauth_audience = "<CLIENT_ID_A>"
oauth_scopes = ["openid", "profile", "email"]
authorization_url = "https://login.microsoftonline.com/<TENANT_ID>/v2.0"
# OBO exchange uses App B's credentials (certificate, no secret)
token_exchange_strategy = "entra-obo"
sts_client_id = "<CLIENT_ID_B>"
sts_auth_style = "assertion"
sts_client_cert_file = "/path/to/client.crt"
sts_client_key_file = "/path/to/client.key"
sts_scopes = ["<CLIENT_ID_B>/.default"]This way, the cluster's existing OIDC configuration is untouched, and the MCP server has its own credentials with certificate-based auth.