@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 CHANGED
@@ -1 +1,8 @@
1
1
  #!/usr/bin/env node
2
+ type SftpWriteInput = {
3
+ content: string;
4
+ } | {
5
+ sourcePath: string;
6
+ };
7
+
8
+ export type { SftpWriteInput };
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
- async function sftpWrite(opts, filePath, content, proxy) {
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
- const timer = setTimeout(() => {
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
- }, 6e4);
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
- clearTimeout(timer);
2211
- cleanup?.();
2212
- cleanup = void 0;
2213
- resolve(`Error: ${err.message}`);
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
- const ws = sftp.createWriteStream(safe, { mode: 420 });
2217
- ws.on("close", () => {
2218
- clearTimeout(timer);
2219
- cleanup?.();
2220
- cleanup = void 0;
2221
- resolve(`Written ${content.length} bytes to ${safe}`);
2222
- });
2223
- ws.on("error", (e) => {
2224
- clearTimeout(timer);
2225
- cleanup?.();
2226
- cleanup = void 0;
2227
- resolve(`Error: ${e.message}`);
2228
- });
2229
- ws.end(Buffer.from(content, "utf-8"));
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 content to a file on a remote server via SFTP. Protected system paths are blocked.",
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: "File path to write" },
2512
- content: { type: "string", description: "File content to write" }
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", "content"]
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 result = await sftpWrite(conn, String(a.path), String(a.content), proxy);
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": {