mia
Allows managing Cloudflare DNS records, including listing and changing DNS records.
Provides tools for inspecting Docker on hosts, including container status and service discovery.
Provides tools for managing Gitea runners, including listing, registering, and removing runners.
Provides tools for interacting with Jellyfin media server, including checking status, adding library paths, creating libraries, scanning libraries, and running tasks.
Provides tools for managing Tailscale tailnet devices, including listing devices, removing devices, and joining hosts to the tailnet.
Click on "Deploy 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., "@mia check the status of all my servers"
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.
mia-assist
An MCP server and CLI (mia) that runs a fleet of Linux servers from your own machine over the
system ssh and Tailscale — "a small Ansible you can talk to". From Claude Code (or straight from the
terminal) you see the state of every host, what changed on it since last time, and you run routine
operations: always as a dry run first, then an explicit apply of exactly that plan, one host at a time,
with a journal.
It was built for a fleet of proxy nodes running Remnawave / Xray, but most of it is generic: any Ubuntu/Debian host with docker, systemd, UFW and fail2ban, plus home servers (compose/systemd services, Jellyfin and Tdarr through their APIs). The Remnawave panel itself is only read here (to match hosts to panel nodes and show who is connected); panel changes belong to a separate MCP server (mcp-remnawave), which the runbooks can drive step by step.
Runtime | Node.js 22+, the system |
Hosts | Ubuntu 22.04 / 24.04 (the audit baseline assumes nodes built with vps-edge-run.sh; override it in |
Tools | 63 MCP tools (28 read-only) — full list in docs/TOOLS.md |
Prompts | 8 MCP prompts ( |
Runbooks |
|
UI | MCP (English tool descriptions), terminal console and a local web page (Russian UI text) |
Safety model
This is the point of the project. Nothing changes a host unless you saw the exact plan and said yes.
Two-step plan → apply. Every tool that changes something, called without
apply, returns the exact plan per host (commands, packages, files, current state) and aplanHash. Only the same call withapply: trueand thatplanHashruns it. The hash covers the inputs and a fingerprint of the live state: if the host changed between plan and apply, the apply is refused and a fresh plan is shown. AplanHashis single-use; two concurrent applies of one plan cannot both run.Rolling, one host at a time. Disruptive actions (restarts, reboots, upgrades, firewall) default to
batch: 1: each host must come back (ssh answers, the service runs, the panel node is connected) before the next one starts; the first failure stops the rollout. Cheap actions (host_exec,disk_cleanup,cert_renew, geo/blacklist updates) default to 10 at a time; passbatch: 1to go slower.Blockers and warnings. Plans carry blockers (the host is unreachable, a busy runner, a non-apt system…) and warnings (users online on a node you are about to restart). Irreversible actions in the console must be confirmed by typing the host count or name, not by Enter.
Journal. Every apply is written to a local journal (
journal_list) with the plan, the result per host and the time; snapshots (fleet_snapshot,fleet_changes) keep a history of what changed on each host, including changes made by hand outside mia.Read-only mode.
MIA_READONLY=truemakes every write tool refuse to apply. Start with it.Secrets stay local. ssh keys, API keys (
~/.config/mia/secrets.env, mode 600,op://references allowed) and the journal stay on your machine. Every known secret value and common secret shapes (tokens, WireGuard/PEM keys, subscription links, shadow hashes) are masked in all output. Remote scripts are sent over stdin; inputs go through quoted env vars, never interpolated into code.No automatic reboots. Upgrades report which hosts want a reboot; rebooting is a separate, explicit action.
Web UI binds to 127.0.0.1 only, with a per-run token cookie, a Host check and a custom header required on anything that starts work.
Related MCP server: Flotilla
Install
git clone https://github.com/akadorkin/mia-assist.git && cd mia-assist
npm install && npm run build
mkdir -p ~/.config/mia && cp fleet.example.yaml ~/.config/mia/fleet.yaml # then edit it
node dist/cli.js --demo # a made-up fleet of 7 hosts: nothing leaves this machine
MIA_READONLY=true node dist/cli.js statusnpm link gives you a plain mia command. Optional API keys are stored once with
mia secrets set TAILSCALE_API_KEY (values are never echoed); see .env.example for
every setting.
Plug into Claude Code
claude mcp add mia -s user \
-e MIA_INVENTORY=$HOME/.config/mia/fleet.yaml \
-e MIA_READONLY=true \
-e REMNAWAVE_BASE_URL=https://panel.example.com \
-e REMNAWAVE_API_TOKEN=<token> \
-- node /path/to/mia-assist/dist/cli.js mcpThen ask: "what's wrong with the fleet?", "why did nl-1 drop out of the panel?", "update remnanode on
all nodes one by one", "what changed on the servers this week?". Remove MIA_READONLY once the status
and audit tell the truth about your hosts.
Inventory example
~/.config/mia/fleet.yaml (full annotated version: fleet.example.yaml; the real
file is git-ignored):
defaults:
user: ops # ssh user with NOPASSWD sudo
policy: # audit baseline, global or per host
publicPorts: ["80/tcp", "443/tcp", "443/udp"]
certWarnDays: 21
groups:
eu: [nl-1, de-1]
hands-off: [nas] # fleet_update never touches these
hosts:
nl-1:
address: nl-1 # Tailscale MagicDNS name, tailnet IP or ~/.ssh/config alias
roles: [remnanode] # edge baseline + remnanode/certbot/geo checks
domain: nl1.example.com
tags: [prod]
bootstrap: { address: 203.0.113.10, user: root } # used before Tailscale exists
panel:
address: panel
roles: [panel]
services:
- name: remnawave
compose: /opt/remnawave/docker-compose.yml
home:
address: home
roles: [docker]
apps:
jellyfin: { url: http://127.0.0.1:8096, apiKey: env:JELLYFIN_API_KEY, container: jellyfin }Targets everywhere: all | host | glob | group | role:remnanode | tag:x, comma-separated, !x excludes.
fleet_discover cross-checks the inventory with the tailnet and the panel and prints ready YAML for
missing hosts; host_services_discover does the same for services on one host.
Tools
Generated by npm run docs:tools into docs/TOOLS.md. By area (RO = read-only):
Area | Tools |
Fleet |
|
One host |
|
History |
|
Actions |
|
Scheduling |
|
Your scripts |
|
Network |
|
Integrations |
|
Home media |
|
Runbooks |
|
Runbooks
A runbook is an ordered, resumable list of steps (src/core/scenario.ts): check (read-only, may
poll), mia (a write tool: dry run → planHash → apply), panel (an mcp-remnawave tool, same two-step
gate), manual (something only you can do, answered with "done"), inventory (a fleet.yaml edit that
keeps layout and comments) and local (git/files in your checkouts, shown before they run). Checks run
by themselves; every change waits for an explicit apply. Runs are saved and can be continued later. A
failed gate check cannot be skipped past: only its rollback step runs.
Included:
fleet_update— security (or all) updates host by host, canary first; after each host: back over the tailnet,dpkg --auditclean, panel node connected; never a reboot, never thehands-offgroup.xray_rules— add/remove domains or IPs in a routing rule, change an outbound, insert a rule, one config profile at a time through mcp-remnawave; each profile's nodes must reconnect before the next.
The original private version also had runbooks for adding a node end to end, moving an ingress to a new
machine, removing a node and DR failover. They were tied to one specific fleet (its repos, monitoring
and load balancer layout) and were not included. The helpers they used (dnsStep, ruGateStep,
tailnetOf, gitCommit in src/scenarios/common.ts) are here for writing your own.
Configuration
Environment (or ~/.config/mia/secrets.env, managed by mia secrets):
Variable | Purpose |
| path of fleet.yaml (default |
|
|
| journal, snapshots, oversized outputs (default |
| ssh behaviour |
| panel, read-only use |
| command that starts mcp-remnawave for panel steps of runbooks |
| local checkouts for |
|
|
| optional integrations |
Also in the box
mia— an interactive terminal console (menus, search, plan → Enter) andmia web— the same as a local page on 127.0.0.1 (mia web --demoto try it). Both are in Russian.mia sessions-digest— reads your local Claude Code transcripts (~/.claude/projects, secrets masked) and writes a per-session digest plus a summary of hosts, IPs, domains and containers seen, to bootstrap an inventory. Local only.
Development
npm run build # tsc --noEmit + tsup
npm test # node:test via tsx (3 tests are skipped on macOS: they need GNU coreutils)
npm run docs:tools # regenerate docs/TOOLS.md after changing a toolThe lockfile is not committed; npm install resolves it.
Honest notes
It is a personal ops tool published as is. The defaults and the audit baseline (MGMT-PORTS iptables chain, ipset blacklists, beszel-agent, unattended security updates, no auto-reboots) reflect how the original fleet was built; adjust
policyfor yours.node_bootstrapandtuning_applyrun public install scripts pinned by URL (MIA_EDGE_SCRIPT_URL,MIA_TUNING_SCRIPT_URL); pin them to a commit you have reviewed.Remote commands run as root via sudo on your hosts. Read the plan.
License
MIT, see LICENSE.
Кратко по-русски
mia — MCP-сервер и консоль для управления флотом Linux-серверов по ssh/Tailscale (ноды
Remnawave/Xray, панель, домашние серверы). Всё, что меняет сервер, работает в два шага: холостой
прогон с точным планом и planHash, затем apply именно этого плана; опасные действия — по одному
хосту с проверкой, что он вернулся; всё пишется в журнал, снимки показывают ручные изменения. Начинать
стоит с MIA_READONLY=true. Консоль и веб-страница на русском, описания MCP-инструментов на английском.
Ранбуки, завязанные на конкретный флот (новая нода, перенос ингресса, вывод ноды, DR), в публичную
версию не вошли.
Available Tools
63 toolsapt_upgradeA
Install pending apt updates on hosts (noninteractive, keeps existing config files). Shows what will be upgraded; warns when docker/containerd are in the list (remnanode restarts). Optional reboot afterwards when the kernel asks for it. Rolling, batch 1 by default.
Inputs: apply, batch, target, planHash, +7 more.
beszel_system_editA
Rename or delete a Beszel card, found by exact name or host (tailnet name / IP). Delete only after the agent on that machine is stopped, or it recreates the card.
Inputs: find (required), apply, remove, rename, +1 more.
beszel_systemsC
Systems (cards) in the Beszel hub: name, host, status — to find duplicates a new agent created and cards of removed nodes.
Inputs: filter.
blacklist_updateB
Refresh the ipset blacklists now with the fleet script /usr/local/bin/ufw-blacklist.sh (normally run by /etc/cron.d/ufw-blacklist every 2 h) and show the entry counts of the sets afterwards.
Inputs: apply, batch, target, planHash, +2 more.
cert_renewA
Renew the remnanode TLS certificate with the docker certbot from vps-edge-run.sh (cd /opt/certbot && docker compose run --rm certbot renew). Skips certificates with more than minDays left unless force. Port 80 must be publicly open (it is in the baseline). Optionally restarts remnanode so xray loads the new certificate.
Inputs: apply, batch, force, target, +5 more.
cron_loggingA
Make every cron job write its output to a log and rotate those logs. A line that already appends to a file keeps it and the file joins rotation unless another logrotate config covers it; output to /dev/null or nowhere → >> /var/log/cron/.log 2>&1; a pipe into logger stays in syslog. Rotation: /etc/logrotate.d/cron-jobs (weekly, 8 copies, compress, copytruncate). Every line also runs through /usr/local/sbin/mia-cronrun, which records start, end and exit code so fleet_schedule shows a result for cron jobs (the command itself is unchanged). Stock OS cron files are left alone; copies of touched files go to /root/mia-backup; a logrotate error after the change rolls the rotation file back. Roll out a few hosts at a time and look at fleet_schedule in between.
Inputs: apply, batch, target, planHash, +2 more.
disk_cleanupA
Free disk space on hosts: apt cache, journald vacuum to a size, unused docker images and build cache (never volumes), rotated logs. The dry run shows what takes space now.
Inputs: apply, batch, target, planHash, +4 more.
dns_changeA
Change A records in Cloudflare with a dry run: set (name → IP, created or updated; one A record per name), move (every A record on one IP to another — an ingress switch), remove (records by name, or every A record on an IP). The plan shows each record before and after; after apply every record is read back.
Inputs: set, move, apply, remove, +1 more.
dns_recordsB
DNS records in Cloudflare (CLOUDFLARE_API_TOKEN): by name, or every record that points at an IP across all zones — e.g. everything still on an old ingress address.
Inputs: ip, name, type.
fail2ban_unbanA
Unban an IP in fail2ban (all jails, or one) on hosts — e.g. after locking yourself out or a false positive.
Inputs: ip (required), jail, apply, target, +1 more.
fleet_auditC
Audit hosts against the fleet baseline (as the nodes run now: vps-edge-run.sh bootstrap + ufw-blacklist.sh ipsets, MGMT-PORTS chain, beszel-agent, security updates automatic with manual reboots) and general health: firewall (ufw off, management ports public, other public ports listed), Tailscale, sshd hardening, fail2ban jails, node_exporter, beszel-agent, MGMT-PORTS, ipset blacklists, auto-updates, tuning (bbr/fq, conntrack), remnanode container/compose/SECRET_KEY, certificate expiry, geo updater, disks, memory, temperatures, pending security updates, reboot, failed units, NTP, inventory services, panel node state. Findings have stable codes (policy.skip in fleet.yaml mutes them).
Inputs: code, target, minSeverity.
fleet_changesA
What changed on hosts: diff of recorded snapshots (latest vs previous, or vs the first one after since), or of the latest snapshot vs the live host (live:true, catches changes made by hand since). Next to the diff: actions this server ran on those hosts in the same window (journal), so changes made outside mia stand out.
Inputs: live, since, target, section.
fleet_discoverA
Cross-check the inventory with the tailnet (local tailscale status) and the Remnawave panel: nodes/peers missing from fleet.yaml (with ready YAML), inventory hosts that are offline or unknown to both, panel nodes and their state.
Inputs: no named parameters.
fleet_host_editA
Edit a host in fleet.yaml (this machine's inventory, not the server): add, change fields, move between groups, switch off/on (disabled), remove. The dry run shows the diff of fleet.yaml (layout and comments kept) and refuses a result mia could not load; apply writes it with a backup in the state directory and refuses if the file changed since the plan.
Inputs: op (required), name (required), port, sudo, +13 more.
fleet_hostsA
The inventory (fleet.yaml): hosts with address, roles, tags, groups, services. No host is contacted. Start here to learn the names used by every other tool.
Inputs: target, details.
fleet_scheduleA
Everything that runs on a schedule, Cronicle-style: systemd timers and cron jobs on every host — schedule, last run (when, result, duration), next run, failures first. cron does not log results, so for cron only the last start (journal/syslog) is known. Stock OS jobs (apt-daily, fstrim, logrotate …) are hidden unless all=true or they fail.
Inputs: all, target, failedOnly.
fleet_snapshotA
Record the configuration state of hosts (packages, enabled units, containers/images, ufw rules, listening ports, sshd, users/sudo/authorized keys, crontabs, sysctl, certificates, tracked config files …) to the local state dir, and report what changed since the previous snapshot. Run it on a schedule (e.g. daily cron mia call fleet_snapshot) to keep a change history.
Inputs: reason, target.
fleet_statusA
One-screen health of the fleet: every host probed over ssh in parallel — uptime, load, memory, disk, pending updates/reboot, remnanode container, certificate days, Tailscale, panel state and users online — plus the codes of open issues (details: fleet_audit). With target all it also lists every tailnet device that is not in fleet.yaml: Linux machines are probed with the default ssh user, others shown with their online state.
Inputs: target, tailnet.
geo_updateB
Run the roscomvpn geoip/geosite updater now (update-roscomvpn-geo.service from vps-edge-run.sh) and check it succeeded.
Inputs: apply, batch, target, planHash, +2 more.
gitea_runner_registerA
Register the Gitea Actions runner on a node with a runner bootstrap script from your own repo checkout (MIA_REPOS; default ops:deploy/bootstrap-node-runner.sh ): a fresh registration token comes from the Gitea API at run time and reaches the script through an environment variable, never the plan or journal. Verified afterwards: act_runner active and the label is exactly node-:host (copied configs announced other nodes' labels before).
Inputs: host (required), name, repo, apply, +2 more.
gitea_runner_removeA
Delete a runner registration in Gitea (a node taken out of service). Dry run shows the runner; a busy runner is refused.
Inputs: name (required), apply, planHash.
gitea_runnersB
Gitea Actions runners (GITEA_URL + GITEA_TOKEN): name, online/offline, busy, labels — to spot stale registrations and runners that announce another node's label.
Inputs: filter.
host_connectionsA
Established TCP connections per host on a local port (default 443), total and by remote address family — to watch traffic move from an old ingress to a new one, or drain before removing a node.
Inputs: port, target.
host_copyA
Copy files or directories from one host to others through this machine (certificates, /opt/remnanode/docker-compose.yml with the node key, /etc/log-upload/secrets.env, haproxy.cfg …): what the owner did by hand when moving an ingress or cloning a node. Contents are never shown: the plan lists size and sha256 on the source and on each target. Existing target paths are archived to /root/mia-backup-.tgz first. For one env-style file, set changes KEY=value lines on the way (e.g. LOG_NODE_NAME).
Inputs: from (required), paths (required), target (required), set, +6 more.
host_dockerC
Docker on a host: containers (state, image, restarts), compose projects, disk usage of images/volumes/build cache.
Inputs: host (required).
host_execA
Run an arbitrary shell command on hosts (bash, through sudo when configured). For anything the dedicated tools do not cover. Output tail per host; the journal keeps the command. Parallel by default (batch 10).
Inputs: command (required), apply, batch, asUser, +5 more.
host_file_editA
Edit one of the fleet's registry files on a host the way it is done by hand: clone the entry of a template node for a new node (Prometheus overrides, log-collector inventory, reboot-reminder lists, IP lists) or remove the entries of a removed node (e.g. node_servers.keep.txt). Works on plain lines (an IP inside a one-line list is edited in place), JSON and YAML (comments kept). The dry run shows the diff; the file is backed up (.bak-mia-), written in place (owner and mode kept), optionally a unit is reloaded.
Inputs: op (required), host (required), path (required), match (required), +5 more.
host_file_readA
Read a file on a host (as root when sudo is configured) or list a directory. Secrets (SECRET_KEY, private keys, tokens, passwords) are masked.
Inputs: host (required), path (required), maxBytes.
host_firewallB
Firewall detail of a host: ufw status numbered, fail2ban jails with the banned IP lists, ipset blacklists (ufw-blacklist.sh) with entry counts, and the MGMT-PORTS iptables chain.
Inputs: host (required).
host_inspectB
Everything about one host in one call: system, pending updates, systemd units, listening sockets, firewall, Tailscale, docker, remnanode (compose, certificate, geo files), inventory services, tuning — plus audit findings and the matching panel node.
Inputs: host (required), sections.
host_logsA
Tail logs on a host. source: a service name (container stdout / systemd journal of the unit — e.g. remnanode, fail2ban, tailscaled), a log file name (remnanode.log = xray logs in /var/log/remnanode, auth, syslog, apt, install-*), or explicit unit:, container:, file:, journal (whole journal).
Inputs: host (required), source (required), grep, lines, +1 more.
host_net_checkA
Network checks run FROM hosts (in parallel): tcp connect, tls (TLS1.3 + h2 — what a REALITY dest needs), http timings, dns, ping, trace. Use to pick/verify a REALITY dest, check a node reaches the panel, or see if a destination is filtered from a location.
Inputs: to (required), check (required), sni, target.
host_portsB
What is reachable on a host and from where: every listening socket classified as local / tailnet-only / public-allowed / blocked by ufw / PUBLIC because ufw is off, with the process name. Plus the public UFW allows.
Inputs: host (required).
host_rebootA
Reboot hosts (shutdown -r +delay) and wait for each to come back over ssh with remnanode running before the next one (rolling, batch 1). The dry run shows users online per node (panel) and whether a reboot is pending anyway.
Inputs: wait, apply, batch, target, +4 more.
host_services_discoverA
What runs on a host that the inventory does not know yet: docker compose projects (with their files), standalone containers, non-stock systemd services, and known apps with an API (Jellyfin, Tdarr) — returned as ready services: / apps: YAML for fleet.yaml. Read-only; use it to describe a home server once.
Inputs: host (required).
jellyfin_library_add_pathA
Add a folder to an existing Jellyfin library. The folder may be given as a host path (translated through the Jellyfin container mounts) or as the path inside the container; it must exist on both sides. Optionally scans the library afterwards.
Inputs: host (required), path (required), library (required), scan, +2 more.
jellyfin_library_createC
Create a Jellyfin library with folders. Library options (metadata language, savers, fetchers …) can be copied from an existing library of your choice; otherwise Jellyfin defaults apply.
Inputs: host (required), name (required), type (required), paths (required), +4 more.
jellyfin_scanC
Scan Jellyfin for new/changed files: one library (metadata/images for new items only) or all libraries.
Inputs: host (required), apply, library, planHash.
jellyfin_statusA
Jellyfin on a host: version and pending restart, libraries (type, folders — with their host paths — and scan state), who is watching what right now (direct play vs transcode), item counts, scheduled tasks that failed last time or are running.
Inputs: host (required).
jellyfin_task_runC
Start a Jellyfin scheduled task by name or key (e.g. "Scan Media Library", "Extract Chapter Images", "Clean Cache", RefreshLibrary).
Inputs: host (required), task (required), apply, planHash.
job_statusA
State of a detached remote job (node_bootstrap): running or finished with exit code, the log tail, a Tailscale login URL if the node waits for authorization. Tries the host over Tailscale (inventory address) first, then the public bootstrap address. Without jobId: lists known jobs.
Inputs: jobId, lines.
journal_listB
What this server did to hosts: every action with time, host, outcome and planHash (newest first).
Inputs: host, limit, since, contains.
node_bootstrapA
Turn a fresh Ubuntu server into a node with vps-edge-run.sh (github.com/akadorkin/remnanode-install-script): user + sudo, Tailscale (--ssh, exit node), Docker, hardening (ssh, fail2ban, UFW tailscale-only), tuning, node_exporter, geo updater, certbot certificate for the domain, remnanode with the panel's SECRET_KEY. That is only the start of the current baseline: afterwards ops-scripts fleet-node-seal.sh (ipset blocklists, MGMT-PORTS, node_exporter, beszel-agent, log-upload) — the node_add runbook (scenario_start) runs the whole sequence; fleet_audit shows what is missing. Runs detached on the server over its public IP (the firewall reset cuts public ssh); poll with job_status. The server reboots 3 min after success. Needs the domain's A record pointing at the server.
Inputs: host (required), user, apply, domain, +8 more.
remnanode_updateA
Update remnanode (remnawave/node image) on nodes: compares the running image with Docker Hub, then per node docker compose pull && up -d, waits for the container and (if the panel is configured) for the panel to see the node connected before moving on. Rolling, batch 1 by default; stops at the first node that does not come back.
Inputs: apply, batch, force, prune, +4 more.
repo_scriptA
Run one of your own fleet scripts from a local repo checkout (MIA_REPOS, e.g. ops=~/src/ops-scripts) on hosts: deploy/migrate-blacklist.sh, lock-mgmt-ports-apply.sh, install-beszel.sh … The script (plus files listed in with, same relative paths) is sent over ssh, run as root from a temp dir, then removed. Exactly the checked-out version runs: the plan shows the commit, uncommitted changes and a hash of the files. Secrets go by env var name (envFrom): the value is read from mia's own environment and never shown.
Inputs: script (required), target (required), env, args, +11 more.
ru_probeA
Is an address reachable from Russia (or any censored network): a TLS handshake from a vantage host through EVERY local ISP interface (MIA_PROBE_FROM, MIA_PROBE_IFACES) with the address forced (--resolve, not DNS), plus a control from a datacenter host (MIA_PROBE_CONTROL). Verdict per target: ok | partial (one ISP) | blocked-ru (both ISPs fail, datacenter passes = DPI (TSPU) blocks address:port — roll the DNS back first) | down. Read-only, one handshake per pair; the gate before and after any address switch (ingress, new node).
Inputs: to (required), sni, from, ifaces, +1 more.
scenario_listB
Runbooks for whole operations (add a node and bring it to the fleet standard, switch the ingress, remove a node, change xray rules): their parameters, and the runs started so far. With run: the step log of that run.
Inputs: run.
scenario_startA
Start a runbook. Read-only checks run on their own; the first step that changes something comes back with its exact plan (dry run) and waits. Show that plan to the owner and continue with scenario_step only after the owner said yes to that step.
Inputs: scenario (required), params.
scenario_stepA
Answer the step a run is waiting on: apply (run the plan shown — only with the owner's explicit yes for this step), done (a manual step the owner has finished), skip, or retry (re-check / re-plan). Returns what happened and the next step that needs an answer. Runs are saved: resume any time with decision retry.
Inputs: run (required), decision (required), params.
schedule_toggleA
Switch a scheduled job off / on, or run a timer now (the «Расписание» buttons): a systemd timer — disable --now / enable --now (+ reset-failed of its service), run = start its service; a cron job — the exact line in /etc/crontab, /etc/cron.d/* or a user crontab is commented out with «#mia-off » (enable removes the marker; a copy of the file goes to /root/mia-backup first). Reversible.
Inputs: host (required), action (required), unit, apply, +4 more.
service_actionA
restart / start / stop / update (compose pull + up -d) / recreate a service on hosts — a compose project, systemd unit or container from the inventory (remnanode, certbot, remnawave, tailscaled, fail2ban, node_exporter, docker … or your own). Rolling by default (batch 1 for disruptive actions), verified: the service must be up again.
Inputs: action (required), service (required), apply, batch, +4 more.
snapshots_listC
Recorded snapshots of a host (newest first), or the hosts that have any.
Inputs: host.
tailnet_device_removeA
Remove a machine from the tailnet (Tailscale API, needs TAILSCALE_API_KEY): the last step of taking a server out of service. The dry run shows the device, when it was last seen and whether it is still in fleet.yaml; an online device or one still in the inventory is refused unless force.
Inputs: device (required), apply, force, planHash.
tailnet_devicesA
Every device of the tailnet (Tailscale API with TAILSCALE_API_KEY, else this machine's tailscale status): online / last seen, OS and client version, update available, node key expiry, tags — and which inventory host and panel node it is.
Inputs: filter, offlineDays.
tailnet_joinA
Join a server to the tailnet over its public address (hosts..bootstrap in fleet.yaml): for a machine that is already set up but logged out of Tailscale or never joined. The key is a fresh one-time key from the Tailscale API (TAILSCALE_API_KEY), made only on apply and passed through the environment — never in the plan or journal; without the API it falls back to TAILSCALE_AUTH_KEY. Runs tailscale up --reset --ssh --hostname node-<name> (+ exit node like the fleet), then verifies the node shows up online.
Inputs: host (required), apply, install, sshPort, +3 more.
tdarr_library_addA
Add folders to Tdarr: one new library per folder, cloned from an existing library (same flow/plugin stack, transcode/health-check settings, cache and output unless given), with counters reset. Folders may be host paths (translated through the Tdarr container mounts). The dry run shows every library object that will be inserted. Optionally starts a "find new" scan of each.
Inputs: host (required), folders (required), template (required), scan, +5 more.
tdarr_pauseA
Pause or resume all Tdarr nodes (global setting), e.g. while the server is busy streaming.
Inputs: host (required), paused (required), apply, planHash.
tdarr_scanA
Scan a Tdarr library: scanFindNew (only new files, default) or scanFresh (re-scan everything).
Inputs: host (required), library (required), mode, apply, +1 more.
tdarr_statusB
Tdarr on a host: server version/uptime, nodes (paused, workers), libraries (folder, cache, output, watching, processing), global pause, overall statistics.
Inputs: host (required).
tuning_applyA
Apply or roll back the kernel/network tuning of vps-network-tuning-script (initial.sh apply|rollback): BBR+fq, conntrack, NOFILE, swap, journald limits. apply creates a backup in /root/edge-tuning-backup-*; rollback restores the latest (or backupDir).
Inputs: op (required), apply, batch, target, +4 more.
tuning_statusB
Kernel/network tuning state from the owner's vps-network-tuning-script (initial.sh status): profile, BBR/fq, conntrack, NOFILE, swap, journald — plus available rollback backups.
Inputs: target.
ufw_ruleA
Add or delete a UFW rule on hosts, e.g. open a new public port for an inbound (allow 8443/tcp on the internet interface) or close one. Public exposure of 22/2222/5443/5444/9100 is blocked unless allowDangerous. The rule list before the change is shown in the dry run.
Inputs: op (required), port (required), from, apply, +5 more.
updates_policyA
Bring hosts to the fleet update policy: security updates install automatically (unattended-upgrades, stock security-only origins), NO automatic reboots — reboots stay manual (host_reboot). Writes /etc/apt/apt.conf.d/20auto-upgrades and 99zz-mia-policy, enables apt-daily timers. apt-based hosts only.
Inputs: apply, batch, target, planHash, +2 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 63 tools
Tools are organized by clear domain prefixes (fleet_, host_, jellyfin_, tdarr_, dns_, gitea_, tailnet_, scenario_) and each names a distinct resource+action. A few boundaries blur where a fleet-wide tool overlaps host-level ones (fleet_status vs fleet_audit vs host_inspect; fleet_changes vs fleet_snapshot vs snapshots_list; host_firewall vs host_ports), but descriptions clarify the split.
Uniform snake_case with predictable domain prefixes throughout. Minor deviations exist in verb placement (gitea_runner_remove/gitea_runner_register vs gitea_runners; jellyfin_library_add_path vs jellyfin_library_create), but the convention remains readable and largely consistent.
63 tools is far beyond the 25+ heavy threshold, and the surface spans many unrelated subsystems (fleet ops, Jellyfin, Tdarr, DNS, Gitea, Tailscale, runbooks). Even with broad legitimate scope, the sheer count risks overwhelming an agent and makes selection costly.
Near-exhaustive lifecycle coverage: reads, writes, edits, dry-run/plan modalitities, backups, verification, and orchestration runbooks. CRUD plus ops (reboot, service actions, cert renewal, disk cleanup, snapshots, journal) is present across every domain, with no obvious dead ends.
Maintenance
Related MCP Connectors
Let AI operate servers without SSH. Choose actions, approve risky changes, and audit every step.
Operate Linux, macOS and Windows from your LLM. Every action runs through an auditable allowlist.
Manage remote servers, SSH terminals, files, databases and proxies with scoped access and approvals.
Scoped, audited SSH exec, sessions, and SFTP on your saved servers without exposing credentials
Related MCP Servers
- AlicenseBqualityAmaintenanceYour sysadmin co-pilot — an AI that administers Linux through typed, approval-gated, Ed25519-audited actions instead of shell strings. Reference implementation of the LACS standard.58187MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to manage an entire fleet of servers over SSH through MCP, fanning a single intent out across many hosts with target expressions while enforcing a policy engine, approval gates, and tamper-evident audit logs. Supports rolling execution with circuit breakers, cross-host diffing, bulk file transfer, tmux sessions, and YAML workflows.20 npm18AGPL 3.0
- AlicenseAqualityBmaintenanceEnables AI agents to inspect and manage remote hosts over SSH with tiered blast-radius containment that classifies and blocks destructive commands, plus forensic post-flight assertions that verify ports, processes, files, and HTTP health. It also provides long-running background task envelopes and atomic SFTP file editing with automatic backups, unified diffs, and one-click rollback.12MIT
- AlicenseBqualityBmaintenanceThe safety gate between AI agents and your servers: 25 typed, policy-checked tools for services, logs, Docker, disk, ports and nginx, with human approval for changes and a hash-chained audit log.251MIT