This guide covers setting up Keycloak as an OAuth2/OpenID Connect identity provider for the Solr MCP Server running in HTTP mode.
The Solr MCP Server supports OAuth2 authentication via JWT tokens. Keycloak acts as the authorization server, issuing tokens that the MCP server validates. This enables:
User MCP Client Keycloak Solr MCP Server │ │ │ │ │─── Request Access ────►│ │ │ │ │─── Auth Request ───►│ │ │◄────────────────────── Login Page ◄──────────│ │ │─── Credentials ───────────────────────────────► │ │ │◄─── JWT Token ──────│ │ │ │─── API Request + Token ────────────────────►│ │ │ │ (validates JWT) │ │ │◄─────────────────────────── Response ────────│ │◄─── Result ────────────│ │ │
# 1. Start Keycloak docker run -d --name keycloak \ -p 8180:8080 \ -e KC_BOOTSTRAP_ADMIN_USERNAME=admin \ -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin \ quay.io/keycloak/keycloak:26.0 start-dev # 2. Configure Keycloak (see detailed steps below) # - Create realm: solr-mcp # - Create client: solr-mcp-client # 3. Run Solr MCP Server with security enabled export PROFILES=http export HTTP_SECURITY_ENABLED=true export OAUTH2_ISSUER_URI=http://localhost:8180/realms/solr-mcp ./gradlew bootRun
Development Mode (Docker):
docker run -d --name keycloak \ -p 8180:8080 \ -e KC_BOOTSTRAP_ADMIN_USERNAME=admin \ -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin \ quay.io/keycloak/keycloak:26.0 start-dev
Access the admin console at http://localhost:8180 and log in with admin/admin.
Production Mode:
For production deployments, use a proper database backend and TLS:
docker run -d --name keycloak \ -p 8443:8443 \ -e KC_BOOTSTRAP_ADMIN_USERNAME=admin \ -e KC_BOOTSTRAP_ADMIN_PASSWORD=<secure-password> \ -e KC_DB=postgres \ -e KC_DB_URL=jdbc:postgresql://db-host:5432/keycloak \ -e KC_DB_USERNAME=keycloak \ -e KC_DB_PASSWORD=<db-password> \ -e KC_HOSTNAME=keycloak.example.com \ quay.io/keycloak/keycloak:26.0 start
http://localhost:8180/adminsolr-mcpYou need at least one client for applications to authenticate against.
solr-mcp-serverOpenID Connectsolr-mcp-clientOpenID Connecthttp://localhost:6274/*, http://localhost:** or http://localhost:6274testusertest@example.comThe Solr MCP Server is pre-configured to work with any OAuth2/OIDC provider. Update application-http.properties:
# Security toggle - set to true to enable OAuth2 authentication http.security.enabled=${HTTP_SECURITY_ENABLED:true} # Keycloak OAuth2 Configuration # Format: https://<keycloak-host>/realms/<realm-name> spring.security.oauth2.resourceserver.jwt.issuer-uri=${OAUTH2_ISSUER_URI:http://localhost:8180/realms/solr-mcp}
No code changes are required—the existing McpServerConfiguration handles JWT validation automatically by discovering Keycloak's JWKS endpoint from the issuer URI.
With Security Enabled:
export PROFILES=http export HTTP_SECURITY_ENABLED=true export OAUTH2_ISSUER_URI=http://localhost:8180/realms/solr-mcp ./gradlew bootRun
Without Security (Development Only):
export PROFILES=http export HTTP_SECURITY_ENABLED=false ./gradlew bootRun
Using Resource Owner Password Grant (for testing):
curl -X POST "http://localhost:8180/realms/solr-mcp/protocol/openid-connect/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "client_id=solr-mcp-client" \ -d "username=testuser" \ -d "password=yourpassword" \ -d "grant_type=password"
Response:
{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "expires_in": 300, "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer" }
# Store token in variable TOKEN=$(curl -s -X POST "http://localhost:8180/realms/solr-mcp/protocol/openid-connect/token" \ -d "client_id=solr-mcp-client" \ -d "username=testuser" \ -d "password=yourpassword" \ -d "grant_type=password" | jq -r '.access_token') # Call MCP endpoint curl -X POST http://localhost:8080/mcp \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
Keycloak provides multiple ways to manage users beyond manual creation.
As described above, create users via Users → Add user in the admin console.
Allow users to authenticate via external identity providers. See GitHub Identity Provider Setup for a detailed example.
Supported providers include:
Allow users to create their own accounts:
Programmatically create users:
# Get admin token TOKEN=$(curl -s -X POST "http://localhost:8180/realms/master/protocol/openid-connect/token" \ -d "client_id=admin-cli" \ -d "username=admin" \ -d "password=admin" \ -d "grant_type=password" | jq -r '.access_token') # Create user curl -X POST "http://localhost:8180/admin/realms/solr-mcp/users" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "username": "newuser", "email": "newuser@example.com", "enabled": true, "emailVerified": true, "credentials": [{ "type": "password", "value": "temppassword", "temporary": true }] }'
Bulk import users during realm setup:
{ "realm": "solr-mcp", "users": [ { "username": "user1", "email": "user1@example.com", "enabled": true, "credentials": [{"type": "password", "value": "pass123"}], "realmRoles": ["user", "solr-query"] }, { "username": "user2", "email": "user2@example.com", "enabled": true, "credentials": [{"type": "password", "value": "pass456"}], "realmRoles": ["admin"] } ] }
Import via CLI:
/opt/keycloak/bin/kc.sh import --file realm-export.json
For databases or custom backends, implement Keycloak's User Storage SPI to authenticate against your existing user database without migration.
Go to GitHub → Settings → Developer settings → OAuth Apps → New OAuth App
Direct link: https://github.com/settings/applications/new
Fill in the form:
| Field | Value |
|---|---|
| Application name | Solr MCP Server |
| Homepage URL | http://localhost:8180 |
| Authorization callback URL | http://localhost:8180/realms/solr-mcp/broker/github/endpoint |
Note: The callback URL format is:
https://<keycloak-host>/realms/<realm-name>/broker/github/endpoint
solr-mcp)| Field | Value |
|---|---|
| Client ID | <from GitHub> |
| Client Secret | <from GitHub> |
| Default Scopes | user:email (optional) |
http://localhost:8180/realms/solr-mcp/accountAdd mappers to import GitHub profile data:
| Name | Mapper Type | Claim | User Attribute |
|---|---|---|---|
| GitHub Username | Attribute Importer | login | github_username |
| GitHub Avatar | Attribute Importer | avatar_url | avatar |
| GitHub Email | Attribute Importer | email | email |
Automatically assign roles to GitHub users:
Hardcoded Rolesolr-query)To use Keycloak roles with Spring Security's @PreAuthorize annotations, add a JWT converter.
admin, solr-query, solr-adminAdd to McpServerConfiguration.java:
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; @Bean @ConditionalOnProperty(name = "http.security.enabled", havingValue = "true", matchIfMissing = true) public JwtAuthenticationConverter jwtAuthenticationConverter() { JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); // Keycloak stores realm roles in realm_access.roles grantedAuthoritiesConverter.setAuthoritiesClaimName("realm_access.roles"); grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_"); JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter(); jwtConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter); return jwtConverter; }
Wire it into the security filter chain:
@Bean @ConditionalOnProperty(name = "http.security.enabled", havingValue = "true", matchIfMissing = true) SecurityFilterChain securityFilterChain(HttpSecurity http, JwtAuthenticationConverter jwtAuthenticationConverter) throws Exception { return http .authorizeHttpRequests(auth -> { auth.requestMatchers("/actuator").permitAll(); auth.requestMatchers("/actuator/*").permitAll(); auth.requestMatchers("/mcp").permitAll(); auth.anyRequest().authenticated(); }) .oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter)) ) .with(McpServerOAuth2Configurer.mcpServerOAuth2(), (mcpAuthorization) -> { mcpAuthorization.authorizationServer(issuerUrl); }) .cors(cors -> cors.configurationSource(corsConfigurationSource())) .csrf(CsrfConfigurer::disable) .build(); }
@PreAuthorize("hasRole('admin')") public String deleteCollection(String name) { ... } @PreAuthorize("hasRole('solr-query')") public String executeQuery(String collection, String query) { ... } @PreAuthorize("hasAnyRole('solr-query', 'solr-admin')") public String getSchema(String collection) { ... }
| Location | Claim Path | Use Case |
|---|---|---|
| Realm roles | realm_access.roles | Global roles across all clients |
| Client roles | resource_access.<client-id>.roles | Roles specific to a client |
“Invalid token” or 401 Unauthorized:
OAUTH2_ISSUER_URI matches your Keycloak realm URL exactly“Unable to resolve issuer”:
http://localhost:8180/realms/solr-mcp/.well-known/openid-configurationCORS errors with MCP Inspector:
http://localhost:6274 to Web originsToken doesn't contain roles:
realm_access.roles)Inspect a JWT token:
# Decode token (without verification) echo $TOKEN | cut -d'.' -f2 | base64 -d 2>/dev/null | jq
Check Keycloak OpenID configuration:
curl http://localhost:8180/realms/solr-mcp/.well-known/openid-configuration | jq
View Keycloak logs:
docker logs -f keycloak