LinuxaiTested on real hardware

A Read-Only Homelab MCP Server (FastMCP)

A minimal, read-only MCP server that lets Claude or any LLM query Proxmox and Prometheus safely — read tools only, read-only credentials, no way to change anything.

DistrosDebian 13, Ubuntu 24.04
Shellbash
Updated
Script
bash
# A read-only homelab MCP server: read tools + read-only creds only.
# Full walkthrough: /articles/read-only-homelab-mcp-server

# 1. Isolated Python env with FastMCP.
python3 -m venv /opt/homelab-mcp/venv
/opt/homelab-mcp/venv/bin/pip install fastmcp requests

# 2. The server. Read-only by design: only query tools, no write/delete/create.
#    Point PROM_URL at your Prometheus; keep any token in a secret store + env.
cat > /opt/homelab-mcp/server.py <<'PY'
import os, requests
from fastmcp import FastMCP

PROM_URL = os.environ.get("PROM_URL", "http://10.0.0.104:9090")
mcp = FastMCP("homelab-readonly")

@mcp.tool
def prometheus_query(promql: str) -> dict:
    """Run a read-only Prometheus instant query."""
    r = requests.get(f"{PROM_URL}/api/v1/query",
                     params={"query": promql}, timeout=10)
    r.raise_for_status()
    data = r.json()["data"]["result"]
    return {"query": promql, "series": len(data),
            "sample": [{"metric": s["metric"], "value": s["value"][1]}
                       for s in data[:5]]}

@mcp.tool
def cluster_targets_up() -> dict:
    """How many scrape targets are up vs down (read-only)."""
    r = requests.get(f"{PROM_URL}/api/v1/query",
                     params={"query": "up"}, timeout=10)
    r.raise_for_status()
    res = r.json()["data"]["result"]
    up = sum(1 for s in res if s["value"][1] == "1")
    return {"targets_total": len(res), "targets_up": up,
            "targets_down": len(res) - up}

if __name__ == "__main__":
    mcp.run()
PY

# 3. Smoke-test over the real MCP protocol (in-memory client) BEFORE wiring a chat client.
cat > /opt/homelab-mcp/test_client.py <<'PY'
import asyncio
from fastmcp import Client
from server import mcp
async def main():
    async with Client(mcp) as c:
        print([t.name for t in await c.list_tools()])
        print((await c.call_tool("cluster_targets_up", {})).data)
asyncio.run(main())
PY
cd /opt/homelab-mcp && PROM_URL="http://10.0.0.104:9090" venv/bin/python test_client.py

# 4. Register it in your MCP client (Claude Desktop / Claude Code):
#    "mcpServers": { "homelab-readonly": {
#        "command": "/opt/homelab-mcp/venv/bin/python",
#        "args": ["/opt/homelab-mcp/server.py"],
#        "env": { "PROM_URL": "http://10.0.0.104:9090" } } }

What this does

This builds a read-only MCP server with FastMCP so an AI client (Claude Desktop, Claude Code, or any MCP client) can query your homelab — Prometheus metrics and Proxmox status — and cannot change anything. The safety is structural: only query tools are registered, and the credentials it uses can only read.

Full reasoning, the security layers, and a self-hosted variant are in Build a Read-Only Homelab MCP Server for Your LLM.

Prerequisites

  • Python 3.10+ on a host that can reach your services (a small LXC is ideal).
  • A Prometheus endpoint (and, if you add Proxmox tools, a token bound to the read-only PVEAuditor role).
  • An MCP client to connect it to.

Notes

  • Make these values your own before you rely on the result: replace 10.0.0.104 with your Prometheus host, and store any Proxmox/API token in a secret store, injected via an env var — never in server.py. If a value looks specific to one machine, it’s a placeholder to change, not a literal to copy.
  • The tool surface is the security boundary. Register only read tools. There is no reboot, delete, or create here on purpose — an AI cannot call a tool that does not exist.
  • The credential is the real lock. Use a PVEAuditor token for Proxmox (read-only by role) and a query-only user for any database. Even a mistaken write tool can’t act with a read-only credential.
  • Bound the inputs. Expose instant Prometheus queries, not arbitrary admin endpoints; validate/whitelist where it matters.
  • Keep it private. Run it over stdio locally or reach it over Tailscale; never expose an infrastructure tool server to the public internet.
  • Smoke-test over the protocol first. FastMCP’s in-memory Client lets you list and call tools without a chat client — verify real data comes back before you wire it into anything.
  • Composes with a local model: point a local model via Olla at this server for a fully self-hosted “ask your homelab” loop.