ssh-mcp 1.0.8 → 1.1.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.
Files changed (3) hide show
  1. package/README.md +7 -1
  2. package/build/index.js +39 -12
  3. package/package.json +13 -5
package/README.md CHANGED
@@ -48,6 +48,10 @@
48
48
  - Timeout is configured via command line argument `--timeout` (in milliseconds)
49
49
  - Default timeout: 60000ms (1 minute)
50
50
  - When a command times out, the server automatically attempts to abort the running process before closing the connection
51
+ - **Max Command Length Configuration:**
52
+ - Max command characters are configured via `--maxChars`
53
+ - Default: `1000`
54
+ - No-limit mode: set `--maxChars=none` or any `<= 0` value (e.g. `--maxChars=0`)
51
55
 
52
56
  ## Installation
53
57
 
@@ -74,6 +78,7 @@ You can configure Claude Desktop to use this MCP Server.
74
78
  - `password`: SSH password (or use `key` for key-based auth)
75
79
  - `key`: Path to private SSH key
76
80
  - `timeout`: Command execution timeout in milliseconds (default: 60000ms = 1 minute)
81
+ - `maxChars`: Maximum allowed characters for the `command` input (default: 1000). Use `none` or `0` to disable the limit.
77
82
 
78
83
 
79
84
  ```commandline
@@ -90,7 +95,8 @@ You can configure Claude Desktop to use this MCP Server.
90
95
  "--user=root",
91
96
  "--password=pass",
92
97
  "--key=path/to/key",
93
- "--timeout=30000"
98
+ "--timeout=30000",
99
+ "--maxChars=none"
94
100
  ]
95
101
  }
96
102
  }
package/build/index.js CHANGED
@@ -16,13 +16,35 @@ function parseArgv() {
16
16
  }
17
17
  return config;
18
18
  }
19
- const argvConfig = parseArgv();
19
+ const isCliEnabled = process.env.SSH_MCP_DISABLE_MAIN !== '1';
20
+ const argvConfig = isCliEnabled ? parseArgv() : {};
20
21
  const HOST = argvConfig.host;
21
22
  const PORT = argvConfig.port ? parseInt(argvConfig.port) : 22;
22
23
  const USER = argvConfig.user;
23
24
  const PASSWORD = argvConfig.password;
24
25
  const KEY = argvConfig.key;
25
26
  const DEFAULT_TIMEOUT = argvConfig.timeout ? parseInt(argvConfig.timeout) : 60000; // 60 seconds default timeout
27
+ // Max characters configuration:
28
+ // - Default: 1000 characters
29
+ // - When set via --maxChars:
30
+ // * a positive integer enforces that limit
31
+ // * 0 or a negative value disables the limit (no max)
32
+ // * the string "none" (case-insensitive) disables the limit (no max)
33
+ const MAX_CHARS_RAW = argvConfig.maxChars;
34
+ const MAX_CHARS = (() => {
35
+ if (typeof MAX_CHARS_RAW === 'string') {
36
+ const lowered = MAX_CHARS_RAW.toLowerCase();
37
+ if (lowered === 'none')
38
+ return Infinity;
39
+ const parsed = parseInt(MAX_CHARS_RAW);
40
+ if (isNaN(parsed))
41
+ return 1000;
42
+ if (parsed <= 0)
43
+ return Infinity;
44
+ return parsed;
45
+ }
46
+ return 1000;
47
+ })();
26
48
  function validateConfig(config) {
27
49
  const errors = [];
28
50
  if (!config.host)
@@ -35,9 +57,11 @@ function validateConfig(config) {
35
57
  throw new Error('Configuration error:\n' + errors.join('\n'));
36
58
  }
37
59
  }
38
- validateConfig(argvConfig);
60
+ if (isCliEnabled) {
61
+ validateConfig(argvConfig);
62
+ }
39
63
  // Command sanitization and validation
40
- function sanitizeCommand(command) {
64
+ export function sanitizeCommand(command) {
41
65
  if (typeof command !== 'string') {
42
66
  throw new McpError(ErrorCode.InvalidParams, 'Command must be a string');
43
67
  }
@@ -46,19 +70,19 @@ function sanitizeCommand(command) {
46
70
  throw new McpError(ErrorCode.InvalidParams, 'Command cannot be empty');
47
71
  }
48
72
  // Length check
49
- if (trimmedCommand.length > 1000) {
50
- throw new McpError(ErrorCode.InvalidParams, 'Command is too long (max 1000 characters)');
73
+ if (Number.isFinite(MAX_CHARS) && trimmedCommand.length > MAX_CHARS) {
74
+ throw new McpError(ErrorCode.InvalidParams, `Command is too long (max ${MAX_CHARS} characters)`);
51
75
  }
52
76
  return trimmedCommand;
53
77
  }
54
78
  // Escape command for use in shell contexts (like pkill)
55
- function escapeCommandForShell(command) {
79
+ export function escapeCommandForShell(command) {
56
80
  // Replace single quotes with escaped single quotes
57
81
  return command.replace(/'/g, "'\"'\"'");
58
82
  }
59
83
  const server = new McpServer({
60
84
  name: 'SSH MCP Server',
61
- version: '1.0.7',
85
+ version: '1.0.9',
62
86
  capabilities: {
63
87
  resources: {},
64
88
  tools: {},
@@ -92,7 +116,7 @@ server.tool("exec", "Execute a shell command on the remote SSH server and return
92
116
  throw new McpError(ErrorCode.InternalError, `Unexpected error: ${err?.message || err}`);
93
117
  }
94
118
  });
95
- async function execSshCommand(sshConfig, command) {
119
+ export async function execSshCommand(sshConfig, command) {
96
120
  return new Promise((resolve, reject) => {
97
121
  const conn = new SSHClient();
98
122
  let timeoutId;
@@ -175,7 +199,10 @@ async function main() {
175
199
  await server.connect(transport);
176
200
  console.error("SSH MCP Server running on stdio");
177
201
  }
178
- main().catch((error) => {
179
- console.error("Fatal error in main():", error);
180
- process.exit(1);
181
- });
202
+ if (process.env.SSH_MCP_DISABLE_MAIN !== '1') {
203
+ main().catch((error) => {
204
+ console.error("Fatal error in main():", error);
205
+ process.exit(1);
206
+ });
207
+ }
208
+ export { parseArgv, validateConfig };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ssh-mcp",
3
3
  "license": "MIT",
4
- "version": "1.0.8",
4
+ "version": "1.1.0",
5
5
  "description": "MCP server exposing SSH control for Linux and Windows systems via Model Context Protocol.",
6
6
  "type": "module",
7
7
  "bin": {
@@ -10,7 +10,10 @@
10
10
  "scripts": {
11
11
  "prepare": "npm run build",
12
12
  "build": "tsc && shx chmod +x build/*.js",
13
- "inspect": "npx @modelcontextprotocol/inspector node build/index.js"
13
+ "inspect": "npx @modelcontextprotocol/inspector node build/index.js",
14
+ "test": "cross-env SSH_MCP_DISABLE_MAIN=1 vitest --run",
15
+ "test:watch": "cross-env SSH_MCP_DISABLE_MAIN=1 vitest",
16
+ "coverage": "cross-env SSH_MCP_DISABLE_MAIN=1 vitest --run --coverage"
14
17
  },
15
18
  "files": [
16
19
  "build"
@@ -18,13 +21,18 @@
18
21
  "dependencies": {
19
22
  "@modelcontextprotocol/sdk": "^1.17.5",
20
23
  "ssh2": "^1.17.0",
21
- "zod": "^4.1.5"
24
+ "zod": "3.23.8"
22
25
  },
23
26
  "devDependencies": {
24
- "@types/node": "^24.3.0",
27
+ "@types/node": "^24.5.2",
25
28
  "@types/ssh2": "^1.15.5",
29
+ "cross-env": "^7.0.3",
26
30
  "shx": "^0.4.0",
27
- "typescript": "^5.9.2"
31
+ "testcontainers": "^11.7.0",
32
+ "ts-node": "^10.9.2",
33
+ "tsx": "^4.20.6",
34
+ "typescript": "^5.9.2",
35
+ "vitest": "^3.2.4"
28
36
  },
29
37
  "homepage": "https://github.com/tufantunc/ssh-mcp#readme",
30
38
  "repository": {