ssh-mcp 1.0.5 → 1.0.7
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 +35 -7
- package/build/index.js +54 -15
- package/package.json +25 -1
package/README.md
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
# SSH MCP Server
|
|
2
2
|
|
|
3
|
-
[](./LICENSE)
|
|
4
3
|
[](https://www.npmjs.com/package/ssh-mcp)
|
|
4
|
+
[](https://www.npmjs.com/package/ssh-mcp)
|
|
5
|
+
[](https://nodejs.org/)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
[](https://github.com/tufantunc/ssh-mcp/stargazers)
|
|
8
|
+
[](https://github.com/tufantunc/ssh-mcp/forks)
|
|
9
|
+
[](https://github.com/tufantunc/ssh-mcp/actions)
|
|
10
|
+
[](https://github.com/tufantunc/ssh-mcp/issues)
|
|
5
11
|
|
|
6
12
|
**SSH MCP Server** is a local Model Context Protocol (MCP) server that exposes SSH control for Linux and Windows systems, enabling LLMs and other MCP clients to execute shell commands securely via SSH.
|
|
7
13
|
|
|
@@ -28,10 +34,18 @@
|
|
|
28
34
|
- Execute shell commands on remote Linux and Windows systems
|
|
29
35
|
- Secure authentication via password or SSH key
|
|
30
36
|
- Built with TypeScript and the official MCP SDK
|
|
37
|
+
- **Configurable timeout protection** with automatic process abortion
|
|
38
|
+
- **Graceful timeout handling** - attempts to kill hanging processes before closing connections
|
|
31
39
|
|
|
32
40
|
### Tools
|
|
33
41
|
|
|
34
42
|
- `exec`: Execute a shell command on the remote server
|
|
43
|
+
- **Parameters:**
|
|
44
|
+
- `command` (required): Shell command to execute on the remote SSH server
|
|
45
|
+
- **Timeout Configuration:**
|
|
46
|
+
- Timeout is configured via command line argument `--timeout` (in milliseconds)
|
|
47
|
+
- Default timeout: 60000ms (1 minute)
|
|
48
|
+
- When a command times out, the server automatically attempts to abort the running process before closing the connection
|
|
35
49
|
|
|
36
50
|
## Installation
|
|
37
51
|
|
|
@@ -48,11 +62,16 @@
|
|
|
48
62
|
## Client Setup
|
|
49
63
|
|
|
50
64
|
You can configure Claude Desktop to use this MCP Server.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
65
|
+
|
|
66
|
+
**Required Parameters:**
|
|
67
|
+
- `host`: Hostname or IP of the Linux or Windows server
|
|
68
|
+
- `user`: SSH username
|
|
69
|
+
|
|
70
|
+
**Optional Parameters:**
|
|
71
|
+
- `port`: SSH port (default: 22)
|
|
72
|
+
- `password`: SSH password (or use `key` for key-based auth)
|
|
73
|
+
- `key`: Path to private SSH key
|
|
74
|
+
- `timeout`: Command execution timeout in milliseconds (default: 60000ms = 1 minute)
|
|
56
75
|
|
|
57
76
|
|
|
58
77
|
```commandline
|
|
@@ -68,7 +87,8 @@ You can configure Claude Desktop to use this MCP Server.
|
|
|
68
87
|
"--port=22",
|
|
69
88
|
"--user=root",
|
|
70
89
|
"--password=pass",
|
|
71
|
-
"--key=path/to/key"
|
|
90
|
+
"--key=path/to/key",
|
|
91
|
+
"--timeout=30000"
|
|
72
92
|
]
|
|
73
93
|
}
|
|
74
94
|
}
|
|
@@ -87,6 +107,14 @@ npm run inspect
|
|
|
87
107
|
|
|
88
108
|
SSH MCP Server is provided under the [MIT License](./LICENSE). Use at your own risk. This project is not affiliated with or endorsed by any SSH or MCP provider.
|
|
89
109
|
|
|
110
|
+
## Contributing
|
|
111
|
+
|
|
112
|
+
We welcome contributions! Please see our [Contributing Guidelines](./CONTRIBUTING.md) for more information.
|
|
113
|
+
|
|
114
|
+
## Code of Conduct
|
|
115
|
+
|
|
116
|
+
This project follows a [Code of Conduct](./CODE_OF_CONDUCT.md) to ensure a welcoming environment for everyone.
|
|
117
|
+
|
|
90
118
|
## Support
|
|
91
119
|
|
|
92
120
|
If you find SSH MCP Server helpful, consider starring the repository or contributing! Pull requests and feedback are welcome.
|
package/build/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
|
|
|
4
4
|
import { Client as SSHClient } from 'ssh2';
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
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.
|
|
41
|
+
version: '1.0.7',
|
|
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
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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
|
+
"version": "1.0.7",
|
|
5
5
|
"description": "MCP server exposing SSH control for Linux and Windows systems via Model Context Protocol.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -25,5 +25,29 @@
|
|
|
25
25
|
"@types/ssh2": "^1.15.5",
|
|
26
26
|
"shx": "^0.4.0",
|
|
27
27
|
"typescript": "^5.4.5"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/tufantunc/ssh-mcp#readme",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/tufantunc/ssh-mcp.git"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/tufantunc/ssh-mcp/issues"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"ssh",
|
|
39
|
+
"mcp",
|
|
40
|
+
"model-context-protocol",
|
|
41
|
+
"server",
|
|
42
|
+
"windows",
|
|
43
|
+
"linux",
|
|
44
|
+
"automation",
|
|
45
|
+
"remote",
|
|
46
|
+
"cli",
|
|
47
|
+
"typescript"
|
|
48
|
+
],
|
|
49
|
+
"author": "tufantunc",
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=18"
|
|
28
52
|
}
|
|
29
53
|
}
|