@mgsoftwarebv/mg-dashboard-mcp 2.1.0 → 2.1.1

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.js CHANGED
@@ -386,12 +386,29 @@ async function attemptVercelSync(appName, environment, knownStageId) {
386
386
  return `Vercel sync error: ${msg}`;
387
387
  }
388
388
  }
389
+ var SSH_PROXY_SERVER_ID = "03659d55-e194-400d-b82a-bf6457371ded";
390
+ var _proxyConnCache = null;
391
+ async function getProxyConnection() {
392
+ if (_proxyConnCache) return _proxyConnCache;
393
+ const { data, error } = await supabase.from("ssh_server").select("hostname, port, username, password_encrypted, ssh_key_encrypted, ssh_key_passphrase_encrypted").eq("id", SSH_PROXY_SERVER_ID).single();
394
+ if (error || !data) throw new Error("SSH Proxy server not found in database");
395
+ if (!encryptionKey) throw new Error("ENCRYPTION_KEY required to decrypt server credentials");
396
+ _proxyConnCache = {
397
+ hostname: data.hostname,
398
+ port: data.port || 22,
399
+ username: data.username,
400
+ password: data.password_encrypted ? decrypt(data.password_encrypted) : void 0,
401
+ privateKey: data.ssh_key_encrypted ? decrypt(data.ssh_key_encrypted) : void 0,
402
+ passphrase: data.ssh_key_passphrase_encrypted ? decrypt(data.ssh_key_passphrase_encrypted) : void 0
403
+ };
404
+ return _proxyConnCache;
405
+ }
389
406
  async function getServerConnection(serverId) {
390
407
  assertServerAccess(serverId);
391
- const { data, error } = await supabase.from("ssh_server").select("hostname, port, username, password_encrypted, ssh_key_encrypted, ssh_key_passphrase_encrypted").eq("id", serverId).single();
408
+ const { data, error } = await supabase.from("ssh_server").select("hostname, port, username, password_encrypted, ssh_key_encrypted, ssh_key_passphrase_encrypted, allowed_ssh_ips").eq("id", serverId).single();
392
409
  if (error || !data) throw new Error(`Server not found: ${serverId}`);
393
410
  if (!encryptionKey) throw new Error("ENCRYPTION_KEY required to decrypt server credentials");
394
- return {
411
+ const conn = {
395
412
  hostname: data.hostname,
396
413
  port: data.port || 22,
397
414
  username: data.username,
@@ -399,8 +416,12 @@ async function getServerConnection(serverId) {
399
416
  privateKey: data.ssh_key_encrypted ? decrypt(data.ssh_key_encrypted) : void 0,
400
417
  passphrase: data.ssh_key_passphrase_encrypted ? decrypt(data.ssh_key_passphrase_encrypted) : void 0
401
418
  };
419
+ const needsProxy = data.allowed_ssh_ips !== null && serverId !== SSH_PROXY_SERVER_ID;
420
+ const proxy = needsProxy ? await getProxyConnection() : void 0;
421
+ return { conn, proxy };
402
422
  }
403
- async function sshExec(opts, command) {
423
+ async function sshExec(opts, command, proxy) {
424
+ if (proxy) return sshExecViaProxy(proxy, opts, command);
404
425
  return new Promise((resolve) => {
405
426
  const ssh = new Client();
406
427
  let stdout = "";
@@ -459,6 +480,98 @@ async function sshExec(opts, command) {
459
480
  });
460
481
  });
461
482
  }
483
+ function sshExecViaProxy(proxyOpts, targetOpts, command) {
484
+ return new Promise((resolve) => {
485
+ const proxyClient = new Client();
486
+ let done = false;
487
+ const timeout = targetOpts.timeout || 6e4;
488
+ const timer = setTimeout(() => {
489
+ if (!done) {
490
+ done = true;
491
+ proxyClient.end();
492
+ resolve({ stdout: "", stderr: "SSH proxy command timeout", exitCode: -1 });
493
+ }
494
+ }, timeout);
495
+ const cleanup = () => {
496
+ clearTimeout(timer);
497
+ proxyClient.end();
498
+ };
499
+ proxyClient.on("ready", () => {
500
+ proxyClient.forwardOut("127.0.0.1", 0, targetOpts.hostname, targetOpts.port, (err, tunnel) => {
501
+ if (err) {
502
+ if (!done) {
503
+ done = true;
504
+ cleanup();
505
+ resolve({ stdout: "", stderr: err.message, exitCode: -1 });
506
+ }
507
+ return;
508
+ }
509
+ const targetClient = new Client();
510
+ let stdout = "";
511
+ let stderr = "";
512
+ targetClient.on("ready", () => {
513
+ targetClient.exec(command, (execErr, stream) => {
514
+ if (execErr) {
515
+ if (!done) {
516
+ done = true;
517
+ targetClient.end();
518
+ cleanup();
519
+ resolve({ stdout, stderr, exitCode: -1 });
520
+ }
521
+ return;
522
+ }
523
+ stream.on("data", (d) => {
524
+ stdout += d.toString();
525
+ });
526
+ stream.stderr.on("data", (d) => {
527
+ stderr += d.toString();
528
+ });
529
+ stream.on("close", (code) => {
530
+ if (!done) {
531
+ done = true;
532
+ targetClient.end();
533
+ cleanup();
534
+ resolve({ stdout, stderr, exitCode: code ?? 0 });
535
+ }
536
+ });
537
+ });
538
+ });
539
+ targetClient.on("error", (targetErr) => {
540
+ if (!done) {
541
+ done = true;
542
+ targetClient.end();
543
+ cleanup();
544
+ resolve({ stdout, stderr: targetErr.message, exitCode: -1 });
545
+ }
546
+ });
547
+ targetClient.connect({
548
+ sock: tunnel,
549
+ username: targetOpts.username,
550
+ password: targetOpts.password,
551
+ privateKey: targetOpts.privateKey,
552
+ passphrase: targetOpts.passphrase,
553
+ readyTimeout: timeout
554
+ });
555
+ });
556
+ });
557
+ proxyClient.on("error", (err) => {
558
+ if (!done) {
559
+ done = true;
560
+ cleanup();
561
+ resolve({ stdout: "", stderr: err.message, exitCode: -1 });
562
+ }
563
+ });
564
+ proxyClient.connect({
565
+ host: proxyOpts.hostname,
566
+ port: proxyOpts.port,
567
+ username: proxyOpts.username,
568
+ password: proxyOpts.password,
569
+ privateKey: proxyOpts.privateKey,
570
+ passphrase: proxyOpts.passphrase,
571
+ readyTimeout: proxyOpts.timeout || 3e4
572
+ });
573
+ });
574
+ }
462
575
  function sanitizePath(path) {
463
576
  let normalized = path.replace(/\\/g, "/").replace(/\0/g, "");
464
577
  const parts = normalized.split("/");
@@ -743,7 +856,7 @@ function assertAllowedLogPath(filePath) {
743
856
  throw new Error(`Path not allowed. Must be under: ${ALLOWED_LOG_PREFIXES.join(", ")}`);
744
857
  }
745
858
  }
746
- async function discoverSiteDatabases(conn) {
859
+ async function discoverSiteDatabases(conn, proxy) {
747
860
  const script = `
748
861
  check_dir() {
749
862
  local base="$1" root="$2"
@@ -780,7 +893,7 @@ for dir in /var/www/*/; do
780
893
  done
781
894
  done
782
895
  `.trim();
783
- const result = await sshExec(conn, script);
896
+ const result = await sshExec(conn, script, proxy);
784
897
  const sites = [];
785
898
  for (const line of result.stdout.split("\n")) {
786
899
  if (!line.trim()) continue;
@@ -864,9 +977,9 @@ DB_PORT=\${DB_PORT:-3306}
864
977
  mysql --user="$DB_USER" --password="$DB_PASS" --host="$DB_HOST" --port="$DB_PORT" -t -e '${safeQuery}' "$DB_NAME" 2>&1 | grep -v "\\[Warning\\].*password"
865
978
  `.trim();
866
979
  }
867
- async function execSiteMysql(conn, sitePath, query) {
980
+ async function execSiteMysql(conn, sitePath, query, proxy) {
868
981
  const cmd = buildSiteMysqlCommand(sitePath, query);
869
- const result = await sshExec(conn, cmd);
982
+ const result = await sshExec(conn, cmd, proxy);
870
983
  const output = (result.stdout || "").trim();
871
984
  if (output.startsWith("ERROR: No database config found")) {
872
985
  throw new Error(output);
@@ -1338,9 +1451,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1338
1451
  return { content: [{ type: "text", text: lines.length ? lines.join("\n") : "No servers found" }] };
1339
1452
  }
1340
1453
  case "server-status": {
1341
- const conn = await getServerConnection(String(a.serverId));
1454
+ const { conn, proxy } = await getServerConnection(String(a.serverId));
1342
1455
  const cmd = 'echo "=== UPTIME ===" && uptime && echo "=== DISK ===" && df -h --total && echo "=== MEMORY ===" && free -h && echo "=== LOAD ===" && cat /proc/loadavg';
1343
- const result = await sshExec(conn, cmd);
1456
+ const result = await sshExec(conn, cmd, proxy);
1344
1457
  const output = result.exitCode === 0 ? result.stdout : `Exit ${result.exitCode}
1345
1458
  ${result.stdout}
1346
1459
  ${result.stderr}`;
@@ -1350,9 +1463,9 @@ ${result.stderr}`;
1350
1463
  case "ssh-execute": {
1351
1464
  const command = String(a.command);
1352
1465
  assertSafeCommand(command);
1353
- const conn = await getServerConnection(String(a.serverId));
1466
+ const { conn, proxy } = await getServerConnection(String(a.serverId));
1354
1467
  if (a.timeout) conn.timeout = Number(a.timeout);
1355
- const result = await sshExec(conn, command);
1468
+ const result = await sshExec(conn, command, proxy);
1356
1469
  const output = [`Exit code: ${result.exitCode}`];
1357
1470
  if (result.stdout) output.push(`--- stdout ---
1358
1471
  ${result.stdout}`);
@@ -1361,45 +1474,45 @@ ${result.stderr}`);
1361
1474
  return { content: [{ type: "text", text: output.join("\n") }] };
1362
1475
  }
1363
1476
  case "server-reboot": {
1364
- const conn = await getServerConnection(String(a.serverId));
1365
- const result = await sshExec(conn, "sudo reboot");
1477
+ const { conn, proxy } = await getServerConnection(String(a.serverId));
1478
+ const result = await sshExec(conn, "sudo reboot", proxy);
1366
1479
  return { content: [{ type: "text", text: result.exitCode === 0 ? "Reboot command sent. Server will be unavailable shortly." : `Reboot failed: ${result.stderr}` }] };
1367
1480
  }
1368
1481
  case "server-restart-service": {
1369
1482
  const service = String(a.serviceName).replace(/[^a-zA-Z0-9._@-]/g, "");
1370
- const conn = await getServerConnection(String(a.serverId));
1371
- const result = await sshExec(conn, `sudo systemctl restart ${service}`);
1483
+ const { conn, proxy } = await getServerConnection(String(a.serverId));
1484
+ const result = await sshExec(conn, `sudo systemctl restart ${service}`, proxy);
1372
1485
  if (result.exitCode === 0) {
1373
- const status = await sshExec(conn, `sudo systemctl is-active ${service}`);
1486
+ const status = await sshExec(conn, `sudo systemctl is-active ${service}`, proxy);
1374
1487
  return { content: [{ type: "text", text: `Service "${service}" restarted. Status: ${status.stdout.trim()}` }] };
1375
1488
  }
1376
1489
  return { content: [{ type: "text", text: `Failed to restart "${service}": ${result.stderr}` }] };
1377
1490
  }
1378
- // ----- SFTP -----
1491
+ // ----- SFTP (no proxy support yet — direct connection only) -----
1379
1492
  case "sftp-list": {
1380
- const conn = await getServerConnection(String(a.serverId));
1493
+ const { conn } = await getServerConnection(String(a.serverId));
1381
1494
  const listing = await sftpReaddir(conn, String(a.path || "/"));
1382
1495
  return { content: [{ type: "text", text: listing }] };
1383
1496
  }
1384
1497
  case "sftp-read": {
1385
- const conn = await getServerConnection(String(a.serverId));
1498
+ const { conn } = await getServerConnection(String(a.serverId));
1386
1499
  const content = await sftpRead(conn, String(a.path));
1387
1500
  return { content: [{ type: "text", text: content }] };
1388
1501
  }
1389
1502
  case "sftp-write": {
1390
- const conn = await getServerConnection(String(a.serverId));
1503
+ const { conn } = await getServerConnection(String(a.serverId));
1391
1504
  const result = await sftpWrite(conn, String(a.path), String(a.content));
1392
1505
  return { content: [{ type: "text", text: result }] };
1393
1506
  }
1394
1507
  case "sftp-delete": {
1395
- const conn = await getServerConnection(String(a.serverId));
1508
+ const { conn } = await getServerConnection(String(a.serverId));
1396
1509
  const result = await sftpDelete(conn, String(a.path));
1397
1510
  return { content: [{ type: "text", text: result }] };
1398
1511
  }
1399
1512
  // ----- Docker -----
1400
1513
  case "docker-list": {
1401
- const conn = await getServerConnection(String(a.serverId));
1402
- const result = await sshExec(conn, 'docker ps -a --format "table {{.Names}} {{.Image}} {{.Status}} {{.Ports}}"');
1514
+ const { conn, proxy } = await getServerConnection(String(a.serverId));
1515
+ const result = await sshExec(conn, 'docker ps -a --format "table {{.Names}} {{.Image}} {{.Status}} {{.Ports}}"', proxy);
1403
1516
  return { content: [{ type: "text", text: result.exitCode === 0 ? result.stdout : `Error: ${result.stderr}` }] };
1404
1517
  }
1405
1518
  case "docker-action": {
@@ -1408,22 +1521,22 @@ ${result.stderr}`);
1408
1521
  if (!["start", "stop", "restart", "remove"].includes(action)) {
1409
1522
  throw new Error(`Invalid action: ${action}. Use start, stop, restart, or remove.`);
1410
1523
  }
1411
- const conn = await getServerConnection(String(a.serverId));
1524
+ const { conn, proxy } = await getServerConnection(String(a.serverId));
1412
1525
  const dockerCmd = action === "remove" ? `docker rm -f ${container}` : `docker ${action} ${container}`;
1413
- const result = await sshExec(conn, dockerCmd);
1526
+ const result = await sshExec(conn, dockerCmd, proxy);
1414
1527
  return { content: [{ type: "text", text: result.exitCode === 0 ? `Container "${container}" ${action}ed successfully` : `Error: ${result.stderr}` }] };
1415
1528
  }
1416
1529
  case "docker-logs": {
1417
1530
  const container = String(a.containerName).replace(/[^a-zA-Z0-9._-]/g, "");
1418
1531
  const lines = Number(a.lines) || 100;
1419
- const conn = await getServerConnection(String(a.serverId));
1420
- const result = await sshExec(conn, `docker logs --tail ${lines} ${container} 2>&1`);
1532
+ const { conn, proxy } = await getServerConnection(String(a.serverId));
1533
+ const result = await sshExec(conn, `docker logs --tail ${lines} ${container} 2>&1`, proxy);
1421
1534
  return { content: [{ type: "text", text: result.exitCode === 0 ? result.stdout : `Error: ${result.stderr}` }] };
1422
1535
  }
1423
1536
  // ----- Database -----
1424
1537
  case "db-discover": {
1425
- const conn = await getServerConnection(String(a.serverId));
1426
- const sites = await discoverSiteDatabases(conn);
1538
+ const { conn, proxy } = await getServerConnection(String(a.serverId));
1539
+ const sites = await discoverSiteDatabases(conn, proxy);
1427
1540
  if (!sites.length) {
1428
1541
  return { content: [{ type: "text", text: "No web applications with database configs found in /var/www/" }] };
1429
1542
  }
@@ -1433,26 +1546,27 @@ ${result.stderr}`);
1433
1546
  return { content: [{ type: "text", text: lines.join("\n") }] };
1434
1547
  }
1435
1548
  case "db-tables": {
1436
- const conn = await getServerConnection(String(a.serverId));
1549
+ const { conn, proxy } = await getServerConnection(String(a.serverId));
1437
1550
  const sql = "SELECT TABLE_NAME, ENGINE, TABLE_ROWS, ROUND(DATA_LENGTH/1024/1024, 2) AS `Size (MB)`, TABLE_COLLATION FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() ORDER BY TABLE_NAME";
1438
- const output = await execSiteMysql(conn, String(a.sitePath), sql);
1551
+ const output = await execSiteMysql(conn, String(a.sitePath), sql, proxy);
1439
1552
  return { content: [{ type: "text", text: output || "No tables found" }] };
1440
1553
  }
1441
1554
  case "db-describe": {
1442
1555
  const table = String(a.table).replace(/[^a-zA-Z0-9_]/g, "");
1443
- const conn = await getServerConnection(String(a.serverId));
1556
+ const { conn, proxy } = await getServerConnection(String(a.serverId));
1444
1557
  const output = await execSiteMysql(
1445
1558
  conn,
1446
1559
  String(a.sitePath),
1447
- `DESCRIBE \`${table}\`; SHOW INDEX FROM \`${table}\``
1560
+ `DESCRIBE \`${table}\`; SHOW INDEX FROM \`${table}\``,
1561
+ proxy
1448
1562
  );
1449
1563
  return { content: [{ type: "text", text: output }] };
1450
1564
  }
1451
1565
  case "db-query": {
1452
1566
  const query = String(a.query).trim();
1453
1567
  assertSafeSql(query);
1454
- const conn = await getServerConnection(String(a.serverId));
1455
- const output = await execSiteMysql(conn, String(a.sitePath), query);
1568
+ const { conn, proxy } = await getServerConnection(String(a.serverId));
1569
+ const output = await execSiteMysql(conn, String(a.sitePath), query, proxy);
1456
1570
  return { content: [{ type: "text", text: output || "Query executed successfully (no output)" }] };
1457
1571
  }
1458
1572
  // ----- Env Config -----
@@ -1550,7 +1664,7 @@ ${result.stderr}`);
1550
1664
  }
1551
1665
  // ----- Cache Purge -----
1552
1666
  case "cache-purge": {
1553
- const conn = await getServerConnection(String(a.serverId));
1667
+ const { conn, proxy } = await getServerConnection(String(a.serverId));
1554
1668
  conn.timeout = 12e4;
1555
1669
  const script = `
1556
1670
  R=""
@@ -1631,13 +1745,13 @@ else
1631
1745
  fi
1632
1746
  echo -e "$R"
1633
1747
  `.trim();
1634
- const result = await sshExec(conn, script);
1748
+ const result = await sshExec(conn, script, proxy);
1635
1749
  const output = (result.stdout || "").trim();
1636
1750
  return { content: [{ type: "text", text: output || "Cache purge completed (no output)" }] };
1637
1751
  }
1638
1752
  // ----- Log Reading -----
1639
1753
  case "log-list": {
1640
- const conn = await getServerConnection(String(a.serverId));
1754
+ const { conn, proxy } = await getServerConnection(String(a.serverId));
1641
1755
  const script = `
1642
1756
  {
1643
1757
  [ -d /usr/local/lsws/logs ] && find /usr/local/lsws/logs -maxdepth 2 -name "*.log" -type f 2>/dev/null
@@ -1663,7 +1777,7 @@ echo -e "$R"
1663
1777
  echo "$f $HR $MOD"
1664
1778
  done
1665
1779
  `.trim();
1666
- const result = await sshExec(conn, script);
1780
+ const result = await sshExec(conn, script, proxy);
1667
1781
  return { content: [{ type: "text", text: result.stdout || "No log files found" }] };
1668
1782
  }
1669
1783
  case "log-read": {
@@ -1671,9 +1785,9 @@ done
1671
1785
  assertAllowedLogPath(logPath);
1672
1786
  const lineCount = Math.min(Math.max(Number(a.lines) || 100, 1), 500);
1673
1787
  const filter = a.filter ? String(a.filter).replace(/'/g, "'\\''") : "";
1674
- const conn = await getServerConnection(String(a.serverId));
1788
+ const { conn, proxy } = await getServerConnection(String(a.serverId));
1675
1789
  const cmd = filter ? `grep '${filter}' '${logPath}' 2>/dev/null | tail -n ${lineCount}` : `tail -n ${lineCount} '${logPath}' 2>/dev/null`;
1676
- const result = await sshExec(conn, cmd);
1790
+ const result = await sshExec(conn, cmd, proxy);
1677
1791
  if (result.exitCode !== 0 && !result.stdout) {
1678
1792
  throw new Error(result.stderr || `Failed to read log: ${logPath}`);
1679
1793
  }
@@ -1681,21 +1795,21 @@ done
1681
1795
  }
1682
1796
  // ----- Cron Jobs -----
1683
1797
  case "cron-list": {
1684
- const conn = await getServerConnection(a.serverId);
1798
+ const { conn, proxy } = await getServerConnection(a.serverId);
1685
1799
  const user = a.user ? String(a.user) : null;
1686
1800
  const parts = [];
1687
1801
  if (user) {
1688
- const result = await sshExec(conn, `crontab -l -u ${user} 2>/dev/null || echo '(no crontab for ${user})'`);
1802
+ const result = await sshExec(conn, `crontab -l -u ${user} 2>/dev/null || echo '(no crontab for ${user})'`, proxy);
1689
1803
  parts.push(`## Crontab for ${user}
1690
1804
  ${result.stdout}`);
1691
1805
  } else {
1692
- const rootCron = await sshExec(conn, `crontab -l 2>/dev/null || echo '(no crontab for root)'`);
1806
+ const rootCron = await sshExec(conn, `crontab -l 2>/dev/null || echo '(no crontab for root)'`, proxy);
1693
1807
  parts.push(`## Root crontab
1694
1808
  ${rootCron.stdout}`);
1695
- const wwwCron = await sshExec(conn, `crontab -l -u www-data 2>/dev/null || echo '(no crontab for www-data)'`);
1809
+ const wwwCron = await sshExec(conn, `crontab -l -u www-data 2>/dev/null || echo '(no crontab for www-data)'`, proxy);
1696
1810
  parts.push(`## www-data crontab
1697
1811
  ${wwwCron.stdout}`);
1698
- const cronD = await sshExec(conn, `for f in /etc/cron.d/*; do [ -f "$f" ] && echo "--- $f ---" && cat "$f" && echo; done 2>/dev/null || echo '(no files in /etc/cron.d/)'`);
1812
+ const cronD = await sshExec(conn, `for f in /etc/cron.d/*; do [ -f "$f" ] && echo "--- $f ---" && cat "$f" && echo; done 2>/dev/null || echo '(no files in /etc/cron.d/)'`, proxy);
1699
1813
  parts.push(`## /etc/cron.d/
1700
1814
  ${cronD.stdout}`);
1701
1815
  const summary = await sshExec(conn, [
@@ -1704,13 +1818,13 @@ ${cronD.stdout}`);
1704
1818
  `echo "cron.daily: $(ls /etc/cron.daily/ 2>/dev/null | wc -l) scripts"`,
1705
1819
  `echo "cron.weekly: $(ls /etc/cron.weekly/ 2>/dev/null | wc -l) scripts"`,
1706
1820
  `echo "cron.monthly: $(ls /etc/cron.monthly/ 2>/dev/null | wc -l) scripts"`
1707
- ].join(" && "));
1821
+ ].join(" && "), proxy);
1708
1822
  parts.push(summary.stdout);
1709
1823
  }
1710
1824
  return { content: [{ type: "text", text: parts.join("\n\n") }] };
1711
1825
  }
1712
1826
  case "cron-add": {
1713
- const conn = await getServerConnection(a.serverId);
1827
+ const { conn, proxy } = await getServerConnection(a.serverId);
1714
1828
  const user = a.user ? String(a.user) : "root";
1715
1829
  const schedule = String(a.schedule).trim();
1716
1830
  const command = String(a.command).trim();
@@ -1720,7 +1834,7 @@ ${cronD.stdout}`);
1720
1834
  }
1721
1835
  const entry = commentText ? `# ${commentText}
1722
1836
  ${schedule} ${command}` : `${schedule} ${command}`;
1723
- const result = await sshExec(conn, `(crontab -l -u ${user} 2>/dev/null; echo '${entry.replace(/'/g, "'\\''")}') | crontab -u ${user} -`);
1837
+ const result = await sshExec(conn, `(crontab -l -u ${user} 2>/dev/null; echo '${entry.replace(/'/g, "'\\''")}') | crontab -u ${user} -`, proxy);
1724
1838
  if (result.exitCode !== 0) {
1725
1839
  throw new Error(result.stderr || "Failed to add cron job");
1726
1840
  }
@@ -1728,13 +1842,13 @@ ${schedule} ${command}` : `${schedule} ${command}`;
1728
1842
  ${schedule} ${command}` }] };
1729
1843
  }
1730
1844
  case "cron-remove": {
1731
- const conn = await getServerConnection(a.serverId);
1845
+ const { conn, proxy } = await getServerConnection(a.serverId);
1732
1846
  const user = a.user ? String(a.user) : "root";
1733
1847
  const match = String(a.commandMatch).trim();
1734
1848
  if (!match) {
1735
1849
  throw new Error("commandMatch is required");
1736
1850
  }
1737
- const current = await sshExec(conn, `crontab -l -u ${user} 2>/dev/null`);
1851
+ const current = await sshExec(conn, `crontab -l -u ${user} 2>/dev/null`, proxy);
1738
1852
  const lines = current.stdout.split("\n");
1739
1853
  const before = lines.length;
1740
1854
  const filtered = lines.filter((line) => {
@@ -1746,21 +1860,21 @@ ${schedule} ${command}` }] };
1746
1860
  return { content: [{ type: "text", text: `No cron entries found matching "${match}"` }] };
1747
1861
  }
1748
1862
  const escapedCrontab = filtered.join("\n").replace(/'/g, "'\\''");
1749
- const result = await sshExec(conn, `echo '${escapedCrontab}' | crontab -u ${user} -`);
1863
+ const result = await sshExec(conn, `echo '${escapedCrontab}' | crontab -u ${user} -`, proxy);
1750
1864
  if (result.exitCode !== 0) {
1751
1865
  throw new Error(result.stderr || "Failed to update crontab");
1752
1866
  }
1753
1867
  return { content: [{ type: "text", text: `Removed ${removed} cron entry/entries matching "${match}" from ${user} crontab` }] };
1754
1868
  }
1755
1869
  case "cron-toggle": {
1756
- const conn = await getServerConnection(a.serverId);
1870
+ const { conn, proxy } = await getServerConnection(a.serverId);
1757
1871
  const user = a.user ? String(a.user) : "root";
1758
1872
  const match = String(a.commandMatch).trim();
1759
1873
  const enable = Boolean(a.enable);
1760
1874
  if (!match) {
1761
1875
  throw new Error("commandMatch is required");
1762
1876
  }
1763
- const current = await sshExec(conn, `crontab -l -u ${user} 2>/dev/null`);
1877
+ const current = await sshExec(conn, `crontab -l -u ${user} 2>/dev/null`, proxy);
1764
1878
  const lines = current.stdout.split("\n");
1765
1879
  let toggled = 0;
1766
1880
  const updated = lines.map((line) => {
@@ -1779,7 +1893,7 @@ ${schedule} ${command}` }] };
1779
1893
  return { content: [{ type: "text", text: `No ${state} cron entries found matching "${match}"` }] };
1780
1894
  }
1781
1895
  const escapedCrontab = updated.join("\n").replace(/'/g, "'\\''");
1782
- const result = await sshExec(conn, `echo '${escapedCrontab}' | crontab -u ${user} -`);
1896
+ const result = await sshExec(conn, `echo '${escapedCrontab}' | crontab -u ${user} -`, proxy);
1783
1897
  if (result.exitCode !== 0) {
1784
1898
  throw new Error(result.stderr || "Failed to update crontab");
1785
1899
  }