MCP server for monitoring homelab infrastructure status across multiple hosts via SSH, with integrations for Cloudflare, Home Assistant, Synology NAS, Uptime Kuma, and Tailscale
Strong foundation with 32 well-named, verb-prefixed tools and comprehensive descriptions (avg 180 chars). All tools have input schemas with type definitions and parameter descriptions. However, output schemas are not explicitly documented in the visible code, responses are inferred from implementation. Error handling is present but inconsistent; some tools return structured errors while others use generic error wrapping. No tool annotations (readOnlyHint, destructiveHint, idempotentHint) despite all tools being READ_ONLY. Parameter validation is implicit in SSH command allowlisting rather than explicit schema constraints (enums, ranges). Missing: pagination guidance for large result sets, per-item success/failure reporting for batch operations.
List available apt package updates on a host. Parses `apt list --upgradable` into per-package JSON: package name, new version, current version, repository, and a security flag (true if the repo string contains -security). Top-level summary breaks out total / security counts so you can scan 'how out of date is this host?' in one tool call.
List DNS records for a Cloudflare zone. Returns each record's type, name, content, TTL, and whether it's proxied through Cloudflare. Pass zone_id or zone_name (the tool resolves name → id automatically). Optionally filter by record type.
List Cloudflare Pages projects under an account. Returns each project's name, domains, production branch, and the latest deployment (id, URL, environment, build status). Account ID resolved from CLOUDFLARE_ACCOUNT_ID env var, or auto-detected as the first account the token can see.
List recent deployments for a Cloudflare Pages project. Returns each deployment's id, environment, build status, and timestamps. Useful for answering 'did the latest deploy succeed?' or 'when was the last production push?'
List active cloudflared tunnels under an account. Returns each tunnel's id, name, status, created_at, and the count of currently-active connections. Excludes deleted tunnels. Account ID resolved the same way as cloudflare_pages.
Output schemas not explicitly documented. Tool descriptions explain what is returned (e.g., 'Returns uptime, load average, memory utilization'), but formal JSON Schema output definitions are absent from visible code. LLMs cannot reliably plan downstream tool chains without knowing exact response field names and types.
No tool annotations despite all 32 tools being READ_ONLY. Missing readOnlyHint, destructiveHint, and idempotentHint annotations in tool definitions. These annotations help LLMs reason about safety and retry semantics.
Inconsistent error handling. Some tools (e.g., host_capabilities) return structured errors with actionable guidance ('Add a `- command -v` line...'), while others use generic errResult() wrapper. No consistent error classification (retryable vs. user-fixable vs. fatal) across the tool set.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 62 | 2026-07-28+ | v2 |
List Cloudflare zones (DNS sites) visible to the configured API token. Returns each zone's name, status, plan, name servers, and timestamps. Reads credentials from the CLOUDFLARE_API_TOKEN env var. Generate a scoped token at dash.cloudflare.com/profile/api-tokens with at least Zone Read permission.
Run `smartctl -H` on a block device to read SMART health status. Requires `smartmontools` on the host.
Tail recent log lines from a Docker container.
List Docker containers on a host. Returns id, names, image, status, state, ports, created_at.
List systemd units in the `failed` state on a host. Parses `systemctl list-units --state=failed --no-pager` into structured per-unit JSON: unit name, load state, active state, sub state, description. Useful for answering 'is anything broken on host X?' in one tool call instead of triaging blindly.
Fetch state-change history for one Home Assistant entity over the past N hours (default 24). Useful for answering 'when did the front door last open?' or 'how often did the AC cycle today?' Returns a list of state snapshots with their last_changed timestamps.
List current entity states from a Home Assistant instance. Returns each entity's id, state, domain, friendly name, unit of measurement, last_changed/last_updated. Optional domain filter so you can ask 'what are all my temperature sensors reading?' without sifting through binary_sensors and switches. Reads HOME_ASSISTANT_URL / HOME_ASSISTANT_TOKEN env vars.
Report which tools each configured host supports based on its allowlist. For each conditional tool (docker_ps, processes, etc.) the response indicates whether the host's allowlist permits the underlying command. Use this for onboarding ("what can I do on this host?") or to verify allowlist coverage before assuming a tool will work.
Detailed status for one host: uptime, load, memory, all disk mounts. Per-probe failures (e.g. df not installed) are recorded under the `_errors` map rather than silently dropping fields, so the caller can tell 'data missing' from 'probe failed'.
List configured hosts with their addresses and the commands each is allowed to run.
List network interfaces and their IPs on a host. Parses `ip a` output into structured per-interface JSON: index, name, state (UP/DOWN), MTU, link type, MAC address, IPv4 addresses, IPv6 addresses. Useful for answering 'what's the LAN IP of X?' or 'is interface Y up?'
Tail recent journalctl entries for a systemd unit. Useful for debugging service failures.
Fetch monitor status from an Uptime Kuma instance's public status page. Returns each monitor's name + latest heartbeat (up/down/pending/maintenance + ping time + last-checked) plus an overall up/down/total summary. Hits two unauthenticated endpoints: /api/status-page/<slug> and /api/status-page/heartbeat/<slug>. Requires the Kuma instance to have a public status page configured for the given slug.
List filesystem mounts on a host. Parses `mount` output into structured per-mount JSON: source, target, fstype, options (parsed list), plus derived flags (read_only, remote — true for nfs/cifs/sshfs/fuse). Useful for answering 'is my NAS mount writable?' or 'what fuse filesystems are mounted?'
List the routing table on a host. Parses `ip route` output into structured per-route JSON: destination CIDR, gateway (via), device, source IP, scope, metric. Useful for answering 'how does host X reach the internet?' or 'is this traffic going out the wireguard tunnel or the LAN interface?'
List TCP listening sockets on a host (ss -tlnp). Returns address, port, and the process name where available.
Top N processes on a host, sorted by CPU or memory.
Check whether a host needs a reboot to finish applying updates. Tests for /var/run/reboot-required (Debian/Ubuntu convention) and reads /var/run/reboot-required.pkgs if present to list which packages triggered the requirement. Returns required: true/false.
Check TLS certificate expiry and details for a list of hosts. Returns each host's leaf-cert expiry (days remaining), full chain summary (subject, issuer, SAN list, signature algorithm), and flags for expired / not-yet-valid / self-signed certs. Uses InsecureSkipVerify so it succeeds even on broken certs (those are exactly the findings you want). Hosts probed in parallel; per-host failures don't block the rest.
One-shot health overview across every configured host. Returns uptime, load average, memory utilization, and root-volume free space. Hosts probed in parallel; per-host failures don't block the others.
Snapshot of current CPU, memory, network, and disk I/O on a Synology DSM NAS. Returns per-call samples (call again to track trends). Reads credentials from the same SYNOLOGY_BASE_URL/USERNAME/PASSWORD env vars as synology_volumes.
Fetch volume status from a Synology DSM NAS. Returns each volume's id, status, filesystem type, RAID config, and total/used/free size in GB. Reads credentials from SYNOLOGY_BASE_URL/USERNAME/PASSWORD environment variables (set them where the MCP server runs — passing passwords through MCP inputs would log them to the conversation).
Show systemd unit status: active state, last state change, recent log tail.
List every device in your tailnet via the Tailscale API. Complements tailscale_peers (which reads `tailscale status` from a single host) by surfacing devices the MCP host can't SSH to: phones, NAS appliances, ephemeral nodes, etc. Returns per-device id, name, hostname, OS, tailnet IPs, last_seen + last_seen_minutes_ago, online flag (true when last_seen_minutes_ago < online_threshold_minutes — surfaced at the top level so callers know how it was computed and can re-threshold against the raw minutes if they need different semantics), client version, advertised + enabled subnet routes, key expiry. Reads TAILSCALE_API_TOKEN / TAILSCALE_TAILNET env vars.
List Tailscale peers visible to a host. Parses `tailscale status --json` into structured per-peer JSON: hostname, DNS name, OS, tailscale IPs, online status, last seen, transfer counts. Useful for answering 'is X reachable on the tailnet?' or 'who's online right now?'
List WireGuard interfaces and peers on a host. Parses `wg show` output into structured per-interface, per-peer JSON: endpoint, allowed-ips, last-handshake, transfer counts, persistent-keepalive interval.
Report ZFS pool health on a host. Parses `zpool status` into structured per-pool JSON: name, state (ONLINE/DEGRADED/FAULTED/etc), status/action notes, scrub state, error counts, vdev tree with READ/WRITE/CKSUM error counts. Critical for any homelab using ZFS — silent corruption is the worst-case outcome and zpool status is how you catch it.
Pagination not documented for tools returning lists. Tools like cloudflare_pages_deployments accept a 'limit' parameter (default 10, max 50), but no guidance on how to fetch subsequent pages (offset, cursor, or next_token). Large result sets risk context window exhaustion.
Parameter descriptions for docker_logs, ports, and journalctl_tail are minimal (under 80 chars). 'Tail recent log lines' and 'List TCP listening sockets' lack context on when to use each tool or what the output structure is. LLMs may conflate similar tools.