homelab-mcp
Manages Docker containers and compose stacks across multiple hosts, providing diagnostics (status, logs, events) and automated update capabilities (pull, up -d, rollback).
Fetches release notes and changelogs from GitHub Releases API for image drift analysis and risk classification.
Supports release note fetching from LinuxServer.io images for update classification.
Sends notifications for update alerts (e.g., breaking changes) with configurable priority and topic.
Compatible as an LLM endpoint for classifying update risk (SAFE/CAUTION/BREAKING) from release notes.
Compatible as an LLM endpoint for classifying update risk (SAFE/CAUTION/BREAKING) from release notes.
Acts as a remote Docker host managed via SSH, enabling container diagnostics and updates on QNAP NAS.
Stores state for pending updates, scan results, and configuration data in a local SQLite database.
Serves as the main hub running the MCP server, managing local Docker stacks and orchestrating remote hosts.
Acts as a remote Docker host managed via SSH, enabling container diagnostics and updates on Unraid servers.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@homelab-mcpshow stacks on unraid"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
homelab-mcp
MCP server for homelab diagnostics + auto-update pipeline. Runs on TrueNAS (or anywhere) as the central control plane; talks to one or more downstream Docker hosts (Unraid, QNAP, etc.) over SSH.
What it does
Read-only diagnostics (MCP tools, safe to call any time):
list_stacks_tool— every compose project + single container on a hoststack_status_tool— full state of one stack (containers, digests)recent_events_tool— docker events from the last N secondsget_logs_tool— last N log lines of a container (capped at 5000)check_nfs_shares_tool— mounted NFS exportscheck_dns_tool— name resolution against the host's resolvercheck_vpn_health_tool— gluetun container health
Image-drift visibility (MCP tools, populates
pending_updates):trigger_scan_tool— on-demand scan of one or all hostslist_pending_updates_tool— rows from the latest scan / cronpending_update_dismiss_tool— drop one row after deciding not to applydismiss_all_pending_tool(host, stack=None)— bulk-dismiss (one host or one host+stack). Useful for blanket-ignoring a non-actionable drift.
Smart update (MCP tools, runs the full pipeline for one row at a time):
apply_update_tool(host, stack, force=False)— fetch release notes → LLM classify → policy gate → snapshot → pull → up -d → healthcheck → rollback on failure. Returns{action, verdict, apply_result}. Theforce=Trueoverride flips the policy so BREAKING still gets applied (healthcheck + rollback still run).apply_all_pending_tool(host, force=False, max_rows=50)— bulk-apply every pending row on a host. Per-row isolation: a single failure doesn't stop the others. Returns per-row results + counts.get_update_history_tool(host, stack, limit=20)— past update attempts (applied, failed, rolled_back) from the SQLite state DB. Capped at 200 rows.
HTTP endpoints (for monitoring; not MCP tools):
GET /health—{status: "ok", uptime_seconds: N}GET /status— daemon info + state DB summary
Auto-update pipeline (cron-style; one cycle at a time):
Fetch release notes — image → GitHub repo heuristic (ghcr.io / quay.io / lscr.io/linuxserver → GitHub Releases API, then CHANGELOG.md fallback). 8KB cap.
Classify via LLM — OpenAI-compatible chat-completions endpoint (works with Ollama, vLLM, MiniMax M3). Returns
SAFE/CAUTION/BREAKINGplus migration steps, compose changes, env changes. Any LLM error falls back toCAUTION(better to over-apply than under-classify).Apply policy:
default
safe-and-caution— auto-apply SAFE + CAUTION, notify on BREAKINGsafe-only— auto-apply SAFE only, notify on CAUTION + BREAKING
Apply —
docker compose pull && docker compose up -dfrom the resolved stack directory. Stack-dir resolution: 4-tier (label override > compose working_dir > Dockge path > CA compose.manager). Probes container health post-restart.Auto-rollback on apply failure (pull old digest, up -d).
Notify — ntfy by default (configurable); BREAKING alerts go to the ntfy topic with high priority.
Related MCP server: homebutler
Quick start
# 1. Sync deps (the project is configured to use .venv/)
uv sync --extra dev
source .venv/bin/activate
# 2. Configure
cp .env.example .env
# ... edit .env (set HOMELAB_MCP_HOSTS, ntfy topic, LLM endpoint, etc.)
# 3. Test
pytest
# 4. Run the MCP server (SSE on :18790)
python -m homelab_mcp
# 5. Run one cycle of the auto-apply pipeline (dry-run)
python -m homelab_mcp.auto_apply_main --dry-runIf
uv syncputs the venv in avenv/directory instead of.venv/, setUV_PROJECT_ENVIRONMENT=.venvonce or useuv venv .venv && uv sync --extra devto force the location.
Install on Unraid
# On Unraid:
bash scripts/install-on-unraid.sh
# (pulls the GHCR image, starts the daemon, registers a 6h cron)Install on TrueNAS (the "main hub" config)
# On TrueNAS, in a Dockge stack directory:
git clone https://github.com/<owner>/homelab-mcp.git
cd homelab-mcp
cp scripts/dockge-stack/.env.example .env
# ... edit .env (set HOMELAB_MCP_HOSTS=["truenas","unraid"],
# HOMELAB_MCP_LOCAL_HOST_ALIAS=truenas, etc.)
docker compose -f scripts/dockge-stack/compose.yaml up -d
bash scripts/deploy-on-truenas.sh
# (runs 5 in-the-field smoke tests)Configuration
All configuration is via environment variables. See .env.example.
Env var | Default | Notes |
|
| JSON list of host aliases |
|
| MCP SSE transport port |
|
| sqlite + last_scan.txt |
|
| for RemoteSSH |
|
| visibility cron on/off |
|
| seconds between scans (6h) |
|
| notifier base |
|
| required for alerts |
|
| default priority |
|
| Discord webhook (empty = disabled) |
|
| webhook display name |
|
| Pushover app token (empty = disabled) |
|
| Pushover user key |
|
| target device by name (optional) |
|
| alert sound |
|
| OpenAI-compatible |
|
| Bearer token (optional) |
|
| model name (required) |
|
| seconds |
|
| or |
|
| set to |
|
| Dockge stacks path |
Architecture
+------------+
| TrueNAS | <-- MCP daemon here (LocalDocker)
| (main hub) |
+-----+------+
|
SSH | (RemoteSSH for unraid + qnap)
|
+--------------+--------------+
| |
+----+-----+ +------+----+
| Unraid | | QNAP |
| (117 ct) | | (60+ ct) |
+----------+ +-----------+LocalDocker backend: the daemon host's docker socket (
unix:///var/run/docker.sock).RemoteSSH backend: SSH config alias →
ssh <alias> docker ps. Each host alias inHOMELAB_MCP_HOSTSmust have a correspondingHostblock inHOMELAB_MCP_SSH_CONFIG.Stack-dir resolution (in order):
auto-update.stack-dirlabel on the container (override)com.docker.compose.project.working_dir(the standard compose label)If
com.dockge.ownerorauto-update.dockge=trueis set →<HOMELAB_MCP_DOCKGE_STACKS_ROOT>/<project>Otherwise →
<compose_manager_root>/<project>(CA compose.manager style;compose_manager_rootis auto-detected for the local host)
Tests
uv run pytest -qCurrently 183 tests across:
config (env-var loading, validation)
state (SQLite CRUD, idempotent writes, busy_timeout)
hosts (LocalDocker + RemoteSSH, structural conformance to HostClient)
updater.registry (image-ref parsing, registry result kinds)
updater.scanner (drift detection, transient errors, digests)
updater.pipeline (snapshot, apply+probe, rollback, run_pipeline)
updater.release_notes (image→GitHub, GH Releases API, CHANGELOG fallback, truncation)
updater.risk (SAFE/CAUTION/BREAKING classification, error fallbacks)
updater.notifier (ntfy POST, multi-fan-out, console)
updater.notifier_backends (Discord + Pushover: body building, network errors, MultiNotifier dispatch, error isolation)
updater.auto_apply (policy decisions, classifier-fail CAUTION fallback)
auto_apply_main (cron arg parsing, per-row exception isolation, summary)
tools.apply_update (smart-update pipeline: no-pending, BREAKING-notify, SAFE-apply, force-override)
tools.apply_all_pending (bulk apply with per-row isolation, max_rows cap)
tools.get_update_history (limit clamping, state forwarding)
tools.dismiss_all_pending (host-wide + stack-scoped dismiss, per-row error isolation)
server (FastMCP singleton, tool registration, build_hosts wiring)
Live SSH tests (opt-in)
There is a separate test file at tests/test_live_ssh.py that
exercises the RemoteSSH backend against a real homelab host. These
are skipped by default (the sandbox has no LAN egress). To run
them on a host that has SSH access to your homelab:
HOMELAB_MCP_LIVE=1 \
HOMELAB_MCP_LIVE_HOST_NAME=unraid \
HOMELAB_MCP_LIVE_HOST_HOSTNAME=192.168.1.104 \
HOMELAB_MCP_LIVE_HOST_USER=root \
HOMELAB_MCP_SSH_CONFIG=/root/.ssh/config \
pytest tests/test_live_ssh.py -vThe tests cover 9 real-host behaviors:
list_containersreturns real datalist_stacksshape is rightinspect_containerreturns validdocker inspectJSONrun_commandexit code, stdout, stderr are captured correctlyrun_commandtimeout cancels the commandThe configured user can run
docker(no permission errors)A
compose_pullagainst a real directory returns successfully
Full configuration recipe + troubleshooting is in
tests/live/README.md.
Notifiers
The auto-apply pipeline supports three backends, all sharing the
same Notifier protocol. Configure any combination — the orchestrator
dispatches to all of them.
ntfy (default)
Set HOMELAB_MCP_NTFY_TOPIC and you get free-form notifications on
every BREAKING alert. Topic-based, no account required, supports
priorities and tags.
Discord
Set HOMELAB_MCP_DISCORD_WEBHOOK_URL to get color-coded embeds in a
Discord channel. BREAKING alerts are red, CAUTION orange, SAFE green.
To create a webhook: Discord server → channel settings → Integrations
→ Webhooks → New Webhook → copy URL.
Pushover
Set both HOMELAB_MCP_PUSHOVER_APP_TOKEN (create at
https://pushover.net/apps/build) and HOMELAB_MCP_PUSHOVER_USER_KEY
to get push notifications on your phone. Sounds and per-device
targeting are configurable.
Adding a new notifier
Implement the Notifier protocol in homelab_mcp/updater/:
class Notifier(Protocol):
async def notify(
self, text: str, *,
title: str = "",
tags: list[str] | None = None,
priority: str | None = None,
click: str | None = None,
) -> None: ...Then add it to _build_notifier in homelab_mcp/auto_apply_main.py.
Tests should follow the pattern in
tests/test_updater_notifier_backends.py: pure _build_body +
network _post separated.
Security
The MCP server runs in SSE mode and is bound to
0.0.0.0:18790by default. Put a reverse proxy in front of it (npmplus, Traefik, Caddy) with OIDC or HTTP basic auth — don't expose 18790 directly.ntfy topics should be unique and unguessable. Add a token in the URL (
HOMELAB_MCP_NTFY_URL=https://ntfy.sh/?token=...) for private topics.LLM API keys never appear in logs or state. The state DB at
HOMELAB_MCP_STATE_DIR/state.dbis not encrypted — host-level ACLs apply.The
auto-updatemachinery touches docker. Run as a dedicated user with docker-socket access; do not run as root if you can avoid it.
License
MIT.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/compactly8274/homelab-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server