Operations
Deployment artifact
A single Docker image. Released builds are published to Docker Hub at docker.io/miragon/miragon-ai-server — pull a tagged version (or :latest) and run it:
docker run --rm -p 8400:8400 \
-e CAMUNDA_BASE_URL=... \
-e PROMETHEUS_URL=... \
docker.io/miragon/miragon-ai-server:latestReleases are cut by release-please: merging the Release PR tags v<version>, and after manual approval publish-to-docker.yml builds the root Dockerfile and pushes :<version> and :latest. docker build -t miragon-ai-server . builds it locally (all dependencies are public — no registry credential). The image's HEALTHCHECK polls /health/ready (see Observability).
playground/docker/docker-compose.yml is the fully wired local demo (--profile full adds the server); playground/README.md covers deploying the same stack to Fly.io (deploy-playground.yml, manual).
Security
By default the MCP endpoint is unauthenticated — any client that reaches port 8400 gets full tool access. Protect it with an authenticating reverse proxy, or set MCP_OAUTH to make the server an OAuth resource server: bearer tokens on /mcp are validated against your IdP (Keycloak or Auth0), unauthenticated requests get 401, and the .well-known discovery metadata is served. Token audience is validated against the server's canonical MCP URL (RFC 8707) — the IdP must issue tokens whose aud includes it (e.g. a Keycloak audience mapper; the playground realm seeds one as the mcp-resource client scope). Set MCP_URL so advertised URLs are right.
The 1.x providers oidc and oidc-proxy are gone with mcp-use 2 (no generic JWKS verifier, no OAuth proxy broker) — both fail the boot with an actionable error. For an IdP without Dynamic Client Registration, front the server with an OAuth-terminating gateway.
CAMUNDA_AUTH_TYPE=passthrough forwards each caller's bearer token to the engine per request (never to Prometheus). With MCP_OAUTH the server validates the token and the engine enforces the caller's permissions — which needs an engine with REST auth enabled; a default engine accepts anonymous requests and ignores the token.
With auth active, user profiles and saved dashboards scope to the authenticated user (sub) and never expire. Without auth there is no caller identity — mcp-use 2 issues no MCP session ids — so settings reads return defaults and saves, incl. the default engine, refuse (boot warning).
To try it locally, docker compose --profile auth up -d adds a Keycloak on localhost:8480 (realm miragon, user demo/demo; the matching MCP_OAUTH block is commented out in .env.example). The Fly playground runs unauthenticated.
Environment variables
| Variable | Default | Notes |
|---|---|---|
PORT | 8400 | HTTP port the MCP server listens on |
MCP_URL | — | Public base URL the server advertises (resource URIs, OAuth callbacks) |
MCP_OAUTH | — | JSON OAuth resource-server config; providers keycloak, auth0 (oidc/oidc-proxy were removed with mcp-use 2 and fail the boot) — full field lists in .env.example |
MCP_ACTIVE_MODULES | all | Comma-separated module or module:toolset entries; e.g. camunda7:read-only,analytics |
DATABASE_URL | — | Postgres for saved dashboards + user profiles (both stores; migrations run at boot). Beats MCP_*_DIR; the Compose stack ships an instance on host port 8440 |
MCP_DASHBOARD_DIR / MCP_PROFILE_DIR | in-memory | Directories persisting saved dashboards / user profiles across restarts — the file-based alternative when no DATABASE_URL is set |
MCP_PROFILE_SESSION_TTL_DAYS | 30 | Expiry for session-keyed profiles (gateway-stamped Mcp-Session-Id or 1.x leftovers), checked at boot + daily. User-bound profiles and the shared stdio anonymous record never expire. 0 disables |
REDIS_URL | — | Ignored since mcp-use 2 (the pluggable session-backend seam was removed upstream; the server warns at boot). Sessions are instance-local — scale out only with sticky routing |
CAMUNDA_ENGINES_FILE | — | Path to a JSON file with the engine list [{id, baseUrl, cockpitUrl?, environment?, flavor?, auth?}, ...] or the environment map {"<environment>": [engines…]}; highest precedence |
CAMUNDA_ENGINES_JSON | — | Same engine JSON inline; ignored when CAMUNDA_ENGINES_FILE is set. environment (field or map key) groups engines for the two-stage environment → engine pickers; flavor (cibseven | operaton | camunda7, default cibseven) selects the vendor's cockpit-link routes — mixed fleets are fine |
CAMUNDA_BASE_URL | http://localhost:8410/engine-rest | Legacy single-engine REST endpoint (registered as id CAMUNDA_ENGINE_ID); ignored when CAMUNDA_ENGINES_* is set. The server warns at boot when no engine source is set at all — the silent fallback works against the Compose engine but breaks the engine_id join |
CAMUNDA_ENGINE_ID | default | Engine id for the CAMUNDA_BASE_URL shorthand. Must match the engine container's ENGINE_ID (= the engine_id metric label) or every engine-scoped analytics query — BPMN heatmap, engine compare — comes back empty |
CAMUNDA_COCKPIT_URL | derived | Used for jump-out links to Cockpit; multi-engine setups use per-engine cockpitUrl instead |
CAMUNDA_AUTH_TYPE | none | basic, bearer, passthrough, or none — fallback for engines without a per-engine auth |
CAMUNDA_USERNAME / CAMUNDA_PASSWORD | — | Required for basic (enforced at boot) |
CAMUNDA_TOKEN | — | Required for bearer (enforced at boot) |
CAMUNDA_INCIDENT_ISSUE_REPO | — | Default owner/repo for the GitHub-issue tool |
CAMUNDA_HEALTH_CRITICAL_* | 50 / 25 | …_INCIDENTS / …_CLUSTER_SIZE — thresholds for the engine-health critical verdict |
PROMETHEUS_URL | http://localhost:9090 | Prometheus HTTP API — the analytics data source (the repo's Compose stack publishes :8460; the server warns at boot when unset) |
Unknown CAMUNDA_*/MCP_* variables are reported at boot (typos aren't silently ignored); mcp-use telemetry is off by default (MCP_USE_ANONYMIZED_TELEMETRY=true opts in). The engine container takes METRICS_ENABLED, ENGINE_ID (must match CAMUNDA_ENGINE_ID or the id in CAMUNDA_ENGINES_*, or that engine's analytics come back empty — the heatmap then says so instead of rendering an uncolored diagram), plus OTEL_EXPORTER_OTLP_ENDPOINT/OTEL_SERVICE_NAME for the OTLP push.
Module activation
Disable a module by listing only the ones you want, e.g. MCP_ACTIVE_MODULES=camunda7. The camunda7 module also takes a toolset suffix to narrow the tool surface: camunda7:read-only (queries + engine discovery), camunda7:operations (adds start/complete/claim/variables/retries/messages), camunda7:admin (everything, incl. delete/modify/suspension, deployments, migrations). No suffix exposes all tools; unknown toolsets warn and degrade to read-only.
Observability
Three operational routes sit next to /mcp, outside the OAuth gate (which is scoped to the MCP path) — probes and scrapers need no token:
| Route | Purpose |
|---|---|
/health/live | Liveness — 200 as soon as the process serves HTTP |
/health/ready | Readiness — 200 once the server's own dependencies respond (the Postgres store under DATABASE_URL), else 503 naming the failing check. Never probes engines or Prometheus: their outages surface as tool errors, not as an unroutable server. /health aliases it (Docker HEALTHCHECK, Compose, Fly) |
/metrics | Prometheus text — mcp_tool_calls_total{tool,outcome}, mcp_tool_call_duration_seconds{tool}, mcp_http_requests_total{method,route,status}, mcp_http_request_duration_seconds{method,route} plus the standard process_*/nodejs_* collectors. Labels are bounded by construction (tool catalogue, known routes) — never users, sessions or arguments |
HTTP transport logs structured JSON to stdout. The server expects Camunda 7 / CIB Seven and Prometheus; Grafana is optional (:8470, playground/docker/grafana/).
Engine metrics take the push path: the Kotlin plugin (engine-plugins/cibseven-history-metrics) records history-event metrics (no sampling) into Micrometer's global registry inside the CIB Seven runtime — any Micrometer export works: OTLP push to the OTEL Collector (micrometer-registry-otlp, the playground default), an Actuator Prometheus scrape, or the OTEL agent's Micrometer bridge. Prometheus scrapes the Collector; the analytics module queries it over PromQL — event-driven counters/histograms (throughput, durations) plus point-in-time gauges (running WIP, open incidents, job/task backlog). Per-instance drill-down is not metric-backed — use the camunda7_query_historic_* tools. Alert rules ship in playground/docker/prometheus/alerts.yml (wire an Alertmanager under alerting: to route them); the analytics_engine_health tool surfaces the same gauges + firing alerts in one call.
CI/CD
.github/workflows/ci.yml runs parallel jobs on every push — TypeScript (build, test, lint, format), Kotlin engine plugins, and the CIB Seven example. All npm dependencies are public, so no registry credential is involved. This docs site deploys to Netlify (root netlify.toml; docs-only pnpm install).