@jadchene/mcp-ssh-service 1.3.0 → 1.4.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/README.md +40 -9
- package/dist/tools/definitions.js +145 -0
- package/dist/tools/handlers.js +113 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -177,7 +177,7 @@ args = ["--config", "./config.json"]
|
|
|
177
177
|
|
|
178
178
|
---
|
|
179
179
|
|
|
180
|
-
## 🛠️ Integrated Toolset (
|
|
180
|
+
## 🛠️ Integrated Toolset (79 Tools)
|
|
181
181
|
|
|
182
182
|
### Discovery & Core (8)
|
|
183
183
|
* `list_servers`
|
|
@@ -193,7 +193,7 @@ args = ["--config", "./config.json"]
|
|
|
193
193
|
* `execute_command` [Auth Required, single command only]
|
|
194
194
|
* `echo`
|
|
195
195
|
|
|
196
|
-
### File Management (
|
|
196
|
+
### File Management (15)
|
|
197
197
|
* `upload_file` [Auth Required]
|
|
198
198
|
* `download_file`
|
|
199
199
|
* `ll`
|
|
@@ -202,14 +202,23 @@ args = ["--config", "./config.json"]
|
|
|
202
202
|
* `grep`
|
|
203
203
|
* `edit_text_file` [Auth Required]
|
|
204
204
|
* `touch`
|
|
205
|
+
* `mkdir` [Auth Required]
|
|
206
|
+
* `mv` [Auth Required]
|
|
207
|
+
* `cp` [Auth Required]
|
|
208
|
+
* `append_text_file` [Auth Required]
|
|
209
|
+
* `replace_in_file` [Auth Required]
|
|
205
210
|
* `rm_safe` [Auth Required]
|
|
206
211
|
* `find`
|
|
207
212
|
|
|
208
|
-
### Git (
|
|
213
|
+
### Git (6)
|
|
209
214
|
* `git_status`
|
|
215
|
+
* `git_fetch` [Auth Required]
|
|
210
216
|
* `git_pull` [Auth Required]
|
|
217
|
+
* `git_switch` [Auth Required]
|
|
218
|
+
* `git_branch`
|
|
219
|
+
* `git_log`
|
|
211
220
|
|
|
212
|
-
### Docker & Compose (
|
|
221
|
+
### Docker & Compose (21)
|
|
213
222
|
* `docker_compose_up` [Auth Required]
|
|
214
223
|
* `docker_compose_down` [Auth Required]
|
|
215
224
|
* `docker_compose_stop` [Auth Required]
|
|
@@ -217,33 +226,53 @@ args = ["--config", "./config.json"]
|
|
|
217
226
|
* `docker_compose_restart` [Auth Required]
|
|
218
227
|
* `docker_ps`
|
|
219
228
|
* `docker_images`
|
|
229
|
+
* `docker_exec` [Auth Required]
|
|
230
|
+
* `docker_inspect`
|
|
231
|
+
* `docker_stats`
|
|
220
232
|
* `docker_pull` [Auth Required]
|
|
221
233
|
* `docker_cp` [Auth Required]
|
|
222
234
|
* `docker_stop` [Auth Required]
|
|
223
235
|
* `docker_rm` [Auth Required]
|
|
224
236
|
* `docker_start` [Auth Required]
|
|
237
|
+
* `docker_restart` [Auth Required]
|
|
225
238
|
* `docker_rmi` [Auth Required]
|
|
226
239
|
* `docker_commit` [Auth Required]
|
|
227
240
|
* `docker_logs`
|
|
228
241
|
* `docker_load` [Auth Required]
|
|
229
242
|
* `docker_save` [Auth Required]
|
|
230
243
|
|
|
231
|
-
### Service & Network (
|
|
244
|
+
### Service & Network (14)
|
|
232
245
|
* `systemctl_status`
|
|
233
246
|
* `systemctl_restart` [Auth Required]
|
|
234
247
|
* `systemctl_start` [Auth Required]
|
|
235
248
|
* `systemctl_stop` [Auth Required]
|
|
236
249
|
* `ip_addr`
|
|
250
|
+
* `journalctl`
|
|
237
251
|
* `firewall_cmd` [Auth Required, structured actions only]
|
|
238
252
|
* `netstat` [uses `args: string[]`]
|
|
239
|
-
|
|
240
|
-
|
|
253
|
+
* `ss` [uses `args: string[]`]
|
|
254
|
+
* `ping_host`
|
|
255
|
+
* `traceroute`
|
|
256
|
+
* `nslookup`
|
|
257
|
+
* `dig`
|
|
258
|
+
* `curl_http` [Auth Required]
|
|
259
|
+
|
|
260
|
+
### Stats & Process (13)
|
|
241
261
|
* `nvidia_smi`
|
|
242
262
|
* `ps`
|
|
263
|
+
* `pgrep`
|
|
264
|
+
* `kill_process` [Auth Required]
|
|
243
265
|
* `df_h`
|
|
244
266
|
* `du_sh`
|
|
245
|
-
|
|
246
|
-
|
|
267
|
+
* `chmod` [Auth Required]
|
|
268
|
+
* `chown` [Auth Required]
|
|
269
|
+
* `ln` [Auth Required]
|
|
270
|
+
* `tar_create` [Auth Required]
|
|
271
|
+
* `tar_extract` [Auth Required]
|
|
272
|
+
* `zip` [Auth Required]
|
|
273
|
+
* `unzip` [Auth Required]
|
|
274
|
+
|
|
275
|
+
Total: 79 tools.
|
|
247
276
|
|
|
248
277
|
---
|
|
249
278
|
|
|
@@ -260,6 +289,8 @@ If a high-risk tool's final command string matches `commandWhitelist`, the serve
|
|
|
260
289
|
`execute_command` is limited to one shell command segment. The server rejects chaining operators such as `&&`, `||`, `;`, pipes, redirection, subshell syntax, and multiline input. For built-in tools, user-provided parameters are shell-escaped before execution to reduce command injection risk.
|
|
261
290
|
|
|
262
291
|
`firewall_cmd` no longer accepts a free-form shell fragment. Use structured fields such as `action`, `port`, `zone`, `permanent`, and `listTarget`. `netstat` now accepts `args: string[]` so each option is validated as an individual token.
|
|
292
|
+
|
|
293
|
+
Use `mkdir` for directory creation instead of `execute_command "mkdir ..."`. Set `parents: true` when you need `mkdir -p` behavior.
|
|
263
294
|
|
|
264
295
|
---
|
|
265
296
|
|
|
@@ -139,6 +139,31 @@ export const toolDefinitions = [
|
|
|
139
139
|
description: 'Timestamp/File creation: Updates access time or creates empty file.',
|
|
140
140
|
inputSchema: baseParams({ filePath: { type: 'string' } }, ['filePath'])
|
|
141
141
|
},
|
|
142
|
+
{
|
|
143
|
+
name: 'mkdir',
|
|
144
|
+
description: 'Directory creation: Creates a directory. Set parents=true for mkdir -p behavior. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
145
|
+
inputSchema: baseParams({ path: { type: 'string' }, parents: { type: 'boolean' }, ...confirmationParams }, ['path'])
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: 'mv',
|
|
149
|
+
description: 'Move or rename a file or directory. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
150
|
+
inputSchema: baseParams({ source: { type: 'string' }, destination: { type: 'string' }, force: { type: 'boolean' }, ...confirmationParams }, ['source', 'destination'])
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: 'cp',
|
|
154
|
+
description: 'Copy a file or directory. Set recursive=true for directories. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
155
|
+
inputSchema: baseParams({ source: { type: 'string' }, destination: { type: 'string' }, recursive: { type: 'boolean' }, preserve: { type: 'boolean' }, ...confirmationParams }, ['source', 'destination'])
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: 'append_text_file',
|
|
159
|
+
description: 'Append text to a file, creating it if needed. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
160
|
+
inputSchema: baseParams({ filePath: { type: 'string' }, content: { type: 'string' }, ...confirmationParams }, ['filePath', 'content'])
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: 'replace_in_file',
|
|
164
|
+
description: 'Replace literal text inside a file. Set replaceAll=false to replace only the first occurrence. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
165
|
+
inputSchema: baseParams({ filePath: { type: 'string' }, search: { type: 'string' }, replace: { type: 'string' }, replaceAll: { type: 'boolean' }, ...confirmationParams }, ['filePath', 'search', 'replace'])
|
|
166
|
+
},
|
|
142
167
|
{
|
|
143
168
|
name: 'rm_safe',
|
|
144
169
|
description: 'File deletion: Removes file or directory. REQUIRES CONFIRMATION.',
|
|
@@ -155,11 +180,31 @@ export const toolDefinitions = [
|
|
|
155
180
|
description: 'Git status: Displays repository status.',
|
|
156
181
|
inputSchema: baseParams(cwdParam)
|
|
157
182
|
},
|
|
183
|
+
{
|
|
184
|
+
name: 'git_fetch',
|
|
185
|
+
description: 'Git fetch: Updates remote tracking refs. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
186
|
+
inputSchema: baseParams({ ...cwdParam, all: { type: 'boolean' }, prune: { type: 'boolean' }, ...confirmationParams })
|
|
187
|
+
},
|
|
158
188
|
{
|
|
159
189
|
name: 'git_pull',
|
|
160
190
|
description: 'Git update: Pulls latest changes. REQUIRES CONFIRMATION.',
|
|
161
191
|
inputSchema: baseParams({ ...cwdParam, ...confirmationParams })
|
|
162
192
|
},
|
|
193
|
+
{
|
|
194
|
+
name: 'git_switch',
|
|
195
|
+
description: 'Git switch: Switches branches, or creates one with create=true. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
196
|
+
inputSchema: baseParams({ ...cwdParam, branch: { type: 'string' }, create: { type: 'boolean' }, startPoint: { type: 'string' }, ...confirmationParams }, ['branch'])
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: 'git_branch',
|
|
200
|
+
description: 'Git branch: Lists local or all branches.',
|
|
201
|
+
inputSchema: baseParams({ ...cwdParam, all: { type: 'boolean' }, verbose: { type: 'boolean' } })
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: 'git_log',
|
|
205
|
+
description: 'Git log: Shows recent commit history.',
|
|
206
|
+
inputSchema: baseParams({ ...cwdParam, maxCount: { type: 'number' }, oneline: { type: 'boolean' }, path: { type: 'string' } })
|
|
207
|
+
},
|
|
163
208
|
// --- Docker & Compose (Requirements) ---
|
|
164
209
|
{
|
|
165
210
|
name: 'docker_compose_up',
|
|
@@ -196,6 +241,21 @@ export const toolDefinitions = [
|
|
|
196
241
|
description: 'List docker images.',
|
|
197
242
|
inputSchema: baseParams(grepParam)
|
|
198
243
|
},
|
|
244
|
+
{
|
|
245
|
+
name: 'docker_exec',
|
|
246
|
+
description: 'Run one process inside a running container without shell expansion. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
247
|
+
inputSchema: baseParams({ container: { type: 'string' }, command: { type: 'string' }, args: { type: 'array', items: { type: 'string' } }, user: { type: 'string' }, workdir: { type: 'string' }, ...confirmationParams }, ['container', 'command'])
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
name: 'docker_inspect',
|
|
251
|
+
description: 'Inspect a container, image, volume, or network.',
|
|
252
|
+
inputSchema: baseParams({ target: { type: 'string' }, format: { type: 'string' } }, ['target'])
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
name: 'docker_stats',
|
|
256
|
+
description: 'Show container resource usage.',
|
|
257
|
+
inputSchema: baseParams({ container: { type: 'string' }, noStream: { type: 'boolean' } })
|
|
258
|
+
},
|
|
199
259
|
{
|
|
200
260
|
name: 'docker_pull',
|
|
201
261
|
description: 'Pull an image from a registry. REQUIRES CONFIRMATION.',
|
|
@@ -221,6 +281,11 @@ export const toolDefinitions = [
|
|
|
221
281
|
description: 'Start one or more stopped containers. REQUIRES CONFIRMATION.',
|
|
222
282
|
inputSchema: baseParams({ container: { type: 'string' }, ...confirmationParams }, ['container'])
|
|
223
283
|
},
|
|
284
|
+
{
|
|
285
|
+
name: 'docker_restart',
|
|
286
|
+
description: 'Restart one or more running containers. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
287
|
+
inputSchema: baseParams({ container: { type: 'string' }, ...confirmationParams }, ['container'])
|
|
288
|
+
},
|
|
224
289
|
{
|
|
225
290
|
name: 'docker_rmi',
|
|
226
291
|
description: 'Remove one or more images. REQUIRES CONFIRMATION.',
|
|
@@ -272,6 +337,11 @@ export const toolDefinitions = [
|
|
|
272
337
|
description: 'Show network interface info.',
|
|
273
338
|
inputSchema: baseParams(grepParam)
|
|
274
339
|
},
|
|
340
|
+
{
|
|
341
|
+
name: 'journalctl',
|
|
342
|
+
description: 'Read systemd journal logs with optional unit, since, and priority filters.',
|
|
343
|
+
inputSchema: baseParams({ unit: { type: 'string' }, lines: { type: 'number' }, since: { type: 'string' }, priority: { type: 'string' }, grep: { type: 'string' } })
|
|
344
|
+
},
|
|
275
345
|
{
|
|
276
346
|
name: 'firewall_cmd',
|
|
277
347
|
description: 'Structured firewall control. Supports action=list|add-port|remove-port|reload with optional zone, permanent, and listTarget. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
@@ -289,6 +359,36 @@ export const toolDefinitions = [
|
|
|
289
359
|
description: 'Monitor ports/connections. Use args as an array of individual option tokens, for example ["-t", "-u", "-l", "-n"].',
|
|
290
360
|
inputSchema: baseParams({ args: { type: 'array', items: { type: 'string' } }, ...grepParam })
|
|
291
361
|
},
|
|
362
|
+
{
|
|
363
|
+
name: 'ss',
|
|
364
|
+
description: 'Socket statistics. Use args as an array of individual option tokens, for example ["-t", "-u", "-l", "-n"].',
|
|
365
|
+
inputSchema: baseParams({ args: { type: 'array', items: { type: 'string' } }, ...grepParam })
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
name: 'ping_host',
|
|
369
|
+
description: 'Ping a host a fixed number of times.',
|
|
370
|
+
inputSchema: baseParams({ host: { type: 'string' }, count: { type: 'number' } }, ['host'])
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
name: 'traceroute',
|
|
374
|
+
description: 'Trace the network path to a host.',
|
|
375
|
+
inputSchema: baseParams({ host: { type: 'string' }, maxHops: { type: 'number' } }, ['host'])
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
name: 'nslookup',
|
|
379
|
+
description: 'Resolve hostnames using nslookup.',
|
|
380
|
+
inputSchema: baseParams({ host: { type: 'string' }, server: { type: 'string' } }, ['host'])
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
name: 'dig',
|
|
384
|
+
description: 'Resolve DNS records using dig.',
|
|
385
|
+
inputSchema: baseParams({ host: { type: 'string' }, recordType: { type: 'string' }, server: { type: 'string' } }, ['host'])
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
name: 'curl_http',
|
|
389
|
+
description: 'Perform an HTTP request with structured method, URL, headers, and optional body. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
390
|
+
inputSchema: baseParams({ method: { type: 'string' }, url: { type: 'string' }, headers: { type: 'array', items: { type: 'string' } }, body: { type: 'string' }, timeoutSeconds: { type: 'number' }, followRedirects: { type: 'boolean' }, ...confirmationParams }, ['method', 'url'])
|
|
391
|
+
},
|
|
292
392
|
// --- Stats & Process (Requirements) ---
|
|
293
393
|
{
|
|
294
394
|
name: 'nvidia_smi',
|
|
@@ -300,6 +400,16 @@ export const toolDefinitions = [
|
|
|
300
400
|
description: 'Report a snapshot of the current processes.',
|
|
301
401
|
inputSchema: baseParams(grepParam)
|
|
302
402
|
},
|
|
403
|
+
{
|
|
404
|
+
name: 'pgrep',
|
|
405
|
+
description: 'Find process IDs by name or full command pattern.',
|
|
406
|
+
inputSchema: baseParams({ pattern: { type: 'string' }, fullCommand: { type: 'boolean' } }, ['pattern'])
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
name: 'kill_process',
|
|
410
|
+
description: 'Send a signal to a process ID. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
411
|
+
inputSchema: baseParams({ pid: { type: 'number' }, signal: { type: 'string' }, ...confirmationParams }, ['pid'])
|
|
412
|
+
},
|
|
303
413
|
{
|
|
304
414
|
name: 'df_h',
|
|
305
415
|
description: 'System disk usage.',
|
|
@@ -309,5 +419,40 @@ export const toolDefinitions = [
|
|
|
309
419
|
name: 'du_sh',
|
|
310
420
|
description: 'Directory size estimation.',
|
|
311
421
|
inputSchema: baseParams({ path: { type: 'string' }, ...grepParam }, ['path'])
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
name: 'chmod',
|
|
425
|
+
description: 'Change file mode bits. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
426
|
+
inputSchema: baseParams({ mode: { type: 'string' }, path: { type: 'string' }, recursive: { type: 'boolean' }, ...confirmationParams }, ['mode', 'path'])
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
name: 'chown',
|
|
430
|
+
description: 'Change file owner and group. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
431
|
+
inputSchema: baseParams({ owner: { type: 'string' }, path: { type: 'string' }, recursive: { type: 'boolean' }, ...confirmationParams }, ['owner', 'path'])
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
name: 'ln',
|
|
435
|
+
description: 'Create a link. Uses symbolic=true by default for symlinks. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
436
|
+
inputSchema: baseParams({ target: { type: 'string' }, linkPath: { type: 'string' }, symbolic: { type: 'boolean' }, force: { type: 'boolean' }, ...confirmationParams }, ['target', 'linkPath'])
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
name: 'tar_create',
|
|
440
|
+
description: 'Create a tar archive from one or more source paths. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
441
|
+
inputSchema: baseParams({ sourcePaths: { type: 'array', items: { type: 'string' } }, outputPath: { type: 'string' }, gzip: { type: 'boolean' }, ...confirmationParams }, ['sourcePaths', 'outputPath'])
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
name: 'tar_extract',
|
|
445
|
+
description: 'Extract a tar archive into a destination directory. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
446
|
+
inputSchema: baseParams({ archivePath: { type: 'string' }, destination: { type: 'string' }, gzip: { type: 'boolean' }, ...confirmationParams }, ['archivePath', 'destination'])
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
name: 'zip',
|
|
450
|
+
description: 'Create a zip archive from one or more source paths. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
451
|
+
inputSchema: baseParams({ sourcePaths: { type: 'array', items: { type: 'string' } }, outputPath: { type: 'string' }, recursive: { type: 'boolean' }, ...confirmationParams }, ['sourcePaths', 'outputPath'])
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
name: 'unzip',
|
|
455
|
+
description: 'Extract a zip archive into a destination directory. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
456
|
+
inputSchema: baseParams({ archivePath: { type: 'string' }, destination: { type: 'string' }, overwrite: { type: 'boolean' }, ...confirmationParams }, ['archivePath', 'destination'])
|
|
312
457
|
}
|
|
313
458
|
];
|
package/dist/tools/handlers.js
CHANGED
|
@@ -4,17 +4,26 @@ const WRITE_TOOLS = [
|
|
|
4
4
|
'execute_command',
|
|
5
5
|
'upload_file',
|
|
6
6
|
'edit_text_file',
|
|
7
|
+
'append_text_file',
|
|
8
|
+
'mkdir',
|
|
9
|
+
'mv',
|
|
10
|
+
'cp',
|
|
11
|
+
'replace_in_file',
|
|
7
12
|
'rm_safe',
|
|
13
|
+
'git_fetch',
|
|
8
14
|
'git_pull',
|
|
15
|
+
'git_switch',
|
|
9
16
|
'docker_compose_up',
|
|
10
17
|
'docker_compose_down',
|
|
11
18
|
'docker_compose_stop',
|
|
12
19
|
'docker_compose_restart',
|
|
20
|
+
'docker_exec',
|
|
13
21
|
'docker_pull',
|
|
14
22
|
'docker_cp',
|
|
15
23
|
'docker_stop',
|
|
16
24
|
'docker_rm',
|
|
17
25
|
'docker_start',
|
|
26
|
+
'docker_restart',
|
|
18
27
|
'docker_rmi',
|
|
19
28
|
'docker_commit',
|
|
20
29
|
'docker_load',
|
|
@@ -22,7 +31,16 @@ const WRITE_TOOLS = [
|
|
|
22
31
|
'systemctl_restart',
|
|
23
32
|
'systemctl_start',
|
|
24
33
|
'systemctl_stop',
|
|
25
|
-
'firewall_cmd'
|
|
34
|
+
'firewall_cmd',
|
|
35
|
+
'kill_process',
|
|
36
|
+
'chmod',
|
|
37
|
+
'chown',
|
|
38
|
+
'ln',
|
|
39
|
+
'tar_create',
|
|
40
|
+
'tar_extract',
|
|
41
|
+
'zip',
|
|
42
|
+
'unzip',
|
|
43
|
+
'curl_http'
|
|
26
44
|
];
|
|
27
45
|
const DEFAULT_BLACKLIST = [
|
|
28
46
|
/rm\s+-(rf|fr|r|f)\s+\//i,
|
|
@@ -91,6 +109,17 @@ export class ToolHandlers {
|
|
|
91
109
|
}
|
|
92
110
|
this.validateShellFragment(value, fieldName);
|
|
93
111
|
}
|
|
112
|
+
shellEscapeList(values) {
|
|
113
|
+
return values.map((value) => this.shellEscape(value)).join(' ');
|
|
114
|
+
}
|
|
115
|
+
validateTokenArray(values, fieldName) {
|
|
116
|
+
for (const [index, value] of (values || []).entries()) {
|
|
117
|
+
this.validateShellToken(value, `${fieldName}[${index}]`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
escapePerlEnvBase64(value) {
|
|
121
|
+
return Buffer.from(value).toString('base64');
|
|
122
|
+
}
|
|
94
123
|
ensureNoShellControl(value, errorMessage) {
|
|
95
124
|
const forbiddenOperators = [/&&/, /\|\|/, /;/, /\|/, /\$\(/, /`/, />/, /</, /\r|\n/];
|
|
96
125
|
for (const pattern of forbiddenOperators) {
|
|
@@ -180,9 +209,23 @@ export class ToolHandlers {
|
|
|
180
209
|
if (name === 'execute_command') {
|
|
181
210
|
this.validateSingleCommand(params.command);
|
|
182
211
|
}
|
|
183
|
-
if (name === 'netstat' && Array.isArray(params.args)) {
|
|
212
|
+
if ((name === 'netstat' || name === 'ss') && Array.isArray(params.args)) {
|
|
213
|
+
this.validateTokenArray(params.args, `${name}.args`);
|
|
214
|
+
}
|
|
215
|
+
if (name === 'docker_exec' && Array.isArray(params.args)) {
|
|
184
216
|
for (const [index, arg] of params.args.entries()) {
|
|
185
|
-
this.
|
|
217
|
+
this.validateShellFragment(arg, `docker_exec.args[${index}]`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if ((name === 'tar_create' || name === 'zip') && Array.isArray(params.sourcePaths)) {
|
|
221
|
+
if (params.sourcePaths.length === 0) {
|
|
222
|
+
throw new Error(`${name}.sourcePaths must contain at least one path.`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (name === 'curl_http') {
|
|
226
|
+
this.validateShellToken(String(params.method || 'GET').toUpperCase(), 'curl_http.method');
|
|
227
|
+
for (const [index, header] of (params.headers || []).entries()) {
|
|
228
|
+
this.validateShellFragment(header, `curl_http.headers[${index}]`);
|
|
186
229
|
}
|
|
187
230
|
}
|
|
188
231
|
const command = this.getExecutableCommand(name, params);
|
|
@@ -203,7 +246,19 @@ export class ToolHandlers {
|
|
|
203
246
|
case 'edit_text_file':
|
|
204
247
|
const edB64 = Buffer.from(params.content).toString('base64');
|
|
205
248
|
return `printf '%s' ${this.shellEscape(edB64)} | base64 -d > ${this.shellEscape(params.filePath)}`;
|
|
249
|
+
case 'append_text_file':
|
|
250
|
+
const appendB64 = Buffer.from(params.content).toString('base64');
|
|
251
|
+
return `printf '%s' ${this.shellEscape(appendB64)} | base64 -d >> ${this.shellEscape(params.filePath)}`;
|
|
206
252
|
case 'touch': return `touch ${this.shellEscape(params.filePath)}`;
|
|
253
|
+
case 'mkdir': return `mkdir ${params.parents ? '-p ' : ''}${this.shellEscape(params.path)}`;
|
|
254
|
+
case 'mv': return `mv ${params.force ? '-f ' : ''}${this.shellEscape(params.source)} ${this.shellEscape(params.destination)}`;
|
|
255
|
+
case 'cp': return `cp ${(params.recursive ? '-r ' : '') + (params.preserve ? '-p ' : '')}${this.shellEscape(params.source)} ${this.shellEscape(params.destination)}`;
|
|
256
|
+
case 'replace_in_file': {
|
|
257
|
+
const searchB64 = this.escapePerlEnvBase64(params.search);
|
|
258
|
+
const replaceB64 = this.escapePerlEnvBase64(params.replace);
|
|
259
|
+
const replaceFlag = params.replaceAll === false ? '' : 'g';
|
|
260
|
+
return `SEARCH_B64=${this.shellEscape(searchB64)} REPLACE_B64=${this.shellEscape(replaceB64)} perl -0i -M MIME::Base64 -pe ${this.shellEscape(`BEGIN { $s = decode_base64($ENV{SEARCH_B64}); $r = decode_base64($ENV{REPLACE_B64}); } s/\\Q$s\\E/$r/${replaceFlag}`)} ${this.shellEscape(params.filePath)}`;
|
|
261
|
+
}
|
|
207
262
|
case 'rm_safe':
|
|
208
263
|
const restricted = ['/', '/etc', '/usr', '/bin', '/var', '/root', '/home'];
|
|
209
264
|
if (restricted.includes(params.path.trim()))
|
|
@@ -212,7 +267,18 @@ export class ToolHandlers {
|
|
|
212
267
|
case 'echo': return `echo ${this.shellEscape(params.text)}`;
|
|
213
268
|
case 'find': return `find ${this.shellEscape(params.path)} -name ${this.shellEscape(params.name)}`;
|
|
214
269
|
case 'git_status': return 'git status';
|
|
270
|
+
case 'git_fetch': return `git fetch ${params.all ? '--all ' : ''}${params.prune ? '--prune' : ''}`.trim();
|
|
215
271
|
case 'git_pull': return 'git pull --no-edit';
|
|
272
|
+
case 'git_switch':
|
|
273
|
+
if (params.create) {
|
|
274
|
+
return `git switch -c ${this.shellEscape(params.branch)}${params.startPoint ? ` ${this.shellEscape(params.startPoint)}` : ''}`;
|
|
275
|
+
}
|
|
276
|
+
if (params.startPoint) {
|
|
277
|
+
throw new Error(`git_switch.startPoint is only valid when create=true.`);
|
|
278
|
+
}
|
|
279
|
+
return `git switch ${this.shellEscape(params.branch)}`;
|
|
280
|
+
case 'git_branch': return `git branch ${params.all ? '-a ' : ''}${params.verbose ? '-v' : ''}`.trim();
|
|
281
|
+
case 'git_log': return `git log ${params.oneline === false ? '' : '--oneline '}-n ${params.maxCount || 20}${params.path ? ` -- ${this.shellEscape(params.path)}` : ''}`.trim();
|
|
216
282
|
case 'execute_command': return params.command;
|
|
217
283
|
case 'docker_compose_up': return 'docker-compose up -d';
|
|
218
284
|
case 'docker_compose_down': return 'docker-compose down --remove-orphans';
|
|
@@ -221,11 +287,18 @@ export class ToolHandlers {
|
|
|
221
287
|
case 'docker_compose_restart': return 'docker-compose restart';
|
|
222
288
|
case 'docker_ps': return 'docker ps';
|
|
223
289
|
case 'docker_images': return 'docker images';
|
|
290
|
+
case 'docker_exec':
|
|
291
|
+
return `docker exec${params.user ? ` --user ${this.shellEscape(params.user)}` : ''}${params.workdir ? ` --workdir ${this.shellEscape(params.workdir)}` : ''} ${this.shellEscape(params.container)} ${this.shellEscape(params.command)}${params.args?.length ? ` ${this.shellEscapeList(params.args)}` : ''}`;
|
|
292
|
+
case 'docker_inspect':
|
|
293
|
+
return `docker inspect${params.format ? ` --format ${this.shellEscape(params.format)}` : ''} ${this.shellEscape(params.target)}`;
|
|
294
|
+
case 'docker_stats':
|
|
295
|
+
return `docker stats ${params.noStream === false ? '' : '--no-stream '}${params.container ? this.shellEscape(params.container) : ''}`.trim();
|
|
224
296
|
case 'docker_pull': return `docker pull ${this.shellEscape(params.image)}`;
|
|
225
297
|
case 'docker_cp': return `docker cp ${this.shellEscape(params.source)} ${this.shellEscape(params.destination)}`;
|
|
226
298
|
case 'docker_stop': return `docker stop ${this.shellEscape(params.container)}`;
|
|
227
299
|
case 'docker_rm': return `docker rm ${this.shellEscape(params.container)}`;
|
|
228
300
|
case 'docker_start': return `docker start ${this.shellEscape(params.container)}`;
|
|
301
|
+
case 'docker_restart': return `docker restart ${this.shellEscape(params.container)}`;
|
|
229
302
|
case 'docker_rmi': return `docker rmi ${this.shellEscape(params.image)}`;
|
|
230
303
|
case 'docker_commit': return `docker commit ${this.shellEscape(params.container)} ${this.shellEscape(params.repository)}`;
|
|
231
304
|
case 'docker_logs': return `docker logs -n ${params.lines || 100} ${this.shellEscape(params.container)}`;
|
|
@@ -236,17 +309,50 @@ export class ToolHandlers {
|
|
|
236
309
|
case 'systemctl_start': return `systemctl start ${this.shellEscape(params.service)}`;
|
|
237
310
|
case 'systemctl_stop': return `systemctl stop ${this.shellEscape(params.service)}`;
|
|
238
311
|
case 'ip_addr': return 'ip addr';
|
|
312
|
+
case 'journalctl':
|
|
313
|
+
return `journalctl --no-pager${params.unit ? ` -u ${this.shellEscape(params.unit)}` : ''}${params.priority ? ` -p ${this.shellEscape(params.priority)}` : ''}${params.since ? ` --since ${this.shellEscape(params.since)}` : ''} -n ${params.lines || 100}`;
|
|
239
314
|
case 'firewall_cmd':
|
|
240
315
|
return this.buildFirewallCommand(params);
|
|
241
316
|
case 'netstat':
|
|
242
|
-
return `netstat ${(params.args && params.args.length > 0) ? params.args.
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
317
|
+
return `netstat ${(params.args && params.args.length > 0) ? params.args.join(' ') : '-tuln'}`;
|
|
318
|
+
case 'ss':
|
|
319
|
+
return `ss ${(params.args && params.args.length > 0) ? params.args.join(' ') : '-tuln'}`;
|
|
320
|
+
case 'ping_host':
|
|
321
|
+
return `ping -c ${params.count || 4} ${this.shellEscape(params.host)}`;
|
|
322
|
+
case 'traceroute':
|
|
323
|
+
return `traceroute${params.maxHops ? ` -m ${params.maxHops}` : ''} ${this.shellEscape(params.host)}`;
|
|
324
|
+
case 'nslookup':
|
|
325
|
+
return `nslookup ${this.shellEscape(params.host)}${params.server ? ` ${this.shellEscape(params.server)}` : ''}`;
|
|
326
|
+
case 'dig':
|
|
327
|
+
return `dig ${this.shellEscape(params.host)}${params.recordType ? ` ${this.shellEscape(params.recordType)}` : ''}${params.server ? ` ${this.shellEscape(`@${params.server}`)}` : ''}`;
|
|
328
|
+
case 'curl_http': {
|
|
329
|
+
const method = String(params.method || 'GET').toUpperCase();
|
|
330
|
+
const headerArgs = (params.headers || []).map((header) => ` -H ${this.shellEscape(header)}`).join('');
|
|
331
|
+
const common = `curl -X ${method}${params.followRedirects ? ' -L' : ''}${params.timeoutSeconds ? ` --max-time ${params.timeoutSeconds}` : ''}${headerArgs} ${this.shellEscape(params.url)}`;
|
|
332
|
+
if (params.body !== undefined) {
|
|
333
|
+
const bodyB64 = Buffer.from(params.body).toString('base64');
|
|
334
|
+
return `printf '%s' ${this.shellEscape(bodyB64)} | base64 -d | ${common} --data-binary @-`;
|
|
335
|
+
}
|
|
336
|
+
return common;
|
|
337
|
+
}
|
|
246
338
|
case 'df_h': return 'df -h';
|
|
247
339
|
case 'du_sh': return `du -sh ${this.shellEscape(params.path)}`;
|
|
248
340
|
case 'nvidia_smi': return 'nvidia-smi';
|
|
249
341
|
case 'ps': return 'ps aux';
|
|
342
|
+
case 'pgrep': return `pgrep ${params.fullCommand ? '-af ' : '-a '}${this.shellEscape(params.pattern)}`;
|
|
343
|
+
case 'kill_process': return `kill -s ${this.shellEscape(params.signal || 'TERM')} ${params.pid}`;
|
|
344
|
+
case 'chmod': return `chmod ${params.recursive ? '-R ' : ''}${this.shellEscape(params.mode)} ${this.shellEscape(params.path)}`;
|
|
345
|
+
case 'chown': return `chown ${params.recursive ? '-R ' : ''}${this.shellEscape(params.owner)} ${this.shellEscape(params.path)}`;
|
|
346
|
+
case 'ln': return `ln ${params.symbolic === false ? '' : '-s '}${params.force ? '-f ' : ''}${this.shellEscape(params.target)} ${this.shellEscape(params.linkPath)}`;
|
|
347
|
+
case 'tar_create':
|
|
348
|
+
return `tar ${params.gzip ? '-czf' : '-cf'} ${this.shellEscape(params.outputPath)} ${this.shellEscapeList(params.sourcePaths)}`;
|
|
349
|
+
case 'tar_extract': {
|
|
350
|
+
return `mkdir -p ${this.shellEscape(params.destination)} && tar ${params.gzip ? '-xzf' : '-xf'} ${this.shellEscape(params.archivePath)} -C ${this.shellEscape(params.destination)}`;
|
|
351
|
+
}
|
|
352
|
+
case 'zip':
|
|
353
|
+
return `zip ${params.recursive === false ? '' : '-r '}${this.shellEscape(params.outputPath)} ${this.shellEscapeList(params.sourcePaths)}`.trim();
|
|
354
|
+
case 'unzip':
|
|
355
|
+
return `mkdir -p ${this.shellEscape(params.destination)} && unzip ${params.overwrite ? '-o ' : '-n '}${this.shellEscape(params.archivePath)} -d ${this.shellEscape(params.destination)}`;
|
|
250
356
|
default: return '';
|
|
251
357
|
}
|
|
252
358
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jadchene/mcp-ssh-service",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "A production-ready, highly secure SSH MCP server featuring stateless connections, two-step operation confirmation, and comprehensive DevOps tool integration.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|