@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 +24 -2
- package/dist/index.js +380 -136
- package/dist/ops.d.ts +1 -1
- package/dist/pool.d.ts +75 -6
- package/dist/server.d.ts +2 -2
- package/dist/server.js +492 -245
- package/package.json +8 -3
package/dist/ops.d.ts
CHANGED
package/dist/pool.d.ts
CHANGED
|
@@ -4,14 +4,49 @@ export interface PoolOptions {
|
|
|
4
4
|
/** Milliseconds before an idle connection is closed. Default: 60000 (60s) */
|
|
5
5
|
idleTtlMs?: number;
|
|
6
6
|
/**
|
|
7
|
-
* Maximum number of connections in the pool
|
|
7
|
+
* Maximum number of connections in the pool, counting dials still in flight.
|
|
8
|
+
* Default: 100, overridable via the
|
|
8
9
|
* `SSH_MCP_MAX_POOL_SIZE` env var. When at capacity, the pool first tries to evict
|
|
9
|
-
* an idle entry; if
|
|
10
|
-
* "Connection pool is full"
|
|
11
|
-
*
|
|
10
|
+
* an idle entry; if no idle entry can be evicted (every slot is in use or dialing),
|
|
11
|
+
* `acquire()` rejects with a {@link PoolFullError} ("Connection pool is full") -- at once
|
|
12
|
+
* by default, or once `waitForCapacityMs` (see {@link AcquireOptions}) passes without winning a slot.
|
|
13
|
+
* Bump this for fan-out workloads against many distinct hosts (e.g. `ssh_multi_exec`
|
|
14
|
+
* across a large fleet, which runs at most this many hosts at once).
|
|
12
15
|
*/
|
|
13
16
|
maxPoolSize?: number;
|
|
14
17
|
}
|
|
18
|
+
export interface AcquireOptions {
|
|
19
|
+
/**
|
|
20
|
+
* How long `acquire()` / `withConnection()` may wait for a slot when the pool is full,
|
|
21
|
+
* in milliseconds. Default 0: reject at once with a {@link PoolFullError}. With a budget,
|
|
22
|
+
* the caller parks (see `waitForCapacity`) and retries on every capacity signal until a
|
|
23
|
+
* slot is won or the budget is spent, then rejects with a {@link PoolFullError} whose
|
|
24
|
+
* message says "no slot became available to this call within <budget>ms". Only a capacity rejection is
|
|
25
|
+
* waited out; connect failures and a drained pool reject at once as before.
|
|
26
|
+
*/
|
|
27
|
+
waitForCapacityMs?: number;
|
|
28
|
+
}
|
|
29
|
+
/** `code` of the error `acquire()` throws when no slot is free. Match on this, not on the message. */
|
|
30
|
+
export declare const POOL_FULL_ERROR_CODE = "ERR_SSH_MCP_POOL_FULL";
|
|
31
|
+
/**
|
|
32
|
+
* Thrown by `acquire()` when a new connection is needed and no slot is free: every slot is
|
|
33
|
+
* held by an in-use entry or an in-flight dial, and no idle entry can be evicted. Distinct
|
|
34
|
+
* from connect and exec failures so a caller that can wait (see `waitForCapacity`) retries
|
|
35
|
+
* only on this. `waitedMs` is set when the caller asked to wait and the budget ran out; the
|
|
36
|
+
* message then carries the "no slot became available to this call within <n>ms" suffix and a
|
|
37
|
+
* remedy sentence -- appended here, once, so no caller has to add it.
|
|
38
|
+
*/
|
|
39
|
+
export declare class PoolFullError extends Error {
|
|
40
|
+
readonly code = "ERR_SSH_MCP_POOL_FULL";
|
|
41
|
+
readonly maxPoolSize: number;
|
|
42
|
+
readonly waitedMs?: number;
|
|
43
|
+
constructor(maxPoolSize: number, waitedMs?: number);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* True for the pool's capacity rejection. Checks `code` rather than `instanceof`, so it still
|
|
47
|
+
* holds when two copies of this module are loaded (the CLI and library bundles are separate).
|
|
48
|
+
*/
|
|
49
|
+
export declare function isPoolFullError(err: unknown): err is PoolFullError;
|
|
15
50
|
export declare class ConnectionPool {
|
|
16
51
|
private entries;
|
|
17
52
|
private pending;
|
|
@@ -19,12 +54,46 @@ export declare class ConnectionPool {
|
|
|
19
54
|
private maxPoolSize;
|
|
20
55
|
private _connectCount;
|
|
21
56
|
private drained;
|
|
57
|
+
private capacityWaiters;
|
|
22
58
|
constructor(options?: PoolOptions);
|
|
23
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Checks out a connection for `config`, dialing one if needed. Fails fast on a full pool
|
|
61
|
+
* unless `options.waitForCapacityMs` is set, in which case the caller parks and retries on
|
|
62
|
+
* every capacity signal until it wins a slot or the budget is spent. The wait is bounded,
|
|
63
|
+
* never a reservation: a woken caller can lose the slot to another and simply parks again
|
|
64
|
+
* for whatever budget is left. The final rejection carries the budget in its message.
|
|
65
|
+
*/
|
|
66
|
+
acquire(config: SSHConfig, options?: AcquireOptions): Promise<Client>;
|
|
67
|
+
private acquireNow;
|
|
24
68
|
release(client: Client): void;
|
|
25
|
-
|
|
69
|
+
/** `acquire()` + `fn` + `release()`, with the same optional bounded wait for a slot. */
|
|
70
|
+
withConnection<T>(config: SSHConfig, fn: (client: Client) => Promise<T>, options?: AcquireOptions): Promise<T>;
|
|
26
71
|
drain(): void;
|
|
72
|
+
/**
|
|
73
|
+
* Parks until the pool's capacity may have changed, for a caller whose `acquire()` just
|
|
74
|
+
* rejected with a {@link PoolFullError} and that would rather wait than fail. This is the
|
|
75
|
+
* primitive behind `acquire()`'s `waitForCapacityMs` option; a bare `acquire()` never waits.
|
|
76
|
+
*
|
|
77
|
+
* Resolves `true` when a slot may be free (woken by a release, an entry closing or expiring,
|
|
78
|
+
* a failed dial, or `drain()`), or at once if a new host would fit right now or the pool is
|
|
79
|
+
* already drained. Resolves `false` after `timeoutMs` with nothing having changed. Never
|
|
80
|
+
* rejects. A `true` is a hint, not a reservation: another caller can take the slot first,
|
|
81
|
+
* so retry `acquire()` and wait again on another `PoolFullError`. The timer is cleared on
|
|
82
|
+
* wake and unref'd, so a parked caller never holds the process open.
|
|
83
|
+
*
|
|
84
|
+
* Aborting `signal` ends the park early with `false`, as if the timer had run out; an
|
|
85
|
+
* already-aborted signal resolves `false` without parking, unless the pool is drained or a
|
|
86
|
+
* new host would fit right now. A wake that lands first still resolves `true`. A caller that
|
|
87
|
+
* gives up on its own (`ssh_multi_exec` declaring the call starved) aborts rather than racing
|
|
88
|
+
* this promise against another: the race would add a promise hop to every wake.
|
|
89
|
+
*/
|
|
90
|
+
waitForCapacity(timeoutMs: number, signal?: AbortSignal): Promise<boolean>;
|
|
91
|
+
private hasCapacity;
|
|
92
|
+
private findEvictable;
|
|
93
|
+
private notifyCapacity;
|
|
27
94
|
get size(): number;
|
|
95
|
+
/** The connection cap (`maxPoolSize`), counting pooled entries plus in-flight dials. */
|
|
96
|
+
get maxSize(): number;
|
|
28
97
|
get stats(): {
|
|
29
98
|
active: number;
|
|
30
99
|
idle: number;
|
package/dist/server.d.ts
CHANGED
|
@@ -10,8 +10,8 @@ export type { FindOptions, MultiExecHost, MultiExecResult, ServiceStatus } from
|
|
|
10
10
|
export { find, multiExec, serviceStatus, tail } from "./ops.js";
|
|
11
11
|
export type { PolicyContext } from "./policy.js";
|
|
12
12
|
export { enforcePolicy, isPolicyConfigured } from "./policy.js";
|
|
13
|
-
export type { PoolOptions } from "./pool.js";
|
|
14
|
-
export { ConnectionPool } from "./pool.js";
|
|
13
|
+
export type { AcquireOptions, PoolOptions } from "./pool.js";
|
|
14
|
+
export { ConnectionPool, isPoolFullError, POOL_FULL_ERROR_CODE, PoolFullError } from "./pool.js";
|
|
15
15
|
export type { ExecResult, FileStats, HostKeyRejection, HostKeyRejectionReason, ResolvedConfig, SSHConfig, } from "./ssh.js";
|
|
16
16
|
export { connect, connectRaw, connectWithProxy, deleteFile, downloadFile, exec, formatDiagnostics, listDir, makeDir, readFile, readKnownHostsKeys, resolveConfig, statFile, uploadFile, writeFile, } from "./ssh.js";
|
|
17
17
|
export { registerTools } from "./tools.js";
|