@aaarc/handfree-ssh-mcp 1.0.6 → 1.0.9
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 +14 -10
- package/build/config/server.js +8 -6
- package/build/services/ssh-connection-manager.js +579 -191
- package/build/tools/download.js +15 -2
- package/build/tools/execute-command.js +1 -0
- package/build/tools/help.js +63 -27
- package/build/tools/transfer.js +13 -6
- package/build/tools/upload.js +13 -1
- package/package.json +1 -1
package/build/tools/download.js
CHANGED
|
@@ -11,10 +11,23 @@ export function registerDownloadTool(server) {
|
|
|
11
11
|
remotePath: z.string().describe("Path to the file on the remote server. Must be an absolute POSIX path (e.g. /var/log/app.log); restricted to allowedRemoteDirectories only if the server configures that list."),
|
|
12
12
|
localPath: z.string().describe("Destination path on the MCP host. Must be inside the MCP working directory or one of the server's allowedLocalDirectories."),
|
|
13
13
|
connectionName: z.string().optional().describe("Target server name from list-servers. Required when multiple servers are enabled; optional when only one server is enabled."),
|
|
14
|
-
|
|
14
|
+
reuseConnection: z.boolean().optional().describe("Default true. Reuse the cached SSH connection for SFTP. Set false after a timeout or suspected stale cached SSH connection to force a fresh TCP/SSH connection for this transfer; the fresh connection closes afterwards."),
|
|
15
|
+
timeout: z.number().positive().optional().describe("Timeout in ms for SSH setup and SFTP channel opening. Transfer stream duration itself is not forcibly interrupted by this option."),
|
|
16
|
+
vvv: z.boolean().optional().describe("Default false. Append bounded SSH/SFTP debug output. For fresh ssh2 handshake logs, also set reuseConnection=false."),
|
|
17
|
+
fast: z.boolean().optional().describe("Default false. When true, use ssh2 fastGet for a single-file download, which performs parallel SFTP reads for better throughput."),
|
|
18
|
+
sftpConcurrency: z.number().int().positive().optional().describe("Only used when fast=true. Number of concurrent SFTP chunks for ssh2 fastGet; omitted uses ssh2's default."),
|
|
19
|
+
chunkSize: z.number().int().positive().optional().describe("Only used when fast=true. Chunk size in bytes for ssh2 fastGet; omitted uses ssh2's default."),
|
|
20
|
+
}, async ({ remotePath, localPath, connectionName, reuseConnection, timeout, vvv, fast, sftpConcurrency, chunkSize }) => {
|
|
15
21
|
try {
|
|
16
22
|
const resolvedName = sshManager.resolveServer(connectionName);
|
|
17
|
-
const result = await sshManager.download(remotePath, localPath, resolvedName
|
|
23
|
+
const result = await sshManager.download(remotePath, localPath, resolvedName, {
|
|
24
|
+
reuseConnection,
|
|
25
|
+
timeout,
|
|
26
|
+
vvv,
|
|
27
|
+
fast,
|
|
28
|
+
sftpConcurrency,
|
|
29
|
+
chunkSize,
|
|
30
|
+
});
|
|
18
31
|
return {
|
|
19
32
|
content: [{ type: "text", text: result }],
|
|
20
33
|
};
|
|
@@ -19,6 +19,7 @@ export function registerExecuteCommandTool(server) {
|
|
|
19
19
|
.describe("Target server name from list-servers. Required when multiple servers are enabled; optional when only one server is enabled."),
|
|
20
20
|
timeout: z
|
|
21
21
|
.number()
|
|
22
|
+
.positive()
|
|
22
23
|
.optional()
|
|
23
24
|
.describe("Timeout in milliseconds applied separately to SSH connection setup, exec-channel opening, and remote command execution for each attempt. Defaults to 300000 when stream=true and 30000 when stream=false. Increase this for long-running commands; reduce it for fast probes."),
|
|
24
25
|
stream: z
|
package/build/tools/help.js
CHANGED
|
@@ -75,29 +75,52 @@ Examples:
|
|
|
75
75
|
Parameters:
|
|
76
76
|
localPath (string, required) File path on the MCP host.
|
|
77
77
|
Must be inside the MCP working directory.
|
|
78
|
-
remotePath (string, required) Destination path on the remote server.
|
|
79
|
-
connectionName (string, see below) Target server name from list-servers.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
78
|
+
remotePath (string, required) Destination path on the remote server.
|
|
79
|
+
connectionName (string, see below) Target server name from list-servers.
|
|
80
|
+
skipIfIdentical (boolean, optional) Default true. Skip when remote matches.
|
|
81
|
+
reuseConnection (boolean, optional) Default true. Set false after timeout
|
|
82
|
+
or suspected stale cached SSH connection.
|
|
83
|
+
timeout (number, optional) SSH setup and SFTP channel-open timeout.
|
|
84
|
+
vvv (boolean, optional) Default false. Append bounded SSH/SFTP debug.
|
|
85
|
+
Recursive success returns structured JSON; debug is surfaced
|
|
86
|
+
for single-file results, relay results, and recursive errors.
|
|
87
|
+
fast (boolean, optional) Default false. Use ssh2 fastPut for
|
|
88
|
+
single-file upload throughput. Not multi-file concurrency.
|
|
89
|
+
sftpConcurrency (number, optional) Only with fast=true. Concurrent SFTP chunks.
|
|
90
|
+
chunkSize (number, optional) Only with fast=true. SFTP chunk bytes.
|
|
91
|
+
|
|
92
|
+
connectionName rule:
|
|
93
|
+
• If only one server is enabled → optional (auto-selected).
|
|
94
|
+
• If multiple servers are enabled → REQUIRED.
|
|
95
|
+
|
|
96
|
+
Example:
|
|
97
|
+
upload { localPath: "data.csv", remotePath: "/tmp/data.csv" }
|
|
98
|
+
upload { localPath: "big.bin", remotePath: "/tmp/big.bin", fast: true,
|
|
99
|
+
sftpConcurrency: 32, chunkSize: 131072 }`,
|
|
87
100
|
"download": `download — Download a single file from a remote server over SFTP.
|
|
88
101
|
|
|
89
102
|
Parameters:
|
|
90
|
-
remotePath (string, required) File path on the remote server.
|
|
91
|
-
localPath (string, required) Destination on the MCP host.
|
|
92
|
-
Must be inside the MCP working directory.
|
|
93
|
-
connectionName (string, see below) Target server name from list-servers.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
103
|
+
remotePath (string, required) File path on the remote server.
|
|
104
|
+
localPath (string, required) Destination on the MCP host.
|
|
105
|
+
Must be inside the MCP working directory.
|
|
106
|
+
connectionName (string, see below) Target server name from list-servers.
|
|
107
|
+
reuseConnection (boolean, optional) Default true. Set false after timeout
|
|
108
|
+
or suspected stale cached SSH connection.
|
|
109
|
+
timeout (number, optional) SSH setup and SFTP channel-open timeout.
|
|
110
|
+
vvv (boolean, optional) Default false. Append bounded SSH/SFTP debug.
|
|
111
|
+
fast (boolean, optional) Default false. Use ssh2 fastGet for
|
|
112
|
+
single-file download throughput. Not multi-file concurrency.
|
|
113
|
+
sftpConcurrency (number, optional) Only with fast=true. Concurrent SFTP chunks.
|
|
114
|
+
chunkSize (number, optional) Only with fast=true. SFTP chunk bytes.
|
|
115
|
+
|
|
116
|
+
connectionName rule:
|
|
117
|
+
• If only one server is enabled → optional (auto-selected).
|
|
118
|
+
• If multiple servers are enabled → REQUIRED.
|
|
119
|
+
|
|
120
|
+
Example:
|
|
121
|
+
download { remotePath: "/var/log/app.log", localPath: "app.log" }
|
|
122
|
+
download { remotePath: "/tmp/big.bin", localPath: "big.bin", fast: true,
|
|
123
|
+
sftpConcurrency: 32, chunkSize: 131072 }`,
|
|
101
124
|
"transfer": `transfer — Move files between hosts (single/recursive/cross-server).
|
|
102
125
|
|
|
103
126
|
Modes:
|
|
@@ -112,15 +135,28 @@ Parameters for upload / download:
|
|
|
112
135
|
mode (string, required) "upload" or "download"
|
|
113
136
|
localPath (string, required) Path on the MCP host.
|
|
114
137
|
remotePath (string, required) Path on the remote server.
|
|
115
|
-
connectionName (string, see below) Target server name.
|
|
116
|
-
recursive (boolean, optional) True to transfer a whole directory tree.
|
|
117
|
-
|
|
118
|
-
|
|
138
|
+
connectionName (string, see below) Target server name.
|
|
139
|
+
recursive (boolean, optional) True to transfer a whole directory tree.
|
|
140
|
+
reuseConnection (boolean, optional) Default true. Set false after timeout.
|
|
141
|
+
timeout (number, optional) SSH setup and SFTP channel-open timeout.
|
|
142
|
+
vvv (boolean, optional) Default false. Append bounded SSH/SFTP debug.
|
|
143
|
+
fast (boolean, optional) Default false. upload/download only:
|
|
144
|
+
use ssh2 fastPut/fastGet for each single file. Directory
|
|
145
|
+
recursion stays sequential; no multi-file concurrency.
|
|
146
|
+
sftpConcurrency (number, optional) Only with fast=true. Concurrent SFTP chunks.
|
|
147
|
+
chunkSize (number, optional) Only with fast=true. SFTP chunk bytes.
|
|
148
|
+
|
|
149
|
+
Parameters for relay:
|
|
119
150
|
mode (string, required) "relay"
|
|
120
151
|
sourceServer (string, required) Server name to read from.
|
|
121
|
-
sourceRemotePath (string, required) File path on source server.
|
|
122
|
-
destServer (string, required) Server name to write to.
|
|
123
|
-
destRemotePath (string, required) File path on dest server.
|
|
152
|
+
sourceRemotePath (string, required) File path on source server.
|
|
153
|
+
destServer (string, required) Server name to write to.
|
|
154
|
+
destRemotePath (string, required) File path on dest server.
|
|
155
|
+
reuseConnection (boolean, optional) Default true. Set false after timeout.
|
|
156
|
+
timeout (number, optional) SSH setup and SFTP channel-open timeout.
|
|
157
|
+
vvv (boolean, optional) Default false. Append bounded SSH/SFTP debug.
|
|
158
|
+
fast (boolean, optional) Accepted but relay keeps the streaming
|
|
159
|
+
SFTP pipe path; fastGet/fastPut apply to host<->remote only.
|
|
124
160
|
|
|
125
161
|
connectionName rule (upload/download):
|
|
126
162
|
• If only one server is enabled → optional.
|
package/build/tools/transfer.js
CHANGED
|
@@ -37,22 +37,28 @@ For relay mode, specify sourceServer, sourceRemotePath, destServer, destRemotePa
|
|
|
37
37
|
"Upload: byte-compare for files \u2264 256 MiB, MD5 otherwise; shell scripts (.sh / .bash / .zsh) ignore CRLF\u2194LF differences. " +
|
|
38
38
|
"Relay: size match + md5sum match on both servers (when available); falls back to transferring if md5sum is missing on either side. " +
|
|
39
39
|
"Download is never skipped. Set to false to force the transfer."),
|
|
40
|
+
reuseConnection: z.boolean().optional().describe("Default true. Reuse cached SSH connection(s) for SFTP. Set false after a timeout or suspected stale cached connection to force fresh TCP/SSH connection(s) for this transfer; fresh connections close afterwards."),
|
|
41
|
+
timeout: z.number().positive().optional().describe("Timeout in ms for SSH setup and SFTP channel opening. Transfer stream duration itself is not forcibly interrupted by this option."),
|
|
42
|
+
vvv: z.boolean().optional().describe("Default false. Append bounded SSH/SFTP debug output for single-file and relay results, and for recursive errors. For fresh ssh2 handshake logs, also set reuseConnection=false."),
|
|
43
|
+
fast: z.boolean().optional().describe("Default false. Upload/download only: use ssh2 fastPut/fastGet for single files, with parallel SFTP chunks for better throughput. Relay mode keeps the streaming pipe path."),
|
|
44
|
+
sftpConcurrency: z.number().int().positive().optional().describe("Only used when fast=true for upload/download. Number of concurrent SFTP chunks; omitted uses ssh2's default."),
|
|
45
|
+
chunkSize: z.number().int().positive().optional().describe("Only used when fast=true for upload/download. Chunk size in bytes; omitted uses ssh2's default."),
|
|
40
46
|
}, async (params) => {
|
|
41
47
|
try {
|
|
42
48
|
const { mode } = params;
|
|
43
49
|
if (mode === "relay") {
|
|
44
|
-
const { sourceServer, sourceRemotePath, destServer, destRemotePath, skipIfIdentical } = params;
|
|
50
|
+
const { sourceServer, sourceRemotePath, destServer, destRemotePath, skipIfIdentical, reuseConnection, timeout, vvv } = params;
|
|
45
51
|
if (!sourceServer || !sourceRemotePath || !destServer || !destRemotePath) {
|
|
46
52
|
return {
|
|
47
53
|
content: [{ type: "text", text: "relay mode requires: sourceServer, sourceRemotePath, destServer, destRemotePath" }],
|
|
48
54
|
isError: true,
|
|
49
55
|
};
|
|
50
56
|
}
|
|
51
|
-
const result = await sshManager.transferBetweenServers(sourceServer, sourceRemotePath, destServer, destRemotePath, { skipIfIdentical: skipIfIdentical !== false });
|
|
57
|
+
const result = await sshManager.transferBetweenServers(sourceServer, sourceRemotePath, destServer, destRemotePath, { skipIfIdentical: skipIfIdentical !== false, reuseConnection, timeout, vvv });
|
|
52
58
|
return { content: [{ type: "text", text: result }] };
|
|
53
59
|
}
|
|
54
60
|
// upload or download
|
|
55
|
-
const { localPath, remotePath, connectionName, recursive, skipIfIdentical } = params;
|
|
61
|
+
const { localPath, remotePath, connectionName, recursive, skipIfIdentical, reuseConnection, timeout, vvv, fast, sftpConcurrency, chunkSize } = params;
|
|
56
62
|
if (!localPath || !remotePath) {
|
|
57
63
|
return {
|
|
58
64
|
content: [{ type: "text", text: `${mode} mode requires: localPath, remotePath` }],
|
|
@@ -60,14 +66,15 @@ For relay mode, specify sourceServer, sourceRemotePath, destServer, destRemotePa
|
|
|
60
66
|
};
|
|
61
67
|
}
|
|
62
68
|
const resolvedName = sshManager.resolveServer(connectionName);
|
|
63
|
-
const
|
|
69
|
+
const sftpOptions = { reuseConnection, timeout, vvv, fast, sftpConcurrency, chunkSize };
|
|
70
|
+
const uploadOptions = { skipIfIdentical: skipIfIdentical !== false, ...sftpOptions };
|
|
64
71
|
if (recursive) {
|
|
65
72
|
let files;
|
|
66
73
|
if (mode === "upload") {
|
|
67
74
|
files = await sshManager.uploadDirectory(localPath, remotePath, resolvedName, uploadOptions);
|
|
68
75
|
}
|
|
69
76
|
else {
|
|
70
|
-
files = await sshManager.downloadDirectory(remotePath, localPath, resolvedName);
|
|
77
|
+
files = await sshManager.downloadDirectory(remotePath, localPath, resolvedName, sftpOptions);
|
|
71
78
|
}
|
|
72
79
|
const summary = `Recursive ${mode} complete. ${files.length} file(s) transferred.`;
|
|
73
80
|
return {
|
|
@@ -80,7 +87,7 @@ For relay mode, specify sourceServer, sourceRemotePath, destServer, destRemotePa
|
|
|
80
87
|
return { content: [{ type: "text", text: result }] };
|
|
81
88
|
}
|
|
82
89
|
else {
|
|
83
|
-
const result = await sshManager.download(remotePath, localPath, resolvedName);
|
|
90
|
+
const result = await sshManager.download(remotePath, localPath, resolvedName, sftpOptions);
|
|
84
91
|
return { content: [{ type: "text", text: result }] };
|
|
85
92
|
}
|
|
86
93
|
}
|
package/build/tools/upload.js
CHANGED
|
@@ -14,11 +14,23 @@ export function registerUploadTool(server) {
|
|
|
14
14
|
remotePath: z.string().describe("Destination path on the remote server. Must be an absolute POSIX path (e.g. /home/user/uploads/file.txt); restricted to allowedRemoteDirectories only if the server configures that list."),
|
|
15
15
|
connectionName: z.string().optional().describe("Target server name from list-servers. Required when multiple servers are enabled; optional when only one server is enabled."),
|
|
16
16
|
skipIfIdentical: z.boolean().optional().describe("When true (default), skip the upload if the remote file is already identical (byte-compare for files \u2264 256 MiB, MD5 otherwise; shell scripts ignore CRLF\u2194LF differences). Set to false to force re-upload."),
|
|
17
|
-
|
|
17
|
+
reuseConnection: z.boolean().optional().describe("Default true. Reuse the cached SSH connection for SFTP. Set false after a timeout or suspected stale cached SSH connection to force a fresh TCP/SSH connection for this transfer; the fresh connection closes afterwards."),
|
|
18
|
+
timeout: z.number().positive().optional().describe("Timeout in ms for SSH setup and SFTP channel opening. Transfer stream duration itself is not forcibly interrupted by this option."),
|
|
19
|
+
vvv: z.boolean().optional().describe("Default false. Append bounded SSH/SFTP debug output. For fresh ssh2 handshake logs, also set reuseConnection=false."),
|
|
20
|
+
fast: z.boolean().optional().describe("Default false. When true, use ssh2 fastPut for a single-file upload, which performs parallel SFTP reads/writes for better throughput. If a shell script needs CRLF-to-LF conversion, the upload falls back to the normal safe path."),
|
|
21
|
+
sftpConcurrency: z.number().int().positive().optional().describe("Only used when fast=true. Number of concurrent SFTP chunks for ssh2 fastPut; omitted uses ssh2's default."),
|
|
22
|
+
chunkSize: z.number().int().positive().optional().describe("Only used when fast=true. Chunk size in bytes for ssh2 fastPut; omitted uses ssh2's default."),
|
|
23
|
+
}, async ({ localPath, remotePath, connectionName, skipIfIdentical, reuseConnection, timeout, vvv, fast, sftpConcurrency, chunkSize }) => {
|
|
18
24
|
try {
|
|
19
25
|
const resolvedName = sshManager.resolveServer(connectionName);
|
|
20
26
|
const result = await sshManager.upload(localPath, remotePath, resolvedName, {
|
|
21
27
|
skipIfIdentical: skipIfIdentical !== false,
|
|
28
|
+
reuseConnection,
|
|
29
|
+
timeout,
|
|
30
|
+
vvv,
|
|
31
|
+
fast,
|
|
32
|
+
sftpConcurrency,
|
|
33
|
+
chunkSize,
|
|
22
34
|
});
|
|
23
35
|
return {
|
|
24
36
|
content: [{ type: "text", text: result }],
|