Skip to main content
Glama
Albe83

mcp-systemd

by Albe83

mcp-systemd

Experimental semantic MCP interface for systemd.

The MCP server is the real implementation. The systemd backend is selected from configuration: an in-memory simulated backend (default) or the host's real system manager over D-Bus.

Architecture

The server uses a small hexagonal boundary:

MCP Resources / Tools
        |
        v
   domain.Systemd
        |
        v
    backend adapter

The domain model and Systemd port live under src/mcp_systemd/domain/. Concrete adapters live under src/mcp_systemd/backends/.

The default composition uses MockSystemd. Setting backend.type: dbus composes DbusSystemd against the local system manager instead. Resources and Tools depend only on the domain port, so the backend can be replaced without changing the MCP interface.

MCP URIs and payload serialization remain outside the domain model.

Related MCP server: Design System MCP Server

Resource templates

systemd://system/unit/{type}/{name}
systemd://user/{user}/unit/{type}/{name}

systemd://system/unit/{type}/{name}/definition
systemd://user/{user}/unit/{type}/{name}/definition

The initial mock supports service and timer as unit types.

For each represented systemd manager, discovery follows the logical semantics of systemctl list-units --all, restricted to supported unit types. resources/list exposes the combined concrete Resource catalog across the represented managers on the host.

Reading a unit resource returns compact runtime state:

{
  "description": "OpenSSH server daemon",
  "load_state": "loaded",
  "active_state": "active",
  "sub_state": "running"
}

The URI carries the unit identity, so the payload does not repeat the unit name.

The /definition sub-resource returns the unit file and drop-ins as text. It is separate because configuration content can be much larger and is not needed for most state-oriented decisions.

Model-facing tools

The server exposes semantic tools that make the Resource model usable even when an MCP harness does not expose Resource operations directly to the model:

  • list_units(type?, user?) discovers unit Resources;

  • read_unit(uri) reads the compact current state of a unit;

  • read_unit_definition(uri) reads its definition and drop-ins;

  • list_unit_types() describes the unit types supported by mcp-systemd.

read_unit and read_unit_definition both accept the canonical base unit Resource URI. The model does not need to construct sub-resource URIs.

The Resources remain canonical. The first three tools are a semantic fallback and belong to the resource_api_fallback exposure group.

Tool exposure

Tool exposure is configured independently from MCP Tool Annotations.

Each tool can belong to zero or more exposure groups. Groups act as feature flags for the model-facing tool surface, and individual tools can be enabled or disabled as explicit overrides.

Example:

tools:
  groups:
    resource_api_fallback: true

  enable: []
  disable: []

The default resource_api_fallback group contains:

list_units
read_unit
read_unit_definition

Set the group to false when the harness already provides equivalent model-controlled Resource discovery, sub-resource/template discovery, and reading.

An explicit per-tool override takes precedence over group membership:

tools:
  groups:
    resource_api_fallback: false

  enable:
    - read_unit

  disable:
    - list_unit_types

A tool cannot appear in both enable and disable.

Semantic evaluations

The evals/ directory contains repeatable model-facing evaluation cases for discovery, state inspection, unit definitions, unsupported capabilities, and token-efficient behavior.

Validate the corpus with:

uv run python evals/validate.py

See evals/README.md for the case format and execution protocol.

Tests

Run the unit and integration suites separately:

uv run python -m unittest discover -s tests/unit
uv run python -m unittest discover -s tests/integration

Semantic model behavior remains covered separately under evals/.

See tests/README.md for the TDD workflow and test-layer definitions.

A manual-only GitHub Actions workflow named CI runs the locked install, functional tests, and semantic-eval validation in a clean environment. It is not triggered by pushes or pull requests.

Run

uv sync --locked
uv run mcp-systemd

Runtime dependencies are committed in uv.lock.

The server uses FastMCP HTTP transport.

By default it listens only on 127.0.0.1:48000.

Backends

The server can run against two backends, selected from configuration:

  • mock (default): the in-memory simulated backend used for development, tests, and semantic evaluation;

  • dbus: the real system manager over D-Bus, read-only.

backend:
  type: mock
backend:
  type: dbus

Backend selection is composition only. Building the server does not connect to D-Bus: the connection is lazy and happens on the first operation. Failures surface on that first operation and are never replaced by an automatic fallback to the mock.

With dbus, only system services and timers are supported:

  • discovery (list_units) and fresh runtime-state reads (read_unit) work;

  • unit definitions (read_unit_definition) and user managers are not implemented and fail explicitly;

  • list_unit_types lists the supported domain unit types, not a backend capability matrix.

Capabilities are not inferred from the selected backend: tool exposure remains controlled by tools.groups, tools.enable, and tools.disable. The example below disables read_unit_definition for a D-Bus trial. Disabling the tool hides that tool only; the definition and user Resource templates still exist, and unsupported reads fail.

Configuration

The default configuration path is /etc/mcp-systemd/config.yaml.

When no --config is given, the default path is used if it exists; if it is absent, built-in defaults are used (mock backend, 127.0.0.1:48000).

An explicit path is required to exist: uv run mcp-systemd --config ./config.yaml exits nonzero if the file is missing or invalid, before any server is started.

Configuration is read once at startup; restart the server after changing it.

Example:

server:
  host: 127.0.0.1
  port: 48000

backend:
  type: dbus

tools:
  disable:
    - read_unit_definition

backend is optional. A missing backend section, or backend: {}, selects mock. Only type is allowed inside backend; unknown keys inside backend, unknown root keys, and unsupported backend types are rejected. Supported values are the exact, case-sensitive strings mock and dbus.

Launch with a configuration file:

uv run mcp-systemd --config ./config.yaml

On startup the CLI prints the selected backend to stderr, for example backend=dbus (local system manager). This announces the selection; it does not mean a D-Bus connection has been established.

Related MCP Connectors

Related MCP Servers