services: keycloak-mcp-db: container_name: stirling-keycloak-mcp-db image: postgres:16-alpine environment: POSTGRES_DB: keycloak POSTGRES_USER: keycloak POSTGRES_PASSWORD: keycloak healthcheck: test: ["CMD-SHELL", "pg_isready -U keycloak"] interval: 5s timeout: 5s retries: 10 networks: - stirling-mcp-test keycloak-mcp: container_name: stirling-keycloak-mcp image: quay.io/keycloak/keycloak:24.0 command: - start-dev - --import-realm environment: KC_DB: postgres KC_DB_URL: jdbc:postgresql://keycloak-mcp-db:5432/keycloak KC_DB_USERNAME: keycloak KC_DB_PASSWORD: keycloak KEYCLOAK_ADMIN: admin KEYCLOAK_ADMIN_PASSWORD: admin # Issuer hostname must resolve identically from host and Stirling container (see start-mcp-test.sh preflight). KC_HOSTNAME: "${KEYCLOAK_HOST:-kubernetes.docker.internal}" KC_HOSTNAME_PORT: 9080 KC_HOSTNAME_STRICT: "false" KC_HTTP_ENABLED: "true" ports: - "9080:8080" volumes: - ./keycloak-realm-mcp.json:/opt/keycloak/data/import/realm-export.json:ro depends_on: keycloak-mcp-db: condition: service_healthy healthcheck: test: ["CMD-SHELL", "exec 3<>/dev/tcp/localhost/8080 && echo -e 'GET /realms/stirling-mcp HTTP/1.1\\nHost: localhost\\nConnection: close\\n\\n' >&3 && timeout 2 cat <&3 | head -n 1 | grep -q '200'"] interval: 10s timeout: 10s retries: 30 start_period: 60s networks: - stirling-mcp-test stirling-pdf-mcp: container_name: stirling-pdf-mcp-test image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest build: context: ../.. dockerfile: docker/embedded/Dockerfile extra_hosts: localhost: host-gateway kubernetes.docker.internal: host-gateway healthcheck: test: ["CMD-SHELL", "curl -f http://localhost:8080$${SYSTEM_ROOTURIPATH:-''}/api/v1/info/status | grep -q 'UP'"] interval: 5s timeout: 10s retries: 30 ports: - "8080:8080" volumes: - ../../../stirling/keycloak-mcp-test/data:/usr/share/tessdata:rw - ../../../stirling/keycloak-mcp-test/config:/configs:rw - ../../../stirling/keycloak-mcp-test/logs:/logs:rw environment: # Security/login on so we have real Stirling accounts to bind to. DOCKER_ENABLE_SECURITY: "true" SECURITY_ENABLELOGIN: "true" SECURITY_LOGINMETHOD: "normal" SYSTEM_DEFAULTLOCALE: en-US SYSTEM_BACKENDURL: "http://localhost:8080" PREMIUM_KEY: "${PREMIUM_KEY:-00000000-0000-0000-0000-000000000000}" PREMIUM_ENABLED: "true" UI_APPNAME: Stirling-PDF MCP Test UI_HOMEDESCRIPTION: Keycloak MCP (OAuth resource server) Test Instance UI_APPNAMENAVBAR: Stirling-PDF MCP SYSTEM_MAXFILESIZE: "100" # Seed the Stirling account the Keycloak "email" claim binds to (proves account-binding). SECURITY_INITIALLOGIN_USERNAME: "mcpuser@stirling.local" SECURITY_INITIALLOGIN_PASSWORD: "mcppassword" # MCP server: OAuth2 resource-server mode (validates Keycloak tokens at /mcp, no web SSO). MCP_ENABLED: "true" MCP_AUTH_MODE: "${MCP_AUTH_MODE:-oauth}" # Lowered so the oversized-body 413 check stays fast; production default is 10MB. MCP_MAXREQUESTBYTES: "262144" # Must match the token "iss" exactly. MCP_AUTH_ISSUERURI: "http://${KEYCLOAK_HOST:-kubernetes.docker.internal}:9080/realms/stirling-mcp" # RFC 8707 audience - must equal the "aud" the realm's mapper injects. MCP_AUTH_RESOURCEID: "http://localhost:8080/mcp" # Bind the JWT to a Stirling user by the "email" claim. MCP_AUTH_USERNAMECLAIM: "email" MCP_AUTH_REQUIREEXISTINGACCOUNT: "true" # Resource server only - disable the web SSO client paths. SECURITY_OAUTH2_ENABLED: "false" SECURITY_SAML2_ENABLED: "false" # Debug logging LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_SECURITY_OAUTH2: DEBUG LOGGING_LEVEL_STIRLING_SOFTWARE_PROPRIETARY_MCP: DEBUG # LibreOffice settings PROCESS_EXECUTOR_AUTO_UNO_SERVER: "true" PROCESS_EXECUTOR_SESSION_LIMIT_LIBRE_OFFICE_SESSION_LIMIT: "1" # Permissions PUID: 1002 PGID: 1002 UMASK: "022" # Features DISABLE_ADDITIONAL_FEATURES: "false" METRICS_ENABLED: "true" SYSTEM_GOOGLEVISIBILITY: "false" SHOW_SURVEY: "false" depends_on: keycloak-mcp: condition: service_healthy networks: - stirling-mcp-test restart: on-failure:5 networks: stirling-mcp-test: driver: bridge