@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 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,7 +193,7 @@ 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 (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 (2)
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 (17)
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 (7)
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
- ### Stats & Process (4)
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
- Total: 50 tools.
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
  ];
@@ -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.validateShellToken(arg, `netstat.args[${index}]`);
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.map((arg, index) => {
243
- this.validateShellToken(arg, `netstat.args[${index}]`);
244
- return arg;
245
- }).join(' ') : '-tuln'}`;
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.0",
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",