@mgsoftwarebv/mg-dashboard-mcp 3.4.4 → 3.5.0
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/dist/index.d.ts +7 -0
- package/dist/index.js +123 -35
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from 'child_process';
|
|
3
|
-
import { existsSync, readFileSync } from 'fs';
|
|
4
|
-
import { join } from 'path';
|
|
3
|
+
import { existsSync, statSync, readFileSync } from 'fs';
|
|
4
|
+
import { join, isAbsolute } from 'path';
|
|
5
5
|
import { Client as Client$1 } from '@modelcontextprotocol/sdk/client/index.js';
|
|
6
6
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
7
7
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
@@ -2005,7 +2005,8 @@ function sshExecViaProxy(proxyOpts, targetOpts, command) {
|
|
|
2005
2005
|
});
|
|
2006
2006
|
});
|
|
2007
2007
|
}
|
|
2008
|
-
function connectSshClient(opts, proxy, readyTimeout = 6e4) {
|
|
2008
|
+
function connectSshClient(opts, proxy, readyTimeout = 6e4, extraConnect) {
|
|
2009
|
+
const compress = extraConnect?.compress;
|
|
2009
2010
|
if (!proxy) {
|
|
2010
2011
|
return new Promise((resolve, reject) => {
|
|
2011
2012
|
const ssh = new Client();
|
|
@@ -2018,7 +2019,8 @@ function connectSshClient(opts, proxy, readyTimeout = 6e4) {
|
|
|
2018
2019
|
password: opts.password,
|
|
2019
2020
|
privateKey: opts.privateKey,
|
|
2020
2021
|
passphrase: opts.passphrase,
|
|
2021
|
-
readyTimeout
|
|
2022
|
+
readyTimeout,
|
|
2023
|
+
...compress ? { compress } : {}
|
|
2022
2024
|
});
|
|
2023
2025
|
});
|
|
2024
2026
|
}
|
|
@@ -2052,7 +2054,8 @@ function connectSshClient(opts, proxy, readyTimeout = 6e4) {
|
|
|
2052
2054
|
password: opts.password,
|
|
2053
2055
|
privateKey: opts.privateKey,
|
|
2054
2056
|
passphrase: opts.passphrase,
|
|
2055
|
-
readyTimeout
|
|
2057
|
+
readyTimeout,
|
|
2058
|
+
...compress ? { compress } : {}
|
|
2056
2059
|
});
|
|
2057
2060
|
});
|
|
2058
2061
|
});
|
|
@@ -2192,46 +2195,129 @@ async function sftpRead(opts, filePath, proxy) {
|
|
|
2192
2195
|
return `Error: ${e.message}`;
|
|
2193
2196
|
}
|
|
2194
2197
|
}
|
|
2195
|
-
|
|
2198
|
+
var SFTP_FASTPUT_CONCURRENCY = 64;
|
|
2199
|
+
var SFTP_FASTPUT_CHUNK_SIZE = 65536;
|
|
2200
|
+
var SFTP_IDLE_TIMEOUT_MS = 12e4;
|
|
2201
|
+
var SFTP_INLINE_TIMEOUT_MS = 6e4;
|
|
2202
|
+
var SFTP_PROGRESS_LOG_BYTES = 50 * 1024 * 1024;
|
|
2203
|
+
function formatBytes(bytes) {
|
|
2204
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
2205
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
2206
|
+
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
2207
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
2208
|
+
}
|
|
2209
|
+
async function sftpWrite(opts, filePath, input, proxy) {
|
|
2196
2210
|
const safe = sanitizePath(filePath);
|
|
2197
2211
|
assertWritablePath(safe);
|
|
2212
|
+
let mode;
|
|
2213
|
+
let inlineBuffer;
|
|
2214
|
+
let localPath;
|
|
2215
|
+
let expectedBytes = 0;
|
|
2216
|
+
if ("content" in input && typeof input.content === "string") {
|
|
2217
|
+
mode = "content";
|
|
2218
|
+
inlineBuffer = Buffer.from(input.content, "utf-8");
|
|
2219
|
+
expectedBytes = inlineBuffer.length;
|
|
2220
|
+
} else if ("sourcePath" in input && typeof input.sourcePath === "string") {
|
|
2221
|
+
mode = "sourcePath";
|
|
2222
|
+
localPath = input.sourcePath;
|
|
2223
|
+
if (!isAbsolute(localPath)) return `Error: sourcePath must be an absolute path: ${localPath}`;
|
|
2224
|
+
if (!existsSync(localPath)) return `Error: sourcePath does not exist: ${localPath}`;
|
|
2225
|
+
const st = statSync(localPath);
|
|
2226
|
+
if (!st.isFile()) return `Error: sourcePath is not a regular file: ${localPath}`;
|
|
2227
|
+
expectedBytes = st.size;
|
|
2228
|
+
} else {
|
|
2229
|
+
return "Error: sftp-write requires exactly one of: content (string) or sourcePath (absolute local path)";
|
|
2230
|
+
}
|
|
2231
|
+
const compress = mode === "content";
|
|
2232
|
+
const startedAt = Date.now();
|
|
2198
2233
|
let cleanup;
|
|
2199
2234
|
try {
|
|
2200
|
-
const { client, cleanup: c } = await connectSshClient(opts, proxy, 6e4);
|
|
2235
|
+
const { client, cleanup: c } = await connectSshClient(opts, proxy, 6e4, { compress });
|
|
2201
2236
|
cleanup = c;
|
|
2202
2237
|
return await new Promise((resolve) => {
|
|
2203
|
-
|
|
2238
|
+
let resolved = false;
|
|
2239
|
+
let bytesWritten = 0;
|
|
2240
|
+
const finish = (msg) => {
|
|
2241
|
+
if (resolved) return;
|
|
2242
|
+
resolved = true;
|
|
2243
|
+
watchdog?.cancel();
|
|
2244
|
+
wallTimer && clearTimeout(wallTimer);
|
|
2204
2245
|
cleanup?.();
|
|
2205
|
-
resolve("Error: timeout");
|
|
2206
2246
|
cleanup = void 0;
|
|
2207
|
-
|
|
2247
|
+
resolve(msg);
|
|
2248
|
+
};
|
|
2249
|
+
let watchdog;
|
|
2250
|
+
let wallTimer;
|
|
2251
|
+
if (mode === "sourcePath") {
|
|
2252
|
+
const armWatchdog = () => {
|
|
2253
|
+
let timer = setTimeout(
|
|
2254
|
+
() => finish(`Error: idle timeout (no SFTP progress for ${SFTP_IDLE_TIMEOUT_MS / 1e3}s, wrote ${formatBytes(bytesWritten)} of ${formatBytes(expectedBytes)})`),
|
|
2255
|
+
SFTP_IDLE_TIMEOUT_MS
|
|
2256
|
+
);
|
|
2257
|
+
return {
|
|
2258
|
+
reset: () => {
|
|
2259
|
+
clearTimeout(timer);
|
|
2260
|
+
timer = setTimeout(
|
|
2261
|
+
() => finish(`Error: idle timeout (no SFTP progress for ${SFTP_IDLE_TIMEOUT_MS / 1e3}s, wrote ${formatBytes(bytesWritten)} of ${formatBytes(expectedBytes)})`),
|
|
2262
|
+
SFTP_IDLE_TIMEOUT_MS
|
|
2263
|
+
);
|
|
2264
|
+
},
|
|
2265
|
+
cancel: () => clearTimeout(timer)
|
|
2266
|
+
};
|
|
2267
|
+
};
|
|
2268
|
+
watchdog = armWatchdog();
|
|
2269
|
+
} else {
|
|
2270
|
+
wallTimer = setTimeout(
|
|
2271
|
+
() => finish(`Error: timeout after ${SFTP_INLINE_TIMEOUT_MS / 1e3}s`),
|
|
2272
|
+
SFTP_INLINE_TIMEOUT_MS
|
|
2273
|
+
);
|
|
2274
|
+
}
|
|
2208
2275
|
client.sftp((err, sftp) => {
|
|
2209
|
-
if (err) {
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2276
|
+
if (err) return finish(`Error: ${err.message}`);
|
|
2277
|
+
if (mode === "content") {
|
|
2278
|
+
const ws = sftp.createWriteStream(safe, { mode: 420 });
|
|
2279
|
+
ws.on("error", (e) => finish(`Error: ${e.message}`));
|
|
2280
|
+
ws.on("close", () => {
|
|
2281
|
+
const elapsed = Date.now() - startedAt;
|
|
2282
|
+
finish(`Written ${expectedBytes} bytes to ${safe} in ${elapsed}ms`);
|
|
2283
|
+
});
|
|
2284
|
+
ws.end(inlineBuffer);
|
|
2214
2285
|
return;
|
|
2215
2286
|
}
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2287
|
+
let nextProgressLog = SFTP_PROGRESS_LOG_BYTES;
|
|
2288
|
+
sftp.fastPut(
|
|
2289
|
+
localPath,
|
|
2290
|
+
safe,
|
|
2291
|
+
{
|
|
2292
|
+
concurrency: SFTP_FASTPUT_CONCURRENCY,
|
|
2293
|
+
chunkSize: SFTP_FASTPUT_CHUNK_SIZE,
|
|
2294
|
+
mode: 420,
|
|
2295
|
+
fileSize: expectedBytes,
|
|
2296
|
+
step: (transferred) => {
|
|
2297
|
+
bytesWritten = transferred;
|
|
2298
|
+
watchdog?.reset();
|
|
2299
|
+
if (transferred >= nextProgressLog) {
|
|
2300
|
+
console.error(
|
|
2301
|
+
`[sftp-write] ${formatBytes(transferred)} / ${formatBytes(expectedBytes)} \u2192 ${safe}`
|
|
2302
|
+
);
|
|
2303
|
+
nextProgressLog += SFTP_PROGRESS_LOG_BYTES;
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
},
|
|
2307
|
+
(fpErr) => {
|
|
2308
|
+
if (fpErr) return finish(`Error: ${fpErr.message}`);
|
|
2309
|
+
const elapsed = Date.now() - startedAt;
|
|
2310
|
+
const mbps = expectedBytes / (1024 * 1024) / Math.max(1e-3, elapsed / 1e3);
|
|
2311
|
+
finish(
|
|
2312
|
+
`Uploaded ${formatBytes(expectedBytes)} from ${localPath} to ${safe} in ${(elapsed / 1e3).toFixed(1)}s (${mbps.toFixed(1)} MB/s)`
|
|
2313
|
+
);
|
|
2314
|
+
}
|
|
2315
|
+
);
|
|
2230
2316
|
});
|
|
2231
2317
|
});
|
|
2232
2318
|
} catch (e) {
|
|
2233
2319
|
cleanup?.();
|
|
2234
|
-
return `Error: ${e.message}`;
|
|
2320
|
+
return `Error: ${e instanceof Error ? e.message : String(e)}`;
|
|
2235
2321
|
}
|
|
2236
2322
|
}
|
|
2237
2323
|
async function sftpDelete(opts, filePath, proxy) {
|
|
@@ -2503,15 +2589,16 @@ var TOOLS = [
|
|
|
2503
2589
|
},
|
|
2504
2590
|
{
|
|
2505
2591
|
name: "sftp-write",
|
|
2506
|
-
description: "Write
|
|
2592
|
+
description: "Write a file to a remote server via SFTP. Two modes (provide exactly one of `content` or `sourcePath`):\n- `content` (string): inline UTF-8 text. Best for configs, scripts, small JSON. Practical max ~1 MB.\n- `sourcePath` (absolute local path): streams a local file with ssh2 fastPut (64 parallel pipelined writes, 64 KiB chunks). Handles GB-scale files (zips, dumps, builds) without going through the LLM. Only usable when the MCP server runs locally on your machine (i.e. not via --proxy-url).\nProtected system paths on the remote are blocked.",
|
|
2507
2593
|
inputSchema: {
|
|
2508
2594
|
type: "object",
|
|
2509
2595
|
properties: {
|
|
2510
2596
|
serverId: { type: "string", description: "UUID of the SSH server" },
|
|
2511
|
-
path: { type: "string", description: "
|
|
2512
|
-
content: { type: "string", description: "
|
|
2597
|
+
path: { type: "string", description: "Remote file path to write" },
|
|
2598
|
+
content: { type: "string", description: "Inline UTF-8 file content (mutually exclusive with sourcePath)" },
|
|
2599
|
+
sourcePath: { type: "string", description: "Absolute local file path to upload via fastPut (mutually exclusive with content). Use for files >1 MB or any binary." }
|
|
2513
2600
|
},
|
|
2514
|
-
required: ["serverId", "path"
|
|
2601
|
+
required: ["serverId", "path"]
|
|
2515
2602
|
}
|
|
2516
2603
|
},
|
|
2517
2604
|
{
|
|
@@ -2824,7 +2911,8 @@ ${result.stderr}`);
|
|
|
2824
2911
|
}
|
|
2825
2912
|
case "sftp-write": {
|
|
2826
2913
|
const { conn, proxy } = await getServerConnection(String(a.serverId));
|
|
2827
|
-
const
|
|
2914
|
+
const input = typeof a.sourcePath === "string" && a.sourcePath.length > 0 ? { sourcePath: a.sourcePath } : { content: String(a.content ?? "") };
|
|
2915
|
+
const result = await sftpWrite(conn, String(a.path), input, proxy);
|
|
2828
2916
|
return { content: [{ type: "text", text: result }] };
|
|
2829
2917
|
}
|
|
2830
2918
|
case "sftp-delete": {
|