| files/listA | Lists the entries of ONE directory like ls -l (dotfiles, . and .. are hidden unless all: true): type and permissions, link count, owner, group, size, modification time and symlink targets (symlinks are shown, never followed). Read-only, not recursive, no entry cap; path must be absolute. For a recursive or filtered search use files/find, for a directory's total size disks/usage, for a file's MIME type files/filetype. On permission denied retry with privileged: true if granted (root calls also need a paths: entry covering the path). Text output is ls -l lines (Directory is empty. when empty; long: false gives names only, directories suffixed /). output_format: json returns an array of objects (name, type, mode, mode_octal, links, owner, group, uid, gid, size, modified, is_dir, target), [] when empty. |
| files/readA | Reads a text file and returns its contents as raw text (no line numbers or metadata). Read-only; path must be absolute. To find a file use files/find, for size or mode files/list, to check whether it is binary files/filetype (binary files are refused with cannot read binary file). Selection: start_line/end_line (1-indexed, inclusive; start_line alone reads to the end, end_line alone starts at line 1) take precedence over the byte range offset/limit. With no selection, or offset without limit, at most 10240 bytes come back followed by a [WARNING: File truncated ...] line, so page large files with start_line/end_line or pass limit. There is no streaming: one call reads into memory, and a line over 64 KiB fails line mode. A start_line past the end is an error; an empty file returns an empty string. privileged: true (needs a grant, and a paths: entry for root) reads files your account cannot. |
| files/createA | Writes a whole file: creates it, or REPLACES the content of an existing one entirely. Mutating and not atomic. Missing parent directories are created (mode 0755); a new file gets mode 0644, an existing file keeps its mode. With content omitted or empty it only touches: creates an empty file or refreshes the modification time and never truncates an existing file. To change part of an existing file use files/update (append or replace lines); to check first whether a path exists use files/list or files/find; afterwards set mode or owner with files/chmod/files/chown. content is text and no trailing newline is added; path must be absolute. Writing where your account cannot needs privileged: true (a grant, and for root a paths: entry covering the path, which also refuses symlinks in the path; otherwise a symlink at the path is followed). Returns one line, Successfully created and wrote to PATH or Successfully touched PATH; failures are plain text such as failed to write to file. |
| files/updateA | Edits part of an EXISTING file in place: appends text or replaces an inclusive line range. Mutating and not atomic; the file keeps its mode and owner. To write a whole file use files/create; to see the lines first use files/read with start_line/end_line. With append: true exactly content is added at the end (no newline is added, include your own) and the file is created if missing, though not its parent directories; append wins over any line range. Otherwise start_line AND end_line are both required (1-indexed, end_line >= start_line), the file must exist, and those lines are replaced by content (one trailing newline of content is ignored; an end_line past the end is clamped; a start_line past the end adds content as a new last line). There is no insert or delete mode: replacing with empty content leaves one empty line. Returns Successfully appended to PATH or Successfully updated lines A-B in PATH, no diff. path must be absolute; privileged: true (a grant, and for root a paths: entry) edits files you cannot write. |
| files/findA | Searches a directory tree (default /) by name, type, age or size, like find. Read-only; never follows symlinks; skips /proc, /sys, /dev and /run when the search starts at / or above them (not when path is inside one). Use files/list to see one known directory and disks/usage to see which folders take the space. All filters are ANDed and none is required. There is NO result cap: path: / without a filter lists every file on the host, so give name, type or max_depth; the 30 s worker timeout applies. Syntax: name is a case-sensitive glob on the base name only (*.log, not a path); type is one of f d l b c p s or a comma list (f,d); mtime in days: +7 older than 7 days, -1 within the last day, 7 exactly 7 days; size: +100M larger, -10k smaller (units b c w k M G, rounded up); max_depth 1 = direct children, omitted or 0 = unlimited. Unreadable directories are skipped silently. Text output: one SIZE PATH line per match, sorted by path (no size for directories; empty output = no match). output_format: json returns an array of objects (path, size in bytes, type). |
| files/filetypeA | Returns a file's MIME type, like file -b --mime-type, detected natively from its first 8 KiB (no file(1) needed); read-only, path must be absolute. A symlink is reported as inode/symlink (never followed); directories, devices, fifos and sockets as inode/.... Use it before files/read, which refuses binary files. For size, permissions or ownership use files/list; for contents files/read. The reply is always one plain-text line (no output_format). privileged: true (a grant, and for root a paths: entry) reads files your account cannot. |
| files/chmodA | Changes a file's or directory's permission bits (chmod). Mutating and idempotent; for owner or group use files/chown, to check the result files/list. Never follows symbolic links: a path containing a symlink in any component is refused, and recursive changes skip symlinks and report them. Numeric modes follow GNU chmod semantics (on directories a 4-digit mode keeps setuid/setgid; use 5 digits, e.g. 00755, to set them exactly); a bare 755 is octal. Changing a file you do not own needs privileged: true (a grant, and a paths: entry for root). Single change returns PATH: 0644 (-rw-r--r--) -> 0755 (-rwxr-xr-x), or ... unchanged if already set. A recursive run prints one line per changed entry, then changed N, unchanged M (plus skipped symlinks) and per-entry errors; it is not atomic, so partial success is possible. |
| files/chownA | Changes a file's or directory's owner and/or group (chown). Mutating and idempotent; for permission bits use files/chmod, to check the result files/list. Never follows symbolic links: a path containing a symlink in any component is refused, and recursive changes skip symlinks and report them. Changing the owner requires privileged: true (a grant, and a paths: entry for root). owner is user, user:group, :group or user: (the user's login group), with names or numeric ids; names are looked up in the host's /etc/passwd and /etc/group and an unknown one fails (no such user). Returns PATH: old -> new as owner:group, or unchanged; a recursive run prints one line per changed entry then a summary with skipped symlinks and per-entry errors, and is not atomic. |
| disks/freeA | Reports space on the ONE filesystem holding path (statfs, like df for a single path): total, used and free bytes and use percent (human_readable for GiB). Read-only; path must be absolute. It does not name the device or mount point: use disks/mounts for that, disks/list for block devices, disks/usage to see which folders use the space. There is no all-filesystems mode; call it once per mount point. free is what non-root users can use, and used is total minus that. inodes: true returns inode counts as plain text and ignores output_format and human_readable. Text output is three lines; output_format: json returns an object (path, total_bytes, used_bytes, free_bytes, use_percent, plus total_human, used_human, free_human with human_readable). privileged: true (a grant, and a paths: entry for root) only for paths you cannot stat. |
| disks/usageA | Measures how much disk space a directory tree uses (native walk, like du). Read-only. For the free space of a whole filesystem use disks/free; to find individual big files use files/find with size. By default the reply is one grand total; max_depth: N also lists directories up to N levels deep, largest first; all: true adds a per-file list (text output only). Sizes are allocated blocks unless apparent_size: true; hard links count once, symlinks are never followed. exclude patterns containing / match the full path (/proc, /var/lib/*), others the base name. Unreadable directories are skipped silently, so an unprivileged total can under-count: use privileged: true (a grant with a paths: entry). Results are cached for 60 s per user and arguments. The shipped config allows 300 s, otherwise the default is 30 s. Text ends with Total size of PATH: N; output_format: json returns an object (path, total_size, human_size with human_readable, directory_sizes as a path-to-bytes map only when max_depth > 0). |
| processes/topA | Snapshot like top -b -n 1, read from /proc: header (uptime, users, load average, task counts by state, CPU us/sy/ni/id/wa/hi/si/st, memory and swap) plus the process table (PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND), sorted by sort_by (default cpu). Read-only. %CPU is measured over interval_ms (default 1000, max 10000), so the call takes about that long. For a lighter PID list or a single PID use processes/list; to signal a process processes/delete; for memory totals only memory/usage; for which process owns a port network/connections. limit defaults to all processes; user is an exact username. Sizes are bytes (MiB with human_readable). output_format: default and table give top's layout, wide adds PPID, THR and full command lines, json/yaml return an object with summary and processes (memory in bytes). |
| processes/listA | Lists processes (PID, PPID, user, state, RSS, command) read from /proc. Read-only. Use it to find a PID, filter by user or one pid, or sort by memory. For the CPU/memory header and %CPU on every row use processes/top; for the process that owns a port network/connections; to signal a process processes/delete; for deep per-PID metrics the process://<pid>/<target> resource. Sorted by PID unless sort_by is mem (RSS, largest first) or cpu (samples for 0.5 s, so the call takes at least that long, and only then does cpu_percent appear); limit applies after sorting. Kernel threads appear as [name]. Command lines can contain secrets passed as arguments. Text output is a table; output_format: json returns an array of objects (pid, user, comm, state, ppid, rss_bytes, cmdline, cpu_percent). Sizes are bytes unless human_readable. |
| processes/deleteA | Sends ONE signal to ONE process by PID (kill(2)); the default SIGTERM asks the process to exit. Mutating and not idempotent: it returns as soon as the signal is delivered and does not check that the process exited, and signalling a PID that is gone fails with no such process. Allowed signal values: SIGTERM, SIGKILL, SIGHUP, SIGINT, SIGQUIT, SIGUSR1, SIGUSR2, SIGSTOP, SIGCONT, SIGABRT (SIG prefix optional, case-insensitive, numbers rejected), so it can also pause (SIGSTOP) and resume (SIGCONT). Refuses PID 1 and the mcpd daemon itself. Another user's process fails with not permitted unless privileged: true (needs a grant). Find the PID first with processes/list or processes/top. To stop a managed service use services/manage (systemd may restart a killed one), for a container docker/manage. Returns one text line, Successfully sent signal SIGTERM to process N; output_format has no effect. |
| network/nslookupA | Resolves DNS records for a hostname through the host's resolver (/etc/resolv.conf; a DNS server cannot be chosen), so answers may come from a local cache. Read-only, but it sends queries off-host. record_type (case-insensitive) is A, AAAA, CNAME, TXT, MX, NS or ANY; the default ANY is not a DNS ANY query but runs the CNAME, A/AAAA, TXT, MX and NS lookups in turn. Other types (SOA, PTR, SRV) are rejected, and host must be a name: an IP address is not reverse-resolved. Always returns JSON: an object with host and records (array of objects with type and value; an MX value looks like 10 mail.example.com.). Empty records means the name exists but has no record of that type; a name that does not exist is an error (HOST: no such host). To test reachability use network/ping, to fetch a URL network/curl. |
| network/curlA | Makes one HTTP(S) request with Go's HTTP client and returns status, headers and body. NOT read-only: any method (default GET) is sent as given, so POST, PUT or DELETE change the remote system. Follows redirects (up to 10); a non-2xx status is not an error, check status_code. The default timeout is 10 s (timeout, whole seconds) and the 30 s worker limit caps anything larger. The body is cut at max_body (default 1 MiB, max 10 MiB) and truncated is then true. insecure skips TLS verification. The daemon's proxy environment is honored unless the user has a network: policy in mcp-sudo.yaml; such a policy applies to every call and redirect hop, and a blocked destination fails to connect. Header and body values are redacted in the audit log. Returns JSON with status_code, status, headers (values comma-joined), body, truncated; output_format is ignored. For DNS use network/nslookup, for TCP reachability network/ping, for local files files/read. |
| network/arpA | Shows the kernel's ARP cache (IPv4 address to MAC address) from /proc/net/arp in the daemon's network namespace. Read-only. It is a cache, not a scan: only hosts contacted recently appear, and IPv6 neighbours are not included. interface is an exact device name (eth0); omit it for all. For sockets and connections use network/connections, for reachability network/ping. Always returns JSON: an array of objects (ip_address, hw_type and flags as raw hex such as 0x1, hw_address, mask, device). When nothing matches the output is null, not []. |
| network/pingA | Tests TCP reachability: opens one TCP connection to host:port and closes it. This is NOT ICMP, so it needs a listening port (default 80) and says nothing about other ports or ICMP. Single attempt, no loss statistics; latency includes DNS resolution. Read-only, but it connects off-host and honors the user's network: policy in mcp-sudo.yaml. timeout is whole seconds (default 5). A failed connect is not a tool error: the JSON has success: false and an error text. Returns JSON with host, port, success, latency_ms and error. For DNS only use network/nslookup, for the hop path network/trace-path, for an HTTP check network/curl. |
| network/connectionsA | Lists TCP and UDP sockets in every state with their owning processes, like ss -tuanp, read natively from /proc/net (no ss needed). Read-only; unix and raw sockets are not included. state (case-insensitive, _ and - interchangeable) filters by LISTEN (includes unconnected UDP), ESTABLISHED, TIME_WAIT, CLOSE_WAIT, SYN_SENT, ... or the groups connected/synchronized; an invalid state is an error listing the valid names. port matches the local OR peer port. The owning process (pid, fd) is shown only for sockets whose /proc/PID/fd you can read; other users' processes need privileged: true (needs a grant). Text output has ss -tuanp columns; output_format: json returns an array of objects (netid, state, recv_q, send_q, local_address, local_port, peer_address, peer_port, uid, inode, processes), [] when empty. For the ARP cache use network/arp, for interfaces and IPs the network://interfaces resource, for process details processes/list. |
| memory/usageA | Reports memory and swap use from /proc/meminfo. Read-only. used is total - free - (buffers + cached + reclaimable slab); available is the kernel's MemAvailable. Text output is a free-like table with Mem: and Swap: rows (bytes, or e.g. 1.8Gi with human_readable); detailed: true returns the raw /proc/meminfo instead, but only for text output (it is ignored with output_format: json). JSON (also yaml/table/wide) returns an object (total, used, free, shared, buffCache, available, swap_total, swap_used, swap_free) in bytes; human_readable is ignored there. For per-process memory use processes/top or processes/list, for CPU load cpu/load-average. |
| services/manageA | Starts, stops, restarts, reloads, enables or disables ONE systemd service over D-Bus (.service is appended when missing). Mutating. Ordinary users are usually refused by polkit, so privileged: true (needs a grant in mcp-sudo.yaml) is normally required. start, stop, restart and reload wait for the systemd job and return Job N completed with status: done (or failed, canceled, timeout, dependency, skipped); a slow one can hit the 30 s worker limit. reload asks the service to re-read its config without stopping it, only if the unit supports it. enable and disable only change whether it starts at boot; they do not start or stop it. To see state use services/list or the service://<name>/status resource, for logs logs/journal-control, for containers docker/manage, for a raw signal to a PID processes/delete. |
| services/listA | Lists systemd .service units that systemd currently has loaded, with load, active and sub state (D-Bus ListUnits). Read-only. An installed but never-loaded unit file may be missing, and timers, sockets and other unit types are not included. pattern supports only a leading and/or trailing * (kube*, *ssh*); without * it is an exact unit name including .service. The state filters are exact strings: active_state active/failed/inactive, sub_state running/exited/dead, load_state loaded/not-found. Text output is a block per unit plus a hint line, or No services found matching the criteria.; output_format: json returns an array of objects (name, description, load_state, active_state, sub_state), [] when empty. To change a service use services/manage, for one unit's details the service://<name>/status resource, for its logs logs/journal-control. |
| timers/listA | Lists the systemd timers of the host - systemd's scheduler, the modern counterpart of cron - each with the unit it starts, its schedule (OnCalendar= and monotonic settings such as OnBootSec=), its next and last run, whether it is Persistent (catches up runs missed while the host was off) and its last result. Read-only. Use it to answer "what runs on a schedule, and when next": services/list shows only .service units, so timers never appear there. To inspect the unit a timer starts, use linuxctl describe system <name> or the service://<name>/status resource; a timer's own state is in this listing. Times are RFC 3339 UTC, and never means systemd reports none (a timer that has not fired yet, or one with no upcoming trigger). pattern accepts a leading and/or trailing * (apt*, *.timer, *daily*); the timer name includes .timer. Unprivileged calls need the host's systemd bus (a bare-metal or VM host); inside a container use privileged: true, which joins the host. output_format: json returns an array with the same fields; text is the default. Empty result means no timer matched. Managing timers (start, stop, enable) is not offered by this tool. |
| logs/journal-controlA | Reads the systemd journal (wraps journalctl -n LINES --no-pager). Read-only. Returns the last lines entries (default 100, the only size limit), oldest first unless reverse. Filter with unit (sshd.service), since/until in journalctl syntax (1 hour ago, yesterday, 2026-09-29 10:00), boot: true (current boot) or boot_offset (-1 = previous boot; takes precedence over boot). Without root an ordinary user sees only their own entries unless in the systemd-journal or adm group. Requires privileged: true in containerized deployments (needs a grant), since journalctl only exists on the host, never in this daemon's own image. Plain text by default; output_format: json gives one JSON object per line (journalctl -o json), not an array. For kernel messages use logs/dmesg, for logins logs/logins, for container output docker/logs, for unit state services/list. |
| logs/dmesgA | Reads the kernel ring buffer (wraps dmesg --human, relative timestamps). Read-only. Output is cut to the LAST 30 KiB with a [WARNING: Output truncated to last 30KB] prefix and cannot be paged; narrow it with level, a comma list of emerg, alert, crit, err, warn, notice, info, debug (err,warn). On hosts with kernel.dmesg_restrict=1 an unprivileged call fails (dmesg's error is returned); use privileged: true (needs a grant). output_format is accepted and ignored: the reply is always plain text. For older history or service logs use logs/journal-control, for login records logs/logins, for drive faults disks/health. |
| logs/loginsA | Lists login history (wraps last) or failed login attempts (type: "failed", wraps lastb). Read-only; returns raw text, not JSON. limit keeps the N most recent entries and user filters by username; the trailing wtmp begins... summary line is dropped and an empty history returns an empty line. last/lastb must be installed on the host. For account details use users/list, for authentication messages logs/journal-control. |
| kernel/system-controlA | Reads or writes a kernel parameter (sysctl) at runtime through /proc/sys. With value omitted it reads: key (dotted net.ipv4.ip_forward or slash form; a directory such as net.ipv4 prints its subtree) returns key = value lines, and read_all: true (only when key is empty) prints every parameter, thousands of lines, uncapped. Reading needs no grant. With value it WRITES: that needs privileged: true and a grant, is refused for keys outside the user's sysctl.write_keys globs in mcp-sudo.yaml, and the reply shows the value the kernel now holds. Writes last until reboot; nothing is persisted to /etc/sysctl.d. For OS and kernel version use system/os-release, for memory figures memory/usage. |
| cpu/listA | Lists the machine's CPUs from /proc/cpuinfo. Read-only. Text output shows the number of logical processors and, for the FIRST processor only, vendor, model name, MHz (or BogoMIPS) and cache size. output_format: json (also yaml/table/wide) returns an array with one object per logical CPU using /proc/cpuinfo's own field names, which differ by architecture (x86 model name, cpu MHz, flags; ARM CPU implementer). It does not report sockets, cores or threads separately, and topology_only has no effect. For current load use cpu/load-average, for per-process CPU processes/top, for OS and kernel system/os-release. |
| cpu/load-averageA | Returns the 1, 5 and 15 minute load averages from sysinfo(2). Read-only; the only parameter is output_format. Load counts runnable plus uninterruptible tasks, not CPU percent: compare it with the number of logical CPUs from cpu/list. Text is Load Average: 0.52, 0.48, 0.45; output_format: json (also yaml/table/wide) returns an object with numbers 1_min, 5_min, 15_min. For per-process CPU use processes/top, for memory memory/usage. |
| disks/listA | Lists block devices as a tree (like lsblk): disks, partitions, and LVM/dm-crypt/RAID volumes nested under the devices they are built on, with MAJ:MIN, RM, SIZE, RO, TYPE and MOUNTPOINTS, read from /sys/class/block (no lsblk needed). Read-only. Empty devices and RAM disks are hidden unless all: true; SIZE is in bytes unless human_readable. output_format: json (also yaml/table/wide) returns an object blockdevices, an array of objects (name, kname, maj:min, rm, size, size_bytes, ro, type, mountpoints) with nested children. In containerized deployments use privileged: true (needs a grant) to see the host's mount points. For free space or inodes use disks/free, for folder sizes disks/usage, for the mount table disks/mounts, for partition boundaries disks/partitions, for I/O counters disks/performance, for SMART disks/health. |
| disks/mountsA | Lists mounted filesystems (device, mount point, type, options) from /proc/thread-self/mounts, sorted by mount point. Read-only. fs_type filters by exact type (ext4, overlay, tmpfs; no globs). It reads the daemon's own mount namespace, so in a container use privileged: true (needs a grant) to get the host's mounts. Text lines look like /dev/sda1 on /mnt type ext4 (rw,...); output_format: json returns an array of objects (device, mount_point, fs_type, options), or null rather than [] when nothing matches (text is then empty). For block devices use disks/list, for the space used on a mount disks/free. |
| disks/performanceA | Returns block-device I/O counters from /proc/diskstats: reads and writes completed and merged, sectors (512 bytes) and milliseconds spent, in-flight I/Os and weighted I/O time. Values are CUMULATIVE since boot, not rates and not iostat's per-interval figures; there is no %util or await, so sample twice and subtract to get a rate. Read-only. Without device all devices are listed except loop* and ram*; a name such as sda (find them with disks/list) selects one, and an unknown name gives no such block device. Text output is a table; output_format: json and yaml (real YAML here) return an array of objects with 14 fields (major, minor, device_name, reads_completed, ..., weighted_time_ios_ms). For capacity use disks/free, for SMART health disks/health. |
| disks/healthA | Returns a drive's SMART data as smartctl's JSON (wraps smartctl -j -a; the smartmontools package must be installed or the call fails saying so). Read-only, but normally needs root: use privileged: true (needs a grant), since without it smartctl usually cannot open the device. device is a bare kernel name such as sda or nvme0n1, never a path; find names with disks/list. The reply is smartctl's JSON verbatim (keys such as smart_status, temperature, ata_smart_attributes or nvme_smart_health_information_log vary by drive type); a non-zero smartctl exit is not an error when it printed JSON, and virtual disks report SMART as unsupported. For I/O counters use disks/performance. |
| disks/partitionsA | Lists the partitions of a disk with start sector and size in sectors and bytes, read natively from /sys/class/block (no fdisk). Read-only. device names the PARENT DISK (sda, nvme0n1), not a partition; without it every disk's partitions are listed. A sector size of 512 bytes is assumed. It does not report partition type, label, UUID or filesystem: use disks/list or disks/mounts for those, and disks/list to find device names. A named disk without partitions is an error (no partitions found for device). Output is a text table; when output_format is set to any non-empty value (the parameter is accepted although not listed in the schema) it is an indented JSON array of objects (device, parent_disk, number, start_sector, size_sectors, size_bytes). |
| network/trace-pathA | Traces the network path to a host by running the external traceroute binary (not tracepath; it must be installed on the host). Read-only, but it sends probe packets. host is a hostname or IP; max_hops defaults to traceroute's 30 and is capped at 255. There is no timeout parameter and the 30 s worker limit kills slow traces (30 hops x 3 probes can take minutes), so set a low max_hops such as 15. Non-responding hops show as * * *. Returns traceroute's raw text; there is no output_format. Use network/ping first for basic reachability, network/nslookup for DNS problems. |
| system/os-releaseA | Returns the Linux distribution and kernel version: the contents of /etc/os-release plus the uname line (system, host name, release, version, machine). Read-only. In a containerized daemon /etc/os-release is the container image's, not the host's. Text has an OS Release Info: block with the raw file and a Kernel Info: line; output_format: json (also yaml/table/wide) returns an object with os_release (the raw file text, not parsed into fields) and kernel (the uname line). For CPU details use cpu/list, for kernel parameters kernel/system-control. |
| system/packagesA | Lists installed packages by parsing the package database (dpkg on Debian/Ubuntu, apk on Alpine); rpm-based systems return an error, not supported yet. Read-only. The whole list is returned with no cap (hundreds of entries), so filter with name: an exact name or a glob (openssh-*, *ssl*). Only packages with status installed are listed. Text has a header N packages installed (dpkg) and a NAME VERSION ARCH table; output_format: json returns an array of objects (name, version, architecture), [] when none; there are no description or size fields. For OS and kernel version use system/os-release. |
| users/listA | Lists local user accounts from /etc/passwd and /etc/group (uid, gid, home, shell, supplementary group memberships), sorted by uid. Read-only. Never reads /etc/shadow - this reports account identity, not credentials. Local files only: LDAP/SSSD users are not listed. min_uid (e.g. 1000) hides system accounts. Text lines look like name (uid=N gid=N(group)) home=... shell=... groups=a,b; output_format: json returns an array of objects (username, uid, gid, group_name, comment, home_dir, shell, groups). For who logged in use logs/logins, for your own root grants auth/sudo-rules. |
| auth/sudo-rulesA | Shows which tools YOU may run as root: your privileged grants from mcp-sudo.yaml with their paths, containers, prune, network and sysctl restrictions. It does not list which tools you may call at all: unprivileged calls need no grant, except docker/* and daemon/reload-config, which always do. Read-only and answered by the daemon itself. Call it before a privileged: true request or after a permission-denied error. Text is Your authorized privileged tools: followed by JSON; output_format: json returns only that JSON, whose keys are capitalised Go field names (Tools, Resources, Allowed, Paths, Containers, Prune, Network, Sysctl). No grants gives You have no privileged tools authorized in mcp-sudo.yaml. (JSON: {}). After an operator edits grants, daemon/reload-config applies them. |
| docker/containersA | Lists Docker containers through the Engine API on the local socket (no docker CLI needed): name, image, state, status, ports. Read-only. Only running containers unless all: true; state (created, running, paused, exited, ...) filters to one state and implies all; pattern is a glob on the container name (web-*); limit returns the newest N. Every container is listed: the grant's containers: list applies only to docker/manage, docker/logs and docker/exec. Text is a block per container plus a hint line, or No containers found matching the criteria.; output_format: json (also yaml/table/wide) returns an array of objects (name, names, id, full_id, image, image_id, command, state, status, created, ports, labels), [] when empty. For logs use docker/logs, to start or stop docker/manage, for one container's details the docker-container://<name>/status resource. Always runs as root: no privileged argument, refused unless the user's grant has allowed: true. |
| docker/manageA | Changes a container's lifecycle: start, stop, restart, kill, pause, unpause or remove; the container counterpart of services/manage. Mutating. Only containers in the grant's containers: list may be touched (name and ID are both checked; an empty list refuses everything). Always runs as root: no privileged argument, refused unless the grant has allowed: true. stop uses Docker's default 10 s grace. remove is irreversible for the container and deliberately fenced: it never sends force or volume removal, so a running container is refused (stop or kill it first) and anonymous volumes are kept. start on a running or stop on a stopped container reports unchanged, not an error. container is a name, full ID or ID prefix. Text: Container NAME (ID12): stop succeeded.; output_format: json returns container, id, action and result (ok or unchanged). For bulk cleanup use docker/prune, to run a command inside docker/exec, to check state first docker/containers. |
| docker/logsA | Reads one container's recent logs, the container counterpart of logs/journal-control: stdout and stderr interleaved in Docker's order, the last lines lines (default 100), never a follow. Read-only. since/until take a unix timestamp (1759005000) or an RFC3339 time; stdout and stderr default to true (both false returns nothing); timestamps prefixes each line. The answer is capped at 1 MiB and an oversized tail is cut at the END without a marker, so the newest lines can be lost: ask for fewer lines. Only containers in the grant's containers: list; always runs as root, refused unless the grant has allowed: true. Plain log text by default (Container X (ID) has no log output for this selection. when empty); output_format: json returns container, id, lines, logs. To run a command use docker/exec; for host logs logs/journal-control. |
| docker/execA | Runs ONE command inside a running container and returns its stdout, stderr and exit code: no TTY, no stdin, no follow. The highest-risk docker tool: arbitrary code, as the image's user (often root) unless user is set, with its own containers: grant list. Always runs as root at the Docker socket: no privileged argument, refused unless the grant has allowed: true. command is an argv array, not a shell line: ["sh", "-c", "ls /app"] for a shell. A non-zero exit code is not a tool error; read exit_code. timeout (seconds, default 30, capped at 300) is bounded by the worker limit, which is 30 s unless the operator sets timeout_seconds for docker/exec in daemon.yaml: then the answer is timed out after N seconds and the command keeps running in the container. Output is capped at 1 MiB. Text shows Exit code: N and the output; output_format: json returns container, id, command, exit_code, running, stdout, stderr. To read logs use docker/logs, to manage the container docker/manage. |
| docker/imagesA | Lists Docker images through the Engine API on the local socket: tags, ID, size, creation time. Read-only (no pull, build or remove). Untagged intermediate layers are hidden unless all: true; untagged images that are shown appear as <none>:<none>. pattern is a glob on any tag or repository (nginx*, */api:*). Text is tag lines with ID | Size | Created; output_format: json (also yaml/table/wide) returns an array of objects (id, full_id, repo_tags, repo_digests, size_bytes, created, containers = how many containers use it, labels), [] when empty. To delete unused images use docker/prune (target images, dangling only); for containers docker/containers; for one image's config the docker-image://<ref>/inspect resource. Always runs as root: no privileged argument, refused unless the grant has allowed: true. |
| docker/volumesA | Lists Docker volumes with driver, mountpoint (a host path) and the names of the containers, running or stopped, that mount each. Read-only: volumes are never created or removed here. pattern is a glob on the volume name. Text is a block per volume; output_format: json (also yaml/table/wide) returns an array of objects (name, driver, mountpoint, created, scope, labels, options, in_use_by), [] when empty. docker/prune with target volumes removes anonymous unused volumes only. For one volume's details use the docker-volume://<name>/inspect resource, for containers docker/containers. Always runs as root: no privileged argument, refused unless the grant has allowed: true. |
| docker/networksA | Lists Docker networks (not the host's: for host interfaces and routes use the network:// resources and network/* tools) with driver, scope, subnet, gateway and the containers attached with their addresses. Read-only. pattern is a glob on the name; driver an exact driver (bridge, host, none, overlay, macvlan). Text shows Attached: (nothing) for an empty network; output_format: json (also yaml/table/wide) returns an array of objects (name, id, full_id, driver, scope, created, internal, attachable, ingress, ipam_driver, subnets, gateways, containers, options, labels). Removing unused networks is docker/prune (target networks); one network's full detail is the docker-network://<name>/inspect resource. Always runs as root: no privileged argument, refused unless the grant has allowed: true. |
| docker/pruneA | Deletes ONE kind of unused Docker object per call: target is containers (stopped), images (dangling only), volumes (anonymous, unused only; named volumes are never touched), networks (unused) or build-cache. Mutating and irreversible, and it deletes objects you did not name; call again for the next kind (pruning containers is what makes images dangling). The user's grant needs a prune: list containing the target, otherwise the call is refused before anything is deleted. Always runs as root: no privileged argument, refused unless the grant has allowed: true. The 30 s worker limit can end the call while the Engine keeps pruning. Text is TARGET: N removed, X MiB reclaimed plus the removed IDs (networks report no size); output_format: json returns target, deleted, count, space_reclaimed_bytes. To remove one named container use docker/manage (remove); to see what exists first docker/containers, docker/images, docker/volumes, docker/networks. |
| cron/manageA | Reads or replaces a user's crontab, the list of commands cron runs for that account on a schedule, and lists which accounts have one. Without content it reads: your own crontab as raw text exactly as crontab -l prints it (empty when you have none), or with privileged: true and no user, a table of the accounts that have a crontab and that your grant lets you view. With content it writes: the WHOLE crontab is replaced (an empty string clears it); the crontab command rejects a file it cannot parse and then the old crontab is unchanged; a missing final newline is added; the limit is 64 KiB. Your own crontab needs no grant and runs as you. Another account's needs privileged: true and a rule naming that account in your cron/manage grant (users: {name: {view: true, edit: true}}); edit implies view, root's crontab can be viewed but never edited, and cron.allow/cron.deny still apply to that account. WARNING: writing a crontab schedules commands as that user, and the job outlives this session and the revocation of your token; every write is audit-logged (size, lines, hash, never the content). To avoid overwriting a change made meanwhile, pass if_match with the sha256 you read (output_format: json returns it). For systemd timers use timers/list. Needs the crontab command on the host; inside a container a call sees the container's own crontabs. |
| daemon/reload-configA | Re-reads mcpd's config files (daemon.yaml, users.yaml, mcp-sudo.yaml) and applies them without a restart: users and tokens, per-user grants, rate limits and tool timeouts. The files themselves are edited on the host (linuxctl); this only reloads them. Mutating (replaces the in-memory config) and always needs allowed: true for daemon/reload-config in the caller's grant; there is no unprivileged mode. The files are validated strictly first (a misspelled key is an error): if any is invalid nothing changes and the error is returned. Sessions of removed users and of users whose token changed are closed, possibly the caller's own. Server settings (port, TLS, worker.containerized, Docker socket) still need a restart. Returns free text listing what changed. Takes no parameters; verify grants afterwards with auth/sudo-rules. |