devops-infra MCP Server
Production-grade MCP (Model Context Protocol) server in Quarkus - safely expose your observability stack to AI assistants.
Discovered via github-topic:model-context-protocol and last synced 3mo ago.
Install instructions not detected yet
Check the source repository for the latest setup steps.
`namespace`, `name`
Choice
Docker + minimal K8s manifests
OK
Inputs
JUnit + Quarkus Test + Testcontainers
Quarkus 3.x
`namespace`, `pod`, `lines` (≤500)
GitHub Actions
Java 21
JSON-RPC over Streamable HTTP
**On the cap:** `query_prometheus` rejects a result with more than the configured `prometheus.tool.max-series` (default 1000) rather than truncating. Silent truncation lets an AI confidently act on partial data; an explicit error tells it to narrow the query with stricter label matchers or an aggregation. `query_prometheus_range` applies the same reject-don't-truncate rule to *total samples* — series × points across the window — via `prometheus.tool.range.max-samples` (default 11000), since a range payload grows with both series count and step resolution. ## Authentication Every call to `/mcp` requires a bearer token: ``` Authorization: Bearer mcp_<random> ``` Tokens are random 32-byte secrets prefixed with `mcp_` (GitHub-style, so secret scanners can spot them in leaked diffs). The server only stores the **SHA-256 hash** of the token — the raw value never lives on disk. A token's `principal` becomes the `caller` recorded in every audit row, so a leaked or misused key is traceable to a single identity. `/q/health`, `/q/metrics`, `/q/openapi`, and `/q/swagger-ui` are left open for ops tooling. **Dev mode** (`mvn quarkus:dev`) auto-seeds a known key on first startup and prints the token to the log so the demo path works out-of-the-box. **Prod operators** insert keys via SQL — no auto-seed, no admin endpoint: ```sql INSERT INTO api_keys (key_hash, principal, label, created_at, revoked) VALUES (encode(sha256('mcp_…'::bytea), 'hex'), 'ci-runner', 'github-actions', now(), false); ``` ## Rate limiting Every authenticated client gets a **token bucket** keyed by its API-key `principal`. `burst` is the bucket size (how many requests a client can fire back-to-back); `requests-per-minute` is the sustained refill rate. When a client drains its bucket, `/mcp` returns **HTTP 429** with a `Retry-After` header and a structured error body — *before any tool runs* — and increments `mcp_ratelimit_rejected_total`, so throttling shows up on the server's own Prometheus scrape. ``` mcp.rate-limit.enabled=true mcp.rate-limit.requests-per-minute=60 mcp.rate-limit.burst=20 ``` The buckets are in-memory, which is the right call for a single self-hosted server — no extra dependency on the hot path. Running multiple replicas would move the counters to a shared store (e.g. Redis) so the limit holds cluster-wide rather than per-instance: a deliberate v1 trade-off, not an oversight. ## Live demo The compose stack ships a Prometheus that scrapes **both itself and this server** (`monitoring/prometheus.yml`), so `query_prometheus` has real data to hit out of the box. End to end: ```bash docker compose up -d # Postgres + Prometheus mvn quarkus:dev # seeds a known dev API key and prints it # ... Seeded dev API key. Authenticate with: Authorization: Bearer mcp_dev_local_do_not_use_in_prod ``` **Auth is enforced** — no token, no entry: ```console $ curl -s -o /dev/null -w '%{http_code}\n' -X POST localhost:8080/mcp \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' 401 ``` **Call the tool over MCP** (`tools/call`). Prometheus is scraping itself *and* this server, so `up` comes back with both targets healthy: ```console $ curl -s -X POST localhost:8080/mcp \ -H 'Authorization: Bearer mcp_dev_local_do_not_use_in_prod' \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":2,"method":"tools/call", "params":{"name":"query_prometheus","arguments":{"promql":"up"}}}' { "jsonrpc": "2.0", "id": 2, "result": { "content": [{ "type": "text", "text": "{ \"resultType\": \"vector\", \"result\": [ { \"metric\": {\"__name__\":\"up\",\"job\":\"quarkus-mcp\",\"instance\":\"host.docker.internal:8080\"}, \"value\": [t, \"1\"] }, { \"metric\": {\"__name__\":\"up\",\"job\":\"prometheus\",\"instance\":\"localhost:9090\"}, \"value\": [t, \"1\"] } ] }" }], "isError": false } } ``` **Every call is audited.** That one invocation wrote a row attributed to the token's principal: ```console $ docker exec mcp-postgres psql -U mcp -d mcp \ -c "SELECT caller, tool, status, latency_ms, result_size, args FROM audit_log ORDER BY id DESC LIMIT 1;" caller
latency_ms
{"promql": "up"} ``` `caller` is the `principal` bound to the bearer key — not a guess — so a misused key traces to one identity. `latency_ms` and `result_size` ride along on every audit row, giving per-call cost/latency visibility for free. ## Container image Every push to `main` publishes a JVM image to the GitHub Container Registry (the `Publish image` workflow). It's a multi-stage build — Maven → `eclipse-temurin:21-jre`, running as a non-root user. ```bash docker pull ghcr.io/toansh/quarkus-mcp-observability:latest ``` The server needs a Postgres (and, for `query_prometheus`, a reachable Prometheus). Point it at them with env vars — config keys map to `UPPER_SNAKE_CASE`: ```bash docker run --rm -p 8080:8080 \ -e QUARKUS_DATASOURCE_JDBC_URL=jdbc:postgresql://host.docker.internal:5432/mcp \ -e QUARKUS_DATASOURCE_USERNAME=mcp -e QUARKUS_DATASOURCE_PASSWORD=mcp \ -e PROMETHEUS_URL=http://host.docker.internal:9090 \ ghcr.io/toansh/quarkus-mcp-observability:latest ``` The image runs Quarkus's `prod` profile, so there is **no** dev-seeded key — insert one with the SQL in [Authentication](#authentication) before calling `/mcp`. Tags: `latest` and `sha-<short>` per `main` build, plus `vX.Y.Z` on release tags. ## Stack
Why
Micrometer → Prometheus