sysflow-mcp 1.0.1__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.
- sysflow_mcp-1.0.1/LICENSE +21 -0
- sysflow_mcp-1.0.1/PKG-INFO +172 -0
- sysflow_mcp-1.0.1/README.md +154 -0
- sysflow_mcp-1.0.1/pyproject.toml +32 -0
- sysflow_mcp-1.0.1/setup.cfg +4 -0
- sysflow_mcp-1.0.1/sysflow/__init__.py +8 -0
- sysflow_mcp-1.0.1/sysflow/_genesis.py +11 -0
- sysflow_mcp-1.0.1/sysflow/cli.py +61 -0
- sysflow_mcp-1.0.1/sysflow/monitors/logs.py +71 -0
- sysflow_mcp-1.0.1/sysflow/monitors/resources.py +76 -0
- sysflow_mcp-1.0.1/sysflow/monitors/systemd.py +55 -0
- sysflow_mcp-1.0.1/sysflow/server.py +193 -0
- sysflow_mcp-1.0.1/sysflow_mcp.egg-info/PKG-INFO +172 -0
- sysflow_mcp-1.0.1/sysflow_mcp.egg-info/SOURCES.txt +17 -0
- sysflow_mcp-1.0.1/sysflow_mcp.egg-info/dependency_links.txt +1 -0
- sysflow_mcp-1.0.1/sysflow_mcp.egg-info/entry_points.txt +3 -0
- sysflow_mcp-1.0.1/sysflow_mcp.egg-info/requires.txt +3 -0
- sysflow_mcp-1.0.1/sysflow_mcp.egg-info/top_level.txt +1 -0
- sysflow_mcp-1.0.1/tests/test_genesis_entrypoints.py +25 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Johann Schiffer / NEXUS
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sysflow-mcp
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Safe Linux Sysadmin, Process & Build Automation Engine for AI Agents
|
|
5
|
+
Author: Johann Schiffer
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://johannschiffer.github.io
|
|
8
|
+
Project-URL: Repository, https://github.com/johannschiffer/sysflow-mcp
|
|
9
|
+
Project-URL: Starter Kit, https://johanld.gumroad.com/l/genesis-starter-kit
|
|
10
|
+
Project-URL: Enterprise Suite, https://johanld.gumroad.com/l/genesis-enterprise-suite
|
|
11
|
+
Keywords: mcp,model-context-protocol,sysadmin,linux,systemd,claude,cursor,monitoring,ai-agent
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Provides-Extra: test
|
|
16
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# SysFlow MCP (Lite Edition)
|
|
20
|
+
|
|
21
|
+
<a href="https://glama.ai/mcp/servers/@johannschiffer/sysflow-mcp">
|
|
22
|
+
<img width="380" height="200" src="https://glama.ai/mcp/servers/@johannschiffer/sysflow-mcp/badge" alt="sysflow-mcp MCP server" />
|
|
23
|
+
</a>
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
> **Free:** [Genesis Starter Kit](https://johanld.gumroad.com/l/genesis-starter-kit) (25 `.cursorrules`/`CLAUDE.md` rulesets + MCP configs). Try the [live config builder](https://johannschiffer.github.io).
|
|
27
|
+
> **Full stack:** all six servers in [Genesis Enterprise Suite ($19)](https://johanld.gumroad.com/l/genesis-enterprise-suite).
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
[](LICENSE)
|
|
31
|
+
[](https://python.org)
|
|
32
|
+
[](https://modelcontextprotocol.io)
|
|
33
|
+
|
|
34
|
+
**SysFlow MCP** is a lightweight, zero-overhead Model Context Protocol server that gives AI agents in **Claude Desktop**, **Cursor**, **Windsurf**, and **Antigravity** safe, structured access to Linux sysadmin inspection, log tailing, and service health without granting unrestricted root shells or risking interactive terminal freezes.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Run without cloning
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
uvx sysflow-mcp
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Claude Desktop / Cursor config entry:
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"mcpServers": {
|
|
49
|
+
"sysflow-mcp": {
|
|
50
|
+
"command": "uvx",
|
|
51
|
+
"args": [
|
|
52
|
+
"--from",
|
|
53
|
+
"git+https://github.com/johannschiffer/sysflow-mcp",
|
|
54
|
+
"sysflow-mcp"
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
[](https://github.com/johannschiffer/sysflow-mcp/actions/workflows/ci.yml)
|
|
62
|
+
|
|
63
|
+
## ðĨïļ Why SysFlow MCP?
|
|
64
|
+
|
|
65
|
+
Giving an AI assistant raw terminal execution often ends in frustration:
|
|
66
|
+
1. **Interactive Terminal Freezes:** Commands like `journalctl`, `systemctl status`, or `git log` default to interactive pagers (`less`), leaving the AI agent permanently hung waiting for user input.
|
|
67
|
+
2. **Context Window Flooding:** Tailing an unconstrained `/var/log/nginx/access.log` dumps thousands of lines into the LLM prompt, wasting tokens and causing memory errors.
|
|
68
|
+
3. **Destructive Shell Commands:** Without sandboxing, a hallucinated command can kill critical background daemons or delete workspace assets.
|
|
69
|
+
|
|
70
|
+
**SysFlow** exposes safe, bounded, non-blocking tools specifically tailored for autonomous developer agents.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## ⥠Features (Lite Edition)
|
|
75
|
+
|
|
76
|
+
- ð **Resource Healthcheck**: Reports CPU core load averages (1m, 5m, 15m), RAM availability (MB and percentage), and disk partition metrics.
|
|
77
|
+
- ð **Safe Log Tailer**: Reads the last N lines (bounded and memory-safe) with optional regex filtering; automatically rejects binary files.
|
|
78
|
+
- âïļ **Systemd User Service Inspector**: Lists user-level services and timers (`systemctl --user`), highlighting failed units without requiring `sudo`.
|
|
79
|
+
- ðŠķ **Zero External Dependencies**: Operates strictly on Python standard library and Linux `/proc` filesystem.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## ð Quick Setup in 2 Minutes
|
|
84
|
+
|
|
85
|
+
### 1. Installation
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
git clone https://github.com/johannschiffer/sysflow-mcp.git
|
|
89
|
+
cd sysflow-mcp
|
|
90
|
+
pip install .
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 2. Configure Claude Desktop (`claude_desktop_config.json`)
|
|
94
|
+
|
|
95
|
+
On Linux: `~/.config/Claude/claude_desktop_config.json`
|
|
96
|
+
On macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
97
|
+
On Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"mcpServers": {
|
|
102
|
+
"sysflow": {
|
|
103
|
+
"command": "python",
|
|
104
|
+
"args": ["-m", "sysflow.server"],
|
|
105
|
+
"cwd": "/path/to/sysflow-mcp"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 3. Verify via CLI
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
python -m sysflow.cli resources
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## ð ïļ MCP Tools Exposed to AI Agents
|
|
120
|
+
|
|
121
|
+
| Tool | Parameters | Description |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| `check_system_resources` | *(none)* | Returns CPU load, memory usage, and partition disk metrics |
|
|
124
|
+
| `tail_logs` | `filepath`, `lines`, `pattern` | Safely tails recent log lines with optional regex filter |
|
|
125
|
+
| `inspect_systemd_services` | `failed_only` | Lists user-level services, states, and detects failed daemons |
|
|
126
|
+
| `inspect_systemd_timers` | *(none)* | Lists scheduled systemd user timers |
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## ð Need Enterprise Build Sandboxing? Upgrade to SysFlow Pro
|
|
131
|
+
|
|
132
|
+
Looking to automate shell task execution, manage Docker containers, or generate verified systemd units?
|
|
133
|
+
|
|
134
|
+
| Feature | Lite (Open Source) | Enterprise Suite ($19) |
|
|
135
|
+
|---|:---:|:---:|
|
|
136
|
+
| **CPU, RAM & Disk Monitoring** | â
| â
|
|
|
137
|
+
| **Safe Bounded Log Tailer** | â
| â
|
|
|
138
|
+
| **Systemd User Service Auditing** | â
| â
|
|
|
139
|
+
| **Sandboxed Task Runner with Timeouts** *(Anti-freeze)* | â | â
|
|
|
140
|
+
| **Docker Container Health & Log Inspector** | â | â
|
|
|
141
|
+
| **Verified Systemd Unit File Generator** | â | â
|
|
|
142
|
+
| **Listening Socket & Port Conflict Auditor** | â | â
|
|
|
143
|
+
| **Production Docker Container** | â | â
|
|
|
144
|
+
| **Full Pytest Suite & Sysadmin Automation Guide** | â | â
|
|
|
145
|
+
|
|
146
|
+
ð **[Get the Genesis Enterprise Suite ($19)](https://johanld.gumroad.com/l/genesis-enterprise-suite)**
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## ð License
|
|
151
|
+
|
|
152
|
+
MIT License. See [LICENSE](LICENSE) for details.
|
|
153
|
+
|
|
154
|
+
## Recommended Infrastructure
|
|
155
|
+
|
|
156
|
+
VPS hosting that work well with SysFlow MCP. Some links are referral links.
|
|
157
|
+
|
|
158
|
+
- [Hetzner Cloud](https://www.hetzner.com/cloud)
|
|
159
|
+
- [DigitalOcean](https://www.digitalocean.com/)
|
|
160
|
+
|
|
161
|
+
## Genesis MCP Ecosystem
|
|
162
|
+
|
|
163
|
+
| Module | What it does | Repo |
|
|
164
|
+
|---|---|---|
|
|
165
|
+
| **CrawlEngine MCP** | Anti-bot web scraping, clean Markdown, proxy rotation and TLS evasion | [GitHub](https://github.com/johannschiffer/crawlengine-mcp) |
|
|
166
|
+
| **BrowserBridge MCP** | Deterministic headless browser automation with cryptographic action receipts | [GitHub](https://github.com/johannschiffer/browserbridge-mcp) |
|
|
167
|
+
| **PgSafe MCP** | Zero-trust PostgreSQL/SQLite guard, live PII masking, zero-downtime migration checks | [GitHub](https://github.com/johannschiffer/pgsafe-mcp) |
|
|
168
|
+
| **OmniBridge MCP** | Multi-channel notification and webhook bus with a persistent SQLite WAL queue | [GitHub](https://github.com/johannschiffer/omnibridge-mcp) |
|
|
169
|
+
| **SysFlow MCP** (this repo) | Safe Linux administration plane: systemd, port audit, isolated execution | [GitHub](https://github.com/johannschiffer/sysflow-mcp) |
|
|
170
|
+
| **FastMCP Forge** | Production MCP server scaffolder: stdio, SSE and auth middleware | [GitHub](https://github.com/johannschiffer/fastmcp-forge) |
|
|
171
|
+
|
|
172
|
+
[Live config builder](https://johannschiffer.github.io) | [Starter Kit, free](https://johanld.gumroad.com/l/genesis-starter-kit) | [Enterprise Suite, $19](https://johanld.gumroad.com/l/genesis-enterprise-suite)
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# SysFlow MCP (Lite Edition)
|
|
2
|
+
|
|
3
|
+
<a href="https://glama.ai/mcp/servers/@johannschiffer/sysflow-mcp">
|
|
4
|
+
<img width="380" height="200" src="https://glama.ai/mcp/servers/@johannschiffer/sysflow-mcp/badge" alt="sysflow-mcp MCP server" />
|
|
5
|
+
</a>
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
> **Free:** [Genesis Starter Kit](https://johanld.gumroad.com/l/genesis-starter-kit) (25 `.cursorrules`/`CLAUDE.md` rulesets + MCP configs). Try the [live config builder](https://johannschiffer.github.io).
|
|
9
|
+
> **Full stack:** all six servers in [Genesis Enterprise Suite ($19)](https://johanld.gumroad.com/l/genesis-enterprise-suite).
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
[](LICENSE)
|
|
13
|
+
[](https://python.org)
|
|
14
|
+
[](https://modelcontextprotocol.io)
|
|
15
|
+
|
|
16
|
+
**SysFlow MCP** is a lightweight, zero-overhead Model Context Protocol server that gives AI agents in **Claude Desktop**, **Cursor**, **Windsurf**, and **Antigravity** safe, structured access to Linux sysadmin inspection, log tailing, and service health without granting unrestricted root shells or risking interactive terminal freezes.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Run without cloning
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
uvx sysflow-mcp
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Claude Desktop / Cursor config entry:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"mcpServers": {
|
|
31
|
+
"sysflow-mcp": {
|
|
32
|
+
"command": "uvx",
|
|
33
|
+
"args": [
|
|
34
|
+
"--from",
|
|
35
|
+
"git+https://github.com/johannschiffer/sysflow-mcp",
|
|
36
|
+
"sysflow-mcp"
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
[](https://github.com/johannschiffer/sysflow-mcp/actions/workflows/ci.yml)
|
|
44
|
+
|
|
45
|
+
## ðĨïļ Why SysFlow MCP?
|
|
46
|
+
|
|
47
|
+
Giving an AI assistant raw terminal execution often ends in frustration:
|
|
48
|
+
1. **Interactive Terminal Freezes:** Commands like `journalctl`, `systemctl status`, or `git log` default to interactive pagers (`less`), leaving the AI agent permanently hung waiting for user input.
|
|
49
|
+
2. **Context Window Flooding:** Tailing an unconstrained `/var/log/nginx/access.log` dumps thousands of lines into the LLM prompt, wasting tokens and causing memory errors.
|
|
50
|
+
3. **Destructive Shell Commands:** Without sandboxing, a hallucinated command can kill critical background daemons or delete workspace assets.
|
|
51
|
+
|
|
52
|
+
**SysFlow** exposes safe, bounded, non-blocking tools specifically tailored for autonomous developer agents.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## ⥠Features (Lite Edition)
|
|
57
|
+
|
|
58
|
+
- ð **Resource Healthcheck**: Reports CPU core load averages (1m, 5m, 15m), RAM availability (MB and percentage), and disk partition metrics.
|
|
59
|
+
- ð **Safe Log Tailer**: Reads the last N lines (bounded and memory-safe) with optional regex filtering; automatically rejects binary files.
|
|
60
|
+
- âïļ **Systemd User Service Inspector**: Lists user-level services and timers (`systemctl --user`), highlighting failed units without requiring `sudo`.
|
|
61
|
+
- ðŠķ **Zero External Dependencies**: Operates strictly on Python standard library and Linux `/proc` filesystem.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## ð Quick Setup in 2 Minutes
|
|
66
|
+
|
|
67
|
+
### 1. Installation
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
git clone https://github.com/johannschiffer/sysflow-mcp.git
|
|
71
|
+
cd sysflow-mcp
|
|
72
|
+
pip install .
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 2. Configure Claude Desktop (`claude_desktop_config.json`)
|
|
76
|
+
|
|
77
|
+
On Linux: `~/.config/Claude/claude_desktop_config.json`
|
|
78
|
+
On macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
79
|
+
On Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"mcpServers": {
|
|
84
|
+
"sysflow": {
|
|
85
|
+
"command": "python",
|
|
86
|
+
"args": ["-m", "sysflow.server"],
|
|
87
|
+
"cwd": "/path/to/sysflow-mcp"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 3. Verify via CLI
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
python -m sysflow.cli resources
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## ð ïļ MCP Tools Exposed to AI Agents
|
|
102
|
+
|
|
103
|
+
| Tool | Parameters | Description |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| `check_system_resources` | *(none)* | Returns CPU load, memory usage, and partition disk metrics |
|
|
106
|
+
| `tail_logs` | `filepath`, `lines`, `pattern` | Safely tails recent log lines with optional regex filter |
|
|
107
|
+
| `inspect_systemd_services` | `failed_only` | Lists user-level services, states, and detects failed daemons |
|
|
108
|
+
| `inspect_systemd_timers` | *(none)* | Lists scheduled systemd user timers |
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## ð Need Enterprise Build Sandboxing? Upgrade to SysFlow Pro
|
|
113
|
+
|
|
114
|
+
Looking to automate shell task execution, manage Docker containers, or generate verified systemd units?
|
|
115
|
+
|
|
116
|
+
| Feature | Lite (Open Source) | Enterprise Suite ($19) |
|
|
117
|
+
|---|:---:|:---:|
|
|
118
|
+
| **CPU, RAM & Disk Monitoring** | â
| â
|
|
|
119
|
+
| **Safe Bounded Log Tailer** | â
| â
|
|
|
120
|
+
| **Systemd User Service Auditing** | â
| â
|
|
|
121
|
+
| **Sandboxed Task Runner with Timeouts** *(Anti-freeze)* | â | â
|
|
|
122
|
+
| **Docker Container Health & Log Inspector** | â | â
|
|
|
123
|
+
| **Verified Systemd Unit File Generator** | â | â
|
|
|
124
|
+
| **Listening Socket & Port Conflict Auditor** | â | â
|
|
|
125
|
+
| **Production Docker Container** | â | â
|
|
|
126
|
+
| **Full Pytest Suite & Sysadmin Automation Guide** | â | â
|
|
|
127
|
+
|
|
128
|
+
ð **[Get the Genesis Enterprise Suite ($19)](https://johanld.gumroad.com/l/genesis-enterprise-suite)**
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## ð License
|
|
133
|
+
|
|
134
|
+
MIT License. See [LICENSE](LICENSE) for details.
|
|
135
|
+
|
|
136
|
+
## Recommended Infrastructure
|
|
137
|
+
|
|
138
|
+
VPS hosting that work well with SysFlow MCP. Some links are referral links.
|
|
139
|
+
|
|
140
|
+
- [Hetzner Cloud](https://www.hetzner.com/cloud)
|
|
141
|
+
- [DigitalOcean](https://www.digitalocean.com/)
|
|
142
|
+
|
|
143
|
+
## Genesis MCP Ecosystem
|
|
144
|
+
|
|
145
|
+
| Module | What it does | Repo |
|
|
146
|
+
|---|---|---|
|
|
147
|
+
| **CrawlEngine MCP** | Anti-bot web scraping, clean Markdown, proxy rotation and TLS evasion | [GitHub](https://github.com/johannschiffer/crawlengine-mcp) |
|
|
148
|
+
| **BrowserBridge MCP** | Deterministic headless browser automation with cryptographic action receipts | [GitHub](https://github.com/johannschiffer/browserbridge-mcp) |
|
|
149
|
+
| **PgSafe MCP** | Zero-trust PostgreSQL/SQLite guard, live PII masking, zero-downtime migration checks | [GitHub](https://github.com/johannschiffer/pgsafe-mcp) |
|
|
150
|
+
| **OmniBridge MCP** | Multi-channel notification and webhook bus with a persistent SQLite WAL queue | [GitHub](https://github.com/johannschiffer/omnibridge-mcp) |
|
|
151
|
+
| **SysFlow MCP** (this repo) | Safe Linux administration plane: systemd, port audit, isolated execution | [GitHub](https://github.com/johannschiffer/sysflow-mcp) |
|
|
152
|
+
| **FastMCP Forge** | Production MCP server scaffolder: stdio, SSE and auth middleware | [GitHub](https://github.com/johannschiffer/fastmcp-forge) |
|
|
153
|
+
|
|
154
|
+
[Live config builder](https://johannschiffer.github.io) | [Starter Kit, free](https://johanld.gumroad.com/l/genesis-starter-kit) | [Enterprise Suite, $19](https://johanld.gumroad.com/l/genesis-enterprise-suite)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sysflow-mcp"
|
|
7
|
+
version = "1.0.1"
|
|
8
|
+
description = "Safe Linux Sysadmin, Process & Build Automation Engine for AI Agents"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Johann Schiffer"}
|
|
14
|
+
]
|
|
15
|
+
keywords = ["mcp", "model-context-protocol", "sysadmin", "linux", "systemd", "claude", "cursor", "monitoring", "ai-agent"]
|
|
16
|
+
dependencies = []
|
|
17
|
+
|
|
18
|
+
[project.optional-dependencies]
|
|
19
|
+
test = ["pytest>=7"]
|
|
20
|
+
|
|
21
|
+
[project.scripts]
|
|
22
|
+
sysflow-mcp = "sysflow.server:main"
|
|
23
|
+
sysflow-cli = "sysflow.cli:main"
|
|
24
|
+
|
|
25
|
+
[tool.setuptools.packages.find]
|
|
26
|
+
include = ["sysflow*"]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://johannschiffer.github.io"
|
|
30
|
+
Repository = "https://github.com/johannschiffer/sysflow-mcp"
|
|
31
|
+
"Starter Kit" = "https://johanld.gumroad.com/l/genesis-starter-kit"
|
|
32
|
+
"Enterprise Suite" = "https://johanld.gumroad.com/l/genesis-enterprise-suite"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Upgrade notice. Printed to stderr so MCP stdio traffic on stdout is never affected."""
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
NOTICE = '[Genesis MCP Ecosystem] Free Starter Kit (25+ .cursorrules & Blueprints): https://johanld.gumroad.com/l/genesis-starter-kit | Enterprise Pro Suite (6 Pro Servers + Docker Stack): https://johanld.gumroad.com/l/genesis-enterprise-suite | Config Builder: https://johannschiffer.github.io/'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def print_upgrade_notice():
|
|
9
|
+
if os.environ.get("GENESIS_NO_NOTICE") == "1":
|
|
10
|
+
return
|
|
11
|
+
print(NOTICE, file=sys.stderr)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SysFlow CLI testing utility.
|
|
3
|
+
Usage:
|
|
4
|
+
python -m sysflow.cli resources
|
|
5
|
+
python -m sysflow.cli tail /var/log/syslog --lines 20
|
|
6
|
+
python -m sysflow.cli systemd --failed
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import json
|
|
11
|
+
import sys
|
|
12
|
+
from .monitors.resources import ResourceMonitor
|
|
13
|
+
from .monitors.logs import LogTailer
|
|
14
|
+
from .monitors.systemd import SystemdInspector
|
|
15
|
+
|
|
16
|
+
def main():
|
|
17
|
+
from ._genesis import NOTICE
|
|
18
|
+
parser = argparse.ArgumentParser(description="SysFlow CLI utility", epilog=NOTICE, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
19
|
+
subparsers = parser.add_subparsers(dest="command", help="Sub-commands")
|
|
20
|
+
|
|
21
|
+
subparsers.add_parser("resources", help="Check system CPU, RAM, and disk utilization")
|
|
22
|
+
|
|
23
|
+
tail_parser = subparsers.add_parser("tail", help="Tail recent lines from log file")
|
|
24
|
+
tail_parser.add_argument("file", help="Path to log file")
|
|
25
|
+
tail_parser.add_argument("--lines", type=int, default=50, help="Number of lines")
|
|
26
|
+
tail_parser.add_argument("--pattern", help="Regex filter pattern")
|
|
27
|
+
|
|
28
|
+
systemd_parser = subparsers.add_parser("systemd", help="Inspect systemd --user units")
|
|
29
|
+
systemd_parser.add_argument("--failed", action="store_true", help="Only show failed units")
|
|
30
|
+
|
|
31
|
+
args = parser.parse_args()
|
|
32
|
+
|
|
33
|
+
if args.command == "resources":
|
|
34
|
+
stats = ResourceMonitor.get_system_stats()
|
|
35
|
+
print(json.dumps(stats, indent=2))
|
|
36
|
+
|
|
37
|
+
elif args.command == "tail":
|
|
38
|
+
res = LogTailer.tail(args.file, lines=args.lines, pattern=args.pattern)
|
|
39
|
+
print(json.dumps(res, indent=2))
|
|
40
|
+
|
|
41
|
+
elif args.command == "systemd":
|
|
42
|
+
res = SystemdInspector.list_user_units(failed_only=args.failed)
|
|
43
|
+
print(json.dumps(res, indent=2))
|
|
44
|
+
|
|
45
|
+
else:
|
|
46
|
+
parser.print_help()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
_main_impl = main
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def main():
|
|
54
|
+
from ._genesis import print_upgrade_notice
|
|
55
|
+
try:
|
|
56
|
+
return _main_impl()
|
|
57
|
+
finally:
|
|
58
|
+
print_upgrade_notice()
|
|
59
|
+
|
|
60
|
+
if __name__ == "__main__":
|
|
61
|
+
main()
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Safe Log File Reader & Tailer.
|
|
3
|
+
Tails recent log entries efficiently, filters by regex patterns, and guards against binary files.
|
|
4
|
+
"""
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
from typing import Dict, Any, List, Optional
|
|
8
|
+
|
|
9
|
+
class LogTailer:
|
|
10
|
+
@staticmethod
|
|
11
|
+
def is_binary_file(filepath: str, check_bytes: int = 1024) -> bool:
|
|
12
|
+
"""Heuristic check to prevent reading binary files."""
|
|
13
|
+
try:
|
|
14
|
+
with open(filepath, "rb") as f:
|
|
15
|
+
chunk = f.read(check_bytes)
|
|
16
|
+
return b"\x00" in chunk
|
|
17
|
+
except Exception:
|
|
18
|
+
return True
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def tail(cls, filepath: str, lines: int = 50, pattern: Optional[str] = None, max_lines: int = 500) -> Dict[str, Any]:
|
|
22
|
+
target_path = os.path.abspath(filepath)
|
|
23
|
+
if not os.path.isfile(target_path):
|
|
24
|
+
return {
|
|
25
|
+
"success": False,
|
|
26
|
+
"error": f"File does not exist or is not a regular file: '{target_path}'"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if cls.is_binary_file(target_path):
|
|
30
|
+
return {
|
|
31
|
+
"success": False,
|
|
32
|
+
"error": f"Target file appears to be a binary file: '{target_path}'"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
capped_lines = min(max_lines, max(1, lines))
|
|
36
|
+
file_size = os.path.getsize(target_path)
|
|
37
|
+
|
|
38
|
+
# Efficiently read from end of file for large files
|
|
39
|
+
chunk_size = 8192
|
|
40
|
+
collected_lines: List[str] = []
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
with open(target_path, "r", encoding="utf-8", errors="replace") as f:
|
|
44
|
+
if file_size < 100 * 1024: # Under 100KB, read directly
|
|
45
|
+
all_lines = f.readlines()
|
|
46
|
+
collected_lines = all_lines[-capped_lines:]
|
|
47
|
+
else:
|
|
48
|
+
# Seek backwards from end
|
|
49
|
+
f.seek(max(0, file_size - (capped_lines * 500)))
|
|
50
|
+
f.readline() # Discard partial line
|
|
51
|
+
collected_lines = f.readlines()[-capped_lines:]
|
|
52
|
+
except Exception as e:
|
|
53
|
+
return {"success": False, "error": f"Error reading log file: {str(e)}"}
|
|
54
|
+
|
|
55
|
+
clean_lines = [l.rstrip("\r\n") for l in collected_lines]
|
|
56
|
+
|
|
57
|
+
# Optional regex filter
|
|
58
|
+
if pattern:
|
|
59
|
+
try:
|
|
60
|
+
regex = re.compile(pattern, re.IGNORECASE)
|
|
61
|
+
clean_lines = [l for l in clean_lines if regex.search(l)]
|
|
62
|
+
except re.error as e:
|
|
63
|
+
return {"success": False, "error": f"Invalid regex pattern: {str(e)}"}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
"success": True,
|
|
67
|
+
"filepath": target_path,
|
|
68
|
+
"file_size_bytes": file_size,
|
|
69
|
+
"lines_returned": len(clean_lines),
|
|
70
|
+
"lines": clean_lines
|
|
71
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""
|
|
2
|
+
System Resource Monitor.
|
|
3
|
+
Gathers CPU load average, RAM utilization, disk usage, and host details without root.
|
|
4
|
+
"""
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
import platform
|
|
8
|
+
from typing import Dict, Any
|
|
9
|
+
|
|
10
|
+
class ResourceMonitor:
|
|
11
|
+
@staticmethod
|
|
12
|
+
def get_system_stats() -> Dict[str, Any]:
|
|
13
|
+
# 1. CPU
|
|
14
|
+
cpu_count = os.cpu_count() or 1
|
|
15
|
+
load_1, load_5, load_15 = (0.0, 0.0, 0.0)
|
|
16
|
+
try:
|
|
17
|
+
load_1, load_5, load_15 = os.getloadavg()
|
|
18
|
+
except (AttributeError, OSError):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
# 2. Disk
|
|
22
|
+
disk_total, disk_used, disk_free = (0, 0, 0)
|
|
23
|
+
disk_pct = 0.0
|
|
24
|
+
try:
|
|
25
|
+
usage = shutil.disk_usage("/")
|
|
26
|
+
disk_total = round(usage.total / (1024 ** 3), 2) # GB
|
|
27
|
+
disk_used = round(usage.used / (1024 ** 3), 2)
|
|
28
|
+
disk_free = round(usage.free / (1024 ** 3), 2)
|
|
29
|
+
disk_pct = round((usage.used / usage.total) * 100, 1)
|
|
30
|
+
except Exception:
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
# 3. RAM (Linux /proc/meminfo)
|
|
34
|
+
mem_total_mb = 0
|
|
35
|
+
mem_avail_mb = 0
|
|
36
|
+
mem_pct = 0.0
|
|
37
|
+
if os.path.exists("/proc/meminfo"):
|
|
38
|
+
try:
|
|
39
|
+
mem_dict = {}
|
|
40
|
+
with open("/proc/meminfo", "r") as f:
|
|
41
|
+
for line in f:
|
|
42
|
+
parts = line.split(":")
|
|
43
|
+
if len(parts) == 2:
|
|
44
|
+
key = parts[0].strip()
|
|
45
|
+
val = parts[1].split()[0].strip()
|
|
46
|
+
mem_dict[key] = int(val)
|
|
47
|
+
total_kb = mem_dict.get("MemTotal", 0)
|
|
48
|
+
avail_kb = mem_dict.get("MemAvailable", mem_dict.get("MemFree", 0))
|
|
49
|
+
mem_total_mb = round(total_kb / 1024, 1)
|
|
50
|
+
mem_avail_mb = round(avail_kb / 1024, 1)
|
|
51
|
+
if total_kb > 0:
|
|
52
|
+
mem_pct = round(((total_kb - avail_kb) / total_kb) * 100, 1)
|
|
53
|
+
except Exception:
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
"platform": f"{platform.system()} {platform.release()} ({platform.machine()})",
|
|
58
|
+
"cpu": {
|
|
59
|
+
"cores": cpu_count,
|
|
60
|
+
"load_average_1m": round(load_1, 2),
|
|
61
|
+
"load_average_5m": round(load_5, 2),
|
|
62
|
+
"load_average_15m": round(load_15, 2),
|
|
63
|
+
"load_per_core_1m": round(load_1 / cpu_count, 2)
|
|
64
|
+
},
|
|
65
|
+
"memory": {
|
|
66
|
+
"total_mb": mem_total_mb,
|
|
67
|
+
"available_mb": mem_avail_mb,
|
|
68
|
+
"used_percentage": mem_pct
|
|
69
|
+
},
|
|
70
|
+
"disk": {
|
|
71
|
+
"total_gb": disk_total,
|
|
72
|
+
"used_gb": disk_used,
|
|
73
|
+
"free_gb": disk_free,
|
|
74
|
+
"used_percentage": disk_pct
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Systemd User Service & Timer Inspector.
|
|
3
|
+
Inspects systemd --user units, detects failed services, and monitors timers without root.
|
|
4
|
+
"""
|
|
5
|
+
import subprocess
|
|
6
|
+
from typing import Dict, Any, List
|
|
7
|
+
|
|
8
|
+
class SystemdInspector:
|
|
9
|
+
@staticmethod
|
|
10
|
+
def list_user_units(failed_only: bool = False) -> Dict[str, Any]:
|
|
11
|
+
cmd = ["systemctl", "--user", "list-units", "--type=service", "--no-pager", "--no-legend"]
|
|
12
|
+
if failed_only:
|
|
13
|
+
cmd.append("--state=failed")
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
res = subprocess.run(cmd, capture_output=True, text=True, timeout=5)
|
|
17
|
+
units: List[Dict[str, str]] = []
|
|
18
|
+
|
|
19
|
+
for line in res.stdout.strip().split("\n"):
|
|
20
|
+
line = line.strip()
|
|
21
|
+
if not line:
|
|
22
|
+
continue
|
|
23
|
+
parts = line.split(maxsplit=4)
|
|
24
|
+
if len(parts) >= 4:
|
|
25
|
+
units.append({
|
|
26
|
+
"unit": parts[0].replace("â", "").strip(),
|
|
27
|
+
"load": parts[1],
|
|
28
|
+
"active": parts[2],
|
|
29
|
+
"sub": parts[3],
|
|
30
|
+
"description": parts[4] if len(parts) > 4 else ""
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
"success": True,
|
|
35
|
+
"count": len(units),
|
|
36
|
+
"failed_only": failed_only,
|
|
37
|
+
"units": units
|
|
38
|
+
}
|
|
39
|
+
except Exception as e:
|
|
40
|
+
return {"success": False, "error": f"Error querying systemctl: {str(e)}"}
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def list_user_timers() -> Dict[str, Any]:
|
|
44
|
+
cmd = ["systemctl", "--user", "list-timers", "--no-pager", "--no-legend"]
|
|
45
|
+
try:
|
|
46
|
+
res = subprocess.run(cmd, capture_output=True, text=True, timeout=5)
|
|
47
|
+
timers = []
|
|
48
|
+
for line in res.stdout.strip().split("\n"):
|
|
49
|
+
line = line.strip()
|
|
50
|
+
if not line:
|
|
51
|
+
continue
|
|
52
|
+
timers.append(line)
|
|
53
|
+
return {"success": True, "count": len(timers), "timers": timers}
|
|
54
|
+
except Exception as e:
|
|
55
|
+
return {"success": False, "error": f"Error querying systemd timers: {str(e)}"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SysFlow MCP: Model Context Protocol Server (Lite Edition)
|
|
3
|
+
Implements standard JSON-RPC 2.0 stdio transport for safe system inspection and log monitoring.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Dict, Any, List, Optional
|
|
10
|
+
from .monitors.resources import ResourceMonitor
|
|
11
|
+
from .monitors.logs import LogTailer
|
|
12
|
+
from .monitors.systemd import SystemdInspector
|
|
13
|
+
|
|
14
|
+
logging.basicConfig(
|
|
15
|
+
level=logging.INFO,
|
|
16
|
+
format="%(asctime)s [%(levelname)s] [SysFlow] %(message)s",
|
|
17
|
+
stream=sys.stderr
|
|
18
|
+
)
|
|
19
|
+
logger = logging.getLogger("SysFlowServer")
|
|
20
|
+
|
|
21
|
+
class SysFlowMCPServer:
|
|
22
|
+
def __init__(self):
|
|
23
|
+
self.server_name = "sysflow-mcp"
|
|
24
|
+
self.server_version = "1.0.1"
|
|
25
|
+
|
|
26
|
+
def get_tools_definition(self) -> List[Dict[str, Any]]:
|
|
27
|
+
return [
|
|
28
|
+
{
|
|
29
|
+
"name": "check_system_resources",
|
|
30
|
+
"description": "Inspect CPU load average, RAM utilization, and disk partition space.",
|
|
31
|
+
"inputSchema": {"type": "object", "properties": {}}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "tail_logs",
|
|
35
|
+
"description": "Safely read recent lines from a log or text file with optional regex filtering.",
|
|
36
|
+
"inputSchema": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"properties": {
|
|
39
|
+
"filepath": {"type": "string", "description": "Absolute or relative path to log file"},
|
|
40
|
+
"lines": {"type": "integer", "description": "Number of recent lines to retrieve (default: 50, max: 500)"},
|
|
41
|
+
"pattern": {"type": "string", "description": "Optional regex pattern to filter lines"}
|
|
42
|
+
},
|
|
43
|
+
"required": ["filepath"]
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"name": "inspect_systemd_services",
|
|
48
|
+
"description": "List systemd --user services, active states, and detect failed background units.",
|
|
49
|
+
"inputSchema": {
|
|
50
|
+
"type": "object",
|
|
51
|
+
"properties": {
|
|
52
|
+
"failed_only": {"type": "boolean", "description": "Only return units in failed state (default: false)"}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "inspect_systemd_timers",
|
|
58
|
+
"description": "List systemd --user scheduled timers and their next trigger times.",
|
|
59
|
+
"inputSchema": {"type": "object", "properties": {}}
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
def handle_tool_call(self, name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
64
|
+
if name == "check_system_resources":
|
|
65
|
+
return {
|
|
66
|
+
"success": True,
|
|
67
|
+
"resources": ResourceMonitor.get_system_stats()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
elif name == "tail_logs":
|
|
71
|
+
filepath = arguments.get("filepath", "")
|
|
72
|
+
lines = arguments.get("lines", 50)
|
|
73
|
+
pattern = arguments.get("pattern")
|
|
74
|
+
return LogTailer.tail(filepath, lines=lines, pattern=pattern)
|
|
75
|
+
|
|
76
|
+
elif name == "inspect_systemd_services":
|
|
77
|
+
failed_only = arguments.get("failed_only", False)
|
|
78
|
+
return SystemdInspector.list_user_units(failed_only=failed_only)
|
|
79
|
+
|
|
80
|
+
elif name == "inspect_systemd_timers":
|
|
81
|
+
return SystemdInspector.list_user_timers()
|
|
82
|
+
|
|
83
|
+
else:
|
|
84
|
+
raise ValueError(f"Unknown tool: {name}")
|
|
85
|
+
|
|
86
|
+
def handle_request(self, request: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
87
|
+
req_id = request.get("id")
|
|
88
|
+
method = request.get("method")
|
|
89
|
+
params = request.get("params", {})
|
|
90
|
+
|
|
91
|
+
if req_id is None:
|
|
92
|
+
if method == "notifications/initialized":
|
|
93
|
+
logger.info("Client completed initialization handshake.")
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
if method == "initialize":
|
|
97
|
+
return {
|
|
98
|
+
"jsonrpc": "2.0",
|
|
99
|
+
"id": req_id,
|
|
100
|
+
"result": {
|
|
101
|
+
"protocolVersion": "2024-11-05",
|
|
102
|
+
"capabilities": {"tools": {}},
|
|
103
|
+
"serverInfo": {
|
|
104
|
+
"name": self.server_name,
|
|
105
|
+
"version": self.server_version
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
elif method == "ping":
|
|
111
|
+
return {"jsonrpc": "2.0", "id": req_id, "result": {}}
|
|
112
|
+
|
|
113
|
+
elif method == "tools/list":
|
|
114
|
+
return {
|
|
115
|
+
"jsonrpc": "2.0",
|
|
116
|
+
"id": req_id,
|
|
117
|
+
"result": {"tools": self.get_tools_definition()}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
elif method == "tools/call":
|
|
121
|
+
tool_name = params.get("name")
|
|
122
|
+
tool_args = params.get("arguments", {})
|
|
123
|
+
try:
|
|
124
|
+
output = self.handle_tool_call(tool_name, tool_args)
|
|
125
|
+
return {
|
|
126
|
+
"jsonrpc": "2.0",
|
|
127
|
+
"id": req_id,
|
|
128
|
+
"result": {
|
|
129
|
+
"content": [
|
|
130
|
+
{
|
|
131
|
+
"type": "text",
|
|
132
|
+
"text": json.dumps(output, indent=2, ensure_ascii=False)
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
"isError": not output.get("success", True) if "success" in output else False
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
except Exception as e:
|
|
139
|
+
logger.error(f"Error calling tool {tool_name}: {e}")
|
|
140
|
+
return {
|
|
141
|
+
"jsonrpc": "2.0",
|
|
142
|
+
"id": req_id,
|
|
143
|
+
"error": {"code": -32000, "message": str(e)}
|
|
144
|
+
}
|
|
145
|
+
else:
|
|
146
|
+
return {
|
|
147
|
+
"jsonrpc": "2.0",
|
|
148
|
+
"id": req_id,
|
|
149
|
+
"error": {"code": -32601, "message": f"Method not found: {method}"}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
def run_stdio(self):
|
|
153
|
+
from ._genesis import print_upgrade_notice
|
|
154
|
+
print_upgrade_notice()
|
|
155
|
+
logger.info(f"Starting {self.server_name} v{self.server_version} stdio listener...")
|
|
156
|
+
for line in sys.stdin:
|
|
157
|
+
line = line.strip()
|
|
158
|
+
if not line:
|
|
159
|
+
continue
|
|
160
|
+
try:
|
|
161
|
+
request = json.loads(line)
|
|
162
|
+
response = self.handle_request(request)
|
|
163
|
+
if response is not None:
|
|
164
|
+
sys.stdout.write(json.dumps(response) + "\n")
|
|
165
|
+
sys.stdout.flush()
|
|
166
|
+
except Exception as e:
|
|
167
|
+
logger.error(f"Error processing stdio line: {e}")
|
|
168
|
+
|
|
169
|
+
def main():
|
|
170
|
+
import argparse
|
|
171
|
+
from ._genesis import NOTICE
|
|
172
|
+
parser = argparse.ArgumentParser(
|
|
173
|
+
prog="sysflow-mcp",
|
|
174
|
+
description="SysFlow MCP: Safe Linux Sysadmin, Process & Build Automation Engine for AI Agents",
|
|
175
|
+
epilog=NOTICE,
|
|
176
|
+
formatter_class=argparse.RawDescriptionHelpFormatter
|
|
177
|
+
)
|
|
178
|
+
parser.add_argument("-v", "--version", action="version", version="%(prog)s 1.0.1")
|
|
179
|
+
args, unknown = parser.parse_known_args()
|
|
180
|
+
server = SysFlowMCPServer()
|
|
181
|
+
server.run_stdio()
|
|
182
|
+
|
|
183
|
+
_main_impl = main
|
|
184
|
+
|
|
185
|
+
def main():
|
|
186
|
+
from ._genesis import print_upgrade_notice
|
|
187
|
+
try:
|
|
188
|
+
return _main_impl()
|
|
189
|
+
finally:
|
|
190
|
+
print_upgrade_notice()
|
|
191
|
+
|
|
192
|
+
if __name__ == "__main__":
|
|
193
|
+
main()
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sysflow-mcp
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Safe Linux Sysadmin, Process & Build Automation Engine for AI Agents
|
|
5
|
+
Author: Johann Schiffer
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://johannschiffer.github.io
|
|
8
|
+
Project-URL: Repository, https://github.com/johannschiffer/sysflow-mcp
|
|
9
|
+
Project-URL: Starter Kit, https://johanld.gumroad.com/l/genesis-starter-kit
|
|
10
|
+
Project-URL: Enterprise Suite, https://johanld.gumroad.com/l/genesis-enterprise-suite
|
|
11
|
+
Keywords: mcp,model-context-protocol,sysadmin,linux,systemd,claude,cursor,monitoring,ai-agent
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Provides-Extra: test
|
|
16
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# SysFlow MCP (Lite Edition)
|
|
20
|
+
|
|
21
|
+
<a href="https://glama.ai/mcp/servers/@johannschiffer/sysflow-mcp">
|
|
22
|
+
<img width="380" height="200" src="https://glama.ai/mcp/servers/@johannschiffer/sysflow-mcp/badge" alt="sysflow-mcp MCP server" />
|
|
23
|
+
</a>
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
> **Free:** [Genesis Starter Kit](https://johanld.gumroad.com/l/genesis-starter-kit) (25 `.cursorrules`/`CLAUDE.md` rulesets + MCP configs). Try the [live config builder](https://johannschiffer.github.io).
|
|
27
|
+
> **Full stack:** all six servers in [Genesis Enterprise Suite ($19)](https://johanld.gumroad.com/l/genesis-enterprise-suite).
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
[](LICENSE)
|
|
31
|
+
[](https://python.org)
|
|
32
|
+
[](https://modelcontextprotocol.io)
|
|
33
|
+
|
|
34
|
+
**SysFlow MCP** is a lightweight, zero-overhead Model Context Protocol server that gives AI agents in **Claude Desktop**, **Cursor**, **Windsurf**, and **Antigravity** safe, structured access to Linux sysadmin inspection, log tailing, and service health without granting unrestricted root shells or risking interactive terminal freezes.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Run without cloning
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
uvx sysflow-mcp
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Claude Desktop / Cursor config entry:
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"mcpServers": {
|
|
49
|
+
"sysflow-mcp": {
|
|
50
|
+
"command": "uvx",
|
|
51
|
+
"args": [
|
|
52
|
+
"--from",
|
|
53
|
+
"git+https://github.com/johannschiffer/sysflow-mcp",
|
|
54
|
+
"sysflow-mcp"
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
[](https://github.com/johannschiffer/sysflow-mcp/actions/workflows/ci.yml)
|
|
62
|
+
|
|
63
|
+
## ðĨïļ Why SysFlow MCP?
|
|
64
|
+
|
|
65
|
+
Giving an AI assistant raw terminal execution often ends in frustration:
|
|
66
|
+
1. **Interactive Terminal Freezes:** Commands like `journalctl`, `systemctl status`, or `git log` default to interactive pagers (`less`), leaving the AI agent permanently hung waiting for user input.
|
|
67
|
+
2. **Context Window Flooding:** Tailing an unconstrained `/var/log/nginx/access.log` dumps thousands of lines into the LLM prompt, wasting tokens and causing memory errors.
|
|
68
|
+
3. **Destructive Shell Commands:** Without sandboxing, a hallucinated command can kill critical background daemons or delete workspace assets.
|
|
69
|
+
|
|
70
|
+
**SysFlow** exposes safe, bounded, non-blocking tools specifically tailored for autonomous developer agents.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## ⥠Features (Lite Edition)
|
|
75
|
+
|
|
76
|
+
- ð **Resource Healthcheck**: Reports CPU core load averages (1m, 5m, 15m), RAM availability (MB and percentage), and disk partition metrics.
|
|
77
|
+
- ð **Safe Log Tailer**: Reads the last N lines (bounded and memory-safe) with optional regex filtering; automatically rejects binary files.
|
|
78
|
+
- âïļ **Systemd User Service Inspector**: Lists user-level services and timers (`systemctl --user`), highlighting failed units without requiring `sudo`.
|
|
79
|
+
- ðŠķ **Zero External Dependencies**: Operates strictly on Python standard library and Linux `/proc` filesystem.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## ð Quick Setup in 2 Minutes
|
|
84
|
+
|
|
85
|
+
### 1. Installation
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
git clone https://github.com/johannschiffer/sysflow-mcp.git
|
|
89
|
+
cd sysflow-mcp
|
|
90
|
+
pip install .
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 2. Configure Claude Desktop (`claude_desktop_config.json`)
|
|
94
|
+
|
|
95
|
+
On Linux: `~/.config/Claude/claude_desktop_config.json`
|
|
96
|
+
On macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
97
|
+
On Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"mcpServers": {
|
|
102
|
+
"sysflow": {
|
|
103
|
+
"command": "python",
|
|
104
|
+
"args": ["-m", "sysflow.server"],
|
|
105
|
+
"cwd": "/path/to/sysflow-mcp"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 3. Verify via CLI
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
python -m sysflow.cli resources
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## ð ïļ MCP Tools Exposed to AI Agents
|
|
120
|
+
|
|
121
|
+
| Tool | Parameters | Description |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| `check_system_resources` | *(none)* | Returns CPU load, memory usage, and partition disk metrics |
|
|
124
|
+
| `tail_logs` | `filepath`, `lines`, `pattern` | Safely tails recent log lines with optional regex filter |
|
|
125
|
+
| `inspect_systemd_services` | `failed_only` | Lists user-level services, states, and detects failed daemons |
|
|
126
|
+
| `inspect_systemd_timers` | *(none)* | Lists scheduled systemd user timers |
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## ð Need Enterprise Build Sandboxing? Upgrade to SysFlow Pro
|
|
131
|
+
|
|
132
|
+
Looking to automate shell task execution, manage Docker containers, or generate verified systemd units?
|
|
133
|
+
|
|
134
|
+
| Feature | Lite (Open Source) | Enterprise Suite ($19) |
|
|
135
|
+
|---|:---:|:---:|
|
|
136
|
+
| **CPU, RAM & Disk Monitoring** | â
| â
|
|
|
137
|
+
| **Safe Bounded Log Tailer** | â
| â
|
|
|
138
|
+
| **Systemd User Service Auditing** | â
| â
|
|
|
139
|
+
| **Sandboxed Task Runner with Timeouts** *(Anti-freeze)* | â | â
|
|
|
140
|
+
| **Docker Container Health & Log Inspector** | â | â
|
|
|
141
|
+
| **Verified Systemd Unit File Generator** | â | â
|
|
|
142
|
+
| **Listening Socket & Port Conflict Auditor** | â | â
|
|
|
143
|
+
| **Production Docker Container** | â | â
|
|
|
144
|
+
| **Full Pytest Suite & Sysadmin Automation Guide** | â | â
|
|
|
145
|
+
|
|
146
|
+
ð **[Get the Genesis Enterprise Suite ($19)](https://johanld.gumroad.com/l/genesis-enterprise-suite)**
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## ð License
|
|
151
|
+
|
|
152
|
+
MIT License. See [LICENSE](LICENSE) for details.
|
|
153
|
+
|
|
154
|
+
## Recommended Infrastructure
|
|
155
|
+
|
|
156
|
+
VPS hosting that work well with SysFlow MCP. Some links are referral links.
|
|
157
|
+
|
|
158
|
+
- [Hetzner Cloud](https://www.hetzner.com/cloud)
|
|
159
|
+
- [DigitalOcean](https://www.digitalocean.com/)
|
|
160
|
+
|
|
161
|
+
## Genesis MCP Ecosystem
|
|
162
|
+
|
|
163
|
+
| Module | What it does | Repo |
|
|
164
|
+
|---|---|---|
|
|
165
|
+
| **CrawlEngine MCP** | Anti-bot web scraping, clean Markdown, proxy rotation and TLS evasion | [GitHub](https://github.com/johannschiffer/crawlengine-mcp) |
|
|
166
|
+
| **BrowserBridge MCP** | Deterministic headless browser automation with cryptographic action receipts | [GitHub](https://github.com/johannschiffer/browserbridge-mcp) |
|
|
167
|
+
| **PgSafe MCP** | Zero-trust PostgreSQL/SQLite guard, live PII masking, zero-downtime migration checks | [GitHub](https://github.com/johannschiffer/pgsafe-mcp) |
|
|
168
|
+
| **OmniBridge MCP** | Multi-channel notification and webhook bus with a persistent SQLite WAL queue | [GitHub](https://github.com/johannschiffer/omnibridge-mcp) |
|
|
169
|
+
| **SysFlow MCP** (this repo) | Safe Linux administration plane: systemd, port audit, isolated execution | [GitHub](https://github.com/johannschiffer/sysflow-mcp) |
|
|
170
|
+
| **FastMCP Forge** | Production MCP server scaffolder: stdio, SSE and auth middleware | [GitHub](https://github.com/johannschiffer/fastmcp-forge) |
|
|
171
|
+
|
|
172
|
+
[Live config builder](https://johannschiffer.github.io) | [Starter Kit, free](https://johanld.gumroad.com/l/genesis-starter-kit) | [Enterprise Suite, $19](https://johanld.gumroad.com/l/genesis-enterprise-suite)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
sysflow/__init__.py
|
|
5
|
+
sysflow/_genesis.py
|
|
6
|
+
sysflow/cli.py
|
|
7
|
+
sysflow/server.py
|
|
8
|
+
sysflow/monitors/logs.py
|
|
9
|
+
sysflow/monitors/resources.py
|
|
10
|
+
sysflow/monitors/systemd.py
|
|
11
|
+
sysflow_mcp.egg-info/PKG-INFO
|
|
12
|
+
sysflow_mcp.egg-info/SOURCES.txt
|
|
13
|
+
sysflow_mcp.egg-info/dependency_links.txt
|
|
14
|
+
sysflow_mcp.egg-info/entry_points.txt
|
|
15
|
+
sysflow_mcp.egg-info/requires.txt
|
|
16
|
+
sysflow_mcp.egg-info/top_level.txt
|
|
17
|
+
tests/test_genesis_entrypoints.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sysflow
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import io
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_script_target_is_callable():
|
|
8
|
+
mod = importlib.import_module('sysflow.server')
|
|
9
|
+
assert callable(getattr(mod, 'main'))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_upgrade_notice_goes_to_stderr(capsys, monkeypatch):
|
|
13
|
+
monkeypatch.delenv('GENESIS_NO_NOTICE', raising=False)
|
|
14
|
+
from sysflow._genesis import print_upgrade_notice
|
|
15
|
+
print_upgrade_notice()
|
|
16
|
+
out = capsys.readouterr()
|
|
17
|
+
assert out.out == ''
|
|
18
|
+
assert 'gumroad.com' in out.err
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_upgrade_notice_can_be_silenced(capsys, monkeypatch):
|
|
22
|
+
monkeypatch.setenv('GENESIS_NO_NOTICE', '1')
|
|
23
|
+
from sysflow._genesis import print_upgrade_notice
|
|
24
|
+
print_upgrade_notice()
|
|
25
|
+
assert capsys.readouterr().err == ''
|