@mgsoftwarebv/mg-dashboard-mcp 3.4.3 → 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,12 +1,12 @@
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';
8
8
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
9
- import { ListToolsRequestSchema, CallToolRequestSchema, isInitializeRequest, ListPromptsRequestSchema, GetPromptRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListResourceTemplatesRequestSchema, CompleteRequestSchema, SubscribeRequestSchema, UnsubscribeRequestSchema, ListToolsResultSchema, CallToolResultSchema, ListPromptsResultSchema, GetPromptResultSchema, ListResourcesResultSchema, ReadResourceResultSchema, ListResourceTemplatesResultSchema, CompleteResultSchema, EmptyResultSchema } from '@modelcontextprotocol/sdk/types.js';
9
+ import { ListToolsRequestSchema, CallToolRequestSchema, isInitializeRequest, ListPromptsRequestSchema, GetPromptRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListResourceTemplatesRequestSchema, SubscribeRequestSchema, UnsubscribeRequestSchema, ListToolsResultSchema, CallToolResultSchema, ListPromptsResultSchema, GetPromptResultSchema, ListResourcesResultSchema, ReadResourceResultSchema, ListResourceTemplatesResultSchema, EmptyResultSchema } from '@modelcontextprotocol/sdk/types.js';
10
10
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
11
11
  import { createServer } from 'http';
12
12
  import { randomUUID, createHash, randomBytes, createCipheriv, createDecipheriv } from 'crypto';
@@ -187,10 +187,6 @@ async function runBridge(handshake, proxyUrl2, refresh) {
187
187
  ListResourceTemplatesRequestSchema,
188
188
  forwardRequest(ListResourceTemplatesResultSchema)
189
189
  );
190
- stdioServer.setRequestHandler(
191
- CompleteRequestSchema,
192
- forwardRequest(CompleteResultSchema)
193
- );
194
190
  stdioServer.setRequestHandler(
195
191
  SubscribeRequestSchema,
196
192
  forwardRequest(EmptyResultSchema)
@@ -2009,7 +2005,8 @@ function sshExecViaProxy(proxyOpts, targetOpts, command) {
2009
2005
  });
2010
2006
  });
2011
2007
  }
2012
- function connectSshClient(opts, proxy, readyTimeout = 6e4) {
2008
+ function connectSshClient(opts, proxy, readyTimeout = 6e4, extraConnect) {
2009
+ const compress = extraConnect?.compress;
2013
2010
  if (!proxy) {
2014
2011
  return new Promise((resolve, reject) => {
2015
2012
  const ssh = new Client();
@@ -2022,7 +2019,8 @@ function connectSshClient(opts, proxy, readyTimeout = 6e4) {
2022
2019
  password: opts.password,
2023
2020
  privateKey: opts.privateKey,
2024
2021
  passphrase: opts.passphrase,
2025
- readyTimeout
2022
+ readyTimeout,
2023
+ ...compress ? { compress } : {}
2026
2024
  });
2027
2025
  });
2028
2026
  }
@@ -2056,7 +2054,8 @@ function connectSshClient(opts, proxy, readyTimeout = 6e4) {
2056
2054
  password: opts.password,
2057
2055
  privateKey: opts.privateKey,
2058
2056
  passphrase: opts.passphrase,
2059
- readyTimeout
2057
+ readyTimeout,
2058
+ ...compress ? { compress } : {}
2060
2059
  });
2061
2060
  });
2062
2061
  });
@@ -2196,46 +2195,129 @@ async function sftpRead(opts, filePath, proxy) {
2196
2195
  return `Error: ${e.message}`;
2197
2196
  }
2198
2197
  }
2199
- 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) {
2200
2210
  const safe = sanitizePath(filePath);
2201
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();
2202
2233
  let cleanup;
2203
2234
  try {
2204
- const { client, cleanup: c } = await connectSshClient(opts, proxy, 6e4);
2235
+ const { client, cleanup: c } = await connectSshClient(opts, proxy, 6e4, { compress });
2205
2236
  cleanup = c;
2206
2237
  return await new Promise((resolve) => {
2207
- 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);
2208
2245
  cleanup?.();
2209
- resolve("Error: timeout");
2210
2246
  cleanup = void 0;
2211
- }, 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
+ }
2212
2275
  client.sftp((err, sftp) => {
2213
- if (err) {
2214
- clearTimeout(timer);
2215
- cleanup?.();
2216
- cleanup = void 0;
2217
- 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);
2218
2285
  return;
2219
2286
  }
2220
- const ws = sftp.createWriteStream(safe, { mode: 420 });
2221
- ws.on("close", () => {
2222
- clearTimeout(timer);
2223
- cleanup?.();
2224
- cleanup = void 0;
2225
- resolve(`Written ${content.length} bytes to ${safe}`);
2226
- });
2227
- ws.on("error", (e) => {
2228
- clearTimeout(timer);
2229
- cleanup?.();
2230
- cleanup = void 0;
2231
- resolve(`Error: ${e.message}`);
2232
- });
2233
- 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
+ );
2234
2316
  });
2235
2317
  });
2236
2318
  } catch (e) {
2237
2319
  cleanup?.();
2238
- return `Error: ${e.message}`;
2320
+ return `Error: ${e instanceof Error ? e.message : String(e)}`;
2239
2321
  }
2240
2322
  }
2241
2323
  async function sftpDelete(opts, filePath, proxy) {
@@ -2507,15 +2589,16 @@ var TOOLS = [
2507
2589
  },
2508
2590
  {
2509
2591
  name: "sftp-write",
2510
- 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.",
2511
2593
  inputSchema: {
2512
2594
  type: "object",
2513
2595
  properties: {
2514
2596
  serverId: { type: "string", description: "UUID of the SSH server" },
2515
- path: { type: "string", description: "File path to write" },
2516
- 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." }
2517
2600
  },
2518
- required: ["serverId", "path", "content"]
2601
+ required: ["serverId", "path"]
2519
2602
  }
2520
2603
  },
2521
2604
  {
@@ -2828,7 +2911,8 @@ ${result.stderr}`);
2828
2911
  }
2829
2912
  case "sftp-write": {
2830
2913
  const { conn, proxy } = await getServerConnection(String(a.serverId));
2831
- 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);
2832
2916
  return { content: [{ type: "text", text: result }] };
2833
2917
  }
2834
2918
  case "sftp-delete": {