ssh-mcp 1.0.4 → 1.0.6

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 +20 -6
  2. package/build/index.js +55 -16
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -28,10 +28,18 @@
28
28
  - Execute shell commands on remote Linux and Windows systems
29
29
  - Secure authentication via password or SSH key
30
30
  - Built with TypeScript and the official MCP SDK
31
+ - **Configurable timeout protection** with automatic process abortion
32
+ - **Graceful timeout handling** - attempts to kill hanging processes before closing connections
31
33
 
32
34
  ### Tools
33
35
 
34
36
  - `exec`: Execute a shell command on the remote server
37
+ - **Parameters:**
38
+ - `command` (required): Shell command to execute on the remote SSH server
39
+ - **Timeout Configuration:**
40
+ - Timeout is configured via command line argument `--timeout` (in milliseconds)
41
+ - Default timeout: 60000ms (1 minute)
42
+ - When a command times out, the server automatically attempts to abort the running process before closing the connection
35
43
 
36
44
  ## Installation
37
45
 
@@ -48,11 +56,16 @@
48
56
  ## Client Setup
49
57
 
50
58
  You can configure Claude Desktop to use this MCP Server.
51
- - `host`: Hostname or IP of the Linux or Windows server
52
- - `port`: SSH port (default: 22)
53
- - `user`: SSH username
54
- - `password`: SSH password (or use `key` for key-based auth) (optional)
55
- - `key`: Path to private SSH key (optional)
59
+
60
+ **Required Parameters:**
61
+ - `host`: Hostname or IP of the Linux or Windows server
62
+ - `user`: SSH username
63
+
64
+ **Optional Parameters:**
65
+ - `port`: SSH port (default: 22)
66
+ - `password`: SSH password (or use `key` for key-based auth)
67
+ - `key`: Path to private SSH key
68
+ - `timeout`: Command execution timeout in milliseconds (default: 60000ms = 1 minute)
56
69
 
57
70
 
58
71
  ```commandline
@@ -68,7 +81,8 @@ You can configure Claude Desktop to use this MCP Server.
68
81
  "--port=22",
69
82
  "--user=root",
70
83
  "--password=pass",
71
- "--key=path/to/key"
84
+ "--key=path/to/key",
85
+ "--timeout=30000"
72
86
  ]
73
87
  }
74
88
  }
package/build/index.js CHANGED
@@ -1,10 +1,10 @@
1
+ #!/usr/bin/env node
1
2
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
3
  import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
3
4
  import { Client as SSHClient } from 'ssh2';
4
5
  import { z } from 'zod';
5
6
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
6
- // Parse SSH config from process.argv
7
- // Example usage: node build/index.js --host=1.2.3.4 --port=22 --user=root --password=pass --key=path/to/key
7
+ // Example usage: node build/index.js --host=1.2.3.4 --port=22 --user=root --password=pass --key=path/to/key --timeout=5000
8
8
  function parseArgv() {
9
9
  const args = process.argv.slice(2);
10
10
  const config = {};
@@ -22,6 +22,7 @@ const PORT = argvConfig.port ? parseInt(argvConfig.port) : 22;
22
22
  const USER = argvConfig.user;
23
23
  const PASSWORD = argvConfig.password;
24
24
  const KEY = argvConfig.key;
25
+ const DEFAULT_TIMEOUT = argvConfig.timeout ? parseInt(argvConfig.timeout) : 60000; // 60 seconds default timeout
25
26
  function validateConfig(config) {
26
27
  const errors = [];
27
28
  if (!config.host)
@@ -37,7 +38,7 @@ function validateConfig(config) {
37
38
  validateConfig(argvConfig);
38
39
  const server = new McpServer({
39
40
  name: 'SSH MCP Server',
40
- version: '1.0.4',
41
+ version: '1.0.6',
41
42
  capabilities: {
42
43
  resources: {},
43
44
  tools: {},
@@ -76,27 +77,61 @@ server.tool("exec", "Execute a shell command on the remote SSH server and return
76
77
  async function execSshCommand(sshConfig, command) {
77
78
  return new Promise((resolve, reject) => {
78
79
  const conn = new SSHClient();
80
+ let timeoutId;
81
+ let isResolved = false;
82
+ // Set up timeout
83
+ timeoutId = setTimeout(() => {
84
+ if (!isResolved) {
85
+ isResolved = true;
86
+ // Try to abort the running command before closing connection
87
+ const abortTimeout = setTimeout(() => {
88
+ // If abort command itself times out, force close connection
89
+ conn.end();
90
+ }, 5000); // 5 second timeout for abort command
91
+ conn.exec('timeout 3s pkill -f "' + command + '" 2>/dev/null || true', (err, abortStream) => {
92
+ if (abortStream) {
93
+ abortStream.on('close', () => {
94
+ clearTimeout(abortTimeout);
95
+ conn.end();
96
+ });
97
+ }
98
+ else {
99
+ clearTimeout(abortTimeout);
100
+ conn.end();
101
+ }
102
+ });
103
+ reject(new McpError(ErrorCode.InternalError, `Command execution timed out after ${DEFAULT_TIMEOUT}ms`));
104
+ }
105
+ }, DEFAULT_TIMEOUT);
79
106
  conn.on('ready', () => {
80
107
  conn.exec(command, (err, stream) => {
81
108
  if (err) {
82
- reject(new McpError(ErrorCode.InternalError, `SSH exec error: ${err.message}`));
109
+ if (!isResolved) {
110
+ isResolved = true;
111
+ clearTimeout(timeoutId);
112
+ reject(new McpError(ErrorCode.InternalError, `SSH exec error: ${err.message}`));
113
+ }
83
114
  conn.end();
84
115
  return;
85
116
  }
86
117
  let stdout = '';
87
118
  let stderr = '';
88
119
  stream.on('close', (code, signal) => {
89
- conn.end();
90
- if (stderr) {
91
- reject(new McpError(ErrorCode.InternalError, `Error (code ${code}):\n${stderr}`));
92
- }
93
- else {
94
- resolve({
95
- content: [{
96
- type: 'text',
97
- text: stdout,
98
- }],
99
- });
120
+ if (!isResolved) {
121
+ isResolved = true;
122
+ clearTimeout(timeoutId);
123
+ conn.end();
124
+ if (stderr) {
125
+ reject(new McpError(ErrorCode.InternalError, `Error (code ${code}):\n${stderr}`));
126
+ }
127
+ else {
128
+ resolve({
129
+ content: [{
130
+ type: 'text',
131
+ text: stdout,
132
+ }],
133
+ });
134
+ }
100
135
  }
101
136
  });
102
137
  stream.on('data', (data) => {
@@ -108,7 +143,11 @@ async function execSshCommand(sshConfig, command) {
108
143
  });
109
144
  });
110
145
  conn.on('error', (err) => {
111
- reject(new McpError(ErrorCode.InternalError, `SSH connection error: ${err.message}`));
146
+ if (!isResolved) {
147
+ isResolved = true;
148
+ clearTimeout(timeoutId);
149
+ reject(new McpError(ErrorCode.InternalError, `SSH connection error: ${err.message}`));
150
+ }
112
151
  });
113
152
  conn.connect(sshConfig);
114
153
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ssh-mcp",
3
3
  "license": "MIT",
4
- "version": "1.0.4",
4
+ "version": "1.0.6",
5
5
  "description": "MCP server exposing SSH control for Linux and Windows systems via Model Context Protocol.",
6
6
  "type": "module",
7
7
  "bin": {