@yawlabs/ssh-mcp 0.14.1 → 0.15.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 +312 -291
- package/dist/diagnose.d.ts +33 -0
- package/dist/env.d.ts +87 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +990 -673
- package/dist/ops.d.ts +54 -0
- package/dist/policy.d.ts +22 -0
- package/dist/pool.d.ts +34 -0
- package/dist/server.d.ts +17 -223
- package/dist/server.js +989 -672
- package/dist/ssh-config.d.ts +4 -0
- package/dist/ssh.d.ts +217 -0
- package/dist/tools.d.ts +3 -0
- package/package.json +3 -2
package/dist/ops.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Client } from "ssh2";
|
|
2
|
+
import type { ConnectionPool } from "./pool.js";
|
|
3
|
+
export declare function shellQuote(s: string): string;
|
|
4
|
+
export interface MultiExecResult {
|
|
5
|
+
host: string;
|
|
6
|
+
stdout: string;
|
|
7
|
+
stderr: string;
|
|
8
|
+
code: number;
|
|
9
|
+
/**
|
|
10
|
+
* Signal name (e.g. "TERM") when the remote channel closed via signal instead of exit --
|
|
11
|
+
* mirrors ExecResult.signal (src/ssh.ts). A signal-killed command reports `code: -1`, the
|
|
12
|
+
* same sentinel exec() uses for "no exit code", so without the signal name a caller cannot
|
|
13
|
+
* tell "the remote was killed" from "the channel died". ssh_exec already surfaces it; this
|
|
14
|
+
* field is what lets ssh_multi_exec do the same instead of printing a bare `[exit code: -1]`.
|
|
15
|
+
*/
|
|
16
|
+
signal?: string;
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface MultiExecHost {
|
|
20
|
+
host: string;
|
|
21
|
+
port?: number;
|
|
22
|
+
username?: string;
|
|
23
|
+
privateKeyPath?: string;
|
|
24
|
+
password?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function multiExec(pool: ConnectionPool, hosts: MultiExecHost[], command: string, timeoutMs?: number): Promise<MultiExecResult[]>;
|
|
27
|
+
export interface FindOptions {
|
|
28
|
+
path: string;
|
|
29
|
+
name?: string;
|
|
30
|
+
type?: "f" | "d" | "l";
|
|
31
|
+
maxdepth?: number;
|
|
32
|
+
minsize?: string;
|
|
33
|
+
maxsize?: string;
|
|
34
|
+
newer?: string;
|
|
35
|
+
}
|
|
36
|
+
export declare function find(client: Client, options: FindOptions, timeoutMs?: number): Promise<string[]>;
|
|
37
|
+
export declare function tail(client: Client, path: string, lines?: number, grep?: string, timeoutMs?: number): Promise<string>;
|
|
38
|
+
export interface ServiceStatus {
|
|
39
|
+
name: string;
|
|
40
|
+
active: boolean;
|
|
41
|
+
status: string;
|
|
42
|
+
description?: string;
|
|
43
|
+
since?: string;
|
|
44
|
+
pid?: number;
|
|
45
|
+
raw: string;
|
|
46
|
+
/**
|
|
47
|
+
* True when systemctl could not report on the unit at all: no `Active:` line
|
|
48
|
+
* parseable AND non-zero exit. Typical causes: typo'd unit name, unit file
|
|
49
|
+
* doesn't exist, systemd unreachable. Distinct from "service exists but is
|
|
50
|
+
* stopped" (active=false but unknown=false).
|
|
51
|
+
*/
|
|
52
|
+
unknown: boolean;
|
|
53
|
+
}
|
|
54
|
+
export declare function serviceStatus(client: Client, serviceName: string, timeoutMs?: number): Promise<ServiceStatus>;
|
package/dist/policy.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface PolicyContext {
|
|
2
|
+
/**
|
|
3
|
+
* True when `command` carries an env-var prefix (`KEY='value' ...`) built from the
|
|
4
|
+
* ssh_exec / ssh_multi_exec `env` parameter. Enforcement is identical either way -- this
|
|
5
|
+
* only adds an explanation to a whitelist rejection, so an operator whose previously
|
|
6
|
+
* working `^`-anchored pattern just started failing can see why.
|
|
7
|
+
*/
|
|
8
|
+
envPrefixApplied?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Check a command against the env-configured policy. Throws if blocked.
|
|
12
|
+
*
|
|
13
|
+
* Called from MCP tool handlers (ssh_exec, ssh_multi_exec) -- not from `exec()` itself,
|
|
14
|
+
* so library consumers using the programmatic API don't get policy enforcement (they're
|
|
15
|
+
* outside the MCP trust boundary and write their own gating).
|
|
16
|
+
*
|
|
17
|
+
* See the SCOPE LIMIT block at the top of this file: the SFTP mutation tools are not
|
|
18
|
+
* routed through here at all.
|
|
19
|
+
*/
|
|
20
|
+
export declare function enforcePolicy(command: string, context?: PolicyContext): void;
|
|
21
|
+
/** Returns true if any policy is currently configured. Used by tool descriptions to surface that. */
|
|
22
|
+
export declare function isPolicyConfigured(): boolean;
|
package/dist/pool.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Client } from "ssh2";
|
|
2
|
+
import { type SSHConfig } from "./ssh.js";
|
|
3
|
+
export interface PoolOptions {
|
|
4
|
+
/** Milliseconds before an idle connection is closed. Default: 60000 (60s) */
|
|
5
|
+
idleTtlMs?: number;
|
|
6
|
+
/**
|
|
7
|
+
* Maximum number of connections in the pool. Default: 100, overridable via the
|
|
8
|
+
* `SSH_MCP_MAX_POOL_SIZE` env var. When at capacity, the pool first tries to evict
|
|
9
|
+
* an idle entry; if every entry is in use, `acquire()` rejects with
|
|
10
|
+
* "Connection pool is full". Bump this for fan-out workloads against many distinct
|
|
11
|
+
* hosts (e.g. `ssh_multi_exec` across a large fleet).
|
|
12
|
+
*/
|
|
13
|
+
maxPoolSize?: number;
|
|
14
|
+
}
|
|
15
|
+
export declare class ConnectionPool {
|
|
16
|
+
private entries;
|
|
17
|
+
private pending;
|
|
18
|
+
private idleTtlMs;
|
|
19
|
+
private maxPoolSize;
|
|
20
|
+
private _connectCount;
|
|
21
|
+
private drained;
|
|
22
|
+
constructor(options?: PoolOptions);
|
|
23
|
+
acquire(config: SSHConfig): Promise<Client>;
|
|
24
|
+
release(client: Client): void;
|
|
25
|
+
withConnection<T>(config: SSHConfig, fn: (client: Client) => Promise<T>): Promise<T>;
|
|
26
|
+
drain(): void;
|
|
27
|
+
get size(): number;
|
|
28
|
+
get stats(): {
|
|
29
|
+
active: number;
|
|
30
|
+
idle: number;
|
|
31
|
+
};
|
|
32
|
+
/** Total number of successful SSH connects made by this pool since construction. */
|
|
33
|
+
get connectCount(): number;
|
|
34
|
+
}
|
package/dist/server.d.ts
CHANGED
|
@@ -1,223 +1,17 @@
|
|
|
1
|
-
import { McpServer } from
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
/** True when stderr was truncated at the byte cap. */
|
|
19
|
-
stderrTruncated?: boolean;
|
|
20
|
-
/** Signal name (e.g. "TERM") if the remote channel closed via signal instead of exit. */
|
|
21
|
-
signal?: string;
|
|
22
|
-
}
|
|
23
|
-
interface ResolvedConfig {
|
|
24
|
-
connectConfig: ConnectConfig;
|
|
25
|
-
proxyJump?: string;
|
|
26
|
-
}
|
|
27
|
-
declare function readKnownHostsKeys(host: string, port?: number): Buffer[];
|
|
28
|
-
declare function resolveConfig(config: SSHConfig): ResolvedConfig;
|
|
29
|
-
declare function formatDiagnostics(host: string): string;
|
|
30
|
-
declare function connectRaw(connectConfig: ConnectConfig): Promise<Client>;
|
|
31
|
-
declare function connectWithProxy(resolved: ResolvedConfig): Promise<Client>;
|
|
32
|
-
declare function connect(config: SSHConfig): Promise<Client>;
|
|
33
|
-
declare function exec(client: Client, command: string, timeoutMs?: number, maxBytes?: number): Promise<ExecResult>;
|
|
34
|
-
declare function readFile(client: Client, remotePath: string, maxBytes?: number): Promise<string>;
|
|
35
|
-
declare function writeFile(client: Client, remotePath: string, content: string): Promise<void>;
|
|
36
|
-
declare function uploadFile(client: Client, localPath: string, remotePath: string): Promise<void>;
|
|
37
|
-
declare function downloadFile(client: Client, remotePath: string, localPath: string): Promise<void>;
|
|
38
|
-
declare function listDir(client: Client, remotePath: string): Promise<string[]>;
|
|
39
|
-
interface FileStats {
|
|
40
|
-
size: number;
|
|
41
|
-
/** POSIX mode as a decimal number. Use modeOctal for the human-readable form. */
|
|
42
|
-
mode: number;
|
|
43
|
-
/** POSIX mode formatted as a 4-digit octal string (e.g. "0755"). */
|
|
44
|
-
modeOctal: string;
|
|
45
|
-
uid: number;
|
|
46
|
-
gid: number;
|
|
47
|
-
/** Unix timestamp (seconds since epoch) of last modification. */
|
|
48
|
-
mtime: number;
|
|
49
|
-
/** Unix timestamp (seconds since epoch) of last access. */
|
|
50
|
-
atime: number;
|
|
51
|
-
isFile: boolean;
|
|
52
|
-
isDirectory: boolean;
|
|
53
|
-
isSymbolicLink: boolean;
|
|
54
|
-
}
|
|
55
|
-
declare function statFile(client: Client, remotePath: string): Promise<FileStats>;
|
|
56
|
-
declare function deleteFile(client: Client, remotePath: string): Promise<void>;
|
|
57
|
-
declare function makeDir(client: Client, remotePath: string, recursive?: boolean): Promise<void>;
|
|
58
|
-
|
|
59
|
-
interface PoolOptions {
|
|
60
|
-
/** Milliseconds before an idle connection is closed. Default: 60000 (60s) */
|
|
61
|
-
idleTtlMs?: number;
|
|
62
|
-
/**
|
|
63
|
-
* Maximum number of connections in the pool. Default: 100, overridable via the
|
|
64
|
-
* `SSH_MCP_MAX_POOL_SIZE` env var. When at capacity, the pool first tries to evict
|
|
65
|
-
* an idle entry; if every entry is in use, `acquire()` rejects with
|
|
66
|
-
* "Connection pool is full". Bump this for fan-out workloads against many distinct
|
|
67
|
-
* hosts (e.g. `ssh_multi_exec` across a large fleet).
|
|
68
|
-
*/
|
|
69
|
-
maxPoolSize?: number;
|
|
70
|
-
}
|
|
71
|
-
declare class ConnectionPool {
|
|
72
|
-
private entries;
|
|
73
|
-
private pending;
|
|
74
|
-
private idleTtlMs;
|
|
75
|
-
private maxPoolSize;
|
|
76
|
-
private _connectCount;
|
|
77
|
-
private drained;
|
|
78
|
-
constructor(options?: PoolOptions);
|
|
79
|
-
acquire(config: SSHConfig): Promise<Client>;
|
|
80
|
-
release(client: Client): void;
|
|
81
|
-
withConnection<T>(config: SSHConfig, fn: (client: Client) => Promise<T>): Promise<T>;
|
|
82
|
-
drain(): void;
|
|
83
|
-
get size(): number;
|
|
84
|
-
get stats(): {
|
|
85
|
-
active: number;
|
|
86
|
-
idle: number;
|
|
87
|
-
};
|
|
88
|
-
/** Total number of successful SSH connects made by this pool since construction. */
|
|
89
|
-
get connectCount(): number;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
interface DiagnosticResult {
|
|
93
|
-
status: "ok" | "warning" | "error";
|
|
94
|
-
message: string;
|
|
95
|
-
}
|
|
96
|
-
interface DiagnosticReport {
|
|
97
|
-
overall: "ok" | "warning" | "error";
|
|
98
|
-
checks: Array<{
|
|
99
|
-
name: string;
|
|
100
|
-
} & DiagnosticResult>;
|
|
101
|
-
suggestions: string[];
|
|
102
|
-
}
|
|
103
|
-
declare function checkSshAgent(): DiagnosticResult;
|
|
104
|
-
declare function checkSshKeys(): DiagnosticResult;
|
|
105
|
-
declare function checkKnownHosts(host: string): DiagnosticResult;
|
|
106
|
-
declare function checkConnectivity(host: string, port?: number): DiagnosticResult;
|
|
107
|
-
declare function checkSshConfig(host: string): DiagnosticResult;
|
|
108
|
-
declare function diagnose(host: string, port?: number): DiagnosticReport;
|
|
109
|
-
|
|
110
|
-
interface KeyInfo {
|
|
111
|
-
name: string;
|
|
112
|
-
path: string;
|
|
113
|
-
type: string;
|
|
114
|
-
fingerprint?: string;
|
|
115
|
-
loadedInAgent: boolean;
|
|
116
|
-
}
|
|
117
|
-
interface AgentResult {
|
|
118
|
-
running: boolean;
|
|
119
|
-
reachable: boolean;
|
|
120
|
-
socket?: string;
|
|
121
|
-
keys: string[];
|
|
122
|
-
started: boolean;
|
|
123
|
-
env?: {
|
|
124
|
-
SSH_AUTH_SOCK?: string;
|
|
125
|
-
SSH_AGENT_PID?: string;
|
|
126
|
-
};
|
|
127
|
-
message: string;
|
|
128
|
-
}
|
|
129
|
-
declare function ensureAgent(): AgentResult;
|
|
130
|
-
declare function listSshKeys(): KeyInfo[];
|
|
131
|
-
declare function loadKey(keyPath: string): {
|
|
132
|
-
status: "ok" | "error";
|
|
133
|
-
message: string;
|
|
134
|
-
};
|
|
135
|
-
interface ConfigLookupResult {
|
|
136
|
-
hostname: string;
|
|
137
|
-
user: string;
|
|
138
|
-
port: string;
|
|
139
|
-
identityFile: string[];
|
|
140
|
-
proxyJump?: string;
|
|
141
|
-
proxyCommand?: string;
|
|
142
|
-
all: Record<string, string>;
|
|
143
|
-
raw: string;
|
|
144
|
-
}
|
|
145
|
-
declare function configLookup(host: string): ConfigLookupResult | {
|
|
146
|
-
error: string;
|
|
147
|
-
};
|
|
148
|
-
declare function fixKnownHosts(host: string, port?: number): {
|
|
149
|
-
status: "ok" | "error";
|
|
150
|
-
message: string;
|
|
151
|
-
actions: string[];
|
|
152
|
-
};
|
|
153
|
-
declare function checkGitSsh(host?: string, user?: string): {
|
|
154
|
-
status: "ok" | "error";
|
|
155
|
-
message: string;
|
|
156
|
-
authenticatedAs?: string;
|
|
157
|
-
};
|
|
158
|
-
declare function testConnection(host: string, port?: number): {
|
|
159
|
-
status: "ok" | "warning" | "error";
|
|
160
|
-
message: string;
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
interface MultiExecResult {
|
|
164
|
-
host: string;
|
|
165
|
-
stdout: string;
|
|
166
|
-
stderr: string;
|
|
167
|
-
code: number;
|
|
168
|
-
error?: string;
|
|
169
|
-
}
|
|
170
|
-
interface MultiExecHost {
|
|
171
|
-
host: string;
|
|
172
|
-
port?: number;
|
|
173
|
-
username?: string;
|
|
174
|
-
privateKeyPath?: string;
|
|
175
|
-
password?: string;
|
|
176
|
-
}
|
|
177
|
-
declare function multiExec(pool: ConnectionPool, hosts: MultiExecHost[], command: string, timeoutMs?: number): Promise<MultiExecResult[]>;
|
|
178
|
-
interface FindOptions {
|
|
179
|
-
path: string;
|
|
180
|
-
name?: string;
|
|
181
|
-
type?: "f" | "d" | "l";
|
|
182
|
-
maxdepth?: number;
|
|
183
|
-
minsize?: string;
|
|
184
|
-
maxsize?: string;
|
|
185
|
-
newer?: string;
|
|
186
|
-
}
|
|
187
|
-
declare function find(client: Client, options: FindOptions, timeoutMs?: number): Promise<string[]>;
|
|
188
|
-
declare function tail(client: Client, path: string, lines?: number, grep?: string, timeoutMs?: number): Promise<string>;
|
|
189
|
-
interface ServiceStatus {
|
|
190
|
-
name: string;
|
|
191
|
-
active: boolean;
|
|
192
|
-
status: string;
|
|
193
|
-
description?: string;
|
|
194
|
-
since?: string;
|
|
195
|
-
pid?: number;
|
|
196
|
-
raw: string;
|
|
197
|
-
/**
|
|
198
|
-
* True when systemctl could not report on the unit at all: no `Active:` line
|
|
199
|
-
* parseable AND non-zero exit. Typical causes: typo'd unit name, unit file
|
|
200
|
-
* doesn't exist, systemd unreachable. Distinct from "service exists but is
|
|
201
|
-
* stopped" (active=false but unknown=false).
|
|
202
|
-
*/
|
|
203
|
-
unknown: boolean;
|
|
204
|
-
}
|
|
205
|
-
declare function serviceStatus(client: Client, serviceName: string, timeoutMs?: number): Promise<ServiceStatus>;
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Check a command against the env-configured policy. Throws if blocked.
|
|
209
|
-
*
|
|
210
|
-
* Called from MCP tool handlers (ssh_exec, ssh_multi_exec) -- not from `exec()` itself,
|
|
211
|
-
* so library consumers using the programmatic API don't get policy enforcement (they're
|
|
212
|
-
* outside the MCP trust boundary and write their own gating).
|
|
213
|
-
*/
|
|
214
|
-
declare function enforcePolicy(command: string): void;
|
|
215
|
-
/** Returns true if any policy is currently configured. Used by tool descriptions to surface that. */
|
|
216
|
-
declare function isPolicyConfigured(): boolean;
|
|
217
|
-
|
|
218
|
-
declare function registerTools(server: McpServer, pool?: ConnectionPool): void;
|
|
219
|
-
|
|
220
|
-
declare const version: string;
|
|
221
|
-
declare function createServer(pool?: ConnectionPool): McpServer;
|
|
222
|
-
|
|
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 };
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { ConnectionPool } from "./pool.js";
|
|
3
|
+
export declare const version: string;
|
|
4
|
+
export declare function createServer(pool?: ConnectionPool): McpServer;
|
|
5
|
+
export type { DiagnosticReport, DiagnosticResult } from "./diagnose.js";
|
|
6
|
+
export { checkConnectivity, checkKnownHosts, checkSshAgent, checkSshConfig, checkSshKeys, diagnose, } from "./diagnose.js";
|
|
7
|
+
export type { AgentResult, ConfigLookupResult, KeyInfo } from "./env.js";
|
|
8
|
+
export { checkGitSsh, configLookup, ensureAgent, fixKnownHosts, listSshKeys, loadKey, testConnection, } from "./env.js";
|
|
9
|
+
export type { FindOptions, MultiExecHost, MultiExecResult, ServiceStatus } from "./ops.js";
|
|
10
|
+
export { find, multiExec, serviceStatus, tail } from "./ops.js";
|
|
11
|
+
export type { PolicyContext } from "./policy.js";
|
|
12
|
+
export { enforcePolicy, isPolicyConfigured } from "./policy.js";
|
|
13
|
+
export type { PoolOptions } from "./pool.js";
|
|
14
|
+
export { ConnectionPool } from "./pool.js";
|
|
15
|
+
export type { ExecResult, FileStats, HostKeyRejection, HostKeyRejectionReason, ResolvedConfig, SSHConfig, } from "./ssh.js";
|
|
16
|
+
export { connect, connectRaw, connectWithProxy, deleteFile, downloadFile, exec, formatDiagnostics, listDir, makeDir, readFile, readKnownHostsKeys, resolveConfig, statFile, uploadFile, writeFile, } from "./ssh.js";
|
|
17
|
+
export { registerTools } from "./tools.js";
|