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
Shell
bashUpdated
Script
# 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.104with your Prometheus host, and store any Proxmox/API token in a secret store, injected via an env var — never inserver.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, orcreatehere 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
Clientlets 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.