@akms/mcp-ssh 0.1.8 → 0.1.9
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/CHANGELOG.md +8 -0
- package/README.md +8 -0
- package/lib/cli.cjs +13 -11
- package/lib/cli.cjs.map +1 -1
- package/lib/cli.js +13 -10
- package/lib/cli.js.map +1 -1
- package/lib/index.cjs +13 -11
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +2 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.js +13 -10
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@akms/mcp-ssh`. Dates are `YYYY-MM-DD`.
|
|
4
4
|
|
|
5
|
+
## 0.1.9 — 2026-10-19
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Load SSH and its crypto runtime on the first connection instead of during idle MCP startup or local configuration work. Direct, proxy and jump-host routes share the loaded client constructor.
|
|
10
|
+
- Preserve shared connection attempts and reject a connection closed while the module was loading, before opening a proxy or network transport.
|
|
11
|
+
- Document idle-memory behavior and direct Node launch as an alternative to a persistent npm wrapper.
|
|
12
|
+
|
|
5
13
|
## 0.1.8 — 2026-10-19
|
|
6
14
|
|
|
7
15
|
### Added
|
package/README.md
CHANGED
|
@@ -276,6 +276,14 @@ Rules are enforced inside `SshConnection.exec` and the SFTP operations — the l
|
|
|
276
276
|
|
|
277
277
|
The file tools run with this server's own privileges on your machine, so `localPath` is guarded in both directions — but narrowly, covering **credentials** (`~/.ssh`, `authorized_keys`, private keys, `.pem`/`.key`, `~/.aws`, `~/.gnupg`, `~/.kube`, `~/.docker`, `.npmrc`, `.netrc`) and **files that define the guards** (`.claude*` / `.mcp.json` / `claude_desktop_config.json` — where this server's `SSH_*` variables live — plus autostart locations). `.env` uploads, `.bashrc` backups and cron fetches are normal deployment work and are allowed. Symlinks are resolved before matching, so a link into `~/.ssh` does not slip past.
|
|
278
278
|
|
|
279
|
+
## Memory and process lifetime
|
|
280
|
+
|
|
281
|
+
SSH and its crypto runtime load on the first connection instead of during idle MCP startup or local configuration work. The same loaded client supports direct, proxy and jump-host routes. Concurrent callers share the connection attempt, and disconnecting during module loading prevents a later transport from opening. The runtime stays loaded after first use; disconnecting a session closes its transport rather than unloading the module.
|
|
282
|
+
|
|
283
|
+
Each configured host in each MCP client runs its own process. An `npx` registration also keeps an npm wrapper running. For a stable local/global installation, an entry can instead use the absolute Node executable as `command` and the installed package's absolute `lib/cli.js` path as its sole argument, retaining its existing `env`. Avoid paths inside npm's temporary execution cache. The configuration form continues to generate portable `npx` registrations and may restore that launch style when saving an edit.
|
|
284
|
+
|
|
285
|
+
Restart MCP connections after updating to apply the new code. Compare process counts and Windows Private Bytes separately from resident memory (RSS), including npm wrappers in the total.
|
|
286
|
+
|
|
279
287
|
## 📄 Logging
|
|
280
288
|
|
|
281
289
|
Everything goes to **stderr** (stdout is the MCP protocol channel). `SSH_MCP_LOG_LEVEL` picks the level — `silent` / `debug` / `info` / `warn` / `error`, default `info`. Guard rejections are logged at `warn` with the full command, and that log is the audit trail for what the agent tried; full command text is otherwise only logged at `debug`, while `password`, `passphrase`, `privateKey` and `token` meta keys are always masked.
|
package/lib/cli.cjs
CHANGED
|
@@ -9,7 +9,6 @@ var child_process = require('child_process');
|
|
|
9
9
|
var crypto = require('crypto');
|
|
10
10
|
var stream = require('stream');
|
|
11
11
|
var string_decoder = require('string_decoder');
|
|
12
|
-
var ssh2 = require('ssh2');
|
|
13
12
|
var timers = require('timers');
|
|
14
13
|
var util = require('util');
|
|
15
14
|
var smolToml = require('smol-toml');
|
|
@@ -21,7 +20,6 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
|
21
20
|
var fs7__default = /*#__PURE__*/_interopDefault(fs7);
|
|
22
21
|
var os5__default = /*#__PURE__*/_interopDefault(os5);
|
|
23
22
|
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
24
|
-
var ssh2__default = /*#__PURE__*/_interopDefault(ssh2);
|
|
25
23
|
|
|
26
24
|
// src/_defs/defaults.ts
|
|
27
25
|
var LOG_LEVEL_ENV = "SSH_MCP_LOG_LEVEL";
|
|
@@ -38,7 +36,7 @@ var CWD_MARKER = "__AKMS_SSH_CWD__";
|
|
|
38
36
|
var MAX_DIRECTORY_ENTRIES = 1e3;
|
|
39
37
|
var SERVER_NAME = "akms-mcp-ssh";
|
|
40
38
|
var PACKAGE_NAME = "@akms/mcp-ssh";
|
|
41
|
-
var SERVER_VERSION = "0.1.
|
|
39
|
+
var SERVER_VERSION = "0.1.9";
|
|
42
40
|
|
|
43
41
|
// src/_defs/command-rules.ts
|
|
44
42
|
var CRITICAL_PATH_PATTERN = new RegExp(
|
|
@@ -1362,7 +1360,6 @@ var GuardRejectionError = class extends Error {
|
|
|
1362
1360
|
};
|
|
1363
1361
|
|
|
1364
1362
|
// src/modules/ssh/ssh-connection.ts
|
|
1365
|
-
var { Client } = ssh2__default.default;
|
|
1366
1363
|
var OUTPUT_HARD_CAP_MULTIPLIER = 4;
|
|
1367
1364
|
var PRIVATE_KEY_CACHE = /* @__PURE__ */ new Map();
|
|
1368
1365
|
var WINDOWS_AGENT_PIPE = "\\\\.\\pipe\\openssh-ssh-agent";
|
|
@@ -1504,7 +1501,7 @@ function openProxyCommand(profile, hop) {
|
|
|
1504
1501
|
}
|
|
1505
1502
|
};
|
|
1506
1503
|
}
|
|
1507
|
-
function openJumpTransport(profile, jump, proxy, onHostKeySeen) {
|
|
1504
|
+
function openJumpTransport(profile, jump, proxy, onHostKeySeen, Client) {
|
|
1508
1505
|
const label = `${profile.name} jump host`;
|
|
1509
1506
|
const endpoint = `${jump.username}@${jump.host}:${jump.port}`;
|
|
1510
1507
|
const _logger = $logger.child({ context: "openJumpTransport", profile: profile.name, jump_host: jump.host });
|
|
@@ -1569,7 +1566,7 @@ function openJumpTransport(profile, jump, proxy, onHostKeySeen) {
|
|
|
1569
1566
|
jumpClient.connect(jumpConfig);
|
|
1570
1567
|
});
|
|
1571
1568
|
}
|
|
1572
|
-
async function openTransport(profile, onJumpHostKeySeen) {
|
|
1569
|
+
async function openTransport(profile, onJumpHostKeySeen, Client) {
|
|
1573
1570
|
let proxy = null;
|
|
1574
1571
|
if (profile.proxyCommand != null) {
|
|
1575
1572
|
proxy = openProxyCommand(profile, profile.jump ?? profile);
|
|
@@ -1578,7 +1575,7 @@ async function openTransport(profile, onJumpHostKeySeen) {
|
|
|
1578
1575
|
return proxy;
|
|
1579
1576
|
}
|
|
1580
1577
|
try {
|
|
1581
|
-
const jumpTransport = await openJumpTransport(profile, profile.jump, proxy, onJumpHostKeySeen);
|
|
1578
|
+
const jumpTransport = await openJumpTransport(profile, profile.jump, proxy, onJumpHostKeySeen, Client);
|
|
1582
1579
|
return jumpTransport;
|
|
1583
1580
|
} catch (ex) {
|
|
1584
1581
|
proxy?.close();
|
|
@@ -1642,6 +1639,11 @@ var SshConnection = class {
|
|
|
1642
1639
|
get wasDropped() {
|
|
1643
1640
|
return this.droppedAfterReady;
|
|
1644
1641
|
}
|
|
1642
|
+
assertNotDisposed() {
|
|
1643
|
+
if (this.disposed == true) {
|
|
1644
|
+
throw new Error(`ssh connection for '${this.profile.name}' has been closed; open a new session`);
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1645
1647
|
/**
|
|
1646
1648
|
* Opens the connection, or joins the in-flight attempt when one is already running.
|
|
1647
1649
|
*
|
|
@@ -1652,9 +1654,7 @@ var SshConnection = class {
|
|
|
1652
1654
|
* unreachable, the handshake times out, or the carrier cannot be opened.
|
|
1653
1655
|
*/
|
|
1654
1656
|
async connect(onProgress) {
|
|
1655
|
-
|
|
1656
|
-
throw new Error(`ssh connection for '${this.profile.name}' has been closed; open a new session`);
|
|
1657
|
-
}
|
|
1657
|
+
this.assertNotDisposed();
|
|
1658
1658
|
if (this.client != null) {
|
|
1659
1659
|
return;
|
|
1660
1660
|
}
|
|
@@ -1673,12 +1673,14 @@ var SshConnection = class {
|
|
|
1673
1673
|
this.observedFingerprint = fingerprint;
|
|
1674
1674
|
});
|
|
1675
1675
|
this.connecting = (async () => {
|
|
1676
|
+
const { default: { Client } } = await import('ssh2');
|
|
1677
|
+
this.assertNotDisposed();
|
|
1676
1678
|
if (this.profile.jump != null || this.profile.proxyCommand != null) {
|
|
1677
1679
|
onProgress?.("transport");
|
|
1678
1680
|
}
|
|
1679
1681
|
const transport = await openTransport(this.profile, (fingerprint) => {
|
|
1680
1682
|
this.observedJumpFingerprint = fingerprint;
|
|
1681
|
-
});
|
|
1683
|
+
}, Client);
|
|
1682
1684
|
if (this.disposed == true) {
|
|
1683
1685
|
transport?.close();
|
|
1684
1686
|
throw new Error(`ssh connection for '${this.profile.name}' was closed during the handshake`);
|