@yawlabs/ssh-mcp 0.11.4 → 0.12.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.js CHANGED
@@ -214,8 +214,6 @@ function checkSshConfig(host) {
214
214
  if (inHostBlock) hostConfig.push(trimmed);
215
215
  } else if (inHostBlock && trimmed) {
216
216
  hostConfig.push(trimmed);
217
- } else if (inHostBlock && !trimmed) {
218
- inHostBlock = false;
219
217
  }
220
218
  }
221
219
  if (hostConfig.length === 0) {
@@ -615,7 +613,15 @@ import { readFileSync as readFileSync3 } from "fs";
615
613
  import { homedir as homedir3 } from "os";
616
614
  import { join as join3 } from "path";
617
615
  import { Client } from "ssh2";
616
+ var sshConfigCache = /* @__PURE__ */ new Map();
618
617
  function resolveFromSshConfig(host) {
618
+ const cached = sshConfigCache.get(host);
619
+ if (cached !== void 0) return cached;
620
+ const result = resolveFromSshConfigUncached(host);
621
+ sshConfigCache.set(host, result);
622
+ return result;
623
+ }
624
+ function resolveFromSshConfigUncached(host) {
619
625
  try {
620
626
  const { stdout, ok } = runArgs("ssh", ["-G", host]);
621
627
  if (!ok) return null;
@@ -695,8 +701,10 @@ function resolveConfig(config) {
695
701
  keepaliveCountMax: 3,
696
702
  hostVerifier: buildHostVerifier(verifierHosts, port)
697
703
  };
704
+ const home = homedir3();
698
705
  if (config.privateKeyPath) {
699
- connectConfig.privateKey = readFileSync3(config.privateKeyPath);
706
+ const keyPath = config.privateKeyPath.startsWith("~") ? join3(home, config.privateKeyPath.slice(1)) : config.privateKeyPath;
707
+ connectConfig.privateKey = readFileSync3(keyPath);
700
708
  } else if (config.password) {
701
709
  connectConfig.password = config.password;
702
710
  } else {
@@ -704,7 +712,6 @@ function resolveConfig(config) {
704
712
  if (agentSock) {
705
713
  connectConfig.agent = agentSock;
706
714
  }
707
- const home = homedir3();
708
715
  const keyPaths = sshConfig && sshConfig.identityFiles.length > 0 ? sshConfig.identityFiles.map((p) => p.startsWith("~") ? join3(home, p.slice(1)) : p) : [join3(home, ".ssh", "id_ed25519"), join3(home, ".ssh", "id_rsa"), join3(home, ".ssh", "id_ecdsa")];
709
716
  for (const keyPath of keyPaths) {
710
717
  try {
@@ -941,10 +948,11 @@ async function writeFile(client, remotePath, content) {
941
948
  }
942
949
  }
943
950
  async function uploadFile(client, localPath, remotePath) {
951
+ const resolvedLocal = localPath.startsWith("~") ? join3(homedir3(), localPath.slice(1)) : localPath;
944
952
  const sftp = await getSftp(client);
945
953
  try {
946
954
  await new Promise((resolve, reject) => {
947
- sftp.fastPut(localPath, remotePath, (err) => {
955
+ sftp.fastPut(resolvedLocal, remotePath, (err) => {
948
956
  if (err) return reject(err);
949
957
  resolve();
950
958
  });
@@ -954,10 +962,11 @@ async function uploadFile(client, localPath, remotePath) {
954
962
  }
955
963
  }
956
964
  async function downloadFile(client, remotePath, localPath) {
965
+ const resolvedLocal = localPath.startsWith("~") ? join3(homedir3(), localPath.slice(1)) : localPath;
957
966
  const sftp = await getSftp(client);
958
967
  try {
959
968
  await new Promise((resolve, reject) => {
960
- sftp.fastGet(remotePath, localPath, (err) => {
969
+ sftp.fastGet(remotePath, resolvedLocal, (err) => {
961
970
  if (err) return reject(err);
962
971
  resolve();
963
972
  });
@@ -1296,16 +1305,16 @@ async function multiExec(pool, hosts, command, timeoutMs = 3e4) {
1296
1305
  };
1297
1306
  });
1298
1307
  }
1299
- var VALID_FIND_SIZE = /^\d+[kMGTP]?$/;
1308
+ var VALID_FIND_SIZE = /^\d+[cwbkMG]?$/;
1300
1309
  async function find(client, options, timeoutMs = 3e4) {
1301
1310
  if (options.minsize && !VALID_FIND_SIZE.test(options.minsize)) {
1302
1311
  throw new Error(
1303
- `Invalid minsize format: "${options.minsize}". Expected: digits followed by optional k/M/G/T/P (e.g. "1M", "100k")`
1312
+ `Invalid minsize format: "${options.minsize}". Expected: digits followed by optional c/w/b/k/M/G (e.g. "1M", "100k")`
1304
1313
  );
1305
1314
  }
1306
1315
  if (options.maxsize && !VALID_FIND_SIZE.test(options.maxsize)) {
1307
1316
  throw new Error(
1308
- `Invalid maxsize format: "${options.maxsize}". Expected: digits followed by optional k/M/G/T/P (e.g. "10M", "500k")`
1317
+ `Invalid maxsize format: "${options.maxsize}". Expected: digits followed by optional c/w/b/k/M/G (e.g. "10M", "500k")`
1309
1318
  );
1310
1319
  }
1311
1320
  const args = ["--", shellQuote(options.path)];
@@ -1386,6 +1395,9 @@ function enforcePolicy(command) {
1386
1395
 
1387
1396
  // src/tools.ts
1388
1397
  var HostSchema = z.string().describe("SSH hostname or IP address");
1398
+ var AbsoluteRemotePathSchema = z.string().refine((p) => p.startsWith("/"), {
1399
+ message: "Path must be absolute (start with /). SFTP does not expand ~ or resolve relative paths through a shell."
1400
+ });
1389
1401
  var PortSchema = z.number().int().min(1).max(65535).optional().describe("SSH port (default: 22)");
1390
1402
  var UsernameSchema = z.string().optional().describe("SSH username (default: current user)");
1391
1403
  var KeyPathSchema = z.string().optional().describe("Path to SSH private key");
@@ -1437,7 +1449,9 @@ ${result.stderr}`);
1437
1449
  "Read a file from a remote host via SFTP.",
1438
1450
  {
1439
1451
  ...connectionParams,
1440
- path: z.string().describe("Absolute path to the remote file")
1452
+ path: z.string().refine((p) => p.startsWith("/"), {
1453
+ message: "Path must be absolute (start with /). Relative paths resolve against the remote user's CWD, which is rarely what you want."
1454
+ }).describe("Absolute path to the remote file. Must start with /.")
1441
1455
  },
1442
1456
  async ({ path, ...conn }) => {
1443
1457
  return connectionPool.withConnection(conn, async (client) => {
@@ -1451,7 +1465,7 @@ ${result.stderr}`);
1451
1465
  "Write content to a file on a remote host via SFTP. Creates or overwrites the file.",
1452
1466
  {
1453
1467
  ...connectionParams,
1454
- path: z.string().describe("Absolute path to the remote file"),
1468
+ path: AbsoluteRemotePathSchema.describe("Absolute path to the remote file. Must start with /."),
1455
1469
  content: z.string().describe("File content to write")
1456
1470
  },
1457
1471
  async ({ path, content, ...conn }) => {
@@ -1467,7 +1481,7 @@ ${result.stderr}`);
1467
1481
  {
1468
1482
  ...connectionParams,
1469
1483
  localPath: z.string().describe("Path to the local file to upload"),
1470
- remotePath: z.string().describe("Absolute path on the remote host")
1484
+ remotePath: AbsoluteRemotePathSchema.describe("Absolute path on the remote host. Must start with /.")
1471
1485
  },
1472
1486
  async ({ localPath, remotePath, ...conn }) => {
1473
1487
  return connectionPool.withConnection(conn, async (client) => {
@@ -1481,7 +1495,7 @@ ${result.stderr}`);
1481
1495
  "Download a file from a remote host to local filesystem via SFTP.",
1482
1496
  {
1483
1497
  ...connectionParams,
1484
- remotePath: z.string().describe("Absolute path to the remote file"),
1498
+ remotePath: AbsoluteRemotePathSchema.describe("Absolute path to the remote file. Must start with /."),
1485
1499
  localPath: z.string().describe("Local path to save the downloaded file")
1486
1500
  },
1487
1501
  async ({ remotePath, localPath, ...conn }) => {
@@ -1496,7 +1510,7 @@ ${result.stderr}`);
1496
1510
  "List files in a directory on a remote host via SFTP.",
1497
1511
  {
1498
1512
  ...connectionParams,
1499
- path: z.string().describe("Absolute path to the remote directory")
1513
+ path: AbsoluteRemotePathSchema.describe("Absolute path to the remote directory. Must start with /.")
1500
1514
  },
1501
1515
  async ({ path, ...conn }) => {
1502
1516
  return connectionPool.withConnection(conn, async (client) => {
@@ -1510,7 +1524,7 @@ ${result.stderr}`);
1510
1524
  "Get metadata for a file or directory on a remote host via SFTP. Returns size, permissions (octal), uid/gid, mtime/atime, and type flags (isFile, isDirectory, isSymbolicLink). Use this instead of parsing `ls -la` output.",
1511
1525
  {
1512
1526
  ...connectionParams,
1513
- path: z.string().describe("Absolute path to the remote file or directory")
1527
+ path: AbsoluteRemotePathSchema.describe("Absolute path to the remote file or directory. Must start with /.")
1514
1528
  },
1515
1529
  async ({ path, ...conn }) => {
1516
1530
  return connectionPool.withConnection(conn, async (client) => {
@@ -1547,7 +1561,9 @@ ${result.stderr}`);
1547
1561
  "Delete a file or empty directory on a remote host via SFTP. Auto-detects the path type and calls the right SFTP op (unlink for files/symlinks, rmdir for empty dirs). Recursive directory delete is intentionally NOT supported -- for that, use ssh_exec with `rm -rf` explicitly so the destructive intent is visible in the tool trace.",
1548
1562
  {
1549
1563
  ...connectionParams,
1550
- path: z.string().describe("Absolute path of the file or empty directory to delete")
1564
+ path: AbsoluteRemotePathSchema.describe(
1565
+ "Absolute path of the file or empty directory to delete. Must start with /."
1566
+ )
1551
1567
  },
1552
1568
  async ({ path, ...conn }) => {
1553
1569
  return connectionPool.withConnection(conn, async (client) => {
@@ -1822,8 +1838,7 @@ ${files.join("\n")}` }] };
1822
1838
  }
1823
1839
 
1824
1840
  // src/server.ts
1825
- var pkgPath = join4(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
1826
- var { version } = JSON.parse(readFileSync4(pkgPath, "utf8"));
1841
+ var version = typeof __VERSION__ !== "undefined" ? __VERSION__ : JSON.parse(readFileSync4(join4(dirname(fileURLToPath(import.meta.url)), "..", "package.json"), "utf8")).version;
1827
1842
  function createServer(pool) {
1828
1843
  const server = new McpServer({
1829
1844
  name: "ssh-mcp",
@@ -1834,6 +1849,10 @@ function createServer(pool) {
1834
1849
  }
1835
1850
 
1836
1851
  // src/index.ts
1852
+ if (process.argv[2] === "--version" || process.argv[2] === "version") {
1853
+ console.log(version);
1854
+ process.exit(0);
1855
+ }
1837
1856
  async function main() {
1838
1857
  const pool = new ConnectionPool();
1839
1858
  const server = createServer(pool);
package/dist/server.d.ts CHANGED
@@ -217,6 +217,7 @@ declare function isPolicyConfigured(): boolean;
217
217
 
218
218
  declare function registerTools(server: McpServer, pool?: ConnectionPool): void;
219
219
 
220
+ declare const version: string;
220
221
  declare function createServer(pool?: ConnectionPool): McpServer;
221
222
 
222
- export { type AgentResult, type ConfigLookupResult, ConnectionPool, type DiagnosticReport, type DiagnosticResult, type ExecResult, type FileStats, type FindOptions, type KeyInfo, type MultiExecHost, type MultiExecResult, type PoolOptions, type ResolvedConfig, type SSHConfig, type ServiceStatus, checkConnectivity, checkGitSsh, checkKnownHosts, checkSshAgent, checkSshConfig, checkSshKeys, configLookup, connect, connectRaw, connectWithProxy, createServer, deleteFile, diagnose, downloadFile, enforcePolicy, ensureAgent, exec, find, fixKnownHosts, formatDiagnostics, isPolicyConfigured, listDir, listSshKeys, loadKey, makeDir, multiExec, readFile, readKnownHostsKeys, registerTools, resolveConfig, serviceStatus, statFile, tail, testConnection, uploadFile, writeFile };
223
+ export { type AgentResult, type ConfigLookupResult, ConnectionPool, type DiagnosticReport, type DiagnosticResult, type ExecResult, type FileStats, type FindOptions, type KeyInfo, type MultiExecHost, type MultiExecResult, type PoolOptions, type ResolvedConfig, type SSHConfig, type ServiceStatus, checkConnectivity, checkGitSsh, checkKnownHosts, checkSshAgent, checkSshConfig, checkSshKeys, configLookup, connect, connectRaw, connectWithProxy, createServer, deleteFile, diagnose, downloadFile, enforcePolicy, ensureAgent, exec, find, fixKnownHosts, formatDiagnostics, isPolicyConfigured, listDir, listSshKeys, loadKey, makeDir, multiExec, readFile, readKnownHostsKeys, registerTools, resolveConfig, serviceStatus, statFile, tail, testConnection, uploadFile, version, writeFile };
package/dist/server.js CHANGED
@@ -212,8 +212,6 @@ function checkSshConfig(host) {
212
212
  if (inHostBlock) hostConfig.push(trimmed);
213
213
  } else if (inHostBlock && trimmed) {
214
214
  hostConfig.push(trimmed);
215
- } else if (inHostBlock && !trimmed) {
216
- inHostBlock = false;
217
215
  }
218
216
  }
219
217
  if (hostConfig.length === 0) {
@@ -608,7 +606,15 @@ import { readFileSync as readFileSync3 } from "fs";
608
606
  import { homedir as homedir3 } from "os";
609
607
  import { join as join3 } from "path";
610
608
  import { Client } from "ssh2";
609
+ var sshConfigCache = /* @__PURE__ */ new Map();
611
610
  function resolveFromSshConfig(host) {
611
+ const cached = sshConfigCache.get(host);
612
+ if (cached !== void 0) return cached;
613
+ const result = resolveFromSshConfigUncached(host);
614
+ sshConfigCache.set(host, result);
615
+ return result;
616
+ }
617
+ function resolveFromSshConfigUncached(host) {
612
618
  try {
613
619
  const { stdout, ok } = runArgs("ssh", ["-G", host]);
614
620
  if (!ok) return null;
@@ -688,8 +694,10 @@ function resolveConfig(config) {
688
694
  keepaliveCountMax: 3,
689
695
  hostVerifier: buildHostVerifier(verifierHosts, port)
690
696
  };
697
+ const home = homedir3();
691
698
  if (config.privateKeyPath) {
692
- connectConfig.privateKey = readFileSync3(config.privateKeyPath);
699
+ const keyPath = config.privateKeyPath.startsWith("~") ? join3(home, config.privateKeyPath.slice(1)) : config.privateKeyPath;
700
+ connectConfig.privateKey = readFileSync3(keyPath);
693
701
  } else if (config.password) {
694
702
  connectConfig.password = config.password;
695
703
  } else {
@@ -697,7 +705,6 @@ function resolveConfig(config) {
697
705
  if (agentSock) {
698
706
  connectConfig.agent = agentSock;
699
707
  }
700
- const home = homedir3();
701
708
  const keyPaths = sshConfig && sshConfig.identityFiles.length > 0 ? sshConfig.identityFiles.map((p) => p.startsWith("~") ? join3(home, p.slice(1)) : p) : [join3(home, ".ssh", "id_ed25519"), join3(home, ".ssh", "id_rsa"), join3(home, ".ssh", "id_ecdsa")];
702
709
  for (const keyPath of keyPaths) {
703
710
  try {
@@ -952,10 +959,11 @@ async function writeFile(client, remotePath, content) {
952
959
  }
953
960
  }
954
961
  async function uploadFile(client, localPath, remotePath) {
962
+ const resolvedLocal = localPath.startsWith("~") ? join3(homedir3(), localPath.slice(1)) : localPath;
955
963
  const sftp = await getSftp(client);
956
964
  try {
957
965
  await new Promise((resolve, reject) => {
958
- sftp.fastPut(localPath, remotePath, (err) => {
966
+ sftp.fastPut(resolvedLocal, remotePath, (err) => {
959
967
  if (err) return reject(err);
960
968
  resolve();
961
969
  });
@@ -965,10 +973,11 @@ async function uploadFile(client, localPath, remotePath) {
965
973
  }
966
974
  }
967
975
  async function downloadFile(client, remotePath, localPath) {
976
+ const resolvedLocal = localPath.startsWith("~") ? join3(homedir3(), localPath.slice(1)) : localPath;
968
977
  const sftp = await getSftp(client);
969
978
  try {
970
979
  await new Promise((resolve, reject) => {
971
- sftp.fastGet(remotePath, localPath, (err) => {
980
+ sftp.fastGet(remotePath, resolvedLocal, (err) => {
972
981
  if (err) return reject(err);
973
982
  resolve();
974
983
  });
@@ -1088,16 +1097,16 @@ async function multiExec(pool, hosts, command, timeoutMs = 3e4) {
1088
1097
  };
1089
1098
  });
1090
1099
  }
1091
- var VALID_FIND_SIZE = /^\d+[kMGTP]?$/;
1100
+ var VALID_FIND_SIZE = /^\d+[cwbkMG]?$/;
1092
1101
  async function find(client, options, timeoutMs = 3e4) {
1093
1102
  if (options.minsize && !VALID_FIND_SIZE.test(options.minsize)) {
1094
1103
  throw new Error(
1095
- `Invalid minsize format: "${options.minsize}". Expected: digits followed by optional k/M/G/T/P (e.g. "1M", "100k")`
1104
+ `Invalid minsize format: "${options.minsize}". Expected: digits followed by optional c/w/b/k/M/G (e.g. "1M", "100k")`
1096
1105
  );
1097
1106
  }
1098
1107
  if (options.maxsize && !VALID_FIND_SIZE.test(options.maxsize)) {
1099
1108
  throw new Error(
1100
- `Invalid maxsize format: "${options.maxsize}". Expected: digits followed by optional k/M/G/T/P (e.g. "10M", "500k")`
1109
+ `Invalid maxsize format: "${options.maxsize}". Expected: digits followed by optional c/w/b/k/M/G (e.g. "10M", "500k")`
1101
1110
  );
1102
1111
  }
1103
1112
  const args = ["--", shellQuote(options.path)];
@@ -1392,6 +1401,9 @@ ${diag}`);
1392
1401
 
1393
1402
  // src/tools.ts
1394
1403
  var HostSchema = z.string().describe("SSH hostname or IP address");
1404
+ var AbsoluteRemotePathSchema = z.string().refine((p) => p.startsWith("/"), {
1405
+ message: "Path must be absolute (start with /). SFTP does not expand ~ or resolve relative paths through a shell."
1406
+ });
1395
1407
  var PortSchema = z.number().int().min(1).max(65535).optional().describe("SSH port (default: 22)");
1396
1408
  var UsernameSchema = z.string().optional().describe("SSH username (default: current user)");
1397
1409
  var KeyPathSchema = z.string().optional().describe("Path to SSH private key");
@@ -1443,7 +1455,9 @@ ${result.stderr}`);
1443
1455
  "Read a file from a remote host via SFTP.",
1444
1456
  {
1445
1457
  ...connectionParams,
1446
- path: z.string().describe("Absolute path to the remote file")
1458
+ path: z.string().refine((p) => p.startsWith("/"), {
1459
+ message: "Path must be absolute (start with /). Relative paths resolve against the remote user's CWD, which is rarely what you want."
1460
+ }).describe("Absolute path to the remote file. Must start with /.")
1447
1461
  },
1448
1462
  async ({ path, ...conn }) => {
1449
1463
  return connectionPool.withConnection(conn, async (client) => {
@@ -1457,7 +1471,7 @@ ${result.stderr}`);
1457
1471
  "Write content to a file on a remote host via SFTP. Creates or overwrites the file.",
1458
1472
  {
1459
1473
  ...connectionParams,
1460
- path: z.string().describe("Absolute path to the remote file"),
1474
+ path: AbsoluteRemotePathSchema.describe("Absolute path to the remote file. Must start with /."),
1461
1475
  content: z.string().describe("File content to write")
1462
1476
  },
1463
1477
  async ({ path, content, ...conn }) => {
@@ -1473,7 +1487,7 @@ ${result.stderr}`);
1473
1487
  {
1474
1488
  ...connectionParams,
1475
1489
  localPath: z.string().describe("Path to the local file to upload"),
1476
- remotePath: z.string().describe("Absolute path on the remote host")
1490
+ remotePath: AbsoluteRemotePathSchema.describe("Absolute path on the remote host. Must start with /.")
1477
1491
  },
1478
1492
  async ({ localPath, remotePath, ...conn }) => {
1479
1493
  return connectionPool.withConnection(conn, async (client) => {
@@ -1487,7 +1501,7 @@ ${result.stderr}`);
1487
1501
  "Download a file from a remote host to local filesystem via SFTP.",
1488
1502
  {
1489
1503
  ...connectionParams,
1490
- remotePath: z.string().describe("Absolute path to the remote file"),
1504
+ remotePath: AbsoluteRemotePathSchema.describe("Absolute path to the remote file. Must start with /."),
1491
1505
  localPath: z.string().describe("Local path to save the downloaded file")
1492
1506
  },
1493
1507
  async ({ remotePath, localPath, ...conn }) => {
@@ -1502,7 +1516,7 @@ ${result.stderr}`);
1502
1516
  "List files in a directory on a remote host via SFTP.",
1503
1517
  {
1504
1518
  ...connectionParams,
1505
- path: z.string().describe("Absolute path to the remote directory")
1519
+ path: AbsoluteRemotePathSchema.describe("Absolute path to the remote directory. Must start with /.")
1506
1520
  },
1507
1521
  async ({ path, ...conn }) => {
1508
1522
  return connectionPool.withConnection(conn, async (client) => {
@@ -1516,7 +1530,7 @@ ${result.stderr}`);
1516
1530
  "Get metadata for a file or directory on a remote host via SFTP. Returns size, permissions (octal), uid/gid, mtime/atime, and type flags (isFile, isDirectory, isSymbolicLink). Use this instead of parsing `ls -la` output.",
1517
1531
  {
1518
1532
  ...connectionParams,
1519
- path: z.string().describe("Absolute path to the remote file or directory")
1533
+ path: AbsoluteRemotePathSchema.describe("Absolute path to the remote file or directory. Must start with /.")
1520
1534
  },
1521
1535
  async ({ path, ...conn }) => {
1522
1536
  return connectionPool.withConnection(conn, async (client) => {
@@ -1553,7 +1567,9 @@ ${result.stderr}`);
1553
1567
  "Delete a file or empty directory on a remote host via SFTP. Auto-detects the path type and calls the right SFTP op (unlink for files/symlinks, rmdir for empty dirs). Recursive directory delete is intentionally NOT supported -- for that, use ssh_exec with `rm -rf` explicitly so the destructive intent is visible in the tool trace.",
1554
1568
  {
1555
1569
  ...connectionParams,
1556
- path: z.string().describe("Absolute path of the file or empty directory to delete")
1570
+ path: AbsoluteRemotePathSchema.describe(
1571
+ "Absolute path of the file or empty directory to delete. Must start with /."
1572
+ )
1557
1573
  },
1558
1574
  async ({ path, ...conn }) => {
1559
1575
  return connectionPool.withConnection(conn, async (client) => {
@@ -1828,8 +1844,7 @@ ${files.join("\n")}` }] };
1828
1844
  }
1829
1845
 
1830
1846
  // src/server.ts
1831
- var pkgPath = join4(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
1832
- var { version } = JSON.parse(readFileSync4(pkgPath, "utf8"));
1847
+ var version = typeof __VERSION__ !== "undefined" ? __VERSION__ : JSON.parse(readFileSync4(join4(dirname(fileURLToPath(import.meta.url)), "..", "package.json"), "utf8")).version;
1833
1848
  function createServer(pool) {
1834
1849
  const server = new McpServer({
1835
1850
  name: "ssh-mcp",
@@ -1875,5 +1890,6 @@ export {
1875
1890
  tail,
1876
1891
  testConnection,
1877
1892
  uploadFile,
1893
+ version,
1878
1894
  writeFile
1879
1895
  };
package/package.json CHANGED
@@ -1,62 +1,67 @@
1
- {
2
- "name": "@yawlabs/ssh-mcp",
3
- "version": "0.11.4",
4
- "mcpName": "io.github.YawLabs/ssh-mcp",
5
- "description": "MCP server for SSH operations with built-in diagnostics",
6
- "type": "module",
7
- "bin": {
8
- "ssh-mcp": "dist/index.js"
9
- },
10
- "exports": {
11
- ".": {
12
- "import": "./dist/server.js",
13
- "types": "./dist/server.d.ts"
14
- }
15
- },
16
- "files": [
17
- "dist",
18
- "LICENSE",
19
- "README.md"
20
- ],
21
- "scripts": {
22
- "build": "tsup",
23
- "dev": "tsup --watch",
24
- "lint": "biome check src/",
25
- "lint:fix": "biome check --write src/",
26
- "typecheck": "tsc --noEmit",
27
- "test": "vitest run",
28
- "test:integration": "docker compose -f test/docker/docker-compose.yml up -d --build --wait && SSH_MCP_INTEGRATION=1 vitest run src/tests/integration.test.ts; docker compose -f test/docker/docker-compose.yml down",
29
- "test:ci": "npm run build && npm test",
30
- "prepublishOnly": "npm run build"
31
- },
32
- "keywords": [
33
- "mcp",
34
- "ssh",
35
- "remote",
36
- "model-context-protocol",
37
- "ai",
38
- "devops"
39
- ],
40
- "author": "Yaw Labs <contact@yaw.sh>",
41
- "license": "MIT",
42
- "repository": {
43
- "type": "git",
44
- "url": "git+https://github.com/YawLabs/ssh-mcp.git"
45
- },
46
- "engines": {
47
- "node": ">=18"
48
- },
49
- "dependencies": {
50
- "@modelcontextprotocol/sdk": "^1.29.0",
51
- "ssh2": "^1.17.0",
52
- "zod": "^4.4.3"
53
- },
54
- "devDependencies": {
55
- "@biomejs/biome": "^2.4.15",
56
- "@types/node": "^25.7.0",
57
- "@types/ssh2": "^1.15.5",
58
- "tsup": "^8.5.1",
59
- "typescript": "^6.0.3",
60
- "vitest": "^4.1.6"
61
- }
62
- }
1
+ {
2
+ "name": "@yawlabs/ssh-mcp",
3
+ "version": "0.12.0",
4
+ "mcpName": "io.github.YawLabs/ssh-mcp",
5
+ "description": "MCP server for SSH operations with built-in diagnostics",
6
+ "type": "module",
7
+ "bin": {
8
+ "ssh-mcp": "dist/index.js"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/server.js",
13
+ "types": "./dist/server.d.ts"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "LICENSE",
19
+ "README.md"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsup",
23
+ "dev": "tsup --watch",
24
+ "lint": "biome check src/",
25
+ "lint:fix": "biome check --write src/",
26
+ "typecheck": "tsc --noEmit",
27
+ "test": "vitest run",
28
+ "test:integration": "docker compose -f test/docker/docker-compose.yml up -d --build --wait && SSH_MCP_INTEGRATION=1 vitest run src/tests/integration.test.ts; docker compose -f test/docker/docker-compose.yml down",
29
+ "test:ci": "npm run build && npm test",
30
+ "prepublishOnly": "npm run build"
31
+ },
32
+ "keywords": [
33
+ "mcp",
34
+ "ssh",
35
+ "remote",
36
+ "model-context-protocol",
37
+ "ai",
38
+ "devops"
39
+ ],
40
+ "author": "Yaw Labs <contact@yaw.sh>",
41
+ "license": "MIT",
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "git+https://github.com/YawLabs/ssh-mcp.git"
45
+ },
46
+ "engines": {
47
+ "node": ">=18"
48
+ },
49
+ "dependencies": {
50
+ "@modelcontextprotocol/sdk": "^1.29.0",
51
+ "ssh2": "^1.17.0",
52
+ "zod": "^4.4.3"
53
+ },
54
+ "devDependencies": {
55
+ "@biomejs/biome": "^2.4.15",
56
+ "@types/node": "^26.1.1",
57
+ "@types/ssh2": "^1.15.5",
58
+ "esbuild": "^0.28.1",
59
+ "postject": "^1.0.0-alpha.6",
60
+ "tsup": "^8.5.1",
61
+ "typescript": "^7.0.2",
62
+ "vitest": "^4.1.6"
63
+ },
64
+ "overrides": {
65
+ "esbuild": "^0.28.1"
66
+ }
67
+ }