@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 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
- | `upload` | Upload local file to a remote server (CRLF-fix for shell scripts, skip-if-identical) |
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) | Timeout in ms |
158
- | `stream` | ❌ | `true` | Real-time streaming output |
159
-
160
- **When to use `stream: false`:**
161
- - Simple, fast commands (ls, pwd, cat)
162
- - When you don't need real-time feedback
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
- # If `allowedRemoteDirectories` is unset or empty, SFTP is DISABLED for the server.
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 = SFTP disabled.** Must list absolute POSIX directories. |
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
- - **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.
277
- - **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.
278
- - **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).
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
  }
@@ -1,6 +1,6 @@
1
1
  export const SERVER_CONFIG = {
2
2
  name: "ssh-mcp-server",
3
- version: "1.0.4",
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. Use upload and download for single-file SFTP transfers. Use transfer for recursive directory transfers or cross-server relay.
14
- 7. Call help or help { tool: "<name>" } for detailed per-tool parameter docs and examples.
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.