@aaarc/handfree-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.
- package/README.md +51 -25
- package/build/config/config-loader.js +4 -0
- package/build/config/server.js +7 -4
- package/build/services/ssh-connection-manager.js +550 -90
- package/build/tools/close-connection.js +36 -0
- package/build/tools/download.js +2 -2
- package/build/tools/execute-command.js +15 -3
- package/build/tools/help.js +45 -14
- package/build/tools/index.js +2 -0
- package/build/tools/show-whitelist.js +27 -17
- package/build/tools/transfer.js +3 -3
- package/build/tools/upload.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -113,9 +113,10 @@ The AI can now execute commands on your servers. All within your defined securit
|
|
|
113
113
|
|
|
114
114
|
| Tool | Description |
|
|
115
115
|
|------|-------------|
|
|
116
|
-
| `execute-command` | Run SSH command (with optional `stream` for real-time output) |
|
|
117
|
-
| `show-whitelist` | Show active command policy + SFTP policy + output-log path for a server |
|
|
118
|
-
| `
|
|
116
|
+
| `execute-command` | Run SSH command (with optional `stream` for real-time output) |
|
|
117
|
+
| `show-whitelist` | Show active command policy + SFTP policy + output-log path for a server |
|
|
118
|
+
| `close-connection` | Close a cached SSH connection for a server |
|
|
119
|
+
| `upload` | Upload local file to a remote server (CRLF-fix for shell scripts, skip-if-identical) |
|
|
119
120
|
| `download` | Download remote file to local disk |
|
|
120
121
|
| `transfer` | Unified upload / download / server-to-server relay (`mode`: `upload` / `download` / `relay`, optional `recursive`, optional `skipIfIdentical`) |
|
|
121
122
|
| `list-servers` | List configured (enabled) servers. Lean by default; `verbose:true` adds cached system status, `refresh:true` re-collects it (implies verbose). |
|
|
@@ -142,24 +143,41 @@ Returns command mode, built-in command guards, configured whitelist/blacklist pa
|
|
|
142
143
|
{
|
|
143
144
|
"tool": "execute-command",
|
|
144
145
|
"params": {
|
|
145
|
-
"cmdString": "docker ps",
|
|
146
|
-
"connectionName": "dev",
|
|
147
|
-
"timeout": 300000,
|
|
148
|
-
"stream": true
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
146
|
+
"cmdString": "docker ps",
|
|
147
|
+
"connectionName": "dev",
|
|
148
|
+
"timeout": 300000,
|
|
149
|
+
"stream": true,
|
|
150
|
+
"reuseConnection": true,
|
|
151
|
+
"vvv": false
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
```
|
|
152
155
|
|
|
153
156
|
| Param | Required | Default | Description |
|
|
154
157
|
|-------|----------|---------|-------------|
|
|
155
158
|
| `cmdString` | ✅ | - | Command to execute |
|
|
156
|
-
| `connectionName` | ❌ | First in `--enable-servers` | Which server to run on |
|
|
157
|
-
| `timeout` | ❌ | 300000ms (stream) / 30000ms (no stream) |
|
|
158
|
-
| `stream` | ❌ | `true` | Real-time streaming output |
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
159
|
+
| `connectionName` | ❌ | First in `--enable-servers` | Which server to run on |
|
|
160
|
+
| `timeout` | ❌ | 300000ms (stream) / 30000ms (no stream) | Per-attempt phase timeout in ms. SSH setup, exec-channel opening, and remote command execution each use this cap. |
|
|
161
|
+
| `stream` | ❌ | `true` | Real-time streaming output |
|
|
162
|
+
| `reuseConnection` | ❌ | `true` | Reuse the cached SSH connection. Set `false` after a timeout or suspected stale cached connection to force a fresh TCP/SSH connection for this command. |
|
|
163
|
+
| `vvv` | ❌ | `false` | Append bounded SSH/channel debug output. Use with `reuseConnection: false` when you need fresh ssh2 handshake logs. |
|
|
164
|
+
|
|
165
|
+
**When to use `stream: false`:**
|
|
166
|
+
- Simple, fast commands (ls, pwd, cat)
|
|
167
|
+
- When you don't need real-time feedback
|
|
168
|
+
|
|
169
|
+
### close-connection
|
|
170
|
+
|
|
171
|
+
```json
|
|
172
|
+
{
|
|
173
|
+
"tool": "close-connection",
|
|
174
|
+
"params": {
|
|
175
|
+
"connectionName": "dev"
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Closes the cached SSH client for a configured server. This is useful after a timeout or suspected stale reused connection when you want the next default `reuseConnection: true` command to reconnect cleanly. Closing a jump host also closes cached targets whose jump chain uses that host. `reuseConnection: false` commands do not need this because their one-shot SSH clients close after each command.
|
|
163
181
|
|
|
164
182
|
## 📄 YAML Config Reference
|
|
165
183
|
|
|
@@ -216,7 +234,8 @@ servers:
|
|
|
216
234
|
|
|
217
235
|
# SFTP path policy — applies ONLY to upload / download / transfer.
|
|
218
236
|
# execute-command is NOT affected by these lists.
|
|
219
|
-
#
|
|
237
|
+
# Default is OPEN: with `allowedRemoteDirectories` unset/empty, any absolute
|
|
238
|
+
# POSIX remote path is allowed. Configure it to opt into an allowlist instead.
|
|
220
239
|
allowedRemoteDirectories:
|
|
221
240
|
- /home/user
|
|
222
241
|
- /tmp
|
|
@@ -224,20 +243,23 @@ servers:
|
|
|
224
243
|
# The MCP working directory is always permitted implicitly.
|
|
225
244
|
allowedLocalDirectories:
|
|
226
245
|
- /path/to/extra/local/dir
|
|
246
|
+
# Bypasses both the remote allowlist and the local directory check entirely.
|
|
247
|
+
# disableSftpPathPolicy: true
|
|
227
248
|
```
|
|
228
249
|
|
|
229
250
|
### Security note: command policy
|
|
230
251
|
|
|
231
|
-
`execute-command` defaults to `commandMode: blacklist`. In that mode commands are allowed unless they match built-in destructive guards, the built-in dangerous-command blacklist, or a server's configured `blacklist:` patterns. Built-in blocked operations include hidden/chained destructive file operations, risky absolute-path output redirection, system power commands (`reboot`, `shutdown`, `halt`, `poweroff`), recursive force delete (`rm -rf`), destructive disk writes (`dd ... of=`), and filesystem formatting commands. Set `commandMode: whitelist` to require every command to match `whitelist:` after blacklist checks. For compatibility, a YAML server that contains `whitelist:` without `commandMode:` is treated as whitelist mode.
|
|
252
|
+
`execute-command` defaults to `commandMode: blacklist`. In that mode commands are allowed unless they match built-in destructive guards, the built-in dangerous-command blacklist, or a server's configured `blacklist:` patterns. Built-in blocked operations include hidden/chained destructive file operations, risky absolute-path output redirection, system power commands (`reboot`, `shutdown`, `halt`, `poweroff`), recursive force delete (`rm -rf`), destructive disk writes (`dd ... of=`), and filesystem formatting commands. Set `commandMode: whitelist` to require every command to match `whitelist:` after blacklist checks. For compatibility, a YAML server that contains `whitelist:` without `commandMode:` is treated as whitelist mode. Set `disableBuiltinGuards: true` and/or `disableBuiltinBlacklist: true` to turn off the built-in guards/blacklist for a server (your own `blacklist:`/`whitelist:` still apply).
|
|
232
253
|
|
|
233
254
|
### SFTP path policy
|
|
234
255
|
|
|
235
256
|
| Field | Scope | Default behavior |
|
|
236
257
|
|---|---|---|
|
|
237
|
-
| `allowedRemoteDirectories` | `upload` / `download` / `transfer` only | **Unset =
|
|
258
|
+
| `allowedRemoteDirectories` | `upload` / `download` / `transfer` only | **Unset/empty = open.** Any absolute POSIX path is allowed. Configuring this list opts into restricting SFTP to those directories. |
|
|
238
259
|
| `allowedLocalDirectories` | `upload` / `download` only | Unset = only the MCP working directory is allowed. |
|
|
260
|
+
| `disableSftpPathPolicy` | all SFTP tools | When `true`, bypasses both checks above entirely — any remote path, any local path. |
|
|
239
261
|
|
|
240
|
-
Path matching is exact-equal or `dir + separator` prefix. `..` segments and null bytes are rejected. Use `show-whitelist` to inspect a server's current SFTP policy.
|
|
262
|
+
Path matching (when an allowlist is configured) is exact-equal or `dir + separator` prefix. `..` segments and null bytes are always rejected regardless of policy. Use `show-whitelist` to inspect a server's current SFTP policy.
|
|
241
263
|
|
|
242
264
|
### Jump host (ProxyJump-style)
|
|
243
265
|
|
|
@@ -272,10 +294,14 @@ Rules (enforced at config load — bad configs fail fast):
|
|
|
272
294
|
|
|
273
295
|
### Connection lifecycle (connect / reconnect)
|
|
274
296
|
|
|
275
|
-
- **Lazy by default.** A server's SSH client is created on its first tool call via `ensureConnected()`. Set `preConnect: true` (or pass `--pre-connect`) to open all enabled servers at startup in parallel; failures are logged but don't block startup.
|
|
276
|
-
- **
|
|
277
|
-
- **
|
|
278
|
-
- **
|
|
297
|
+
- **Lazy by default.** A server's SSH client is created on its first tool call via `ensureConnected()`. Set `preConnect: true` (or pass `--pre-connect`) to open all enabled servers at startup in parallel; failures are logged but don't block startup.
|
|
298
|
+
- **Cached clients stay open while reused.** With the default `reuseConnection: true`, `execute-command` does not close the SSH client after each command. If a cached client must be opened first, SSH setup, jump, SOCKS, and channel-open waits are bounded by the call's `timeout`. The cached client is closed on explicit disconnect/server shutdown, config hot-reload for changed connection fields, underlying SSH close/error cleanup, or reconnect after a connection-shaped command failure.
|
|
299
|
+
- **Manual cached-client close.** Use `close-connection { connectionName: "name" }` to drop a cached SSH client on demand. If the named server is a jump host, cached targets that jump through it are closed too.
|
|
300
|
+
- **Auto-reconnect on `execute-command`.** Every command runs inside a retry loop (default 3 attempts: 1 initial + 2 retries) with exponential backoff. If the underlying error matches a connection-shaped pattern (`econnreset`, `epipe`, `socket`, `closed`, `channel`, `end of stream`, or a `SSH_CONNECTION_FAILED` ToolError), the manager closes the dead client, reconnects, and retries the command. Non-connection errors (permission denied, validation, command-not-found) are returned immediately without retry.
|
|
301
|
+
- **Optional fresh command connections.** `execute-command` reuses the cached SSH client by default (`reuseConnection: true`). If a command times out or you suspect the cached `ssh2.Client` is stale while native `ssh` works, retry with `reuseConnection: false`; that command opens a fresh SSH connection and closes it afterwards.
|
|
302
|
+
- **Optional SSH debug output.** Set `vvv: true` on `execute-command` to append bounded SSH/channel debug output to the result or error. For full ssh2 handshake logs, combine it with `reuseConnection: false`; an already-open cached client cannot retroactively emit handshake debug.
|
|
303
|
+
- **SFTP transfers do NOT auto-retry.** `upload` / `download` / `transfer` lazy-connect via the same path, but a mid-transfer disconnect surfaces as a single failure — re-issue the call manually. This is a known gap.
|
|
304
|
+
- **No background keepalive or health probe.** Dead connections are only discovered on the next tool call. If you idle for hours through a NATed network, expect the first call after the gap to fail-then-reconnect on its own (you'll see one retry in the logs).
|
|
279
305
|
|
|
280
306
|
### Upload behaviors
|
|
281
307
|
|
|
@@ -163,6 +163,9 @@ function buildYamlServerConfig(name, serverConfig, options = {}) {
|
|
|
163
163
|
if (serverConfig.allowedLocalDirectories !== undefined) {
|
|
164
164
|
merged.allowedLocalDirectories = normalizeAllowedLocalDirectories(name, serverConfig.allowedLocalDirectories);
|
|
165
165
|
}
|
|
166
|
+
if (serverConfig.disableSftpPathPolicy !== undefined) {
|
|
167
|
+
merged.disableSftpPathPolicy = serverConfig.disableSftpPathPolicy === true;
|
|
168
|
+
}
|
|
166
169
|
if (!allowPartial) {
|
|
167
170
|
Logger.log(`Loaded server config: ${name} -> ${merged.username}@${merged.host}:${merged.port}`, "info");
|
|
168
171
|
}
|
|
@@ -183,6 +186,7 @@ function mergeConfigMaps(sshConfigs, yamlConfigs) {
|
|
|
183
186
|
commandBlacklist: yamlConfig.commandBlacklist ?? merged[name]?.commandBlacklist,
|
|
184
187
|
disableBuiltinGuards: yamlConfig.disableBuiltinGuards ?? merged[name]?.disableBuiltinGuards,
|
|
185
188
|
disableBuiltinBlacklist: yamlConfig.disableBuiltinBlacklist ?? merged[name]?.disableBuiltinBlacklist,
|
|
189
|
+
disableSftpPathPolicy: yamlConfig.disableSftpPathPolicy ?? merged[name]?.disableSftpPathPolicy,
|
|
186
190
|
safeDirectory: yamlConfig.safeDirectory ?? merged[name]?.safeDirectory,
|
|
187
191
|
};
|
|
188
192
|
}
|
package/build/config/server.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const SERVER_CONFIG = {
|
|
2
2
|
name: "ssh-mcp-server",
|
|
3
|
-
version: "1.0.
|
|
3
|
+
version: "1.0.5",
|
|
4
4
|
};
|
|
5
5
|
export const SERVER_INSTRUCTIONS = `This server provides SSH access to servers loaded from OpenSSH config (~/.ssh/config by default) and optional YAML config/policy overlays.
|
|
6
6
|
|
|
@@ -10,12 +10,15 @@ Recommended workflow:
|
|
|
10
10
|
3. Use execute-command for remote shell commands. Prefer a single command per call. Compound commands may be rejected by command policy even if each subcommand is individually safe.
|
|
11
11
|
4. Use stream=false for short commands that should finish quickly, such as pwd, ls, cat, head, tail, git status, or docker ps.
|
|
12
12
|
5. Use stream=true for commands that may take longer or where incremental output is useful.
|
|
13
|
-
6.
|
|
14
|
-
7.
|
|
13
|
+
6. execute-command reuses SSH connections by default. If an execute-command call times out, retry the next command with reuseConnection=false to bypass a potentially stale cached SSH connection.
|
|
14
|
+
7. Use close-connection to close a cached SSH connection for a server before retrying with a clean reused connection. Closing a jump host also closes cached targets that jump through it.
|
|
15
|
+
8. For SSH/channel diagnostics, add vvv=true. Use it with reuseConnection=false when you need fresh handshake/debug output.
|
|
16
|
+
9. Use upload and download for single-file SFTP transfers. Use transfer for recursive directory transfers or cross-server relay.
|
|
17
|
+
10. Call help or help { tool: "<name>" } for detailed per-tool parameter docs and examples.
|
|
15
18
|
|
|
16
19
|
Server targeting:
|
|
17
20
|
- When only one server is enabled, connectionName can be omitted and the server is auto-selected.
|
|
18
|
-
- When multiple servers are enabled, connectionName is REQUIRED on execute-command, upload, download, show-whitelist, and transfer (upload/download mode). Omitting it returns an error listing the available names.
|
|
21
|
+
- When multiple servers are enabled, connectionName is REQUIRED on execute-command, close-connection, upload, download, show-whitelist, and transfer (upload/download mode). Omitting it returns an error listing the available names.
|
|
19
22
|
|
|
20
23
|
Behavior notes:
|
|
21
24
|
- A tool may automatically establish the SSH connection on first use.
|