@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 CHANGED
@@ -177,7 +177,7 @@ args = ["--config", "./config.json"]
177
177
 
178
178
  ---
179
179
 
180
- ## 🛠️ Integrated Toolset (50 Tools)
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 (10)
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 (2)
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 (17)
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 (7)
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
- ### Stats & Process (4)
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
- Total: 50 tools.
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
  ];
@@ -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.validateShellToken(arg, `netstat.args[${index}]`);
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.map((arg, index) => {
243
- this.validateShellToken(arg, `netstat.args[${index}]`);
244
- return arg;
245
- }).join(' ') : '-tuln'}`;
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.0",
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",