Skip to main content
Glama
wtnb75
by wtnb75
README.md
# rrdmcp

An MCP server that exposes Munin's RRD metric data to an LLM, so you can ask
questions about your monitored hosts in plain language instead of writing
`rrdtool` incantations or one-off scripts. Supports both `stdio` and
`streamable-http` transports.

## What it's for

Munin collects rich time-series data (CPU, memory, disk, network, custom
plugins...) but analyzing it usually means digging through graph images or
writing throwaway scripts against the RRD files. `rrdmcp` exposes that data
directly to an LLM through a handful of tools, so you can just ask:

- "Which days this month had the highest CPU usage?"
- "Free memory has been dropping — is that a trend or a one-off?"
- "fail2ban's ban count spiked a few days ago — does that line up with
  higher process counts or TCP resets on this host?"
- "Is disk usage on this host trending toward full, and how soon?"

The server deliberately stays "dumb" about interpretation: it discovers
hosts/plugins/fields, fetches raw or lightly-aggregated series (bucketed
averages, whole-range summaries, top-N rankings), and renders graphs — but
leaves judgment calls (what counts as "high", whether two metrics are
actually related, what to do about it) to the LLM doing the analysis. This
keeps the tool surface small and lets the LLM reason over real numbers
instead of a pre-baked interpretation.

## Setup

```bash
uv sync
```

Requires the `rrdtool` command on `PATH` (already present on any host
running Munin, since it's a dependency of `munin-node`/`munin`).

## Configuration (environment variables)

| Variable | Default | Description |
|---|---|---|
| `MUNIN_RRD_BASE_PATH` | `/var/lib/munin` | Root directory of the RRD files |
| `MUNIN_DATAFILE_PATH` | `${MUNIN_RRD_BASE_PATH}/datafile` | Location of Munin's `datafile` (config cache) |
| `RRDMCP_TRANSPORT` | `stdio` | Transport to serve: `stdio` or `streamable-http` |
| `RRDMCP_HOST` | `127.0.0.1` | Bind host for `streamable-http` |
| `RRDMCP_PORT` | `8000` | Bind port for `streamable-http` |
| `SAR_BASE_PATH` | (unset — sar support disabled) | Root directory of sar logs, laid out as `<group>/<host>/saXX` (requires the `sadf` command on `PATH`, from the `sysstat` package) |
| `WTMP_BASE_PATH` | (unset — wtmp/btmp support disabled) | Root directory of wtmp/btmp logs, laid out as `<group>/<host>/{wtmp,btmp}` (rotated generations like `wtmp.1`/`wtmp.2.gz` are also read; requires the `utmpdump` command on `PATH`, from the `util-linux` package) |

## Running

```bash
uv run rrdmcp
```

Transport can also be selected with CLI flags, which take precedence over
the environment variables above:

```bash
uv run rrdmcp --transport streamable-http --host 0.0.0.0 --port 8000
```

## MCP client configuration example

```json
{
  "mcpServers": {
    "rrdmcp": {
      "command": "uv",
      "args": ["run", "--directory", "/path/to/rrdmcp", "rrdmcp"],
      "env": {
        "MUNIN_RRD_BASE_PATH": "/var/lib/munin"
      }
    }
  }
}
```

## Running in Docker

```bash
docker build -t rrdmcp .
```

Since this is a stdio transport, mount the directory holding the real
Munin data read-only and keep stdin open with `-i`:

```bash
docker run --rm -i -v /var/lib/munin:/var/lib/munin:ro rrdmcp
```

MCP client configuration example (Docker):

```json
{
  "mcpServers": {
    "rrdmcp": {
      "command": "docker",
      "args": [
        "run", "--rm", "-i",
        "-v", "/var/lib/munin:/var/lib/munin:ro",
        "rrdmcp"
      ]
    }
  }
}
```

If you mount `MUNIN_RRD_BASE_PATH` at a different path, add
`-e MUNIN_RRD_BASE_PATH=...` to `docker run` (the image default is
`/var/lib/munin`).

## Tools

- `list_hosts` — list every discovered `(group, host)` pair
- `list_plugins(group, host)` — list plugins for a host
- `list_fields(group, host, plugin)` — list a plugin's fields (label, type, thresholds, etc.)
- `get_metadata(group, host, plugin, field?)` — detailed metadata for a whole plugin, or a single field
- `fetch_series(group, host, plugin, field, start, end, resolution?, summary?, top_n?, top_by?, order?)` — fetch time series data. `start`/`end` accept a unix timestamp, an ISO 8601 timestamp (e.g. `2026-09-07T12:00:00Z`; naive timestamps are treated as UTC), or any string `rrdtool` understands (`-1d`, `now`, etc.). Fields backed by the sar data source only accept a unix timestamp or an ISO 8601 timestamp for `start`/`end` (rrdtool-style relative expressions like `-1d` are munin-only).
  - With no options, returns raw `points` as-is
  - `resolution` (seconds) aggregates into UTC-epoch-aligned `buckets` (avg/min/max/count) instead of raw points
  - `summary=true` aggregates the whole range into a single `summary` (avg/min/max/count); cannot be combined with `resolution`
  - `top_n` (requires `resolution`) returns only the top N buckets sorted by `top_by` ("avg"/"min"/"max", default "avg") in `order` ("desc"/"asc", default "desc"); `total_buckets` reports the count before filtering, so you can ask things like "the 10 days with the highest average" or "the 5 days with the lowest minimum" directly
- `render_graph(group, host, plugin, fields, start, end, width?, height?)` — render a PNG graph overlaying the given fields
- `list_login_sources()` — list every discovered `(group, host, kind)` triple from wtmp/btmp logs (`kind` is `"wtmp"` or `"btmp"`)
- `list_login_events(group, host, kind, start, end, limit?)` — fetch raw login-event records for one host (no session pairing or duration computed), sorted ascending by timestamp. `start`/`end` accept a unix timestamp or an ISO 8601 timestamp only. If `limit` is given, only the most recent `limit` events are returned; `total_events` reports the count before truncation

## Known limitations

- If `datafile` is unavailable, discovery falls back to best-effort parsing of RRD filenames, losing precision on host/plugin boundaries and all metadata
- Graph rendering is a simplified version — it doesn't reproduce Munin's own threshold bands, stacking, CDEFs, etc.
- sar support requires log files pre-aggregated under `SAR_BASE_PATH/<group>/<host>/saXX` (e.g. via `rsync` from each host's `/var/log/sa`); it does not read `/var/log/sa` directly or collect data itself
- sar plugin/field discovery only recognizes activities matching a fixed set of `sadf -j` JSON shapes (scalar dicts, or arrays keyed by one of `cpu`/`disk-device`/`iface`/`filesystem`/`number`); activities matching a recognized shape but missing from `sar_index.SAR_ACTIVITY_META`/`SAR_FIELD_META` show up without a human-friendly title/label, while activities with an unrecognized shape are not discovered at all
- wtmp/btmp support requires log files pre-aggregated under `WTMP_BASE_PATH/<group>/<host>/{wtmp,btmp}` (e.g. via `rsync`) and the `utmpdump` command on `PATH`; it returns raw per-record events only — no login/logout session pairing, duration computation, or `wtmpdb` (SQLite-based) support

TDQS

A3.7/5.0

Scored across 6 tools

Disambiguation5/5

Each tool targets a distinct level or aspect of the data hierarchy: hosts, plugins, fields, metadata, series data, and rendered graphs. There is no meaningful overlap between tools, and descriptions make the boundaries clear.

Naming Consistency5/5

All tool names follow the same snake_case verb_noun pattern, with list_* for enumeration, get_/fetch_ for retrieval, and render_ for graph generation. The naming is predictable and consistent across the whole set.

Tool Count5/5

With 6 tools, the set is well-scoped for a read-only RRD/Munin data access server. Each tool covers an essential operation without unnecessary redundancy or bloat.

Completeness5/5

The tool set covers the full exploration and retrieval workflow: discover hosts, list plugins, list fields, fetch metadata, fetch time series data, and render graphs. There are no significant dead ends or missing operations for the apparent purpose of reading monitoring data.

Maintenance

ActivityMaintained
ResponsivenessNo issues