mcp-systemd-crunchtools 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. mcp_systemd_crunchtools-0.1.0/.github/workflows/ci.yml +68 -0
  2. mcp_systemd_crunchtools-0.1.0/.github/workflows/container.yml +111 -0
  3. mcp_systemd_crunchtools-0.1.0/.github/workflows/publish.yml +29 -0
  4. mcp_systemd_crunchtools-0.1.0/.github/workflows/security.yml +109 -0
  5. mcp_systemd_crunchtools-0.1.0/.gitignore +96 -0
  6. mcp_systemd_crunchtools-0.1.0/.pre-commit-config.yaml +31 -0
  7. mcp_systemd_crunchtools-0.1.0/.specify/memory/constitution.md +196 -0
  8. mcp_systemd_crunchtools-0.1.0/CLAUDE.md +68 -0
  9. mcp_systemd_crunchtools-0.1.0/Containerfile +33 -0
  10. mcp_systemd_crunchtools-0.1.0/LICENSE +661 -0
  11. mcp_systemd_crunchtools-0.1.0/PKG-INFO +124 -0
  12. mcp_systemd_crunchtools-0.1.0/README.md +98 -0
  13. mcp_systemd_crunchtools-0.1.0/SECURITY.md +53 -0
  14. mcp_systemd_crunchtools-0.1.0/gourmand-exceptions.toml +9 -0
  15. mcp_systemd_crunchtools-0.1.0/gourmand.toml +55 -0
  16. mcp_systemd_crunchtools-0.1.0/pyproject.toml +80 -0
  17. mcp_systemd_crunchtools-0.1.0/server.json +21 -0
  18. mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/__init__.py +59 -0
  19. mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/__main__.py +6 -0
  20. mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/config.py +78 -0
  21. mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/dbus_client.py +587 -0
  22. mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/errors.py +75 -0
  23. mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/models.py +53 -0
  24. mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/server.py +332 -0
  25. mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/tools/__init__.py +61 -0
  26. mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/tools/files.py +23 -0
  27. mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/tools/journal.py +33 -0
  28. mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/tools/lifecycle.py +58 -0
  29. mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/tools/sessions.py +10 -0
  30. mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/tools/system.py +16 -0
  31. mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/tools/timers.py +10 -0
  32. mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/tools/units.py +24 -0
  33. mcp_systemd_crunchtools-0.1.0/tests/__init__.py +1 -0
  34. mcp_systemd_crunchtools-0.1.0/tests/conftest.py +168 -0
  35. mcp_systemd_crunchtools-0.1.0/tests/test_config.py +64 -0
  36. mcp_systemd_crunchtools-0.1.0/tests/test_tools.py +345 -0
  37. mcp_systemd_crunchtools-0.1.0/tests/test_validation.py +99 -0
@@ -0,0 +1,68 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.10", "3.11", "3.12"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Install uv
20
+ uses: astral-sh/setup-uv@v4
21
+ with:
22
+ version: "latest"
23
+
24
+ - name: Set up Python ${{ matrix.python-version }}
25
+ run: uv python install ${{ matrix.python-version }}
26
+
27
+ - name: Install dependencies
28
+ run: uv sync --all-extras
29
+
30
+ - name: Run linter
31
+ run: uv run ruff check src tests
32
+
33
+ - name: Run type checker
34
+ run: uv run mypy src
35
+
36
+ - name: Run tests
37
+ run: uv run pytest -v
38
+
39
+ gourmand:
40
+ name: Code Quality (Gourmand)
41
+ uses: crunchtools/gatehouse/.github/workflows/gourmand.yml@v0.5.0
42
+
43
+ build-container:
44
+ runs-on: ubuntu-latest
45
+ steps:
46
+ - uses: actions/checkout@v4
47
+
48
+ - name: Build container image
49
+ run: |
50
+ docker build -f Containerfile -t mcp-systemd:test .
51
+
52
+ - name: Verify container
53
+ run: |
54
+ timeout 5 docker run --rm mcp-systemd:test || true
55
+
56
+ validate-constitution:
57
+ name: Constitution Validation
58
+ runs-on: ubuntu-latest
59
+ steps:
60
+ - uses: actions/checkout@v4
61
+
62
+ - uses: actions/checkout@v4
63
+ with:
64
+ repository: crunchtools/constitution
65
+ path: .constitution
66
+
67
+ - name: Validate constitution
68
+ run: python3 .constitution/validate-constitution.py .specify/memory/constitution.md --verbose
@@ -0,0 +1,111 @@
1
+ name: Container Build & Push
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ tags: ["v*"]
7
+ pull_request:
8
+ branches: [main]
9
+ workflow_dispatch:
10
+
11
+ env:
12
+ QUAY_IMAGE: quay.io/crunchtools/mcp-systemd
13
+ GHCR_IMAGE: ghcr.io/crunchtools/mcp-systemd
14
+
15
+ jobs:
16
+ build-and-push-quay:
17
+ runs-on: ubuntu-latest
18
+ permissions:
19
+ contents: read
20
+
21
+ steps:
22
+ - name: Checkout repository
23
+ uses: actions/checkout@v4
24
+
25
+ - name: Set up Docker Buildx
26
+ uses: docker/setup-buildx-action@v3
27
+
28
+ - name: Log in to Quay.io
29
+ if: github.event_name != 'pull_request'
30
+ uses: docker/login-action@v3
31
+ with:
32
+ registry: quay.io
33
+ username: ${{ secrets.QUAY_USERNAME }}
34
+ password: ${{ secrets.QUAY_PASSWORD }}
35
+
36
+ - name: Extract metadata
37
+ id: meta
38
+ uses: docker/metadata-action@v5
39
+ with:
40
+ images: ${{ env.QUAY_IMAGE }}
41
+ tags: |
42
+ type=ref,event=branch
43
+ type=ref,event=pr
44
+ type=semver,pattern={{version}}
45
+ type=semver,pattern={{major}}.{{minor}}
46
+ type=raw,value=latest,enable={{is_default_branch}}
47
+
48
+ - name: Build and push
49
+ uses: docker/build-push-action@v6
50
+ with:
51
+ context: .
52
+ file: ./Containerfile
53
+ push: ${{ github.event_name != 'pull_request' }}
54
+ tags: ${{ steps.meta.outputs.tags }}
55
+ labels: ${{ steps.meta.outputs.labels }}
56
+ cache-from: type=gha
57
+ cache-to: type=gha,mode=max
58
+
59
+ - name: Run Trivy vulnerability scanner
60
+ continue-on-error: true
61
+ if: github.event_name != 'pull_request'
62
+ uses: aquasecurity/trivy-action@master
63
+ with:
64
+ image-ref: ${{ env.QUAY_IMAGE }}:latest
65
+ format: "table"
66
+ exit-code: "0"
67
+ severity: "CRITICAL,HIGH"
68
+
69
+ build-and-push-ghcr:
70
+ runs-on: ubuntu-latest
71
+ needs: build-and-push-quay
72
+ if: github.event_name != 'pull_request'
73
+ permissions:
74
+ contents: read
75
+ packages: write
76
+
77
+ steps:
78
+ - name: Checkout repository
79
+ uses: actions/checkout@v4
80
+
81
+ - name: Set up Docker Buildx
82
+ uses: docker/setup-buildx-action@v3
83
+
84
+ - name: Log in to GitHub Container Registry
85
+ uses: docker/login-action@v3
86
+ with:
87
+ registry: ghcr.io
88
+ username: ${{ github.repository_owner }}
89
+ password: ${{ github.token }}
90
+
91
+ - name: Extract metadata
92
+ id: meta
93
+ uses: docker/metadata-action@v5
94
+ with:
95
+ images: ${{ env.GHCR_IMAGE }}
96
+ tags: |
97
+ type=ref,event=branch
98
+ type=ref,event=pr
99
+ type=semver,pattern={{version}}
100
+ type=semver,pattern={{major}}.{{minor}}
101
+ type=raw,value=latest,enable={{is_default_branch}}
102
+
103
+ - name: Build and push
104
+ uses: docker/build-push-action@v6
105
+ with:
106
+ context: .
107
+ file: ./Containerfile
108
+ push: true
109
+ tags: ${{ steps.meta.outputs.tags }}
110
+ labels: ${{ steps.meta.outputs.labels }}
111
+ cache-from: type=gha
@@ -0,0 +1,29 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ id-token: write
13
+
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - name: Install uv
18
+ uses: astral-sh/setup-uv@v4
19
+ with:
20
+ version: "latest"
21
+
22
+ - name: Set up Python
23
+ run: uv python install 3.12
24
+
25
+ - name: Build package
26
+ run: uv build
27
+
28
+ - name: Publish to PyPI
29
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,109 @@
1
+ name: Security Scan
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+ schedule:
9
+ - cron: "0 9 * * 1"
10
+ workflow_dispatch:
11
+
12
+ permissions:
13
+ contents: read
14
+ issues: write
15
+ pull-requests: write
16
+ security-events: write
17
+
18
+ jobs:
19
+ dependency-audit:
20
+ name: Dependency CVE Scan
21
+ runs-on: ubuntu-latest
22
+
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+
26
+ - name: Install uv
27
+ uses: astral-sh/setup-uv@v4
28
+ with:
29
+ version: "latest"
30
+
31
+ - name: Set up Python
32
+ run: uv python install 3.12
33
+
34
+ - name: Install dependencies
35
+ run: uv sync
36
+
37
+ - name: Install pip-audit
38
+ run: uv pip install pip-audit
39
+
40
+ - name: Run pip-audit
41
+ id: audit
42
+ continue-on-error: true
43
+ run: |
44
+ uv run pip-audit --format=json --output=audit-results.json || true
45
+ uv run pip-audit --format=markdown --output=audit-results.md || true
46
+ if [ -f audit-results.json ]; then
47
+ VULN_COUNT=$(cat audit-results.json | python -c "import sys,json; data=json.load(sys.stdin); print(len([d for d in data if d.get('vulns', [])]))" 2>/dev/null || echo "0")
48
+ echo "vuln_count=$VULN_COUNT" >> $GITHUB_OUTPUT
49
+ else
50
+ echo "vuln_count=0" >> $GITHUB_OUTPUT
51
+ fi
52
+
53
+ - name: Upload audit results
54
+ if: always()
55
+ uses: actions/upload-artifact@v4
56
+ with:
57
+ name: security-audit-results
58
+ path: |
59
+ audit-results.json
60
+ audit-results.md
61
+ retention-days: 30
62
+
63
+ - name: Fail on vulnerabilities (PRs only)
64
+ if: steps.audit.outputs.vuln_count != '0' && github.event_name == 'pull_request'
65
+ run: |
66
+ echo "::error::Security vulnerabilities found in dependencies."
67
+ exit 1
68
+
69
+ container-scan:
70
+ name: Container Security Scan
71
+ runs-on: ubuntu-latest
72
+
73
+ steps:
74
+ - uses: actions/checkout@v4
75
+
76
+ - name: Build container image
77
+ run: docker build -f Containerfile -t mcp-systemd:scan .
78
+
79
+ - name: Run Trivy vulnerability scanner
80
+ uses: aquasecurity/trivy-action@master
81
+ with:
82
+ image-ref: "mcp-systemd:scan"
83
+ format: "sarif"
84
+ output: "trivy-results.sarif"
85
+ severity: "CRITICAL,HIGH"
86
+
87
+ - name: Upload Trivy scan results
88
+ uses: github/codeql-action/upload-sarif@v3
89
+ if: always()
90
+ with:
91
+ sarif_file: "trivy-results.sarif"
92
+
93
+ codeql:
94
+ name: CodeQL Analysis
95
+ runs-on: ubuntu-latest
96
+
97
+ steps:
98
+ - uses: actions/checkout@v4
99
+
100
+ - name: Initialize CodeQL
101
+ uses: github/codeql-action/init@v3
102
+ with:
103
+ languages: python
104
+ queries: security-extended
105
+
106
+ - name: Perform CodeQL Analysis
107
+ uses: github/codeql-action/analyze@v3
108
+ with:
109
+ category: "/language:python"
@@ -0,0 +1,96 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ *.manifest
31
+ *.spec
32
+
33
+ # Installer logs
34
+ pip-log.txt
35
+ pip-delete-this-directory.txt
36
+
37
+ # Unit test / coverage reports
38
+ htmlcov/
39
+ .tox/
40
+ .nox/
41
+ .coverage
42
+ .coverage.*
43
+ .cache
44
+ nosetests.xml
45
+ coverage.xml
46
+ *.cover
47
+ *.py,cover
48
+ .hypothesis/
49
+ .pytest_cache/
50
+ cover/
51
+
52
+ # Translations
53
+ *.mo
54
+ *.pot
55
+
56
+ # Environments
57
+ .env
58
+ .venv
59
+ env/
60
+ venv/
61
+ ENV/
62
+ env.bak/
63
+ venv.bak/
64
+
65
+ # IDE
66
+ .idea/
67
+ .vscode/
68
+ *.swp
69
+ *.swo
70
+ *~
71
+
72
+ # mypy
73
+ .mypy_cache/
74
+ .dmypy.json
75
+ dmypy.json
76
+
77
+ # ruff
78
+ .ruff_cache/
79
+
80
+ # uv
81
+ .uv/
82
+ uv.lock
83
+
84
+ # Local development
85
+ .envrc
86
+ .direnv/
87
+
88
+ # Gourmand
89
+ .gourmand-cache/
90
+
91
+ # OS files
92
+ .DS_Store
93
+ Thumbs.db
94
+
95
+ # MCP Registry auth tokens
96
+ .mcpregistry_*
@@ -0,0 +1,31 @@
1
+ repos:
2
+ - repo: local
3
+ hooks:
4
+ - id: ruff-check
5
+ name: ruff check
6
+ entry: uv run ruff check --fix
7
+ language: system
8
+ types: [python]
9
+ stages: [pre-commit, pre-merge-commit]
10
+ - id: ruff-format
11
+ name: ruff format
12
+ entry: uv run ruff format
13
+ language: system
14
+ types: [python]
15
+ stages: [pre-commit, pre-merge-commit]
16
+ - repo: https://github.com/crunchtools/constitution
17
+ rev: v1.0.0
18
+ hooks:
19
+ - id: validate-constitution
20
+
21
+ # Gourmand runs from its container: the PyPI package of the same name
22
+ # shadows the real binary on PATH, so a bare `gourmand` is the wrong tool.
23
+ - repo: local
24
+ hooks:
25
+ - id: gourmand
26
+ name: Gourmand (AI slop detection)
27
+ entry: podman run --rm -v .:/src:Z -w /src quay.io/crunchtools/gourmand:latest --full
28
+ language: system
29
+ pass_filenames: false
30
+ always_run: true
31
+ stages: [pre-commit, pre-merge-commit]
@@ -0,0 +1,196 @@
1
+ # mcp-systemd-crunchtools Constitution
2
+
3
+ > **Version:** 0.1.0
4
+ > **Ratified:** 2026-09-19
5
+ > **Status:** Active
6
+ > **Inherits:** [crunchtools/constitution](https://github.com/crunchtools/constitution) v1.0.0
7
+ > **Profile:** MCP Server
8
+
9
+ This constitution establishes the core principles, constraints, and workflows that govern all development on mcp-systemd-crunchtools.
10
+
11
+ ---
12
+
13
+ ## I. Core Principles
14
+
15
+ ### 1. Five-Layer Security Model
16
+
17
+ Every change MUST preserve all five security layers. No exceptions.
18
+
19
+ **Layer 1 — Credential Protection:**
20
+ - This server uses D-Bus system bus socket file permissions for authentication — no API tokens
21
+ - Socket path loaded from `DBUS_SYSTEM_BUS_SOCKET` (default `/run/dbus/system_bus_socket`)
22
+ - There are no Pydantic `SecretStr` fields today because the server holds no secret. If a
23
+ credential is ever introduced (a remote bus, an auth proxy), it MUST be typed `SecretStr`,
24
+ MUST come from the environment, and MUST never be logged or returned in a tool response
25
+
26
+ **Layer 2 — Input Validation:**
27
+ - Unit names validated against `UNIT_NAME_PATTERN` before every D-Bus call and every filesystem path resolution — no paths, no traversal
28
+ - Pydantic models enforce strict data types with `extra="forbid"` for compound write inputs
29
+ - Field length limits on all user-provided strings
30
+
31
+ **Layer 3 — Protected-Unit Denylist:**
32
+ - `stop`, `restart`, `disable`, `mask`, and `unit_file_remove` refuse to act on a hardcoded set of core system units (dbus, sshd, networking, logind, journald)
33
+ - Extendable via `SYSTEMD_EXTRA_PROTECTED_UNITS`, never shrinkable at runtime
34
+
35
+ **Layer 4 — Backup Before Mutate:**
36
+ - `unit_file_write` and `unit_file_remove` always copy the file they are about to overwrite or delete to a timestamped backup alongside it before touching it
37
+
38
+ **Layer 5 — Supply Chain Security:**
39
+ - Weekly automated CVE scanning via GitHub Actions
40
+ - Hummingbird container base images (minimal CVE surface)
41
+ - Gourmand AI slop detection gating all PRs
42
+
43
+ ### 2. Two-Layer Tool Architecture
44
+
45
+ Tools follow a strict two-layer pattern:
46
+ - `server.py` — `@mcp.tool()` decorated functions that validate args and delegate
47
+ - `tools/*.py` — thin async pass-through functions grouped by domain, calling `dbus_client.py`
48
+ - `dbus_client.py` — all D-Bus wire logic plus journalctl subprocess calls and unit-file disk I/O
49
+
50
+ Never put business logic in `server.py`. Never put MCP registration in `tools/*.py`.
51
+
52
+ ### 3. Scope: Any Unit, Not Just Podman
53
+
54
+ Unlike the `service_*` tools this server superseded in mcp-podman-crunchtools (RT #1465), mcp-systemd-crunchtools operates on **any** systemd unit. The protected-unit denylist is what keeps that scope safe, not a Podman-only `ExecStart` filter.
55
+
56
+ ### 4. Three Distribution Channels
57
+
58
+ Every release MUST be available through all three channels simultaneously:
59
+
60
+ | Channel | Command | Use Case |
61
+ |---------|---------|----------|
62
+ | uvx | `uvx mcp-systemd-crunchtools` | Zero-install, Claude Code |
63
+ | pip | `pip install mcp-systemd-crunchtools` | Virtual environments |
64
+ | Container | `podman run quay.io/crunchtools/mcp-systemd` | Isolated, systemd |
65
+
66
+ ### 5. Three Transport Modes
67
+
68
+ The server MUST support all three MCP transports:
69
+ - **stdio** (default) — spawned per-session by Claude Code
70
+ - **SSE** — legacy HTTP transport
71
+ - **streamable-http** — production HTTP, systemd-managed containers
72
+
73
+ ### 6. Semantic Versioning
74
+
75
+ Follow [Semantic Versioning 2.0.0](https://semver.org/) strictly.
76
+
77
+ **MAJOR** (breaking changes): Removed/renamed tools, changed parameters
78
+ **MINOR** (new functionality): New tools, new optional parameters
79
+ **PATCH** (fixes): Bug fixes, security patches, test improvements
80
+
81
+ ### 7. AI Code Quality
82
+
83
+ All code MUST pass Gourmand checks before merge. Zero violations required.
84
+
85
+ ---
86
+
87
+ ## II. Technology Stack
88
+
89
+ | Layer | Technology | Version |
90
+ |-------|------------|---------|
91
+ | Language | Python | 3.10+ |
92
+ | MCP Framework | FastMCP | Latest |
93
+ | D-Bus Client | dbus-fast | Latest |
94
+ | Validation | Pydantic | v2 |
95
+ | Container Base | Hummingbird | Latest |
96
+ | Package Manager | uv | Latest |
97
+ | Build System | hatchling | Latest |
98
+ | Linter | ruff | Latest |
99
+ | Type Checker | mypy (strict) | Latest |
100
+ | Tests | pytest + pytest-asyncio | Latest |
101
+ | Slop Detector | gourmand | Latest |
102
+
103
+ ---
104
+
105
+ ## III. Testing Standards
106
+
107
+ ### Mocked D-Bus Tests (MANDATORY)
108
+
109
+ Every tool MUST have a corresponding mocked test. Tests patch `dbus_client._get_bus` and `dbus_client._call`/`_get_property` — no live D-Bus connection, no host socket required in CI.
110
+
111
+ **Tool count assertion:** `test_tool_count` MUST be updated whenever tools are added or removed.
112
+
113
+ ### Input Validation Tests
114
+
115
+ Unit-name validation and `UnitFileWriteInput` MUST have tests in `test_validation.py`:
116
+ - Valid minimal input
117
+ - Invalid/rejected inputs (path traversal, bad suffix, empty strings, extra fields)
118
+
119
+ ---
120
+
121
+ ## IV. Gourmand (AI Slop Detection)
122
+
123
+ All code MUST pass `gourmand --full .` with **zero violations** before merge.
124
+
125
+ ### Exception Policy
126
+
127
+ Exceptions MUST have documented justifications in `gourmand-exceptions.toml`. Acceptable reasons:
128
+ - Standard API patterns (assigned port numbers)
129
+ - Framework requirements (CLAUDE.md for Claude Code)
130
+
131
+ ---
132
+
133
+ ## V. Code Quality Gates
134
+
135
+ Every code change must pass through these gates in order:
136
+
137
+ 1. **Lint** — `uv run ruff check src tests`
138
+ 2. **Type Check** — `uv run mypy src`
139
+ 3. **Tests** — `uv run pytest -v` (all passing, mocked D-Bus)
140
+ 4. **Gourmand** — `gourmand --full .` (zero violations)
141
+ 5. **Container Build** — `podman build -f Containerfile .`
142
+
143
+ ---
144
+
145
+ ## VI. Naming Conventions
146
+
147
+ | Context | Name |
148
+ |---------|------|
149
+ | GitHub repo | `crunchtools/mcp-systemd` |
150
+ | PyPI package | `mcp-systemd-crunchtools` |
151
+ | CLI command | `mcp-systemd-crunchtools` |
152
+ | Python module | `mcp_systemd_crunchtools` |
153
+ | Container image | `quay.io/crunchtools/mcp-systemd` |
154
+ | systemd service | `mcp-systemd.service` |
155
+ | HTTP port | 8022 |
156
+ | License | AGPL-3.0-or-later |
157
+
158
+ ---
159
+
160
+ ## VII. Development Workflow
161
+
162
+ ### Adding a New Tool
163
+
164
+ 1. Add the async function to `dbus_client.py`
165
+ 2. Add a thin wrapper to the appropriate `tools/*.py` file and export it from `tools/__init__.py`
166
+ 3. Import it in `server.py` and register with `@mcp.tool()`
167
+ 4. Add a mocked test in `tests/test_tools.py`
168
+ 5. Update the tool count in `test_tool_count`
169
+ 6. Run all five quality gates
170
+ 7. Update CLAUDE.md and README.md tool listings
171
+
172
+ ---
173
+
174
+ ## VIII. SELinux and Container Deployment
175
+
176
+ When running in a container with the host D-Bus socket and/or unit directory mounted:
177
+ - Use `--security-opt label=type:container_runtime_t` on the container
178
+ - Mount both paths with `:z`/`:Z` relabel flags
179
+ - The container runs as `container_runtime_t` domain, which can `connectto` the D-Bus socket
180
+
181
+ ---
182
+
183
+ ## IX. Governance
184
+
185
+ ### Amendment Process
186
+
187
+ 1. Create a PR with proposed changes to this constitution
188
+ 2. Document rationale in PR description
189
+ 3. Require maintainer approval
190
+ 4. Update version number upon merge
191
+
192
+ ### Ratification History
193
+
194
+ | Version | Date | Changes |
195
+ |---------|------|---------|
196
+ | 0.1.0 | 2026-09-19 | Initial constitution — split from mcp-podman-crunchtools per RT #1465 |
@@ -0,0 +1,68 @@
1
+ # mcp-systemd-crunchtools
2
+
3
+ MCP server for systemd unit management via D-Bus (system bus), plus journalctl for filtered log queries (no D-Bus equivalent exists for arbitrary journal filters).
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ uv sync --all-extras
9
+ uv run pytest -v
10
+ ```
11
+
12
+ ## Environment Variables
13
+
14
+ | Variable | Required | Default | Description |
15
+ |----------|----------|---------|-------------|
16
+ | `DBUS_SYSTEM_BUS_SOCKET` | No | `/run/dbus/system_bus_socket` | D-Bus system bus socket path |
17
+ | `SYSTEMD_UNIT_DIR` | No | `/etc/systemd/system` | Directory unit_file_write/remove operate on |
18
+ | `SYSTEMD_EXTRA_PROTECTED_UNITS` | No | — | Comma-separated units added to the built-in denylist |
19
+
20
+ ## Tools (21)
21
+
22
+ ### Units (3)
23
+ unit_list, unit_status, unit_show
24
+
25
+ ### Lifecycle (9)
26
+ unit_start, unit_stop, unit_restart, unit_reload, unit_enable, unit_disable, unit_mask, unit_unmask, daemon_reload
27
+
28
+ ### Unit files (2) — highest blast radius, writes/deletes host files
29
+ unit_file_write, unit_file_remove
30
+
31
+ ### Troubleshooting (3)
32
+ journal_query, failed_units, list_jobs
33
+
34
+ ### Timers (1)
35
+ timer_list
36
+
37
+ ### System (2)
38
+ system_status, hostinfo
39
+
40
+ ### Sessions (1)
41
+ session_list
42
+
43
+ ## Development Commands
44
+
45
+ ```bash
46
+ uv run ruff check src tests # Lint
47
+ uv run mypy src # Type check
48
+ uv run pytest -v # Tests
49
+ gourmand --full . # AI slop detection
50
+ podman build -f Containerfile . # Container build
51
+ ```
52
+
53
+ ## Architecture
54
+
55
+ Two-layer tool pattern:
56
+ - `server.py` — `@mcp.tool()` wrappers with `_tool` suffix
57
+ - `tools/*.py` — thin pass-through async functions grouped by domain, calling `dbus_client.py`
58
+ - `dbus_client.py` — all D-Bus wire logic (systemd1 Manager/Unit/Service/Timer, hostname1, login1) plus the journalctl subprocess call and unit-file disk I/O
59
+
60
+ ## Safety model
61
+
62
+ This server supersedes mcp-podman's old `service_*` tools and operates on **any** systemd unit, not just Podman-managed ones — split out per RT #1465. Three layers keep that broad scope from being a foot-gun:
63
+
64
+ 1. **Unit name validation** (`models.UNIT_NAME_PATTERN`) — bare names only, no paths, no traversal. Rejects anything that isn't `name.suffix` with a recognized systemd unit suffix.
65
+ 2. **Protected-unit denylist** (`config.DEFAULT_PROTECTED_UNITS`, extendable via `SYSTEMD_EXTRA_PROTECTED_UNITS`) — blocks `stop`/`restart`/`disable`/`mask`/`unit_file_remove` on core units (dbus, sshd, networking, logind, journald) so an agent can't take down the box it's running on. Never bypassable for the built-in set.
66
+ 3. **Backup-before-write** — `unit_file_write` and `unit_file_remove` always copy the existing file to `<name>.bak-<timestamp>` alongside the original before overwriting or deleting it.
67
+
68
+ `unit_file_write`/`unit_file_remove` require the host's `SYSTEMD_UNIT_DIR` bind-mounted into the container; every other tool only needs the D-Bus socket.