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.
- mcp_systemd_crunchtools-0.1.0/.github/workflows/ci.yml +68 -0
- mcp_systemd_crunchtools-0.1.0/.github/workflows/container.yml +111 -0
- mcp_systemd_crunchtools-0.1.0/.github/workflows/publish.yml +29 -0
- mcp_systemd_crunchtools-0.1.0/.github/workflows/security.yml +109 -0
- mcp_systemd_crunchtools-0.1.0/.gitignore +96 -0
- mcp_systemd_crunchtools-0.1.0/.pre-commit-config.yaml +31 -0
- mcp_systemd_crunchtools-0.1.0/.specify/memory/constitution.md +196 -0
- mcp_systemd_crunchtools-0.1.0/CLAUDE.md +68 -0
- mcp_systemd_crunchtools-0.1.0/Containerfile +33 -0
- mcp_systemd_crunchtools-0.1.0/LICENSE +661 -0
- mcp_systemd_crunchtools-0.1.0/PKG-INFO +124 -0
- mcp_systemd_crunchtools-0.1.0/README.md +98 -0
- mcp_systemd_crunchtools-0.1.0/SECURITY.md +53 -0
- mcp_systemd_crunchtools-0.1.0/gourmand-exceptions.toml +9 -0
- mcp_systemd_crunchtools-0.1.0/gourmand.toml +55 -0
- mcp_systemd_crunchtools-0.1.0/pyproject.toml +80 -0
- mcp_systemd_crunchtools-0.1.0/server.json +21 -0
- mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/__init__.py +59 -0
- mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/__main__.py +6 -0
- mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/config.py +78 -0
- mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/dbus_client.py +587 -0
- mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/errors.py +75 -0
- mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/models.py +53 -0
- mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/server.py +332 -0
- mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/tools/__init__.py +61 -0
- mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/tools/files.py +23 -0
- mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/tools/journal.py +33 -0
- mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/tools/lifecycle.py +58 -0
- mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/tools/sessions.py +10 -0
- mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/tools/system.py +16 -0
- mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/tools/timers.py +10 -0
- mcp_systemd_crunchtools-0.1.0/src/mcp_systemd_crunchtools/tools/units.py +24 -0
- mcp_systemd_crunchtools-0.1.0/tests/__init__.py +1 -0
- mcp_systemd_crunchtools-0.1.0/tests/conftest.py +168 -0
- mcp_systemd_crunchtools-0.1.0/tests/test_config.py +64 -0
- mcp_systemd_crunchtools-0.1.0/tests/test_tools.py +345 -0
- 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.
|