The full upstream README, mirrored here for reference. Install config, tool schemas, adoption signals, and an original overview live on the Linux MCP daemon (mcpd) listing page.

A high-performance, Go-based Model Context Protocol (MCP) daemon (mcpd) designed to securely bridge AI agents directly with the Linux operating system.
This project implements a zero-dependency (kernel-first) philosophy. It allows AI agents to introspect and interact with the host Linux system directly via raw syscalls and the Virtual File System (/proc, /sys) without requiring bloated third-party parsing libraries.
Communication happens directly between the AI agent and the daemon via Server-Sent Events (SSE) and JSON-RPC over HTTPS on port 9091 (TLS is on by default, with a self-signed certificate generated on first start).
An AI agent that helps run a server needs to see it - load, memory, disks, processes, services, logs, the network - and sometimes to act on it. The usual way is an SSH shell, and a shell is everything at once: any command, any file the account can reach, with sudo all of root, and hard to tell afterwards what was done. mcpd gives the agent typed tools instead of a shell:
processes/top, memory/usage, disks/usage, logs/journal-control, logs/dmesg, network/connections, services/list answer it, with structured output (json/yaml) the agent doesn't have to scrape from top or df.mcp-sudo.yaml and limited by paths, network destinations and sysctl keys - "may read /var/log as root and restart services" rather than "is root"./proc, /sys and systemd over D-Bus itself; it runs on a bare host, in Docker or in Kubernetes, and linuxctl gives people the same tools as a kubectl-like CLI.Grant carefully. A root grant is root for an agent that follows instructions found in what it reads. Some grants that look narrow are full root (writes to
/etc,services/manage, sysctl writes). Read Permissions and Risks before granting anything.
internal/worker/spawner.go re-execs the mcpd binary itself in worker mode (arguments on stdin, never argv), with syscall.Credential{Uid, Gid, Groups} set to a real OS account resolved via user.Lookup(). This is the actual privilege isolation, not a config flag: an unprivileged user's worker process is a genuinely different Linux UID than a privileged one's.configs/mcp-sudo.yaml decides, per user and per tool, whether privileged: true is honored. Two grants exist for resources specifically (see ARCHITECTURE.md's gotcha section) - one for the resource URI itself, one for the internal worker tool name behind it.mcpd runs containerized (configs/daemon.yaml's worker.containerized: true, this project's actual Kubernetes deployment), a privileged worker also joins the real host's mount namespace (setns(CLONE_NEWNS) on /proc/1/ns/mnt, no external nsenter binary) - so privileged: true means root on the real host, not just root inside the daemon's own container image.users.yaml (token_salt + token_hash, sha256, constant-time compared; the file is 0600), not stored in plaintext. Users and grants are edited only locally, on the host, via linuxctl <verb> mcpd user and linuxctl edit mcpd config (validated like visudo) - there is no MCP tool that edits them, so nothing with just a bearer token can grant itself anything. The running daemon applies changes without a restart through daemon/reload-config, which only re-reads the files and rejects invalid ones (see Daemon User Administration).linuxctl fetches tools/list/resources/list/resources/templates/list from the live daemon on every invocation and resolves its <verb> <group> [keyword] grammar against that - a new tool added server-side is immediately usable client-side with zero code changes (see plan/linuxctl-redesign.md).configs/mcp-sudo.yaml.singleflight deduplication and TTL caching for efficient system introspection.Needs sudo and curl on an amd64/arm64 host (in a bare ubuntu/debian container: apt update && apt install -y curl ca-certificates, then pipe to bash as root). This downloads the latest release for your architecture (amd64/arm64), verifies its sha256 checksum, installs mcpd and linuxctl to /usr/local/bin, writes clean configs to /etc/mcpd/configs (no default users or tokens), creates a first user mcp and prints its token once, and starts the mcpd systemd service. Then:
mcpd changed. From v0.1.0, grant your first user daemon/reload-config once - see Upgrading.... | sudo bash -s -- --version v0.1.0 --user alice... | sudo bash -s -- --uninstall (add --purge to delete /etc/mcpd; the mcp OS account stays - sudo userdel -r mcp).deb and .rpm for amd64/arm64 on every release - sudo apt install ./linux-mcp-daemon_<version>_amd64.deb or sudo dnf install ./linux-mcp-daemon-<version>-1.x86_64.rpm, then the next steps it prints.ghcr.io/nucleusv/linux-mcp-daemon (amd64/arm64) - setup steps in the installation docs.linuxctl - curl -fsSL .../install.sh | bash -s -- --bin-dir ~/.local/bin, then export PATH="$HOME/.local/bin:$PATH" (not on macOS's default PATH) - to drive a remote mcpd.mcpd listens on all interfaces over TLS (a self-signed certificate it creates on first start; clients pin its fingerprint). Plain HTTP is off by default - bearer tokens would travel in clear text. Root access for tools is granted per user and per tool in
mcp-sudo.yaml.
Full guide: Installation · Connect an AI agent · mcp-sudo.yaml
mcpd)Build the Docker image:
Deploy to your local Kubernetes cluster:
linuxctl)You can build the CLI client directly on your host machine (e.g. macOS):
Installed from a release (install.sh, .deb/.rpm), linuxctl is already on your PATH.
The daemon runs on port 9091. You can connect via your AI client using SSE, or use the linuxctl CLI tool:
linuxctl speaks a small verb/group grammar (linuxctl <verb> <group> [target-keyword] [args], design in plan/linuxctl-redesign.md) and dynamically discovers every tool and resource from the running daemon - there's no separate client-side command list to keep in sync. Full reference: linuxctl docs or man linuxctl. Every example below shows both forms: linuxctl, and the raw MCP JSON-RPC curl call it resolves to - the full per-tool/resource reference with these side by side for every single one lives at MCP API docs.
mcpd speaks JSON-RPC 2.0 over HTTP + SSE, not plain request/response HTTP - every call is a two-step handshake: open an SSE stream to get a one-time POST endpoint, then POST the JSON-RPC request there (the actual result streams back on the SSE connection, not in the POST's response body):
Every example below follows this exact pattern - only the -d payload changes.
privileged: true in containerized deployments)There's no curl equivalent for this group - see Daemon User Administration for why it's deliberately kept off the network entirely.
cmd/mcpd/: Main server application entrypoint.cmd/linuxctl/: The CLI client application.configs/: Configuration files (Daemon config, Sudo rules).internal/: Encapsulated business logic:
auth/: Per-user rate limiting.config/: daemon.yaml, users.yaml and mcp-sudo.yaml parsing and validation (shared by mcpd and linuxctl).rpc/: MCP JSON-RPC handlers - tool/resource registry, authorization, config reload.worker/: Spawns each call as a short-lived worker under the caller's OS account (or root).tools/, resources/: One package per tool and resource.fsafe/: Opening paths without following symlinks (openat with O_NOFOLLOW).kernel/, procstat/: Parsing /proc (sockets, processes).logging/: Leveled, structured logging.netpolicy/: Network destination policy for network/curl and network/ping.k8s/: Kubernetes deployment manifests.scripts/: Build and deployment automation.Pushing a SemVer tag (git tag -a v0.1.0 -m v0.1.0 && git push origin v0.1.0) runs .github/workflows/release.yml: tests, then GoReleaser publishes per-platform archives, checksums.txt and a changelog to GitHub Releases, and a multi-arch image to ghcr.io/nucleusv/linux-mcp-daemon.
Apache License 2.0 - see also NOTICE.