@fkom13/mcp-sftp-orchestrator 6.0.0 → 11.3.0
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.
- package/.env.example +21 -15
- package/.gencodedoc.yaml +76 -0
- package/README.fr.md +369 -0
- package/README.md +333 -61
- package/ROADMAP.md +21 -0
- package/apis.js +17 -0
- package/compareEngine.js +132 -0
- package/config.js +35 -4
- package/diagnose.js +89 -0
- package/diffEngine.js +141 -0
- package/diffFormatter.js +114 -0
- package/fileOps.js +275 -0
- package/guide.js +236 -0
- package/history.js +8 -1
- package/notes.js +87 -0
- package/package.json +5 -22
- package/policies.js +76 -0
- package/queue.js +107 -121
- package/server.js +1460 -108
- package/sftp.js +131 -41
- package/shellSessions.js +237 -0
- package/snapshotManager.js +324 -0
- package/sourceAdapter.js +357 -0
- package/ssh.js +168 -84
- package/sshPool.js +52 -56
- package/test_mcp.js +55 -0
- package/tunnels.js +224 -0
- package/utils.js +6 -0
- package/publish1github.md +0 -216
package/README.md
CHANGED
|
@@ -1,102 +1,374 @@
|
|
|
1
|
-
# 🚀 MCP SFTP
|
|
1
|
+
# 🚀 MCP Orchestrator — SSH/SFTP Infrastructure Orchestration Server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Version** : 11.3.0
|
|
4
|
+
**License** : MIT
|
|
5
|
+
**Node** : >= 18.0.0
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
A Model Context Protocol (MCP) server that turns any AI agent (Claude, OpenCode, Cursor...) into a full-fledged system administrator. Persistent queue, SSH connection pool, hybrid sync/async execution.
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
9
|
+
### ✨ Key Features
|
|
10
|
+
- **63 MCP tools** — SSH, SFTP, file ops, monitoring, snapshots, tunnels
|
|
11
|
+
- **Built-in security** — Command blocklist, port allowlist, hash protection
|
|
12
|
+
- **Multi-server** — `task_exec {alias:["vps1","vps2"]}` or `alias:"all"}`
|
|
13
|
+
- **Persistence** — tmux + persistent queue = sessions that survive crashes
|
|
14
|
+
- **Snapshots** — Deduplicated versioning of your critical files
|
|
15
|
+
- **SSH Tunnels** — Local, Remote, SOCKS5 with secured port allowlist
|
|
16
|
+
- **AI Guide** — Built-in manual for the agent (section:index/workflows/audit/security)
|
|
17
|
+
|
|
18
|
+
> **🇫🇷 Version française :** [README.fr.md](README.fr.md)
|
|
19
|
+
|
|
20
|
+
---
|
|
14
21
|
|
|
15
22
|
## 📦 Installation
|
|
16
23
|
|
|
17
|
-
|
|
24
|
+
### Via npx (recommended)
|
|
25
|
+
```bash
|
|
26
|
+
npx @fkom13/mcp-sftp-orchestrator
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Via git
|
|
30
|
+
```bash
|
|
31
|
+
git clone https://github.com/fkom13/mcp-sftp-orchestrator.git
|
|
32
|
+
cd mcp-sftp-orchestrator
|
|
33
|
+
npm install
|
|
34
|
+
cp .env.example .env
|
|
35
|
+
# Edit .env with your paths
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Requirements: Node.js >= 18.0.0
|
|
39
|
+
|
|
40
|
+
---
|
|
18
41
|
|
|
19
|
-
|
|
42
|
+
## ⚙️ Configuration (.env)
|
|
43
|
+
|
|
44
|
+
All variables are optional. Defaults are designed for standard usage.
|
|
45
|
+
|
|
46
|
+
| Variable | Default | Description |
|
|
47
|
+
|----------|---------|-------------|
|
|
48
|
+
| `MCP_DATA_DIR` | `~/.config/mcp-orchestrator` | Data directory (servers.json, apis.json, queue.json) |
|
|
49
|
+
| `MCP_SYNC_TIMEOUT_S` | `120` | Seconds before background execution |
|
|
50
|
+
| `MCP_DEFAULT_CMD_TIMEOUT_S` | `600` | Default SSH timeout (0 = unlimited) |
|
|
51
|
+
| `MCP_INTERACTIVE_CMD_TIMEOUT_S` | `300` | Interactive command timeout (0 = unlimited) |
|
|
52
|
+
| `MCP_MAX_WAIT_TIMEOUT_S` | `600` | Max timeout for `task_wait` |
|
|
53
|
+
| `MAX_CONNECTIONS_PER_SERVER` | `5` | Max parallel SSH connections per server |
|
|
54
|
+
| `MIN_CONNECTIONS_PER_SERVER` | `1` | Min pooled connections per server |
|
|
55
|
+
| `IDLE_TIMEOUT` | `300000` | Idle connection close delay (ms) |
|
|
56
|
+
| `KEEP_ALIVE_INTERVAL` | `30000` | SSH keepalive interval (ms) |
|
|
57
|
+
| `MAX_QUEUE_SIZE` | `1000` | Max jobs in queue |
|
|
58
|
+
| `SAVE_INTERVAL` | `5000` | Queue disk save interval (ms) |
|
|
59
|
+
| `MCP_ALLOWED_ROOTS` | *(empty)* | Restrict file access to these roots (comma-separated). Empty = full access |
|
|
60
|
+
| `MCP_DEBUG` | `false` | Enable detailed debug logs |
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 🔌 MCP Client Configuration (OpenCode, Claude Desktop, etc.)
|
|
20
65
|
|
|
21
66
|
```json
|
|
22
67
|
{
|
|
23
68
|
"mcpServers": {
|
|
24
|
-
"
|
|
25
|
-
"command": "
|
|
26
|
-
"args": [
|
|
27
|
-
"@fkom13/mcp-sftp-orchestrator"
|
|
28
|
-
],
|
|
69
|
+
"orchestrator": {
|
|
70
|
+
"command": "node",
|
|
71
|
+
"args": ["/path/to/sftp-mcp/server.js"],
|
|
29
72
|
"env": {
|
|
30
|
-
"MCP_DATA_DIR": "
|
|
73
|
+
"MCP_DATA_DIR": "/path/to/sftp-mcp/data"
|
|
31
74
|
}
|
|
32
75
|
}
|
|
33
76
|
}
|
|
34
77
|
}
|
|
35
78
|
```
|
|
36
79
|
|
|
37
|
-
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 🧰 Tool Reference (63 tools)
|
|
83
|
+
|
|
84
|
+
### Help & Diagnostics
|
|
85
|
+
| Tool | Description |
|
|
86
|
+
|------|-------------|
|
|
87
|
+
| `help` | Complete guide: tools, env vars, parameter schemas |
|
|
88
|
+
| `guide` | AI manual: workflows, cheatsheet, audit, security |
|
|
89
|
+
| `system_diagnostics` | Full system diagnostic (queue, pool, servers, APIs) |
|
|
90
|
+
|
|
91
|
+
### Server Management
|
|
92
|
+
| Tool | Description |
|
|
93
|
+
|------|-------------|
|
|
94
|
+
| `server_add` | Add/update a server alias |
|
|
95
|
+
| `server_list` | List all configured servers |
|
|
96
|
+
| `server_remove` | Remove a server alias |
|
|
97
|
+
| `infra_overview` | Fleet-wide overview (roles, services, warnings) |
|
|
98
|
+
| `server_note_set/get/list/remove` | Documented server context |
|
|
38
99
|
|
|
39
|
-
|
|
100
|
+
### Security (Blocklist)
|
|
101
|
+
| Tool | Description |
|
|
102
|
+
|------|-------------|
|
|
103
|
+
| `policy_blocklist_list` | List blocked commands |
|
|
104
|
+
| `policy_blocklist_add` | Add a pattern to blocklist |
|
|
105
|
+
| `policy_blocklist_remove` | Remove a pattern from blocklist |
|
|
40
106
|
|
|
41
|
-
|
|
107
|
+
### Task Execution
|
|
108
|
+
| Tool | Description |
|
|
109
|
+
|------|-------------|
|
|
110
|
+
| `task_exec` | SSH one-shot. Supports `alias:["vps1","vps2"]` or `alias:"all"` |
|
|
111
|
+
| `task_exec_interactive` | SSH with interactive prompt handling |
|
|
112
|
+
| `task_exec_sequence` | Sequential SSH commands |
|
|
113
|
+
| `task_transfer` | SFTP transfer. Supports `server_to_server` |
|
|
114
|
+
| `task_transfer_multi` | Bulk transfers with glob patterns |
|
|
42
115
|
|
|
43
|
-
|
|
44
|
-
|
|
116
|
+
### Monitoring
|
|
117
|
+
| Tool | Description |
|
|
118
|
+
|------|-------------|
|
|
119
|
+
| `get_system_resources` | CPU, RAM, Disk metrics |
|
|
120
|
+
| `get_services_status` | systemd, Docker, PM2 status (graceful fallback) |
|
|
121
|
+
| `get_fail2ban_status` | Fail2Ban status |
|
|
122
|
+
| `check_api_health` | HTTP health check |
|
|
45
123
|
|
|
46
|
-
|
|
124
|
+
### Logs
|
|
125
|
+
| Tool | Description |
|
|
126
|
+
|------|-------------|
|
|
127
|
+
| `get_pm2_logs` | PM2 logs |
|
|
128
|
+
| `get_docker_logs` | Docker logs |
|
|
129
|
+
| `tail_file` | Tail a remote file |
|
|
47
130
|
|
|
48
|
-
|
|
131
|
+
### File Operations (Local + Remote)
|
|
132
|
+
| Tool | Description |
|
|
133
|
+
|------|-------------|
|
|
134
|
+
| `file_read` | Read file + SHA-256 hash (edit protection) |
|
|
135
|
+
| `file_write` | Create/overwrite with `dryRun` and `backup` |
|
|
136
|
+
| `file_edit` | Surgical or full edit + hash protection |
|
|
49
137
|
|
|
50
|
-
###
|
|
138
|
+
### Comparison & Drift Detection
|
|
139
|
+
| Tool | Description |
|
|
140
|
+
|------|-------------|
|
|
141
|
+
| `diff_files` | Compare 2 files (local/remote, cross-server) |
|
|
142
|
+
| `diff_folders` | Compare 2 directories |
|
|
143
|
+
| `compare_all_sources` | Detect drifts across N servers |
|
|
51
144
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
145
|
+
### Persistent Shell Sessions
|
|
146
|
+
| Tool | Description |
|
|
147
|
+
|------|-------------|
|
|
148
|
+
| `shell_create` | Open a persistent shell (cd/env preserved) |
|
|
149
|
+
| `shell_exec` | Execute in an existing session |
|
|
150
|
+
| `shell_list` / `shell_close` | List/close sessions |
|
|
55
151
|
|
|
56
|
-
###
|
|
152
|
+
### tmux (Surviving Terminal Sessions)
|
|
153
|
+
| Tool | Description |
|
|
154
|
+
|------|-------------|
|
|
155
|
+
| `tmux_create` | Create a persistent tmux session |
|
|
156
|
+
| `tmux_exec` | Send a command to a session |
|
|
157
|
+
| `tmux_read` | Read session buffer |
|
|
158
|
+
| `tmux_list` / `tmux_kill` | List/kill sessions |
|
|
57
159
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
160
|
+
### SSH Tunnels
|
|
161
|
+
| Tool | Description |
|
|
162
|
+
|------|-------------|
|
|
163
|
+
| `tunnel_create` | Local/remote/SOCKS5 tunnel, persistent via tmux |
|
|
164
|
+
| `tunnel_list` | List active tunnels |
|
|
165
|
+
| `tunnel_close` | Close a tunnel |
|
|
166
|
+
| `tunnel_allowlist_add/remove` | Manage allowed ports |
|
|
63
167
|
|
|
64
|
-
###
|
|
168
|
+
### Snapshots (File Versioning)
|
|
169
|
+
| Tool | Description |
|
|
170
|
+
|------|-------------|
|
|
171
|
+
| `snapshot_create` | Capture file state with deduplication |
|
|
172
|
+
| `snapshot_list` | List snapshots |
|
|
173
|
+
| `snapshot_diff` | Compare 2 snapshots |
|
|
174
|
+
| `snapshot_restore` | Restore (dryRun by default) |
|
|
175
|
+
| `snapshot_delete` | Delete + orphan cleanup |
|
|
65
176
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
177
|
+
### Queue & Monitoring
|
|
178
|
+
| Tool | Description |
|
|
179
|
+
|------|-------------|
|
|
180
|
+
| `task_queue` | View all active/pending tasks |
|
|
181
|
+
| `task_status` | Task detail by ID |
|
|
182
|
+
| `task_history` | Filterable history |
|
|
183
|
+
| `task_retry` | Retry a failed task |
|
|
184
|
+
| `task_wait` | Wait for a background task |
|
|
185
|
+
| `task_logs` | MCP internal logs |
|
|
186
|
+
| `queue_stats` / `pool_stats` | Queue and SSH pool stats |
|
|
69
187
|
|
|
70
|
-
###
|
|
188
|
+
### API Catalog
|
|
189
|
+
| Tool | Description |
|
|
190
|
+
|------|-------------|
|
|
191
|
+
| `api_add` / `api_list` / `api_remove` | API monitoring catalog |
|
|
192
|
+
| `api_check` | Health check via SSH + curl |
|
|
71
193
|
|
|
72
|
-
|
|
73
|
-
- `get_docker_logs`: Raccourci pour récupérer les logs d'un container Docker.
|
|
74
|
-
- `tail_file`: Affiche les dernières lignes d'un fichier distant.
|
|
194
|
+
---
|
|
75
195
|
|
|
76
|
-
|
|
196
|
+
## 📖 Usage Examples
|
|
197
|
+
|
|
198
|
+
### Multi-server
|
|
199
|
+
```bash
|
|
200
|
+
# One command, multiple servers
|
|
201
|
+
task_exec {alias:["vps1","vps2","vps3"], cmd:"uptime"}
|
|
202
|
+
|
|
203
|
+
# Entire fleet
|
|
204
|
+
task_exec {alias:"all", cmd:"df -h /"}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### SOCKS5 Proxy Tunnel
|
|
208
|
+
```bash
|
|
209
|
+
tunnel_create {name:"proxy", type:"socks", listen_port:1080, via:"vps_paris"}
|
|
210
|
+
# → Browser → SOCKS5 127.0.0.1:1080 → Paris VPS
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Local Tunnel (access remote service)
|
|
214
|
+
```bash
|
|
215
|
+
tunnel_create {name:"crm", type:"local", listen_port:8080, target:"127.0.0.1:3100", via:"vps_prod"}
|
|
216
|
+
# → http://localhost:8080 → production CRM
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Remote Tunnel (expose local service)
|
|
220
|
+
```bash
|
|
221
|
+
tunnel_create {name:"dev", type:"remote", listen_port:9090, target:"127.0.0.1:3000", via:"vps", source:"vps_prod", key_path:"/home/user/.ssh/vps.key"}
|
|
222
|
+
# → vps_prod:9090 → your local machine:3000
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Persistent tmux Session
|
|
226
|
+
```bash
|
|
227
|
+
tmux_create {alias:"vps", name:"build", start_cmd:"npm run build"}
|
|
228
|
+
tmux_read {alias:"vps", session:"build"}
|
|
229
|
+
tmux_kill {alias:"vps", session:"build"}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Security Blocklist
|
|
233
|
+
```bash
|
|
234
|
+
# List blocked commands
|
|
235
|
+
policy_blocklist_list
|
|
236
|
+
# → ["rm -rf /", "mkfs*", ...]
|
|
237
|
+
|
|
238
|
+
# Conscious bypass
|
|
239
|
+
task_exec {alias:"vps", cmd:"rm -rf /tmp/cache", skip_policy:true}
|
|
240
|
+
```
|
|
77
241
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
242
|
+
### Secure File Editing
|
|
243
|
+
```bash
|
|
244
|
+
# Read + hash
|
|
245
|
+
file_read {source:{type:"remote", alias:"vps", path:"/etc/nginx/nginx.conf"}}
|
|
246
|
+
# → content + hash
|
|
83
247
|
|
|
84
|
-
|
|
248
|
+
# Edit with protection
|
|
249
|
+
file_edit {source:{type:"remote", alias:"vps", path:"/etc/nginx/nginx.conf"},
|
|
250
|
+
oldString:"worker_connections 768;",
|
|
251
|
+
newString:"worker_connections 1024;",
|
|
252
|
+
expectedHash:"abc123...",
|
|
253
|
+
backup:true}
|
|
85
254
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
255
|
+
# Preview without writing
|
|
256
|
+
file_edit {source:{type:"remote", alias:"vps", path:"/etc/nginx/nginx.conf"},
|
|
257
|
+
oldString:"worker_connections 768;",
|
|
258
|
+
newString:"worker_connections 1024;",
|
|
259
|
+
dryRun:true}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Snapshot Before Risky Changes
|
|
263
|
+
```bash
|
|
264
|
+
# Before
|
|
265
|
+
snapshot_create {source:{type:"remote", alias:"vps"}, paths:["/etc/nginx/"], tag:"before-fix"}
|
|
266
|
+
|
|
267
|
+
# After if something broke
|
|
268
|
+
snapshot_restore {snapshotId:"...", target:{type:"remote", alias:"vps"}, dryRun:false, force:true}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Multi-server Drift Detection
|
|
272
|
+
```bash
|
|
273
|
+
compare_all_sources {sources:[
|
|
274
|
+
{type:"remote", alias:"vps1", path:"/etc/nginx/nginx.conf", label:"prod"},
|
|
275
|
+
{type:"remote", alias:"vps2", path:"/etc/nginx/nginx.conf", label:"staging"}
|
|
276
|
+
]}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## 📚 AI Built-in Manual (guide)
|
|
282
|
+
|
|
283
|
+
The orchestrator includes an interactive manual for your AI agent:
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
guide section:index # Table of contents
|
|
287
|
+
guide section:workflows # Copy-paste recipes
|
|
288
|
+
guide section:cheatsheet # Tool → usage table
|
|
289
|
+
guide section:audit # Full fleet audit in 8 steps
|
|
290
|
+
guide section:security # Blocklist + tunnels
|
|
291
|
+
guide section:pitfalls # Common mistakes
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## 🏗️ Architecture
|
|
297
|
+
|
|
298
|
+
```
|
|
299
|
+
MCP Client (stdio)
|
|
300
|
+
│
|
|
301
|
+
server.js ─── 63 MCP tools registered
|
|
302
|
+
│
|
|
303
|
+
├── queue.js ─────── Persistent job queue (JSON + backup)
|
|
304
|
+
├── ssh.js ───────── SSH execution (pool + dedicated connections)
|
|
305
|
+
├── sftp.js ──────── SFTP transfers (upload/download/multi)
|
|
306
|
+
├── sshPool.js ───── Persistent SSH connection pool
|
|
307
|
+
├── servers.js ───── CRUD server aliases
|
|
308
|
+
├── apis.js ──────── CRUD API catalog
|
|
309
|
+
├── history.js ───── Task history
|
|
310
|
+
├── config.js ────── Centralized configuration
|
|
311
|
+
├── utils.js ─────── Utilities (escapeShellArg)
|
|
312
|
+
├── fileOps.js ───── File operations (read/write/edit)
|
|
313
|
+
├── diffEngine.js ── Diff engine (files/dirs/sources)
|
|
314
|
+
├── compareEngine.js ─ Multi-source comparison
|
|
315
|
+
├── diffFormatter.js ─ Diff formatting
|
|
316
|
+
├── sourceAdapter.js ─ Local/remote abstraction
|
|
317
|
+
├── shellSessions.js ─ Persistent shell sessions
|
|
318
|
+
├── snapshotManager.js ─ Versioned snapshots
|
|
319
|
+
├── notes.js ──────── Documented server context
|
|
320
|
+
├── policies.js ──── Command blocklist
|
|
321
|
+
├── tunnels.js ────── SSH tunnels (local/remote/SOCKS)
|
|
322
|
+
├── guide.js ──────── AI built-in manual
|
|
323
|
+
└── diagnose.js ───── Diagnostics
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Job Lifecycle
|
|
327
|
+
|
|
328
|
+
```
|
|
329
|
+
pending → running → completed / failed
|
|
330
|
+
↓ (on restart)
|
|
331
|
+
crashed → retry → pending
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## 🔒 Security
|
|
337
|
+
|
|
338
|
+
- **Command Blocklist** : `rm -rf /`, `mkfs*`, fork bombs, and other destructive commands are blocked by default
|
|
339
|
+
- **Conscious bypass** : `skip_policy: true` to force execution
|
|
340
|
+
- **Tunnel port allowlist** : only explicitly allowed ports can be used
|
|
341
|
+
- **File access restriction** : `MCP_ALLOWED_ROOTS` env var to limit file operations to specific directories
|
|
342
|
+
- **`escapeShellArg()`** : all URLs and paths are escaped before being passed to curl/shell
|
|
343
|
+
- **Plaintext secret detection** : warning on startup if passwords/API keys are in plaintext
|
|
344
|
+
- **Pre-modification snapshots** : `backup:true` on file_edit/file_write for instant rollback
|
|
345
|
+
- **Recommendation** : use SSH keys (not passwords), store secrets in Vaultwarden
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## 🧪 Tests
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
node diagnose.js # Full diagnostic
|
|
353
|
+
node test_mcp.js # MCP smoke test
|
|
354
|
+
node test_features.js # Unit tests (queue, pool, glob, prompts, crash)
|
|
355
|
+
```
|
|
90
356
|
|
|
91
|
-
|
|
357
|
+
---
|
|
92
358
|
|
|
93
|
-
|
|
94
|
-
- `pool_stats`: Affiche les statistiques du pool de connexions SSH.
|
|
359
|
+
## 🛣️ Roadmap
|
|
95
360
|
|
|
96
|
-
|
|
361
|
+
| Version | Changes |
|
|
362
|
+
|---------|---------|
|
|
363
|
+
| 10.0.0 | New tools: file_read/write/edit, diff, snapshots, shell, notes |
|
|
364
|
+
| 10.4.0 | server_to_server, help with schemas, audit guide |
|
|
365
|
+
| 11.0.0 | Command Blocklist, Multi-host (`alias:"all"`), tmux |
|
|
366
|
+
| 11.2.0 | SSH Tunnels (local/remote/SOCKS5), allowlist, ssh2 stderr fix |
|
|
367
|
+
| 11.3.0 | AllowedRoots (`MCP_ALLOWED_ROOTS`) for file restriction |
|
|
368
|
+
| 12.0.0 (planned) | Auto key setup for tunnels, webhooks, static dashboard |
|
|
97
369
|
|
|
98
|
-
|
|
370
|
+
---
|
|
99
371
|
|
|
100
|
-
## 📄
|
|
372
|
+
## 📄 License
|
|
101
373
|
|
|
102
|
-
|
|
374
|
+
MIT — Copyright (c) 2025-2026 Franck (fkom13)
|
package/ROADMAP.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Roadmap MCP Orchestrator
|
|
2
|
+
|
|
3
|
+
## v11.2.0 (actuelle)
|
|
4
|
+
- ✅ Command Blocklist (policies.js) — sécurité des commandes
|
|
5
|
+
- ✅ Multi-host execution (alias array / "all")
|
|
6
|
+
- ✅ tmux sessions persistantes (create/exec/read/list/kill)
|
|
7
|
+
- ✅ Tunnels SSH (local/remote/SOCKS5) avec allowlist
|
|
8
|
+
- ✅ Guide IA intégré (audit, security, cheatsheet)
|
|
9
|
+
- ✅ Help avec schémas JSON des paramètres
|
|
10
|
+
- ✅ Snapshots versionnés avec déduplication
|
|
11
|
+
- ✅ File edit chirurgical avec protection hash
|
|
12
|
+
- ✅ Diff multi-serveur (compare_all_sources)
|
|
13
|
+
- ✅ get_services_status graceful fallback
|
|
14
|
+
|
|
15
|
+
## v12.x (planifiée)
|
|
16
|
+
- [ ] Tunnels auto-setup de clés entre serveurs
|
|
17
|
+
- [ ] Webhook on tunnel up/down (n8n compatible)
|
|
18
|
+
- [ ] Dashboard web statique pour l'état du parc
|
|
19
|
+
- [ ] Mode "agent" : exécution planifiée (cron-like)
|
|
20
|
+
- [ ] Intégration Vaultwarden pour les secrets
|
|
21
|
+
- [ ] Tests automatisés CI/CD
|
package/apis.js
CHANGED
|
@@ -45,6 +45,23 @@ async function saveApis() {
|
|
|
45
45
|
// Ajouter ou mettre à jour une API
|
|
46
46
|
async function addApi(alias, apiConfig) {
|
|
47
47
|
await ensureInitialized();
|
|
48
|
+
|
|
49
|
+
// Validation
|
|
50
|
+
if (!alias || typeof alias !== 'string') {
|
|
51
|
+
throw new Error("L'alias doit être une chaîne non vide.");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!apiConfig.url) {
|
|
55
|
+
throw new Error("L'URL de l'API est obligatoire.");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Valider l'URL
|
|
59
|
+
try {
|
|
60
|
+
new URL(apiConfig.url);
|
|
61
|
+
} catch (e) {
|
|
62
|
+
throw new Error(`URL invalide: ${apiConfig.url}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
48
65
|
apis[alias] = apiConfig;
|
|
49
66
|
await saveApis();
|
|
50
67
|
return { success: true, message: `API '${alias}' ajoutée/mise à jour avec succès.` };
|
package/compareEngine.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { createTwoFilesPatch } from 'diff';
|
|
2
|
+
import sourceAdapter from './sourceAdapter.js';
|
|
3
|
+
import fileOps from './fileOps.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* compareEngine — Comparaison d'un même fichier (ou de fichiers équivalents à
|
|
7
|
+
* des emplacements différents) sur plusieurs sources.
|
|
8
|
+
* Détecte les "drifts" de configuration entre serveurs (et localhost).
|
|
9
|
+
*
|
|
10
|
+
* Regroupe les sources par hash : chaque groupe = ensemble de sources ayant
|
|
11
|
+
* un contenu identique. Plus d'un groupe → drift détecté.
|
|
12
|
+
*
|
|
13
|
+
* Chaque source peut porter :
|
|
14
|
+
* - un path différent d'une source à l'autre (ex: .bashrc ici, .zshrc là)
|
|
15
|
+
* - un label personnalisé (source.label) pour un affichage clair
|
|
16
|
+
* Quand un drift est détecté, on peut générer le DIFF réel entre la version
|
|
17
|
+
* de référence (groupe majoritaire) et chaque version divergente.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export default {
|
|
21
|
+
/**
|
|
22
|
+
* Compare le contenu d'un fichier sur plusieurs sources.
|
|
23
|
+
* sources = [{ type, path, alias?, label? }, ...] (min 2, local et/ou remote)
|
|
24
|
+
* options = { includeContent: bool (défaut false), includeDiff: bool (défaut true) }
|
|
25
|
+
*
|
|
26
|
+
* Retourne :
|
|
27
|
+
* {
|
|
28
|
+
* drift: bool, // true si plus d'une version distincte
|
|
29
|
+
* reference: { label, sources } | null, // groupe majoritaire (référence)
|
|
30
|
+
* groups: [{ hash, size, sources:[{server,path,label}], content?, isReference }],
|
|
31
|
+
* drifts: [{ from, to, added, removed, diff }], // diff référence → chaque divergence
|
|
32
|
+
* errors: [{ source:{server,path,label}, error }],
|
|
33
|
+
* stats: { totalSources, successfulReads, uniqueVersions, unreadable }
|
|
34
|
+
* }
|
|
35
|
+
*/
|
|
36
|
+
async compareSources(sources, options = {}) {
|
|
37
|
+
const includeContent = options.includeContent === true;
|
|
38
|
+
const includeDiff = options.includeDiff !== false; // défaut true
|
|
39
|
+
const results = [];
|
|
40
|
+
const errors = [];
|
|
41
|
+
|
|
42
|
+
// Lecture de chaque source (séquentiel : simple et robuste ; le pool gère le reste)
|
|
43
|
+
for (const source of sources) {
|
|
44
|
+
const origin = sourceAdapter.describe(source);
|
|
45
|
+
try {
|
|
46
|
+
const file = await fileOps.readFile(source);
|
|
47
|
+
results.push({ origin, hash: file.hash, size: file.size, content: file.content });
|
|
48
|
+
} catch (e) {
|
|
49
|
+
errors.push({ source: origin, error: e.message });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Regroupement par hash (on garde le contenu en interne pour le diff)
|
|
54
|
+
const groupsMap = new Map();
|
|
55
|
+
for (const r of results) {
|
|
56
|
+
if (!groupsMap.has(r.hash)) {
|
|
57
|
+
groupsMap.set(r.hash, {
|
|
58
|
+
hash: r.hash,
|
|
59
|
+
size: r.size,
|
|
60
|
+
sources: [],
|
|
61
|
+
_content: r.content // interne, retiré à la fin si includeContent=false
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
groupsMap.get(r.hash).sources.push(r.origin);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const groups = Array.from(groupsMap.values())
|
|
68
|
+
// le groupe le plus répandu en premier (majorité = référence probable)
|
|
69
|
+
.sort((a, b) => b.sources.length - a.sources.length);
|
|
70
|
+
|
|
71
|
+
const drift = groups.length > 1;
|
|
72
|
+
|
|
73
|
+
// Marque le groupe de référence (majoritaire)
|
|
74
|
+
groups.forEach((g, i) => { g.isReference = i === 0; });
|
|
75
|
+
const reference = groups.length > 0
|
|
76
|
+
? { label: groups[0].sources[0].label, sources: groups[0].sources }
|
|
77
|
+
: null;
|
|
78
|
+
|
|
79
|
+
// Génère les diffs référence → chaque groupe divergent
|
|
80
|
+
const drifts = [];
|
|
81
|
+
if (drift && includeDiff) {
|
|
82
|
+
const ref = groups[0];
|
|
83
|
+
const refLabel = ref.sources[0].label;
|
|
84
|
+
for (let i = 1; i < groups.length; i++) {
|
|
85
|
+
const g = groups[i];
|
|
86
|
+
const gLabel = g.sources[0].label;
|
|
87
|
+
try {
|
|
88
|
+
const patch = createTwoFilesPatch(
|
|
89
|
+
refLabel, gLabel,
|
|
90
|
+
ref._content, g._content,
|
|
91
|
+
'référence', 'divergent'
|
|
92
|
+
);
|
|
93
|
+
let added = 0, removed = 0;
|
|
94
|
+
for (const line of patch.split('\n')) {
|
|
95
|
+
if (line.startsWith('+') && !line.startsWith('+++')) added++;
|
|
96
|
+
else if (line.startsWith('-') && !line.startsWith('---')) removed++;
|
|
97
|
+
}
|
|
98
|
+
drifts.push({
|
|
99
|
+
from: refLabel,
|
|
100
|
+
to: gLabel,
|
|
101
|
+
affectedSources: g.sources.map(s => s.label),
|
|
102
|
+
added,
|
|
103
|
+
removed,
|
|
104
|
+
diff: patch
|
|
105
|
+
});
|
|
106
|
+
} catch (e) {
|
|
107
|
+
drifts.push({ from: refLabel, to: gLabel, note: 'diff indisponible (binaire ?)' });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Nettoie le contenu interne (ou l'expose si demandé)
|
|
113
|
+
for (const g of groups) {
|
|
114
|
+
if (includeContent) g.content = g._content;
|
|
115
|
+
delete g._content;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
drift,
|
|
120
|
+
reference,
|
|
121
|
+
groups,
|
|
122
|
+
drifts,
|
|
123
|
+
errors,
|
|
124
|
+
stats: {
|
|
125
|
+
totalSources: sources.length,
|
|
126
|
+
successfulReads: results.length,
|
|
127
|
+
uniqueVersions: groups.length,
|
|
128
|
+
unreadable: errors.length
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
};
|
package/config.js
CHANGED
|
@@ -21,13 +21,44 @@ const args = process.argv.slice(2).reduce((acc, arg) => {
|
|
|
21
21
|
}, {});
|
|
22
22
|
|
|
23
23
|
const config = {
|
|
24
|
-
// La hiérarchie reste la même : les arguments en ligne de commande écrasent le .env, qui écrase le défaut.
|
|
25
24
|
dataDir: args['data-dir'] || process.env.MCP_DATA_DIR || path.join(os.homedir(), '.config', 'mcp-orchestrator'),
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
|
|
26
|
+
// Timeouts en millisecondes (plus cohérent)
|
|
27
|
+
syncTimeout: parseInt(args['sync-timeout'] || process.env.MCP_SYNC_TIMEOUT_S || '120', 10) * 1000,
|
|
28
|
+
defaultCommandTimeout: parseInt(process.env.MCP_DEFAULT_CMD_TIMEOUT_S || '600', 10) * 1000,
|
|
29
|
+
interactiveCommandTimeout: parseInt(process.env.MCP_INTERACTIVE_CMD_TIMEOUT_S || '300', 10) * 1000,
|
|
30
|
+
maxWaitTimeout: parseInt(process.env.MCP_MAX_WAIT_TIMEOUT_S || '600', 10) * 1000,
|
|
31
|
+
|
|
32
|
+
// Configuration Pool SSH
|
|
33
|
+
maxConnectionsPerServer: parseInt(process.env.MAX_CONNECTIONS_PER_SERVER || '5', 10),
|
|
34
|
+
minConnectionsPerServer: parseInt(process.env.MIN_CONNECTIONS_PER_SERVER || '1', 10),
|
|
35
|
+
idleTimeout: parseInt(process.env.IDLE_TIMEOUT || '300000', 10),
|
|
36
|
+
keepAliveInterval: parseInt(process.env.KEEP_ALIVE_INTERVAL || '30000', 10),
|
|
37
|
+
|
|
38
|
+
// Sécurité
|
|
39
|
+
allowedRoots: (process.env.MCP_ALLOWED_ROOTS || '').split(',').map(s => s.trim()).filter(Boolean),
|
|
40
|
+
|
|
41
|
+
// Configuration Queue
|
|
42
|
+
maxQueueSize: parseInt(process.env.MAX_QUEUE_SIZE || '1000', 10),
|
|
43
|
+
saveInterval: parseInt(process.env.SAVE_INTERVAL || '5000', 10),
|
|
44
|
+
historyRetention: parseInt(process.env.HISTORY_RETENTION || '2678400000', 10), // 31 jours
|
|
29
45
|
};
|
|
30
46
|
|
|
47
|
+
// Validation
|
|
48
|
+
if (config.syncTimeout < 1000) {
|
|
49
|
+
if (process.env.MCP_DEBUG === 'true') {
|
|
50
|
+
console.error('⚠️ syncTimeout trop court, minimum 1000ms');
|
|
51
|
+
}
|
|
52
|
+
config.syncTimeout = 1000;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (config.maxConnectionsPerServer < config.minConnectionsPerServer) {
|
|
56
|
+
if (process.env.MCP_DEBUG === 'true') {
|
|
57
|
+
console.error('⚠️ maxConnections < minConnections, ajustement automatique');
|
|
58
|
+
}
|
|
59
|
+
config.maxConnectionsPerServer = config.minConnectionsPerServer;
|
|
60
|
+
}
|
|
61
|
+
|
|
31
62
|
// Créer le dossier de données s'il n'existe pas
|
|
32
63
|
fs.mkdirSync(config.dataDir, { recursive: true });
|
|
33
64
|
|