@yawlabs/ssh-mcp 0.16.0 → 0.17.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/README.md CHANGED
@@ -116,7 +116,14 @@ When any remote operation fails, ssh-mcp automatically runs diagnostics and incl
116
116
 
117
117
  Remote operations reuse SSH connections automatically. When your agent makes multiple calls to the same host, the first call opens a connection and subsequent calls reuse it. Connections are kept alive for 60 seconds after the last use, then closed automatically.
118
118
 
119
- The pool caps at 100 active connections by default. Set `SSH_MCP_MAX_POOL_SIZE=<n>` to raise it for fan-out workloads against many distinct hosts (e.g. `ssh_multi_exec` across a large fleet). When the cap is reached, the pool evicts an idle entry to make room; if every entry is in use it rejects with `Connection pool is full`.
119
+ The pool caps at 100 connections by default, and a connection still being dialed counts against the cap. When the cap is reached the pool closes an idle connection to make room. If every slot is in use or dialing, a tool call waits for one instead of failing:
120
+
121
+ - **A single-host remote tool** (`ssh_exec`, the SFTP tools, `ssh_find`, `ssh_tail`, `ssh_service_status`) waits up to its `timeout` (30s for the SFTP tools, which have no `timeout` parameter) and is next in line for the first slot that frees, so an `ssh_exec` arriving in the middle of a wide `ssh_multi_exec` runs between two of its hosts rather than being refused for the whole run. It reports `Connection pool is full` only if it never won a slot in that time.
122
+ - **`ssh_multi_exec`** runs at most `SSH_MCP_MAX_POOL_SIZE` hosts at once (default 100) and works through a longer list as slots free up, so raising the cap raises its parallelism; `timeout` is per host, not a bound on the whole call. When the pool is full it waits, and gives up only when none of its own hosts holds a slot and a full `timeout` has passed with none of them starting or finishing. The hosts waiting at that point (up to one per parallel slot) and every host still queued then report `Connection pool is full`; the queued ones are never attempted.
123
+
124
+ A call reaches `Connection pool is full` only when other calls — a long `ssh_exec`, a concurrent `ssh_multi_exec` — kept every slot from it for a full `timeout`; a fan-out wider than the cap that has the pool to itself never does, however long it runs. If you see it, rerun once those calls finish (for `ssh_multi_exec`, the hosts that reported it), or raise the cap with `SSH_MCP_MAX_POOL_SIZE=<n>` in the server's environment. The error names the variable.
125
+
126
+ The library's `acquire()` / `withConnection()` do not wait by default: they reject at once with a `PoolFullError` (`code: "ERR_SSH_MCP_POOL_FULL"`, `isPoolFullError()`) unless you pass `{ waitForCapacityMs }` — see [Programmatic usage](#programmatic-usage).
120
127
 
121
128
  ### SSH config support
122
129
 
@@ -259,7 +266,7 @@ Agent reports: "SSH server isn't running on new-server or port 22 is blocked"
259
266
  ## Programmatic usage
260
267
 
261
268
  ```typescript
262
- import { connect, exec, diagnose, ensureAgent, listSshKeys, checkGitSsh, ConnectionPool } from '@yawlabs/ssh-mcp';
269
+ import { connect, exec, diagnose, ensureAgent, listSshKeys, checkGitSsh, ConnectionPool, isPoolFullError } from '@yawlabs/ssh-mcp';
263
270
 
264
271
  // Fix SSH environment
265
272
  const agent = ensureAgent();
@@ -292,6 +299,21 @@ await pool.withConnection({ host: 'my-server' }, async (client) => {
292
299
  const r2 = await exec(client, 'df -h');
293
300
  console.log(r2.stdout);
294
301
  });
302
+ // A full pool rejects at once with a PoolFullError; pass a budget to wait for a slot
303
+ // instead, the way the tools do, and match the rejection on its code, not its text
304
+ try {
305
+ await pool.withConnection(
306
+ { host: 'other-server' },
307
+ async (client) => {
308
+ const r3 = await exec(client, 'uptime');
309
+ console.log(r3.stdout);
310
+ },
311
+ { waitForCapacityMs: 30_000 },
312
+ );
313
+ } catch (err) {
314
+ if (!isPoolFullError(err)) throw err;
315
+ console.log('no slot within 30s:', err.message);
316
+ }
295
317
  pool.drain(); // close all connections when done
296
318
 
297
319
  // Diagnose issues