Skip to main content

Report, doctor and inventory

report, doctor, inventory_scan, inventory_export and config_validate answer what changed and what is wrong. report is classed write because it saves a snapshot.

report​

write

Compares the machine with the last saved snapshot and returns what changed, what needs attention, and what to do next. By default it also saves a new snapshot, which moves the point every later comparison starts from, so a loop that calls report leaves nothing to compare against. In read-only mode the tool is offered and always runs with no_save.

Arguments​

ArgumentTypeDescription
keepintegerNumber of snapshots to keep on disk. Older ones are deleted when a new one is saved. Default: 30.
no_savebooleantrue compares without writing a snapshot. With no earlier snapshot it returns a skipped change and creates no baseline. A server started with --max-risk read sets this to true whatever you send. Default: false.
serverstringServer from your config to run on. See Run a tool on another server.

Response​

FieldTypeMeaning
schema_versionintegerVersion of the document shape. Always 1.
timestampstringWhen the report ran, in RFC 3339 format (UTC).
server_namestringName of the server in your config, or the host name when the config has none.
is_baselinebooleantrue when there was no earlier snapshot to compare with. A baseline has no real changes.
snapshot_savedbooleantrue when this run wrote a new snapshot. false with no_save, and always false when the server runs with --max-risk read.
systemobject or null, optionalThe same fields as system_status, without schema_version. Left out when the system could not be read.
running_countinteger or nullNumber of running containers. null when Docker did not answer.
stopped_countinteger or nullNumber of stopped containers. null when Docker did not answer.
public_port_countinteger or nullNumber of ports listening on every interface. null when the port collector did not answer.
statusarray of stringsThe same facts as system, running_count and public_port_count, written as sentences for a person.
needs_attentionarray of objectsThings that are wrong now or became wrong since the last snapshot.
needs_attention.kindstringWhat the item is about: memory, disk, container or port.
needs_attention.textstringThe sentence a person reads.
needs_attention.targetstring, optionalThe mount, container or port the item is about. Left out when the item is about several.
notable_changesarray of objectsWhat moved since the last snapshot, one entry per change.
notable_changes.kindstringOne of gone, new, replaced, image, state, port, disk or skipped. See Report.
notable_changes.targetstringWhat the change happened to: a container, a port such as :8101/tcp, a process or a mount. For skipped, empty when there is no snapshot to compare with and containers when Docker did not answer.
notable_changes.detailstring, optionalWhat happened, such as recreated, a823d9a5eec8 → 75c7b65b7c79. Left out when the kind says it all.
notable_changes.textstringThe line the CLI prints.
compared_tostring, optionalTimestamp of the snapshot this run was compared with. Left out for a baseline.
suggested_actionsarray of objectsWhat to do about the findings.
suggested_actions.textstringThe suggestion as a sentence.
suggested_actions.commandstring, optionalThe command that carries it out. Left out when the suggestion has none.
suggested_actions.runnerstring, optionalWho can run command: mcp (call tool), cli (the operator runs it in a terminal) or shell (not a homebutler command). Left out when there is no command.
suggested_actions.toolstring, optionalThe MCP tool that does what command does. Present only when runner is mcp.
failed_collectorsarray of strings, optionalCollectors that did not answer: docker, ports or processes. A count is trustworthy only when its collector is not listed here. Left out when all answered.
warningsarray of strings, optionalMessages about collectors that had trouble. Left out when there are none.
Examples

Run report after something changed. A container was stopped and a port closed since the previous snapshot. The response, shortened to the entries about the example containers:

{
"schema_version": 1,
"timestamp": "2026-10-25T18:16:25Z",
"server_name": "homelab",
"is_baseline": false,
"snapshot_saved": true,
"system": {
"hostname": "homelab",
"os": "linux",
"arch": "amd64",
"uptime": "14h 58m",
"cpu": {
"usage_percent": 10.52,
"cores": 4
},
"memory": {
"total_gb": 7.75,
"used_gb": 3.2,
"usage_percent": 41.27
},
"disks": [
{
"mount": "/",
"total_gb": 19,
"used_gb": 1.9,
"usage_percent": 11
}
],
"time": "2026-10-25T11:16:24-07:00"
},
"running_count": 21,
"stopped_count": 19,
"public_port_count": 25,
"status": [
"Host: homelab (linux/amd64), uptime 14h 58m",
"CPU: 10.5% (4 cores), Memory: 3.2/7.8 GB (41%)",
"Disk /: 1.9/19.0 GB (11%)",
"Containers: 21 running, 19 stopped",
"Public ports: 25"
],
"needs_attention": [
{
"kind": "container",
"text": "19 container(s) stopped"
},
{
"kind": "container",
"target": "extra",
"text": "extra stopped since the last report"
}
],
"notable_changes": [
{
"kind": "gone",
"target": ":8103/tcp",
"detail": "was extra (node:lts-alpine)",
"text": "gone: :8103/tcp — was extra (node:lts-alpine)"
},
{
"kind": "state",
"target": "extra",
"detail": "running → exited",
"text": "state: extra — running → exited"
}
],
"compared_to": "2026-10-25T18:15:37Z",
"suggested_actions": [
{
"text": "Check why extra is not running: homebutler docker logs extra",
"command": "homebutler docker logs extra",
"runner": "mcp",
"tool": "docker_logs"
}
]
}

With no_save and no snapshot to compare with, notable_changes carries one skipped line and nothing is written. The response, with the disk entries of two internal mounts removed:

Arguments:

{
"no_save": true
}

Response:

{
"schema_version": 1,
"timestamp": "2026-10-25T18:32:00Z",
"server_name": "homelab",
"is_baseline": true,
"snapshot_saved": false,
"system": {
"hostname": "homelab",
"os": "linux",
"arch": "amd64",
"uptime": "15h 13m",
"cpu": {
"usage_percent": 0,
"cores": 4
},
"memory": {
"total_gb": 7.75,
"used_gb": 3.14,
"usage_percent": 40.59
},
"disks": [
{
"mount": "/",
"total_gb": 19,
"used_gb": 1.3,
"usage_percent": 7
}
],
"time": "2026-10-25T11:31:59-07:00"
},
"running_count": 25,
"stopped_count": 30,
"public_port_count": 31,
"status": [
"Host: homelab (linux/amd64), uptime 15h 13m",
"CPU: 0.0% (4 cores), Memory: 3.1/7.8 GB (41%)",
"Disk /: 1.3/19.0 GB (7%)",
"Containers: 25 running, 30 stopped",
"Public ports: 31"
],
"needs_attention": [
{
"kind": "container",
"text": "30 container(s) stopped"
}
],
"notable_changes": [
{
"kind": "skipped",
"target": "",
"text": "First inspection — no previous snapshot found. --no-save skipped baseline creation."
}
],
"suggested_actions": [
{
"text": "Run report again later to see changes over time."
}
]
}

A recreated container shows as replaced: {"kind": "replaced", "target": "web", "detail": "recreated, a823d9a5eec8 → 75c7b65b7c79"}.

doctor​

read

Runs a read-only diagnosis of resource pressure, stopped containers, ports open on every interface, backup age and drills, notification readiness, watch setup, config file permissions, and configured Proxmox endpoints. Each finding names the command that addresses it, and runner says whether a tool, the CLI or a shell runs it.

Arguments​

ArgumentTypeDescription
backup_max_age_hoursintegerA backup older than this many hours produces a warning. Default: 168.
serverstringServer from your config to run on. See Run a tool on another server.

Response​

FieldTypeMeaning
schema_versionintegerVersion of the document shape. Always 1.
timestampstringWhen doctor ran, in RFC 3339 format (UTC).
server_namestringName of the server in your config, or the host name.
statusstringThe worst severity among the findings: pass, warn or fail.
summaryobjectFindings counted by severity.
summary.passintegerNumber of pass findings.
summary.warnintegerNumber of warn findings.
summary.failintegerNumber of fail findings.
findingsarray of objectsOne entry per finding. A run with no problems returns one pass finding in the overall category.
findings.severitystringpass, warn or fail.
findings.categorystringOne of backup, config, docker, exposure, incomplete, notifications, overall, proxmox, report, system or watch.
findings.titlestringShort name of the finding.
findings.detailstring, optionalWhat doctor found. Left out when the title says it.
findings.actionstring, optionalWhat to do about it. Left out when nothing is needed.
findings.commandstring, optionalThe command that carries the action out. Left out when there is none.
findings.runnerstring, optionalWho can run command: mcp, cli or shell. Present whenever command is.
findings.toolstring, optionalThe MCP tool that does what command does. Present only when runner is mcp.
Examples

The response, shortened to three of the seven findings (the others name containers that are not part of this example). summary counts all seven:

{
"schema_version": 1,
"timestamp": "2026-10-25T18:35:43Z",
"server_name": "homelab",
"status": "fail",
"summary": {
"pass": 0,
"warn": 6,
"fail": 1
},
"findings": [
{
"severity": "warn",
"category": "backup",
"title": "The newest backup has never been drilled",
"detail": "The last drill was 4m0s before the newest archive was taken, so what passed is not what you would restore from.",
"action": "Drill again so the verdict is about the archive you actually have.",
"command": "homebutler backup drill --all",
"runner": "mcp",
"tool": "backup_drill"
},
{
"severity": "warn",
"category": "watch",
"title": "1 target(s) on the watch list and no service installed to check them",
"detail": "Nothing is polling them, so a restart records no incident and sends no notification. This checks whether a service is installed, not whether it is running.",
"action": "Install the watch service so monitoring survives logout and reboot.",
"command": "homebutler watch install",
"runner": "cli"
},
{
"severity": "warn",
"category": "notifications",
"title": "No notification channel configured",
"detail": "If something crashes, homebutler can only report it locally.",
"action": "Configure Telegram, Slack, Discord, or webhook notifications if you want alerts away from the terminal.",
"command": "homebutler notify test",
"runner": "mcp",
"tool": "notify_test"
}
]
}

The watch finding has runner set to cli and no tool: the operator runs homebutler watch install in a terminal.

inventory_scan​

read

Collects the server's system status, every container, and every listening port into one document. Ports that a container publishes carry the container's name and image.

Arguments​

ArgumentTypeDescription
serverstringServer from your config to run on. See Run a tool on another server.

Response​

FieldTypeMeaning
server_namestringName of the server in your config, or the host name.
hoststringAddress of the server from your config, or the host name of the machine when the config has no server entry for it.
systemobject or nullThe same fields as system_status, without schema_version. null when the system could not be read.
containersarray of objectsEvery container, running or not.
containers.idstringShort container ID.
containers.namestringContainer name.
containers.imagestringImage the container runs.
containers.statusstringStatus text from Docker, such as Running · 41s.
containers.statestringDocker state, such as running or exited.
containers.portsstringPublished ports as Docker prints them. Empty when none are published.
portsarray of objectsEvery listening port. Same shape as open_ports.
ports.protocolstringTransport protocol. tcp.
ports.addressstringAddress the socket is bound to.
ports.portstringPort number, as a string.
ports.containerstring, optionalContainer that publishes the port, as name (image). Left out when no container does.
ports.pidstring, optionalProcess ID. Left out when the process is not readable.
ports.processstring, optionalProcess name. Left out when the process is not readable.
failed_collectorsarray of strings, optionalCollectors that did not answer: docker or ports. Left out when all answered.
warningsarray of strings, optionalMessages about collectors that had trouble. Left out when there are none.
Examples

The response, shortened to the example containers and ports:

{
"server_name": "homelab",
"host": "127.0.0.1",
"system": {
"hostname": "homelab",
"os": "linux",
"arch": "amd64",
"uptime": "15h 12m",
"cpu": {
"usage_percent": 26.05,
"cores": 4
},
"memory": {
"total_gb": 7.75,
"used_gb": 3.16,
"usage_percent": 40.81
},
"disks": [
{
"mount": "/",
"total_gb": 19,
"used_gb": 1.3,
"usage_percent": 7
}
],
"time": "2026-10-25T11:30:42-07:00"
},
"containers": [
{
"id": "c7ddb62af7ed",
"name": "hook",
"image": "node:lts-alpine",
"status": "Running · Aboutam",
"state": "running",
"ports": "0.0.0.0:8105->18105/tcp, [::]:8105->18105/tcp"
},
{
"id": "b3b7786bc54a",
"name": "bind",
"image": "app:2",
"status": "Running · 2m",
"state": "running",
"ports": "3001/tcp"
},
{
"id": "4fbc5bbd2114",
"name": "vaultwarden",
"image": "app:2",
"status": "Running · 8m",
"state": "running",
"ports": "3001/tcp, 0.0.0.0:8101->80/tcp, [::]:8101->80/tcp"
},
{
"id": "005f87f08aa4",
"name": "extra",
"image": "node:lts-alpine",
"status": "Stopped · 14m ago",
"state": "exited",
"ports": ""
},
{
"id": "75c7b65b7c79",
"name": "web",
"image": "node:lts-alpine",
"status": "Running · 12m",
"state": "running",
"ports": "0.0.0.0:8102->18102/tcp, [::]:8102->18102/tcp"
}
],
"ports": [
{
"protocol": "tcp",
"address": "0.0.0.0",
"port": "8105",
"container": "hook (node:lts-alpine)"
},
{
"protocol": "tcp",
"address": "0.0.0.0",
"port": "8102",
"container": "web (node:lts-alpine)"
},
{
"protocol": "tcp",
"address": "0.0.0.0",
"port": "8101",
"container": "vaultwarden (app:2)"
},
{
"protocol": "tcp",
"address": "0.0.0.0",
"port": "22"
},
{
"protocol": "tcp",
"address": "[::]",
"port": "8105",
"container": "hook (node:lts-alpine)"
},
{
"protocol": "tcp",
"address": "[::]",
"port": "8102",
"container": "web (node:lts-alpine)"
},
{
"protocol": "tcp",
"address": "[::]",
"port": "8101",
"container": "vaultwarden (app:2)"
},
{
"protocol": "tcp",
"address": "[::]",
"port": "22"
}
]
}

inventory_export​

read

Exports the inventory as a Mermaid diagram or as JSON. mermaid works only on the local machine. With server set, only json is supported, and mermaid returns an error. With format set to json the response is the same document as inventory_scan.

Arguments​

ArgumentTypeDescription
formatstringExport format: mermaid or json. Any other value returns unsupported format: "<value>" (supported: mermaid, json). Default: mermaid.
serverstringServer from your config to run on. See Run a tool on another server.

Response​

FieldTypeMeaning
formatstringThe format that was produced, mermaid.
contentstringThe Mermaid source for a diagram: the home network, the server, its containers, and the ports that containers publish.
Examples

The two fields apply to format set to mermaid.

The response, shortened to the example containers by removing whole lines from content:

Arguments:

{
"format": "mermaid"
}

Response:

{
"format": "mermaid",
"content": "graph TD\n home[\"🏠 Home Network\"] --> homelab[\"🖥 homelab<br/>127.0.0.1\"]\n homelab --> c0[\"📦 web<br/>running\"]\n homelab --> c2[\"📦 uptime-kuma<br/>running\"]\n homelab --> p8[\"🌍 :8102/tcp<br/>web\"]\n c0 -. exposes .-> p8\n homelab --> p9[\"🌍 :8101/tcp<br/>uptime-kuma\"]\n c2 -. exposes .-> p9\n"
}

config_validate​

read

Checks the config file that this MCP server runs on and reports which file was read, which rule selected it, what was read from each section, and anything wrong or silently ignored, such as a misspelled key. The tool takes no server argument: it answers about this machine's file.

Arguments​

ArgumentTypeDescription
strictbooleantrue counts warnings as failures in passed. Default: false.

Response​

FieldTypeMeaning
passedbooleantrue when the file has no errors. With strict, warnings count as failures too.
errorsintegerNumber of findings with severity error.
warningsintegerNumber of findings with severity warning.
resultobject or nullWhat was checked.
result.pathstringThe config path that was checked.
result.sourcestringWhich rule selected the path: flag (--config), env ($HOMEBUTLER_CONFIG), xdg (~/.config/homebutler/config.yaml), cwd (./homebutler.yaml) or none (no file, built-in defaults).
result.existsbooleantrue when the file exists.
result.sectionsarray of objects, optionalWhat was read from each section of the config. Left out when no file exists.
result.sections.namestringSection name, such as servers, alerts or notify.
result.sections.presentbooleantrue when the section is in the file.
result.sections.summarystringWhat homebutler read from the section, or not set.
result.findingsarray of objects, optionalProblems and silently ignored keys. Left out when there are none.
result.findings.severitystringerror (something will not work) or warning (the file parses but probably does not do what you meant).
result.findings.fieldstring, optionalThe config field, such as servers[1].host. Left out when the finding is about the whole file.
result.findings.messagestringWhat is wrong.
result.findings.hintstring, optionalHow to fix it. Left out when there is no hint.
result.validbooleantrue when there are no errors.
Examples

For a config with a misspelled notifiy: key and a remote server with no host, the response has passed set to false. The call itself succeeds:

{
"passed": false,
"errors": 1,
"warnings": 1,
"result": {
"path": "/home/you/config-bad.yaml",
"source": "flag",
"exists": true,
"sections": [
{
"name": "servers",
"present": true,
"summary": "2 servers (homelab, rpi)"
},
{
"name": "proxmox",
"present": false,
"summary": "not set"
},
{
"name": "wake",
"present": false,
"summary": "not set"
},
{
"name": "alerts",
"present": true,
"summary": "cpu 1% · memory 1% · disk 90%"
},
{
"name": "notify",
"present": false,
"summary": "not set"
},
{
"name": "watch",
"present": false,
"summary": "enabled=false · notify_on=flapping · cooldown=5m (defaults)"
},
{
"name": "backup",
"present": false,
"summary": "/home/you/.homebutler/backups (default)"
},
{
"name": "backup_dir",
"present": false,
"summary": "/home/you/.homebutler/backups (default)"
},
{
"name": "web",
"present": false,
"summary": "no token · dashboard is read-only"
}
],
"findings": [
{
"severity": "warning",
"field": "notifiy",
"message": "Line 10: field notifiy not found in the homebutler config",
"hint": "Did you mean \"notify\"? Unrecognised keys are ignored silently, so this line currently has no effect."
},
{
"severity": "error",
"field": "servers[1].host",
"message": "Host is required for remote servers.",
"hint": "Set host, or set local: true if this entry is the machine homebutler runs on."
}
],
"valid": false
}
}