Skip to content

feat(compose): provision Keycloak with the realm the http profile needs - #192

Open
adityamparikh wants to merge 3 commits into
apache:mainfrom
adityamparikh:feat/keycloak-compose
Open

adityamparikh wants to merge 3 commits into
apache:mainfrom
adityamparikh:feat/keycloak-compose

Conversation

@adityamparikh

@adityamparikh adityamparikh commented Aug 31, 2026 •

Copy link
Copy Markdown
Contributor

Problem

The http profile authenticates against Keycloak, but nothing started one. A developer had to run the container by hand and then script a realm, a client and an audience mapper before the server would boot. The mapper is easy to miss and fails quietly: Keycloak does not honour the RFC 8707 resource= parameter, so without it a token is issued normally and validateAudienceClaim(true) answers 401.

Change

compose.yaml gains a keycloak service that imports keycloak/solr-mcp-realm.json at startup, so the realm, both clients and the mapper exist before the server asks for a token:

Client / user Purpose
solr-mcp-service Confidential, service accounts enabled: machine-to-machine callers
solr-mcp-client Public, redirect URIs for MCP Inspector
testuser / testpassword The password-grant examples in keycloak.md

Both clients carry the audience mapper for http://localhost:8080/mcp. The credentials are development credentials, committed on purpose; a real deployment provisions its own.

The service sits behind the http compose profile, which application-http.properties activates through spring.docker.compose.profiles.active, so a plain docker compose up -d for STDIO users does not pull Keycloak or wait on its healthcheck. Start it by hand with docker compose --profile http up -d.

The healthcheck is load-bearing. The server resolves the issuer while building its JWT decoder and fails to boot if the realm is not answering, so Spring Boot's compose support must wait for the container to be healthy. Keycloak's image ships neither curl nor wget, so the probe uses bash's /dev/tcp against the management port.

docs/security/keycloak.md Quick Start points at the imported realm and tells compose users to reuse solr-mcp-service instead of creating a client by hand. The manual walkthrough below it binds its own container to the same port, 8180, so anyone following that path should start the server with SPRING_DOCKER_COMPOSE_ENABLED=false rather than bringing up a second Keycloak.

Verification

Against the running container: the realm's OpenID configuration resolves, a client_credentials token on solr-mcp-service and a password grant on solr-mcp-client both carry aud: http://localhost:8080/mcp, and ./gradlew build passes. Not covered: an end-to-end call against a running MCP server.

🤖 Generated with Claude Code

Comment thread compose.yaml Outdated
Comment on lines +89 to +90
healthcheck:
test: [ "CMD-SHELL", "exec 3<>/dev/tcp/localhost/9000 && echo -e 'GET /health/ready HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n' >&3 && cat <&3 | grep -q '\"status\": \"UP\"'" ]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adityamparikh can you jsut investigate this? It may be we don't have curl or wget available in the image? so we do this very interesting thing?

@epugh epugh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check one thing on the keyclaok htealh check!

Comment thread compose.yaml Outdated
Comment on lines +89 to +90
healthcheck:
test: [ "CMD-SHELL", "exec 3<>/dev/tcp/localhost/9000 && echo -e 'GET /health/ready HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n' >&3 && cat <&3 | grep -q '\"status\": \"UP\"'" ]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adityamparikh and others added 3 commits September 24, 2026 10:44
The http profile authenticates against Keycloak, but nothing started one. A
developer had to run the container by hand and then script a realm, a client
and an audience mapper before the server would boot — and the mapper is easy to
miss, because skipping it produces a token that is issued normally and then
refused with 401.

compose.yaml now defines a keycloak service that imports
keycloak/solr-mcp-realm.json, so the realm, both clients and the mapper exist
before the server asks for a token. The import covers what the Quick Start
created by hand: solr-mcp-service (confidential, service accounts) for
machine-to-machine callers, solr-mcp-client (public) for MCP Inspector, and
testuser. The credentials in it are development credentials, committed on
purpose; a real deployment provisions its own.

The healthcheck is load-bearing rather than decoration. The server resolves the
issuer while building its JWT decoder and fails to boot if the realm is not yet
answering, which is the ordering constraint keycloak.md warns about; declaring a
healthcheck makes Spring Boot's compose support wait for the container instead.
Keycloak's image ships neither curl nor wget, so the probe goes through bash's
/dev/tcp against the management port.

Verified by running the service from this compose file: the container reaches
healthy, the realm resolves, and both grants return tokens carrying
aud http://localhost:8080/mcp — client_credentials for the service client and
the password grant for the imported test user.

Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011nUD34DFfoJeyQRTquPy7a
The service comment said it was started only by the http profile, but it
had no profiles key, so a plain docker compose up -d for STDIO users pulled
Keycloak, bound 8180 and waited on its healthcheck. The service now sits in
the http compose profile, which application-http.properties activates via
spring.docker.compose.profiles.active. The Quick Start no longer says
bootRun alone is enough while also requiring OAUTH2_ISSUER_URI, and it tells
compose users to reuse solr-mcp-service instead of creating a client by hand.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CiUHyyXLTo9ATdgg8eRFZJ
Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
…ommendation

The Keycloak container image (UBI 9 Minimal) intentionally ships without
curl or wget, and health endpoints are exposed on the dedicated management
port 9000. Switch the healthcheck probe to Keycloak's official Containerfile
HEALTHCHECK pattern (https://www.keycloak.org/observability/health#_healthcheck)
using bash socket redirection.

Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
Co-authored-by: Junie <junie@jetbrains.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants