@akms/mcp-ssh 0.0.1 → 0.0.3
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/LICENSE.md +11 -11
- package/README.md +191 -332
- package/lib/cli.cjs +208 -524
- package/lib/cli.cjs.map +1 -1
- package/lib/cli.js +207 -523
- package/lib/cli.js.map +1 -1
- package/lib/index.cjs +159 -448
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +70 -139
- package/lib/index.d.ts +70 -139
- package/lib/index.js +156 -442
- package/lib/index.js.map +1 -1
- package/package.json +10 -2
package/lib/index.d.cts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { SFTPWrapper } from 'ssh2';
|
|
3
|
-
import { z } from 'zod';
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
|
-
* Guard rules applied to every command / file operation on
|
|
7
|
-
* Resolved
|
|
5
|
+
* Guard rules applied to every command / file operation on the host.
|
|
6
|
+
* Resolved at startup: the built-in permissive stance with each `SSH_*` variable applied over it.
|
|
8
7
|
*/
|
|
9
8
|
interface SshPolicy {
|
|
10
9
|
/** Blocks write-shaped commands, output redirection, uploads and remote writes. */
|
|
@@ -29,9 +28,9 @@ interface SshPolicy {
|
|
|
29
28
|
/** Remote path prefixes the file tools may touch. Empty means unrestricted. */
|
|
30
29
|
allowedPaths: string[];
|
|
31
30
|
}
|
|
32
|
-
/**
|
|
31
|
+
/** The registered host: where to connect, how to authenticate, what is permitted. */
|
|
33
32
|
interface SshHostProfile {
|
|
34
|
-
/**
|
|
33
|
+
/** Alias shown to the model, from `SSH_NAME`; defaults to the hostname. */
|
|
35
34
|
name: string;
|
|
36
35
|
host: string;
|
|
37
36
|
port: number;
|
|
@@ -39,8 +38,6 @@ interface SshHostProfile {
|
|
|
39
38
|
description?: string;
|
|
40
39
|
/** Expanded absolute path to a private key file. */
|
|
41
40
|
privateKeyPath?: string;
|
|
42
|
-
/** Private key contents, used when `privateKeyPath` is absent. */
|
|
43
|
-
privateKey?: string;
|
|
44
41
|
passphrase?: string;
|
|
45
42
|
password?: string;
|
|
46
43
|
/**
|
|
@@ -52,18 +49,7 @@ interface SshHostProfile {
|
|
|
52
49
|
defaultCwd?: string;
|
|
53
50
|
policy: SshPolicy;
|
|
54
51
|
}
|
|
55
|
-
/**
|
|
56
|
-
interface SshMcpConfig {
|
|
57
|
-
profiles: Map<string, SshHostProfile>;
|
|
58
|
-
/**
|
|
59
|
-
* Absolute path the config was read from, or null when it came from inline JSON or
|
|
60
|
-
* nothing was found. Only a real file needs protecting from the download tool.
|
|
61
|
-
*/
|
|
62
|
-
sourcePath: string | null;
|
|
63
|
-
/** Where the config came from, for tool output — a path, "inline", or "none". */
|
|
64
|
-
sourceLabel: string;
|
|
65
|
-
}
|
|
66
|
-
/** Credential-free view of a profile, safe to hand back to the model. */
|
|
52
|
+
/** Credential-free view of the profile, safe to hand back to the model. */
|
|
67
53
|
interface SshHostSummary {
|
|
68
54
|
name: string;
|
|
69
55
|
host: string;
|
|
@@ -325,132 +311,45 @@ declare function uploadFile(connection: SshConnection, localPath: string, remote
|
|
|
325
311
|
*/
|
|
326
312
|
declare function downloadFile(connection: SshConnection, remotePath: string, localPath: string): Promise<number>;
|
|
327
313
|
|
|
328
|
-
interface SshMcpServerOptions {
|
|
329
|
-
/** Explicit config path; otherwise `SSH_MCP_CONFIG`, then the home-directory candidates. */
|
|
330
|
-
configPath?: string;
|
|
331
|
-
}
|
|
332
314
|
interface SshMcpServerInstance {
|
|
333
315
|
server: McpServer;
|
|
334
316
|
sessions: SshSessionManager;
|
|
335
|
-
/**
|
|
336
|
-
readonly
|
|
337
|
-
/**
|
|
338
|
-
* Re-reads the config source and applies it.
|
|
339
|
-
*
|
|
340
|
-
* @throws {Error} If the source is now missing or malformed; the previous config
|
|
341
|
-
* stays in force in that case.
|
|
342
|
-
*/
|
|
343
|
-
reloadConfig(): SshMcpConfig;
|
|
317
|
+
/** The host read from the `SSH_*` variables at startup, or null when none is set. */
|
|
318
|
+
readonly profile: SshHostProfile | null;
|
|
344
319
|
/** Stops the sweeper and closes every open session. Call before the process exits. */
|
|
345
320
|
shutdown(): void;
|
|
346
321
|
}
|
|
347
322
|
/**
|
|
348
|
-
* Builds the MCP server:
|
|
323
|
+
* Builds the MCP server: reads the host from the environment, wires the session manager,
|
|
324
|
+
* registers tools.
|
|
349
325
|
*
|
|
350
326
|
* Transport is deliberately left to the caller — `cli.ts` attaches stdio, tests can attach
|
|
351
327
|
* an in-memory pair.
|
|
352
328
|
*
|
|
353
|
-
* @throws {Error} If
|
|
329
|
+
* @throws {Error} If the `SSH_*` variables are malformed (a bad policy value must not
|
|
354
330
|
* silently degrade into "no guard").
|
|
355
331
|
*/
|
|
356
|
-
declare function createSshMcpServer(
|
|
332
|
+
declare function createSshMcpServer(): SshMcpServerInstance;
|
|
357
333
|
|
|
358
334
|
/**
|
|
359
|
-
*
|
|
335
|
+
* Builds the host profile from the `SSH_*` variables, or null when `SSH_HOST` is unset.
|
|
360
336
|
*
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
337
|
+
* One MCP server entry fronts one host, so every setting is a plain variable — no JSON
|
|
338
|
+
* inside JSON, no escaped quotes, and a typo shows up on the line it is on. A malformed
|
|
339
|
+
* value aborts startup rather than being ignored, because silently dropping `SSH_READONLY`
|
|
340
|
+
* would leave a host the operator believed was read-only fully writable.
|
|
365
341
|
*
|
|
366
|
-
*
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* Policy knobs, all optional — unset fields fall back to `defaults`, then to the built-ins.
|
|
342
|
+
* Flow:
|
|
343
|
+
* 1) Guard: no `SSH_HOST` means nothing is registered — the server still starts and says so
|
|
344
|
+
* 2) Resolve the policy over the built-in permissive stance; each variable is opt-in restriction
|
|
345
|
+
* 3) Assemble the connection fields
|
|
346
|
+
* 4) Expand the private key path and check it exists
|
|
374
347
|
*
|
|
375
|
-
*
|
|
376
|
-
* (capital O) parsed successfully and silently vanished, leaving a host the operator
|
|
377
|
-
* believed was read-only fully writable. An unknown key must fail startup instead.
|
|
348
|
+
* @throws {Error} If `SSH_HOST` is set without `SSH_USER`, or a typed value is malformed.
|
|
378
349
|
*/
|
|
379
|
-
declare
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
execTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
383
|
-
connectTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
384
|
-
maxOutputCharacters: z.ZodOptional<z.ZodNumber>;
|
|
385
|
-
maxReadFileBytes: z.ZodOptional<z.ZodNumber>;
|
|
386
|
-
allowCommands: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
387
|
-
denyPatterns: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
388
|
-
allowedPaths: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
389
|
-
}, z.core.$strict>;
|
|
390
|
-
/** One `hosts` entry: connection fields plus the same policy knobs as an override. */
|
|
391
|
-
declare const HOST_INPUT_SCHEMA: z.ZodObject<{
|
|
392
|
-
readonly: z.ZodOptional<z.ZodBoolean>;
|
|
393
|
-
allowSudo: z.ZodOptional<z.ZodBoolean>;
|
|
394
|
-
execTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
395
|
-
connectTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
396
|
-
maxOutputCharacters: z.ZodOptional<z.ZodNumber>;
|
|
397
|
-
maxReadFileBytes: z.ZodOptional<z.ZodNumber>;
|
|
398
|
-
allowCommands: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
399
|
-
denyPatterns: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
400
|
-
allowedPaths: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
401
|
-
host: z.ZodString;
|
|
402
|
-
port: z.ZodOptional<z.ZodNumber>;
|
|
403
|
-
username: z.ZodString;
|
|
404
|
-
description: z.ZodOptional<z.ZodString>;
|
|
405
|
-
privateKeyPath: z.ZodOptional<z.ZodString>;
|
|
406
|
-
privateKey: z.ZodOptional<z.ZodString>;
|
|
407
|
-
passphrase: z.ZodOptional<z.ZodString>;
|
|
408
|
-
passphraseEnv: z.ZodOptional<z.ZodString>;
|
|
409
|
-
password: z.ZodOptional<z.ZodString>;
|
|
410
|
-
passwordEnv: z.ZodOptional<z.ZodString>;
|
|
411
|
-
hostKeyFingerprint: z.ZodOptional<z.ZodString>;
|
|
412
|
-
defaultCwd: z.ZodOptional<z.ZodString>;
|
|
413
|
-
}, z.core.$strict>;
|
|
414
|
-
declare const CONFIG_INPUT_SCHEMA: z.ZodObject<{
|
|
415
|
-
$schema: z.ZodOptional<z.ZodString>;
|
|
416
|
-
defaults: z.ZodOptional<z.ZodObject<{
|
|
417
|
-
readonly: z.ZodOptional<z.ZodBoolean>;
|
|
418
|
-
allowSudo: z.ZodOptional<z.ZodBoolean>;
|
|
419
|
-
execTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
420
|
-
connectTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
421
|
-
maxOutputCharacters: z.ZodOptional<z.ZodNumber>;
|
|
422
|
-
maxReadFileBytes: z.ZodOptional<z.ZodNumber>;
|
|
423
|
-
allowCommands: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
424
|
-
denyPatterns: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
425
|
-
allowedPaths: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
426
|
-
}, z.core.$strict>>;
|
|
427
|
-
hosts: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
428
|
-
readonly: z.ZodOptional<z.ZodBoolean>;
|
|
429
|
-
allowSudo: z.ZodOptional<z.ZodBoolean>;
|
|
430
|
-
execTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
431
|
-
connectTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
432
|
-
maxOutputCharacters: z.ZodOptional<z.ZodNumber>;
|
|
433
|
-
maxReadFileBytes: z.ZodOptional<z.ZodNumber>;
|
|
434
|
-
allowCommands: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
435
|
-
denyPatterns: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
436
|
-
allowedPaths: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
437
|
-
host: z.ZodString;
|
|
438
|
-
port: z.ZodOptional<z.ZodNumber>;
|
|
439
|
-
username: z.ZodString;
|
|
440
|
-
description: z.ZodOptional<z.ZodString>;
|
|
441
|
-
privateKeyPath: z.ZodOptional<z.ZodString>;
|
|
442
|
-
privateKey: z.ZodOptional<z.ZodString>;
|
|
443
|
-
passphrase: z.ZodOptional<z.ZodString>;
|
|
444
|
-
passphraseEnv: z.ZodOptional<z.ZodString>;
|
|
445
|
-
password: z.ZodOptional<z.ZodString>;
|
|
446
|
-
passwordEnv: z.ZodOptional<z.ZodString>;
|
|
447
|
-
hostKeyFingerprint: z.ZodOptional<z.ZodString>;
|
|
448
|
-
defaultCwd: z.ZodOptional<z.ZodString>;
|
|
449
|
-
}, z.core.$strict>>;
|
|
450
|
-
}, z.core.$strict>;
|
|
451
|
-
type PolicyInput = z.infer<typeof POLICY_INPUT_SCHEMA>;
|
|
452
|
-
type HostInput = z.infer<typeof HOST_INPUT_SCHEMA>;
|
|
453
|
-
type ConfigInput = z.infer<typeof CONFIG_INPUT_SCHEMA>;
|
|
350
|
+
declare function loadHostProfile(): SshHostProfile | null;
|
|
351
|
+
/** Strips every secret so the profile can be described back to the model. */
|
|
352
|
+
declare function toHostSummary(profile: SshHostProfile): SshHostSummary;
|
|
454
353
|
|
|
455
354
|
/**
|
|
456
355
|
* Rewrites shell constructs that change how the text tokenizes but not what it runs,
|
|
@@ -546,7 +445,8 @@ declare function inspectRemotePath(remotePath: string, policy: SshPolicy, access
|
|
|
546
445
|
* define the guards themselves (download).
|
|
547
446
|
*
|
|
548
447
|
* @param resolvedLocalPath Absolute path, already `~`-expanded.
|
|
549
|
-
* @param extraProtectedPaths Runtime-known paths to protect
|
|
448
|
+
* @param extraProtectedPaths Runtime-known paths to protect on top of the built-in rules,
|
|
449
|
+
* for a host embedding this server as a library.
|
|
550
450
|
*/
|
|
551
451
|
declare function inspectLocalPath(resolvedLocalPath: string, access: RemotePathAccess, extraProtectedPaths?: string[]): GuardVerdict;
|
|
552
452
|
/**
|
|
@@ -571,15 +471,11 @@ declare class GuardRejectionError extends Error {
|
|
|
571
471
|
constructor(verdict: GuardVerdict);
|
|
572
472
|
}
|
|
573
473
|
|
|
574
|
-
/** Everything the tool handlers share: the
|
|
474
|
+
/** Everything the tool handlers share: the configured host and the live sessions. */
|
|
575
475
|
interface ToolContext {
|
|
576
|
-
|
|
476
|
+
/** The host from the `SSH_*` variables, or null when none is registered. */
|
|
477
|
+
profile: SshHostProfile | null;
|
|
577
478
|
sessions: SshSessionManager;
|
|
578
|
-
/**
|
|
579
|
-
* Re-reads the config source and swaps it in, so `context.config` is always current.
|
|
580
|
-
* Implemented by the server, which owns the source path.
|
|
581
|
-
*/
|
|
582
|
-
reloadConfig(): SshMcpConfig;
|
|
583
479
|
}
|
|
584
480
|
|
|
585
481
|
/** Registers every tool this server exposes, in the order the model should discover them. */
|
|
@@ -596,11 +492,46 @@ interface Logger {
|
|
|
596
492
|
}
|
|
597
493
|
declare const $logger: Logger;
|
|
598
494
|
|
|
599
|
-
/** Env var holding an explicit config file path. */
|
|
600
|
-
declare const CONFIG_PATH_ENV = "SSH_MCP_CONFIG";
|
|
601
495
|
/** Env var overriding the stderr log level ("debug" | "info" | "warn" | "error" | "silent"). */
|
|
602
496
|
declare const LOG_LEVEL_ENV = "SSH_MCP_LOG_LEVEL";
|
|
603
|
-
declare const SERVER_NAME = "akms-ssh";
|
|
604
|
-
declare const SERVER_VERSION = "0.0.
|
|
497
|
+
declare const SERVER_NAME = "akms-mcp-ssh";
|
|
498
|
+
declare const SERVER_VERSION = "0.0.3";
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Environment variables that define the host, one MCP server entry per host.
|
|
502
|
+
*
|
|
503
|
+
* This is the only way to configure the server. Each variable is a plain value, so an
|
|
504
|
+
* MCP client's `env` block reads like a connection form — no JSON inside JSON, no
|
|
505
|
+
* escaped quotes, and a typo shows up on the line it is on.
|
|
506
|
+
*/
|
|
507
|
+
declare const SINGLE_HOST_ENV: {
|
|
508
|
+
/** Presence of this variable is what registers a host at all. */
|
|
509
|
+
readonly HOST: "SSH_HOST";
|
|
510
|
+
readonly PORT: "SSH_PORT";
|
|
511
|
+
readonly USER: "SSH_USER";
|
|
512
|
+
/** Path to a private key file. */
|
|
513
|
+
readonly KEY: "SSH_KEY";
|
|
514
|
+
readonly PASSPHRASE: "SSH_PASSPHRASE";
|
|
515
|
+
readonly PASSWORD: "SSH_PASSWORD";
|
|
516
|
+
/** Pinned host key, `SHA256:…`. */
|
|
517
|
+
readonly HOST_KEY: "SSH_HOST_KEY";
|
|
518
|
+
/** Alias shown to the model; defaults to the hostname. */
|
|
519
|
+
readonly NAME: "SSH_NAME";
|
|
520
|
+
readonly DESCRIPTION: "SSH_DESCRIPTION";
|
|
521
|
+
/** Directory new sessions start in. */
|
|
522
|
+
readonly CWD: "SSH_CWD";
|
|
523
|
+
readonly READONLY: "SSH_READONLY";
|
|
524
|
+
readonly ALLOW_SUDO: "SSH_ALLOW_SUDO";
|
|
525
|
+
/** Comma-separated binary names. */
|
|
526
|
+
readonly ALLOW_COMMANDS: "SSH_ALLOW_COMMANDS";
|
|
527
|
+
/** Comma-separated regex sources. */
|
|
528
|
+
readonly DENY_PATTERNS: "SSH_DENY_PATTERNS";
|
|
529
|
+
/** Comma-separated absolute path prefixes. */
|
|
530
|
+
readonly ALLOWED_PATHS: "SSH_ALLOWED_PATHS";
|
|
531
|
+
readonly EXEC_TIMEOUT_MS: "SSH_EXEC_TIMEOUT_MS";
|
|
532
|
+
readonly CONNECT_TIMEOUT_MS: "SSH_CONNECT_TIMEOUT_MS";
|
|
533
|
+
readonly MAX_OUTPUT_CHARACTERS: "SSH_MAX_OUTPUT";
|
|
534
|
+
readonly MAX_READ_FILE_BYTES: "SSH_MAX_READ_BYTES";
|
|
535
|
+
};
|
|
605
536
|
|
|
606
|
-
export { $logger,
|
|
537
|
+
export { $logger, GuardRejectionError, type GuardVerdict, LOG_LEVEL_ENV, type LogLevel, type Logger, type RemoteDirectoryListing, type RemoteFileContent, type RemotePathAccess, SERVER_NAME, SERVER_VERSION, SINGLE_HOST_ENV, type SegmentAnalysis, SshConnection, type SshExecOptions, type SshExecResult, type SshHostProfile, type SshHostSummary, type SshMcpServerInstance, type SshPolicy, type SshRemoteEntry, SshSession, type SshSessionInfo, SshSessionManager, type ToolContext, analyzeSegment, canonicalizeCommand, createSshMcpServer, downloadFile, extractSubstitutions, inspectCommand, inspectLocalPath, inspectRemotePath, inspectWorkingDirectory, listRemoteDirectory, loadHostProfile, normalizeCommandText, readRemoteFile, registerAllTools, splitCommandSegments, stripQuotedText, toHostSummary, uploadFile, writeRemoteFile };
|
package/lib/index.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { SFTPWrapper } from 'ssh2';
|
|
3
|
-
import { z } from 'zod';
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
|
-
* Guard rules applied to every command / file operation on
|
|
7
|
-
* Resolved
|
|
5
|
+
* Guard rules applied to every command / file operation on the host.
|
|
6
|
+
* Resolved at startup: the built-in permissive stance with each `SSH_*` variable applied over it.
|
|
8
7
|
*/
|
|
9
8
|
interface SshPolicy {
|
|
10
9
|
/** Blocks write-shaped commands, output redirection, uploads and remote writes. */
|
|
@@ -29,9 +28,9 @@ interface SshPolicy {
|
|
|
29
28
|
/** Remote path prefixes the file tools may touch. Empty means unrestricted. */
|
|
30
29
|
allowedPaths: string[];
|
|
31
30
|
}
|
|
32
|
-
/**
|
|
31
|
+
/** The registered host: where to connect, how to authenticate, what is permitted. */
|
|
33
32
|
interface SshHostProfile {
|
|
34
|
-
/**
|
|
33
|
+
/** Alias shown to the model, from `SSH_NAME`; defaults to the hostname. */
|
|
35
34
|
name: string;
|
|
36
35
|
host: string;
|
|
37
36
|
port: number;
|
|
@@ -39,8 +38,6 @@ interface SshHostProfile {
|
|
|
39
38
|
description?: string;
|
|
40
39
|
/** Expanded absolute path to a private key file. */
|
|
41
40
|
privateKeyPath?: string;
|
|
42
|
-
/** Private key contents, used when `privateKeyPath` is absent. */
|
|
43
|
-
privateKey?: string;
|
|
44
41
|
passphrase?: string;
|
|
45
42
|
password?: string;
|
|
46
43
|
/**
|
|
@@ -52,18 +49,7 @@ interface SshHostProfile {
|
|
|
52
49
|
defaultCwd?: string;
|
|
53
50
|
policy: SshPolicy;
|
|
54
51
|
}
|
|
55
|
-
/**
|
|
56
|
-
interface SshMcpConfig {
|
|
57
|
-
profiles: Map<string, SshHostProfile>;
|
|
58
|
-
/**
|
|
59
|
-
* Absolute path the config was read from, or null when it came from inline JSON or
|
|
60
|
-
* nothing was found. Only a real file needs protecting from the download tool.
|
|
61
|
-
*/
|
|
62
|
-
sourcePath: string | null;
|
|
63
|
-
/** Where the config came from, for tool output — a path, "inline", or "none". */
|
|
64
|
-
sourceLabel: string;
|
|
65
|
-
}
|
|
66
|
-
/** Credential-free view of a profile, safe to hand back to the model. */
|
|
52
|
+
/** Credential-free view of the profile, safe to hand back to the model. */
|
|
67
53
|
interface SshHostSummary {
|
|
68
54
|
name: string;
|
|
69
55
|
host: string;
|
|
@@ -325,132 +311,45 @@ declare function uploadFile(connection: SshConnection, localPath: string, remote
|
|
|
325
311
|
*/
|
|
326
312
|
declare function downloadFile(connection: SshConnection, remotePath: string, localPath: string): Promise<number>;
|
|
327
313
|
|
|
328
|
-
interface SshMcpServerOptions {
|
|
329
|
-
/** Explicit config path; otherwise `SSH_MCP_CONFIG`, then the home-directory candidates. */
|
|
330
|
-
configPath?: string;
|
|
331
|
-
}
|
|
332
314
|
interface SshMcpServerInstance {
|
|
333
315
|
server: McpServer;
|
|
334
316
|
sessions: SshSessionManager;
|
|
335
|
-
/**
|
|
336
|
-
readonly
|
|
337
|
-
/**
|
|
338
|
-
* Re-reads the config source and applies it.
|
|
339
|
-
*
|
|
340
|
-
* @throws {Error} If the source is now missing or malformed; the previous config
|
|
341
|
-
* stays in force in that case.
|
|
342
|
-
*/
|
|
343
|
-
reloadConfig(): SshMcpConfig;
|
|
317
|
+
/** The host read from the `SSH_*` variables at startup, or null when none is set. */
|
|
318
|
+
readonly profile: SshHostProfile | null;
|
|
344
319
|
/** Stops the sweeper and closes every open session. Call before the process exits. */
|
|
345
320
|
shutdown(): void;
|
|
346
321
|
}
|
|
347
322
|
/**
|
|
348
|
-
* Builds the MCP server:
|
|
323
|
+
* Builds the MCP server: reads the host from the environment, wires the session manager,
|
|
324
|
+
* registers tools.
|
|
349
325
|
*
|
|
350
326
|
* Transport is deliberately left to the caller — `cli.ts` attaches stdio, tests can attach
|
|
351
327
|
* an in-memory pair.
|
|
352
328
|
*
|
|
353
|
-
* @throws {Error} If
|
|
329
|
+
* @throws {Error} If the `SSH_*` variables are malformed (a bad policy value must not
|
|
354
330
|
* silently degrade into "no guard").
|
|
355
331
|
*/
|
|
356
|
-
declare function createSshMcpServer(
|
|
332
|
+
declare function createSshMcpServer(): SshMcpServerInstance;
|
|
357
333
|
|
|
358
334
|
/**
|
|
359
|
-
*
|
|
335
|
+
* Builds the host profile from the `SSH_*` variables, or null when `SSH_HOST` is unset.
|
|
360
336
|
*
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
337
|
+
* One MCP server entry fronts one host, so every setting is a plain variable — no JSON
|
|
338
|
+
* inside JSON, no escaped quotes, and a typo shows up on the line it is on. A malformed
|
|
339
|
+
* value aborts startup rather than being ignored, because silently dropping `SSH_READONLY`
|
|
340
|
+
* would leave a host the operator believed was read-only fully writable.
|
|
365
341
|
*
|
|
366
|
-
*
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* Policy knobs, all optional — unset fields fall back to `defaults`, then to the built-ins.
|
|
342
|
+
* Flow:
|
|
343
|
+
* 1) Guard: no `SSH_HOST` means nothing is registered — the server still starts and says so
|
|
344
|
+
* 2) Resolve the policy over the built-in permissive stance; each variable is opt-in restriction
|
|
345
|
+
* 3) Assemble the connection fields
|
|
346
|
+
* 4) Expand the private key path and check it exists
|
|
374
347
|
*
|
|
375
|
-
*
|
|
376
|
-
* (capital O) parsed successfully and silently vanished, leaving a host the operator
|
|
377
|
-
* believed was read-only fully writable. An unknown key must fail startup instead.
|
|
348
|
+
* @throws {Error} If `SSH_HOST` is set without `SSH_USER`, or a typed value is malformed.
|
|
378
349
|
*/
|
|
379
|
-
declare
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
execTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
383
|
-
connectTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
384
|
-
maxOutputCharacters: z.ZodOptional<z.ZodNumber>;
|
|
385
|
-
maxReadFileBytes: z.ZodOptional<z.ZodNumber>;
|
|
386
|
-
allowCommands: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
387
|
-
denyPatterns: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
388
|
-
allowedPaths: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
389
|
-
}, z.core.$strict>;
|
|
390
|
-
/** One `hosts` entry: connection fields plus the same policy knobs as an override. */
|
|
391
|
-
declare const HOST_INPUT_SCHEMA: z.ZodObject<{
|
|
392
|
-
readonly: z.ZodOptional<z.ZodBoolean>;
|
|
393
|
-
allowSudo: z.ZodOptional<z.ZodBoolean>;
|
|
394
|
-
execTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
395
|
-
connectTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
396
|
-
maxOutputCharacters: z.ZodOptional<z.ZodNumber>;
|
|
397
|
-
maxReadFileBytes: z.ZodOptional<z.ZodNumber>;
|
|
398
|
-
allowCommands: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
399
|
-
denyPatterns: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
400
|
-
allowedPaths: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
401
|
-
host: z.ZodString;
|
|
402
|
-
port: z.ZodOptional<z.ZodNumber>;
|
|
403
|
-
username: z.ZodString;
|
|
404
|
-
description: z.ZodOptional<z.ZodString>;
|
|
405
|
-
privateKeyPath: z.ZodOptional<z.ZodString>;
|
|
406
|
-
privateKey: z.ZodOptional<z.ZodString>;
|
|
407
|
-
passphrase: z.ZodOptional<z.ZodString>;
|
|
408
|
-
passphraseEnv: z.ZodOptional<z.ZodString>;
|
|
409
|
-
password: z.ZodOptional<z.ZodString>;
|
|
410
|
-
passwordEnv: z.ZodOptional<z.ZodString>;
|
|
411
|
-
hostKeyFingerprint: z.ZodOptional<z.ZodString>;
|
|
412
|
-
defaultCwd: z.ZodOptional<z.ZodString>;
|
|
413
|
-
}, z.core.$strict>;
|
|
414
|
-
declare const CONFIG_INPUT_SCHEMA: z.ZodObject<{
|
|
415
|
-
$schema: z.ZodOptional<z.ZodString>;
|
|
416
|
-
defaults: z.ZodOptional<z.ZodObject<{
|
|
417
|
-
readonly: z.ZodOptional<z.ZodBoolean>;
|
|
418
|
-
allowSudo: z.ZodOptional<z.ZodBoolean>;
|
|
419
|
-
execTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
420
|
-
connectTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
421
|
-
maxOutputCharacters: z.ZodOptional<z.ZodNumber>;
|
|
422
|
-
maxReadFileBytes: z.ZodOptional<z.ZodNumber>;
|
|
423
|
-
allowCommands: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
424
|
-
denyPatterns: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
425
|
-
allowedPaths: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
426
|
-
}, z.core.$strict>>;
|
|
427
|
-
hosts: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
428
|
-
readonly: z.ZodOptional<z.ZodBoolean>;
|
|
429
|
-
allowSudo: z.ZodOptional<z.ZodBoolean>;
|
|
430
|
-
execTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
431
|
-
connectTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
432
|
-
maxOutputCharacters: z.ZodOptional<z.ZodNumber>;
|
|
433
|
-
maxReadFileBytes: z.ZodOptional<z.ZodNumber>;
|
|
434
|
-
allowCommands: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
435
|
-
denyPatterns: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
436
|
-
allowedPaths: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
437
|
-
host: z.ZodString;
|
|
438
|
-
port: z.ZodOptional<z.ZodNumber>;
|
|
439
|
-
username: z.ZodString;
|
|
440
|
-
description: z.ZodOptional<z.ZodString>;
|
|
441
|
-
privateKeyPath: z.ZodOptional<z.ZodString>;
|
|
442
|
-
privateKey: z.ZodOptional<z.ZodString>;
|
|
443
|
-
passphrase: z.ZodOptional<z.ZodString>;
|
|
444
|
-
passphraseEnv: z.ZodOptional<z.ZodString>;
|
|
445
|
-
password: z.ZodOptional<z.ZodString>;
|
|
446
|
-
passwordEnv: z.ZodOptional<z.ZodString>;
|
|
447
|
-
hostKeyFingerprint: z.ZodOptional<z.ZodString>;
|
|
448
|
-
defaultCwd: z.ZodOptional<z.ZodString>;
|
|
449
|
-
}, z.core.$strict>>;
|
|
450
|
-
}, z.core.$strict>;
|
|
451
|
-
type PolicyInput = z.infer<typeof POLICY_INPUT_SCHEMA>;
|
|
452
|
-
type HostInput = z.infer<typeof HOST_INPUT_SCHEMA>;
|
|
453
|
-
type ConfigInput = z.infer<typeof CONFIG_INPUT_SCHEMA>;
|
|
350
|
+
declare function loadHostProfile(): SshHostProfile | null;
|
|
351
|
+
/** Strips every secret so the profile can be described back to the model. */
|
|
352
|
+
declare function toHostSummary(profile: SshHostProfile): SshHostSummary;
|
|
454
353
|
|
|
455
354
|
/**
|
|
456
355
|
* Rewrites shell constructs that change how the text tokenizes but not what it runs,
|
|
@@ -546,7 +445,8 @@ declare function inspectRemotePath(remotePath: string, policy: SshPolicy, access
|
|
|
546
445
|
* define the guards themselves (download).
|
|
547
446
|
*
|
|
548
447
|
* @param resolvedLocalPath Absolute path, already `~`-expanded.
|
|
549
|
-
* @param extraProtectedPaths Runtime-known paths to protect
|
|
448
|
+
* @param extraProtectedPaths Runtime-known paths to protect on top of the built-in rules,
|
|
449
|
+
* for a host embedding this server as a library.
|
|
550
450
|
*/
|
|
551
451
|
declare function inspectLocalPath(resolvedLocalPath: string, access: RemotePathAccess, extraProtectedPaths?: string[]): GuardVerdict;
|
|
552
452
|
/**
|
|
@@ -571,15 +471,11 @@ declare class GuardRejectionError extends Error {
|
|
|
571
471
|
constructor(verdict: GuardVerdict);
|
|
572
472
|
}
|
|
573
473
|
|
|
574
|
-
/** Everything the tool handlers share: the
|
|
474
|
+
/** Everything the tool handlers share: the configured host and the live sessions. */
|
|
575
475
|
interface ToolContext {
|
|
576
|
-
|
|
476
|
+
/** The host from the `SSH_*` variables, or null when none is registered. */
|
|
477
|
+
profile: SshHostProfile | null;
|
|
577
478
|
sessions: SshSessionManager;
|
|
578
|
-
/**
|
|
579
|
-
* Re-reads the config source and swaps it in, so `context.config` is always current.
|
|
580
|
-
* Implemented by the server, which owns the source path.
|
|
581
|
-
*/
|
|
582
|
-
reloadConfig(): SshMcpConfig;
|
|
583
479
|
}
|
|
584
480
|
|
|
585
481
|
/** Registers every tool this server exposes, in the order the model should discover them. */
|
|
@@ -596,11 +492,46 @@ interface Logger {
|
|
|
596
492
|
}
|
|
597
493
|
declare const $logger: Logger;
|
|
598
494
|
|
|
599
|
-
/** Env var holding an explicit config file path. */
|
|
600
|
-
declare const CONFIG_PATH_ENV = "SSH_MCP_CONFIG";
|
|
601
495
|
/** Env var overriding the stderr log level ("debug" | "info" | "warn" | "error" | "silent"). */
|
|
602
496
|
declare const LOG_LEVEL_ENV = "SSH_MCP_LOG_LEVEL";
|
|
603
|
-
declare const SERVER_NAME = "akms-ssh";
|
|
604
|
-
declare const SERVER_VERSION = "0.0.
|
|
497
|
+
declare const SERVER_NAME = "akms-mcp-ssh";
|
|
498
|
+
declare const SERVER_VERSION = "0.0.3";
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Environment variables that define the host, one MCP server entry per host.
|
|
502
|
+
*
|
|
503
|
+
* This is the only way to configure the server. Each variable is a plain value, so an
|
|
504
|
+
* MCP client's `env` block reads like a connection form — no JSON inside JSON, no
|
|
505
|
+
* escaped quotes, and a typo shows up on the line it is on.
|
|
506
|
+
*/
|
|
507
|
+
declare const SINGLE_HOST_ENV: {
|
|
508
|
+
/** Presence of this variable is what registers a host at all. */
|
|
509
|
+
readonly HOST: "SSH_HOST";
|
|
510
|
+
readonly PORT: "SSH_PORT";
|
|
511
|
+
readonly USER: "SSH_USER";
|
|
512
|
+
/** Path to a private key file. */
|
|
513
|
+
readonly KEY: "SSH_KEY";
|
|
514
|
+
readonly PASSPHRASE: "SSH_PASSPHRASE";
|
|
515
|
+
readonly PASSWORD: "SSH_PASSWORD";
|
|
516
|
+
/** Pinned host key, `SHA256:…`. */
|
|
517
|
+
readonly HOST_KEY: "SSH_HOST_KEY";
|
|
518
|
+
/** Alias shown to the model; defaults to the hostname. */
|
|
519
|
+
readonly NAME: "SSH_NAME";
|
|
520
|
+
readonly DESCRIPTION: "SSH_DESCRIPTION";
|
|
521
|
+
/** Directory new sessions start in. */
|
|
522
|
+
readonly CWD: "SSH_CWD";
|
|
523
|
+
readonly READONLY: "SSH_READONLY";
|
|
524
|
+
readonly ALLOW_SUDO: "SSH_ALLOW_SUDO";
|
|
525
|
+
/** Comma-separated binary names. */
|
|
526
|
+
readonly ALLOW_COMMANDS: "SSH_ALLOW_COMMANDS";
|
|
527
|
+
/** Comma-separated regex sources. */
|
|
528
|
+
readonly DENY_PATTERNS: "SSH_DENY_PATTERNS";
|
|
529
|
+
/** Comma-separated absolute path prefixes. */
|
|
530
|
+
readonly ALLOWED_PATHS: "SSH_ALLOWED_PATHS";
|
|
531
|
+
readonly EXEC_TIMEOUT_MS: "SSH_EXEC_TIMEOUT_MS";
|
|
532
|
+
readonly CONNECT_TIMEOUT_MS: "SSH_CONNECT_TIMEOUT_MS";
|
|
533
|
+
readonly MAX_OUTPUT_CHARACTERS: "SSH_MAX_OUTPUT";
|
|
534
|
+
readonly MAX_READ_FILE_BYTES: "SSH_MAX_READ_BYTES";
|
|
535
|
+
};
|
|
605
536
|
|
|
606
|
-
export { $logger,
|
|
537
|
+
export { $logger, GuardRejectionError, type GuardVerdict, LOG_LEVEL_ENV, type LogLevel, type Logger, type RemoteDirectoryListing, type RemoteFileContent, type RemotePathAccess, SERVER_NAME, SERVER_VERSION, SINGLE_HOST_ENV, type SegmentAnalysis, SshConnection, type SshExecOptions, type SshExecResult, type SshHostProfile, type SshHostSummary, type SshMcpServerInstance, type SshPolicy, type SshRemoteEntry, SshSession, type SshSessionInfo, SshSessionManager, type ToolContext, analyzeSegment, canonicalizeCommand, createSshMcpServer, downloadFile, extractSubstitutions, inspectCommand, inspectLocalPath, inspectRemotePath, inspectWorkingDirectory, listRemoteDirectory, loadHostProfile, normalizeCommandText, readRemoteFile, registerAllTools, splitCommandSegments, stripQuotedText, toHostSummary, uploadFile, writeRemoteFile };
|