@jadchene/mcp-ssh-service 1.3.0 → 1.4.1
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 +42 -9
- package/dist/tools/definitions.js +160 -0
- package/dist/tools/handlers.js +133 -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,23 +193,34 @@ args = ["--config", "./config.json"]
|
|
|
193
193
|
* `execute_command` [Auth Required, single command only]
|
|
194
194
|
* `echo`
|
|
195
195
|
|
|
196
|
-
### File Management (
|
|
196
|
+
### File Management (17)
|
|
197
197
|
* `upload_file` [Auth Required]
|
|
198
198
|
* `download_file`
|
|
199
199
|
* `ll`
|
|
200
200
|
* `cat`
|
|
201
|
+
* `head`
|
|
201
202
|
* `tail`
|
|
203
|
+
* `sed`
|
|
202
204
|
* `grep`
|
|
203
205
|
* `edit_text_file` [Auth Required]
|
|
204
206
|
* `touch`
|
|
207
|
+
* `mkdir` [Auth Required]
|
|
208
|
+
* `mv` [Auth Required]
|
|
209
|
+
* `cp` [Auth Required]
|
|
210
|
+
* `append_text_file` [Auth Required]
|
|
211
|
+
* `replace_in_file` [Auth Required]
|
|
205
212
|
* `rm_safe` [Auth Required]
|
|
206
213
|
* `find`
|
|
207
214
|
|
|
208
|
-
### Git (
|
|
215
|
+
### Git (6)
|
|
209
216
|
* `git_status`
|
|
217
|
+
* `git_fetch` [Auth Required]
|
|
210
218
|
* `git_pull` [Auth Required]
|
|
219
|
+
* `git_switch` [Auth Required]
|
|
220
|
+
* `git_branch`
|
|
221
|
+
* `git_log`
|
|
211
222
|
|
|
212
|
-
### Docker & Compose (
|
|
223
|
+
### Docker & Compose (21)
|
|
213
224
|
* `docker_compose_up` [Auth Required]
|
|
214
225
|
* `docker_compose_down` [Auth Required]
|
|
215
226
|
* `docker_compose_stop` [Auth Required]
|
|
@@ -217,33 +228,53 @@ args = ["--config", "./config.json"]
|
|
|
217
228
|
* `docker_compose_restart` [Auth Required]
|
|
218
229
|
* `docker_ps`
|
|
219
230
|
* `docker_images`
|
|
231
|
+
* `docker_exec` [Auth Required]
|
|
232
|
+
* `docker_inspect`
|
|
233
|
+
* `docker_stats`
|
|
220
234
|
* `docker_pull` [Auth Required]
|
|
221
235
|
* `docker_cp` [Auth Required]
|
|
222
236
|
* `docker_stop` [Auth Required]
|
|
223
237
|
* `docker_rm` [Auth Required]
|
|
224
238
|
* `docker_start` [Auth Required]
|
|
239
|
+
* `docker_restart` [Auth Required]
|
|
225
240
|
* `docker_rmi` [Auth Required]
|
|
226
241
|
* `docker_commit` [Auth Required]
|
|
227
242
|
* `docker_logs`
|
|
228
243
|
* `docker_load` [Auth Required]
|
|
229
244
|
* `docker_save` [Auth Required]
|
|
230
245
|
|
|
231
|
-
### Service & Network (
|
|
246
|
+
### Service & Network (14)
|
|
232
247
|
* `systemctl_status`
|
|
233
248
|
* `systemctl_restart` [Auth Required]
|
|
234
249
|
* `systemctl_start` [Auth Required]
|
|
235
250
|
* `systemctl_stop` [Auth Required]
|
|
236
251
|
* `ip_addr`
|
|
252
|
+
* `journalctl`
|
|
237
253
|
* `firewall_cmd` [Auth Required, structured actions only]
|
|
238
254
|
* `netstat` [uses `args: string[]`]
|
|
239
|
-
|
|
240
|
-
|
|
255
|
+
* `ss` [uses `args: string[]`]
|
|
256
|
+
* `ping_host`
|
|
257
|
+
* `traceroute`
|
|
258
|
+
* `nslookup`
|
|
259
|
+
* `dig`
|
|
260
|
+
* `curl_http` [Auth Required]
|
|
261
|
+
|
|
262
|
+
### Stats & Process (13)
|
|
241
263
|
* `nvidia_smi`
|
|
242
264
|
* `ps`
|
|
265
|
+
* `pgrep`
|
|
266
|
+
* `kill_process` [Auth Required]
|
|
243
267
|
* `df_h`
|
|
244
268
|
* `du_sh`
|
|
245
|
-
|
|
246
|
-
|
|
269
|
+
* `chmod` [Auth Required]
|
|
270
|
+
* `chown` [Auth Required]
|
|
271
|
+
* `ln` [Auth Required]
|
|
272
|
+
* `tar_create` [Auth Required]
|
|
273
|
+
* `tar_extract` [Auth Required]
|
|
274
|
+
* `zip` [Auth Required]
|
|
275
|
+
* `unzip` [Auth Required]
|
|
276
|
+
|
|
277
|
+
Total: 81 tools.
|
|
247
278
|
|
|
248
279
|
---
|
|
249
280
|
|
|
@@ -260,6 +291,8 @@ If a high-risk tool's final command string matches `commandWhitelist`, the serve
|
|
|
260
291
|
`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
292
|
|
|
262
293
|
`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.
|
|
294
|
+
|
|
295
|
+
Use `mkdir` for directory creation instead of `execute_command "mkdir ..."`. Set `parents: true` when you need `mkdir -p` behavior.
|
|
263
296
|
|
|
264
297
|
---
|
|
265
298
|
|
|
@@ -115,11 +115,26 @@ export const toolDefinitions = [
|
|
|
115
115
|
description: 'File reading: Reads text file content.',
|
|
116
116
|
inputSchema: baseParams({ filePath: { type: 'string' }, ...grepParam }, ['filePath'])
|
|
117
117
|
},
|
|
118
|
+
{
|
|
119
|
+
name: 'head',
|
|
120
|
+
description: 'File preview: Reads the first N lines of a file.',
|
|
121
|
+
inputSchema: baseParams({ filePath: { type: 'string' }, lines: { type: 'number' }, ...grepParam }, ['filePath'])
|
|
122
|
+
},
|
|
118
123
|
{
|
|
119
124
|
name: 'tail',
|
|
120
125
|
description: 'Log inspection: Reads last N lines of a file.',
|
|
121
126
|
inputSchema: baseParams({ filePath: { type: 'string' }, lines: { type: 'number' }, ...grepParam }, ['filePath'])
|
|
122
127
|
},
|
|
128
|
+
{
|
|
129
|
+
name: 'sed',
|
|
130
|
+
description: 'Line range reading: Reads an inclusive line range from a text file.',
|
|
131
|
+
inputSchema: baseParams({
|
|
132
|
+
filePath: { type: 'string' },
|
|
133
|
+
startLine: { type: 'number' },
|
|
134
|
+
endLine: { type: 'number' },
|
|
135
|
+
...grepParam
|
|
136
|
+
}, ['filePath', 'startLine', 'endLine'])
|
|
137
|
+
},
|
|
123
138
|
{
|
|
124
139
|
name: 'grep',
|
|
125
140
|
description: 'Pattern search: Search for a regex pattern in a file.',
|
|
@@ -139,6 +154,31 @@ export const toolDefinitions = [
|
|
|
139
154
|
description: 'Timestamp/File creation: Updates access time or creates empty file.',
|
|
140
155
|
inputSchema: baseParams({ filePath: { type: 'string' } }, ['filePath'])
|
|
141
156
|
},
|
|
157
|
+
{
|
|
158
|
+
name: 'mkdir',
|
|
159
|
+
description: 'Directory creation: Creates a directory. Set parents=true for mkdir -p behavior. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
160
|
+
inputSchema: baseParams({ path: { type: 'string' }, parents: { type: 'boolean' }, ...confirmationParams }, ['path'])
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: 'mv',
|
|
164
|
+
description: 'Move or rename a file or directory. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
165
|
+
inputSchema: baseParams({ source: { type: 'string' }, destination: { type: 'string' }, force: { type: 'boolean' }, ...confirmationParams }, ['source', 'destination'])
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: 'cp',
|
|
169
|
+
description: 'Copy a file or directory. Set recursive=true for directories. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
170
|
+
inputSchema: baseParams({ source: { type: 'string' }, destination: { type: 'string' }, recursive: { type: 'boolean' }, preserve: { type: 'boolean' }, ...confirmationParams }, ['source', 'destination'])
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: 'append_text_file',
|
|
174
|
+
description: 'Append text to a file, creating it if needed. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
175
|
+
inputSchema: baseParams({ filePath: { type: 'string' }, content: { type: 'string' }, ...confirmationParams }, ['filePath', 'content'])
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: 'replace_in_file',
|
|
179
|
+
description: 'Replace literal text inside a file. Set replaceAll=false to replace only the first occurrence. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
180
|
+
inputSchema: baseParams({ filePath: { type: 'string' }, search: { type: 'string' }, replace: { type: 'string' }, replaceAll: { type: 'boolean' }, ...confirmationParams }, ['filePath', 'search', 'replace'])
|
|
181
|
+
},
|
|
142
182
|
{
|
|
143
183
|
name: 'rm_safe',
|
|
144
184
|
description: 'File deletion: Removes file or directory. REQUIRES CONFIRMATION.',
|
|
@@ -155,11 +195,31 @@ export const toolDefinitions = [
|
|
|
155
195
|
description: 'Git status: Displays repository status.',
|
|
156
196
|
inputSchema: baseParams(cwdParam)
|
|
157
197
|
},
|
|
198
|
+
{
|
|
199
|
+
name: 'git_fetch',
|
|
200
|
+
description: 'Git fetch: Updates remote tracking refs. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
201
|
+
inputSchema: baseParams({ ...cwdParam, all: { type: 'boolean' }, prune: { type: 'boolean' }, ...confirmationParams })
|
|
202
|
+
},
|
|
158
203
|
{
|
|
159
204
|
name: 'git_pull',
|
|
160
205
|
description: 'Git update: Pulls latest changes. REQUIRES CONFIRMATION.',
|
|
161
206
|
inputSchema: baseParams({ ...cwdParam, ...confirmationParams })
|
|
162
207
|
},
|
|
208
|
+
{
|
|
209
|
+
name: 'git_switch',
|
|
210
|
+
description: 'Git switch: Switches branches, or creates one with create=true. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
211
|
+
inputSchema: baseParams({ ...cwdParam, branch: { type: 'string' }, create: { type: 'boolean' }, startPoint: { type: 'string' }, ...confirmationParams }, ['branch'])
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
name: 'git_branch',
|
|
215
|
+
description: 'Git branch: Lists local or all branches.',
|
|
216
|
+
inputSchema: baseParams({ ...cwdParam, all: { type: 'boolean' }, verbose: { type: 'boolean' } })
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
name: 'git_log',
|
|
220
|
+
description: 'Git log: Shows recent commit history.',
|
|
221
|
+
inputSchema: baseParams({ ...cwdParam, maxCount: { type: 'number' }, oneline: { type: 'boolean' }, path: { type: 'string' } })
|
|
222
|
+
},
|
|
163
223
|
// --- Docker & Compose (Requirements) ---
|
|
164
224
|
{
|
|
165
225
|
name: 'docker_compose_up',
|
|
@@ -196,6 +256,21 @@ export const toolDefinitions = [
|
|
|
196
256
|
description: 'List docker images.',
|
|
197
257
|
inputSchema: baseParams(grepParam)
|
|
198
258
|
},
|
|
259
|
+
{
|
|
260
|
+
name: 'docker_exec',
|
|
261
|
+
description: 'Run one process inside a running container without shell expansion. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
262
|
+
inputSchema: baseParams({ container: { type: 'string' }, command: { type: 'string' }, args: { type: 'array', items: { type: 'string' } }, user: { type: 'string' }, workdir: { type: 'string' }, ...confirmationParams }, ['container', 'command'])
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
name: 'docker_inspect',
|
|
266
|
+
description: 'Inspect a container, image, volume, or network.',
|
|
267
|
+
inputSchema: baseParams({ target: { type: 'string' }, format: { type: 'string' } }, ['target'])
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
name: 'docker_stats',
|
|
271
|
+
description: 'Show container resource usage.',
|
|
272
|
+
inputSchema: baseParams({ container: { type: 'string' }, noStream: { type: 'boolean' } })
|
|
273
|
+
},
|
|
199
274
|
{
|
|
200
275
|
name: 'docker_pull',
|
|
201
276
|
description: 'Pull an image from a registry. REQUIRES CONFIRMATION.',
|
|
@@ -221,6 +296,11 @@ export const toolDefinitions = [
|
|
|
221
296
|
description: 'Start one or more stopped containers. REQUIRES CONFIRMATION.',
|
|
222
297
|
inputSchema: baseParams({ container: { type: 'string' }, ...confirmationParams }, ['container'])
|
|
223
298
|
},
|
|
299
|
+
{
|
|
300
|
+
name: 'docker_restart',
|
|
301
|
+
description: 'Restart one or more running containers. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
302
|
+
inputSchema: baseParams({ container: { type: 'string' }, ...confirmationParams }, ['container'])
|
|
303
|
+
},
|
|
224
304
|
{
|
|
225
305
|
name: 'docker_rmi',
|
|
226
306
|
description: 'Remove one or more images. REQUIRES CONFIRMATION.',
|
|
@@ -272,6 +352,11 @@ export const toolDefinitions = [
|
|
|
272
352
|
description: 'Show network interface info.',
|
|
273
353
|
inputSchema: baseParams(grepParam)
|
|
274
354
|
},
|
|
355
|
+
{
|
|
356
|
+
name: 'journalctl',
|
|
357
|
+
description: 'Read systemd journal logs with optional unit, since, and priority filters.',
|
|
358
|
+
inputSchema: baseParams({ unit: { type: 'string' }, lines: { type: 'number' }, since: { type: 'string' }, priority: { type: 'string' }, grep: { type: 'string' } })
|
|
359
|
+
},
|
|
275
360
|
{
|
|
276
361
|
name: 'firewall_cmd',
|
|
277
362
|
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 +374,36 @@ export const toolDefinitions = [
|
|
|
289
374
|
description: 'Monitor ports/connections. Use args as an array of individual option tokens, for example ["-t", "-u", "-l", "-n"].',
|
|
290
375
|
inputSchema: baseParams({ args: { type: 'array', items: { type: 'string' } }, ...grepParam })
|
|
291
376
|
},
|
|
377
|
+
{
|
|
378
|
+
name: 'ss',
|
|
379
|
+
description: 'Socket statistics. Use args as an array of individual option tokens, for example ["-t", "-u", "-l", "-n"].',
|
|
380
|
+
inputSchema: baseParams({ args: { type: 'array', items: { type: 'string' } }, ...grepParam })
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
name: 'ping_host',
|
|
384
|
+
description: 'Ping a host a fixed number of times.',
|
|
385
|
+
inputSchema: baseParams({ host: { type: 'string' }, count: { type: 'number' } }, ['host'])
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
name: 'traceroute',
|
|
389
|
+
description: 'Trace the network path to a host.',
|
|
390
|
+
inputSchema: baseParams({ host: { type: 'string' }, maxHops: { type: 'number' } }, ['host'])
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
name: 'nslookup',
|
|
394
|
+
description: 'Resolve hostnames using nslookup.',
|
|
395
|
+
inputSchema: baseParams({ host: { type: 'string' }, server: { type: 'string' } }, ['host'])
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
name: 'dig',
|
|
399
|
+
description: 'Resolve DNS records using dig.',
|
|
400
|
+
inputSchema: baseParams({ host: { type: 'string' }, recordType: { type: 'string' }, server: { type: 'string' } }, ['host'])
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
name: 'curl_http',
|
|
404
|
+
description: 'Perform an HTTP request with structured method, URL, headers, and optional body. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
405
|
+
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'])
|
|
406
|
+
},
|
|
292
407
|
// --- Stats & Process (Requirements) ---
|
|
293
408
|
{
|
|
294
409
|
name: 'nvidia_smi',
|
|
@@ -300,6 +415,16 @@ export const toolDefinitions = [
|
|
|
300
415
|
description: 'Report a snapshot of the current processes.',
|
|
301
416
|
inputSchema: baseParams(grepParam)
|
|
302
417
|
},
|
|
418
|
+
{
|
|
419
|
+
name: 'pgrep',
|
|
420
|
+
description: 'Find process IDs by name or full command pattern.',
|
|
421
|
+
inputSchema: baseParams({ pattern: { type: 'string' }, fullCommand: { type: 'boolean' } }, ['pattern'])
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
name: 'kill_process',
|
|
425
|
+
description: 'Send a signal to a process ID. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
426
|
+
inputSchema: baseParams({ pid: { type: 'number' }, signal: { type: 'string' }, ...confirmationParams }, ['pid'])
|
|
427
|
+
},
|
|
303
428
|
{
|
|
304
429
|
name: 'df_h',
|
|
305
430
|
description: 'System disk usage.',
|
|
@@ -309,5 +434,40 @@ export const toolDefinitions = [
|
|
|
309
434
|
name: 'du_sh',
|
|
310
435
|
description: 'Directory size estimation.',
|
|
311
436
|
inputSchema: baseParams({ path: { type: 'string' }, ...grepParam }, ['path'])
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
name: 'chmod',
|
|
440
|
+
description: 'Change file mode bits. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
441
|
+
inputSchema: baseParams({ mode: { type: 'string' }, path: { type: 'string' }, recursive: { type: 'boolean' }, ...confirmationParams }, ['mode', 'path'])
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
name: 'chown',
|
|
445
|
+
description: 'Change file owner and group. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
446
|
+
inputSchema: baseParams({ owner: { type: 'string' }, path: { type: 'string' }, recursive: { type: 'boolean' }, ...confirmationParams }, ['owner', 'path'])
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
name: 'ln',
|
|
450
|
+
description: 'Create a link. Uses symbolic=true by default for symlinks. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
451
|
+
inputSchema: baseParams({ target: { type: 'string' }, linkPath: { type: 'string' }, symbolic: { type: 'boolean' }, force: { type: 'boolean' }, ...confirmationParams }, ['target', 'linkPath'])
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
name: 'tar_create',
|
|
455
|
+
description: 'Create a tar archive from one or more source paths. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
456
|
+
inputSchema: baseParams({ sourcePaths: { type: 'array', items: { type: 'string' } }, outputPath: { type: 'string' }, gzip: { type: 'boolean' }, ...confirmationParams }, ['sourcePaths', 'outputPath'])
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
name: 'tar_extract',
|
|
460
|
+
description: 'Extract a tar archive into a destination directory. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
461
|
+
inputSchema: baseParams({ archivePath: { type: 'string' }, destination: { type: 'string' }, gzip: { type: 'boolean' }, ...confirmationParams }, ['archivePath', 'destination'])
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
name: 'zip',
|
|
465
|
+
description: 'Create a zip archive from one or more source paths. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
466
|
+
inputSchema: baseParams({ sourcePaths: { type: 'array', items: { type: 'string' } }, outputPath: { type: 'string' }, recursive: { type: 'boolean' }, ...confirmationParams }, ['sourcePaths', 'outputPath'])
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
name: 'unzip',
|
|
470
|
+
description: 'Extract a zip archive into a destination directory. REQUIRES CONFIRMATION unless the final command is whitelisted.',
|
|
471
|
+
inputSchema: baseParams({ archivePath: { type: 'string' }, destination: { type: 'string' }, overwrite: { type: 'boolean' }, ...confirmationParams }, ['archivePath', 'destination'])
|
|
312
472
|
}
|
|
313
473
|
];
|
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,25 @@ 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
|
+
/**
|
|
121
|
+
* Validate positive line counts for text inspection helpers.
|
|
122
|
+
*/
|
|
123
|
+
validatePositiveInteger(value, fieldName) {
|
|
124
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
125
|
+
throw new Error(`${fieldName} must be a positive integer.`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
escapePerlEnvBase64(value) {
|
|
129
|
+
return Buffer.from(value).toString('base64');
|
|
130
|
+
}
|
|
94
131
|
ensureNoShellControl(value, errorMessage) {
|
|
95
132
|
const forbiddenOperators = [/&&/, /\|\|/, /;/, /\|/, /\$\(/, /`/, />/, /</, /\r|\n/];
|
|
96
133
|
for (const pattern of forbiddenOperators) {
|
|
@@ -180,9 +217,33 @@ export class ToolHandlers {
|
|
|
180
217
|
if (name === 'execute_command') {
|
|
181
218
|
this.validateSingleCommand(params.command);
|
|
182
219
|
}
|
|
183
|
-
if (name === 'netstat' && Array.isArray(params.args)) {
|
|
220
|
+
if ((name === 'netstat' || name === 'ss') && Array.isArray(params.args)) {
|
|
221
|
+
this.validateTokenArray(params.args, `${name}.args`);
|
|
222
|
+
}
|
|
223
|
+
if ((name === 'head' || name === 'tail') && params.lines !== undefined) {
|
|
224
|
+
this.validatePositiveInteger(params.lines, `${name}.lines`);
|
|
225
|
+
}
|
|
226
|
+
if (name === 'sed') {
|
|
227
|
+
this.validatePositiveInteger(params.startLine, 'sed.startLine');
|
|
228
|
+
this.validatePositiveInteger(params.endLine, 'sed.endLine');
|
|
229
|
+
if (params.endLine < params.startLine) {
|
|
230
|
+
throw new Error(`sed.endLine must be greater than or equal to sed.startLine.`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (name === 'docker_exec' && Array.isArray(params.args)) {
|
|
184
234
|
for (const [index, arg] of params.args.entries()) {
|
|
185
|
-
this.
|
|
235
|
+
this.validateShellFragment(arg, `docker_exec.args[${index}]`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if ((name === 'tar_create' || name === 'zip') && Array.isArray(params.sourcePaths)) {
|
|
239
|
+
if (params.sourcePaths.length === 0) {
|
|
240
|
+
throw new Error(`${name}.sourcePaths must contain at least one path.`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (name === 'curl_http') {
|
|
244
|
+
this.validateShellToken(String(params.method || 'GET').toUpperCase(), 'curl_http.method');
|
|
245
|
+
for (const [index, header] of (params.headers || []).entries()) {
|
|
246
|
+
this.validateShellFragment(header, `curl_http.headers[${index}]`);
|
|
186
247
|
}
|
|
187
248
|
}
|
|
188
249
|
const command = this.getExecutableCommand(name, params);
|
|
@@ -198,12 +259,26 @@ export class ToolHandlers {
|
|
|
198
259
|
case 'cd': return `cd ${this.shellEscape(params.path)}`;
|
|
199
260
|
case 'll': return 'ls -l';
|
|
200
261
|
case 'cat': return `cat ${this.shellEscape(params.filePath)}`;
|
|
262
|
+
case 'head': return `head -n ${params.lines || 40} ${this.shellEscape(params.filePath)}`;
|
|
201
263
|
case 'tail': return `tail -n ${params.lines || 50} ${this.shellEscape(params.filePath)}`;
|
|
264
|
+
case 'sed': return `sed -n '${params.startLine},${params.endLine}p' ${this.shellEscape(params.filePath)}`;
|
|
202
265
|
case 'grep': return `grep ${params.ignoreCase ? '-inE' : '-nE'} ${this.shellEscape(params.pattern)} ${this.shellEscape(params.filePath)}`;
|
|
203
266
|
case 'edit_text_file':
|
|
204
267
|
const edB64 = Buffer.from(params.content).toString('base64');
|
|
205
268
|
return `printf '%s' ${this.shellEscape(edB64)} | base64 -d > ${this.shellEscape(params.filePath)}`;
|
|
269
|
+
case 'append_text_file':
|
|
270
|
+
const appendB64 = Buffer.from(params.content).toString('base64');
|
|
271
|
+
return `printf '%s' ${this.shellEscape(appendB64)} | base64 -d >> ${this.shellEscape(params.filePath)}`;
|
|
206
272
|
case 'touch': return `touch ${this.shellEscape(params.filePath)}`;
|
|
273
|
+
case 'mkdir': return `mkdir ${params.parents ? '-p ' : ''}${this.shellEscape(params.path)}`;
|
|
274
|
+
case 'mv': return `mv ${params.force ? '-f ' : ''}${this.shellEscape(params.source)} ${this.shellEscape(params.destination)}`;
|
|
275
|
+
case 'cp': return `cp ${(params.recursive ? '-r ' : '') + (params.preserve ? '-p ' : '')}${this.shellEscape(params.source)} ${this.shellEscape(params.destination)}`;
|
|
276
|
+
case 'replace_in_file': {
|
|
277
|
+
const searchB64 = this.escapePerlEnvBase64(params.search);
|
|
278
|
+
const replaceB64 = this.escapePerlEnvBase64(params.replace);
|
|
279
|
+
const replaceFlag = params.replaceAll === false ? '' : 'g';
|
|
280
|
+
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)}`;
|
|
281
|
+
}
|
|
207
282
|
case 'rm_safe':
|
|
208
283
|
const restricted = ['/', '/etc', '/usr', '/bin', '/var', '/root', '/home'];
|
|
209
284
|
if (restricted.includes(params.path.trim()))
|
|
@@ -212,7 +287,18 @@ export class ToolHandlers {
|
|
|
212
287
|
case 'echo': return `echo ${this.shellEscape(params.text)}`;
|
|
213
288
|
case 'find': return `find ${this.shellEscape(params.path)} -name ${this.shellEscape(params.name)}`;
|
|
214
289
|
case 'git_status': return 'git status';
|
|
290
|
+
case 'git_fetch': return `git fetch ${params.all ? '--all ' : ''}${params.prune ? '--prune' : ''}`.trim();
|
|
215
291
|
case 'git_pull': return 'git pull --no-edit';
|
|
292
|
+
case 'git_switch':
|
|
293
|
+
if (params.create) {
|
|
294
|
+
return `git switch -c ${this.shellEscape(params.branch)}${params.startPoint ? ` ${this.shellEscape(params.startPoint)}` : ''}`;
|
|
295
|
+
}
|
|
296
|
+
if (params.startPoint) {
|
|
297
|
+
throw new Error(`git_switch.startPoint is only valid when create=true.`);
|
|
298
|
+
}
|
|
299
|
+
return `git switch ${this.shellEscape(params.branch)}`;
|
|
300
|
+
case 'git_branch': return `git branch ${params.all ? '-a ' : ''}${params.verbose ? '-v' : ''}`.trim();
|
|
301
|
+
case 'git_log': return `git log ${params.oneline === false ? '' : '--oneline '}-n ${params.maxCount || 20}${params.path ? ` -- ${this.shellEscape(params.path)}` : ''}`.trim();
|
|
216
302
|
case 'execute_command': return params.command;
|
|
217
303
|
case 'docker_compose_up': return 'docker-compose up -d';
|
|
218
304
|
case 'docker_compose_down': return 'docker-compose down --remove-orphans';
|
|
@@ -221,11 +307,18 @@ export class ToolHandlers {
|
|
|
221
307
|
case 'docker_compose_restart': return 'docker-compose restart';
|
|
222
308
|
case 'docker_ps': return 'docker ps';
|
|
223
309
|
case 'docker_images': return 'docker images';
|
|
310
|
+
case 'docker_exec':
|
|
311
|
+
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)}` : ''}`;
|
|
312
|
+
case 'docker_inspect':
|
|
313
|
+
return `docker inspect${params.format ? ` --format ${this.shellEscape(params.format)}` : ''} ${this.shellEscape(params.target)}`;
|
|
314
|
+
case 'docker_stats':
|
|
315
|
+
return `docker stats ${params.noStream === false ? '' : '--no-stream '}${params.container ? this.shellEscape(params.container) : ''}`.trim();
|
|
224
316
|
case 'docker_pull': return `docker pull ${this.shellEscape(params.image)}`;
|
|
225
317
|
case 'docker_cp': return `docker cp ${this.shellEscape(params.source)} ${this.shellEscape(params.destination)}`;
|
|
226
318
|
case 'docker_stop': return `docker stop ${this.shellEscape(params.container)}`;
|
|
227
319
|
case 'docker_rm': return `docker rm ${this.shellEscape(params.container)}`;
|
|
228
320
|
case 'docker_start': return `docker start ${this.shellEscape(params.container)}`;
|
|
321
|
+
case 'docker_restart': return `docker restart ${this.shellEscape(params.container)}`;
|
|
229
322
|
case 'docker_rmi': return `docker rmi ${this.shellEscape(params.image)}`;
|
|
230
323
|
case 'docker_commit': return `docker commit ${this.shellEscape(params.container)} ${this.shellEscape(params.repository)}`;
|
|
231
324
|
case 'docker_logs': return `docker logs -n ${params.lines || 100} ${this.shellEscape(params.container)}`;
|
|
@@ -236,17 +329,50 @@ export class ToolHandlers {
|
|
|
236
329
|
case 'systemctl_start': return `systemctl start ${this.shellEscape(params.service)}`;
|
|
237
330
|
case 'systemctl_stop': return `systemctl stop ${this.shellEscape(params.service)}`;
|
|
238
331
|
case 'ip_addr': return 'ip addr';
|
|
332
|
+
case 'journalctl':
|
|
333
|
+
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
334
|
case 'firewall_cmd':
|
|
240
335
|
return this.buildFirewallCommand(params);
|
|
241
336
|
case 'netstat':
|
|
242
|
-
return `netstat ${(params.args && params.args.length > 0) ? params.args.
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
337
|
+
return `netstat ${(params.args && params.args.length > 0) ? params.args.join(' ') : '-tuln'}`;
|
|
338
|
+
case 'ss':
|
|
339
|
+
return `ss ${(params.args && params.args.length > 0) ? params.args.join(' ') : '-tuln'}`;
|
|
340
|
+
case 'ping_host':
|
|
341
|
+
return `ping -c ${params.count || 4} ${this.shellEscape(params.host)}`;
|
|
342
|
+
case 'traceroute':
|
|
343
|
+
return `traceroute${params.maxHops ? ` -m ${params.maxHops}` : ''} ${this.shellEscape(params.host)}`;
|
|
344
|
+
case 'nslookup':
|
|
345
|
+
return `nslookup ${this.shellEscape(params.host)}${params.server ? ` ${this.shellEscape(params.server)}` : ''}`;
|
|
346
|
+
case 'dig':
|
|
347
|
+
return `dig ${this.shellEscape(params.host)}${params.recordType ? ` ${this.shellEscape(params.recordType)}` : ''}${params.server ? ` ${this.shellEscape(`@${params.server}`)}` : ''}`;
|
|
348
|
+
case 'curl_http': {
|
|
349
|
+
const method = String(params.method || 'GET').toUpperCase();
|
|
350
|
+
const headerArgs = (params.headers || []).map((header) => ` -H ${this.shellEscape(header)}`).join('');
|
|
351
|
+
const common = `curl -X ${method}${params.followRedirects ? ' -L' : ''}${params.timeoutSeconds ? ` --max-time ${params.timeoutSeconds}` : ''}${headerArgs} ${this.shellEscape(params.url)}`;
|
|
352
|
+
if (params.body !== undefined) {
|
|
353
|
+
const bodyB64 = Buffer.from(params.body).toString('base64');
|
|
354
|
+
return `printf '%s' ${this.shellEscape(bodyB64)} | base64 -d | ${common} --data-binary @-`;
|
|
355
|
+
}
|
|
356
|
+
return common;
|
|
357
|
+
}
|
|
246
358
|
case 'df_h': return 'df -h';
|
|
247
359
|
case 'du_sh': return `du -sh ${this.shellEscape(params.path)}`;
|
|
248
360
|
case 'nvidia_smi': return 'nvidia-smi';
|
|
249
361
|
case 'ps': return 'ps aux';
|
|
362
|
+
case 'pgrep': return `pgrep ${params.fullCommand ? '-af ' : '-a '}${this.shellEscape(params.pattern)}`;
|
|
363
|
+
case 'kill_process': return `kill -s ${this.shellEscape(params.signal || 'TERM')} ${params.pid}`;
|
|
364
|
+
case 'chmod': return `chmod ${params.recursive ? '-R ' : ''}${this.shellEscape(params.mode)} ${this.shellEscape(params.path)}`;
|
|
365
|
+
case 'chown': return `chown ${params.recursive ? '-R ' : ''}${this.shellEscape(params.owner)} ${this.shellEscape(params.path)}`;
|
|
366
|
+
case 'ln': return `ln ${params.symbolic === false ? '' : '-s '}${params.force ? '-f ' : ''}${this.shellEscape(params.target)} ${this.shellEscape(params.linkPath)}`;
|
|
367
|
+
case 'tar_create':
|
|
368
|
+
return `tar ${params.gzip ? '-czf' : '-cf'} ${this.shellEscape(params.outputPath)} ${this.shellEscapeList(params.sourcePaths)}`;
|
|
369
|
+
case 'tar_extract': {
|
|
370
|
+
return `mkdir -p ${this.shellEscape(params.destination)} && tar ${params.gzip ? '-xzf' : '-xf'} ${this.shellEscape(params.archivePath)} -C ${this.shellEscape(params.destination)}`;
|
|
371
|
+
}
|
|
372
|
+
case 'zip':
|
|
373
|
+
return `zip ${params.recursive === false ? '' : '-r '}${this.shellEscape(params.outputPath)} ${this.shellEscapeList(params.sourcePaths)}`.trim();
|
|
374
|
+
case 'unzip':
|
|
375
|
+
return `mkdir -p ${this.shellEscape(params.destination)} && unzip ${params.overwrite ? '-o ' : '-n '}${this.shellEscape(params.archivePath)} -d ${this.shellEscape(params.destination)}`;
|
|
250
376
|
default: return '';
|
|
251
377
|
}
|
|
252
378
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jadchene/mcp-ssh-service",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
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",
|