# MCP Hangar + OpenTelemetry Collector + Prometheus
#
# What is delivered, and how:
#   traces      Hangar -> OTLP gRPC -> Collector (debug + file exporters)
#   audit logs  Hangar -> OTLP gRPC -> Collector (debug + file exporters)
#   metrics     NOT sent over OTLP; Prometheus scrapes Hangar's /metrics
#
# Usage:
#   docker compose up
#   # optional: tag this run's telemetry (service.instance.id)
#   HANGAR_RUN_ID=my-run docker compose up
#
# Ports:
#   4317 - OTEL Collector OTLP gRPC receiver
#   9090 - Prometheus UI
#   8080 - Hangar HTTP API and /metrics

services:
  mcp-hangar:
    image: ghcr.io/mcp-hangar/mcp-hangar:latest
    ports:
      - "8080:8080"
    # The image's default CMD is `serve --http --host 0.0.0.0 --port 8080`, and
    # this example configures no authentication -- so the gateway refuses to
    # start and the container exits 1:
    #
    #   Refusing to start HTTP on non-loopback without authentication.
    #   Use --unsafe-no-auth to override.
    #
    # That refusal is correct and stays: binding a wildcard address with no
    # authentication is the mistake worth failing closed on. It is also why this
    # compose file had never once started the gateway -- the flag was simply
    # missing, and `examples/**` has no CI to notice.
    #
    # Overridden here because this is a local first-run example on a published
    # loopback port. Do NOT copy this line into anything reachable by others;
    # see `examples/auth-keycloak/` for the shape with authentication on.
    command:
      - serve
      - --http
      - --host
      - "0.0.0.0"
      - --port
      - "8080"
      - --unsafe-no-auth
    environment:
      # Without this the mounted config below is never read.
      MCP_CONFIG: /app/config.yaml
      MCP_MODE: http
      MCP_HTTP_PORT: "8080"
      # `http://` on purpose: it selects plaintext gRPC for traces. Without a
      # scheme the trace exporter would try TLS against a plaintext Collector.
      # An explicit endpoint also turns on OTLP audit log export;
      # MCP_AUDIT_EXPORT_ENABLED: "false" keeps that off and traces on.
      OTEL_EXPORTER_OTLP_ENDPOINT: http://otel-collector:4317
      OTEL_SERVICE_NAME: mcp-hangar
      # Lands on the resource of both spans and audit records, so one run's
      # telemetry can be picked out of the Collector's output.
      OTEL_RESOURCE_ATTRIBUTES: service.instance.id=${HANGAR_RUN_ID:-local}
      MCP_TRACING_ENABLED: "true"
      MCP_LOG_LEVEL: INFO
    volumes:
      - ./config.yaml:/app/config.yaml:ro
      # The image ships the wheel, not the repository, so the `math` server in
      # config.yaml is mounted where `python -m examples.provider_math.server`
      # finds it.
      - ../provider_math:/app/examples/provider_math:ro
    depends_on:
      - otel-collector
    healthcheck:
      # `python3`, not `curl`: the image is `python:3.14-slim` and ships neither
      # curl nor wget, so every `["CMD", "curl", ...]` healthcheck in this repo
      # reported the container unhealthy forever, whatever path it asked for.
      # `/health/ready` and not `/health`: the gateway serves `/health/live`,
      # `/health/ready` and `/health/startup`; a bare `/health` is a 404.
      test:
        - CMD
        - python3
        - -c
        - |
          import sys, urllib.request
          sys.exit(0 if urllib.request.urlopen("http://localhost:8080/health/ready", timeout=3).status == 200 else 1)
      interval: 10s
      timeout: 5s
      retries: 3

  otel-collector:
    image: otel/opentelemetry-collector-contrib:0.96.0
    ports:
      - "4317:4317"   # OTLP gRPC receiver
      - "4318:4318"   # OTLP HTTP receiver
      - "8888:8888"   # Collector self-metrics
    volumes:
      - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml:ro
      - otel-output:/otel-output
    command: ["--config=/etc/otel-collector-config.yaml"]

  prometheus:
    image: prom/prometheus:v2.50.0
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
    command:
      - "--config.file=/etc/prometheus/prometheus.yml"
      - "--storage.tsdb.path=/prometheus"

volumes:
  # The file exporter's output. The Collector image runs as uid 10001 and has
  # no writable directory of its own, so the volume is a tmpfs owned by that
  # uid. Read it with:
  #   docker compose cp otel-collector:/otel-output/telemetry.jsonl .
  otel-output:
    driver_opts:
      type: tmpfs
      device: tmpfs
      o: "uid=10001,size=64m"
