smooth-ssh-mcp 0.1.1

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.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +319 -0
  3. package/README.md +32 -0
  4. package/README.zh-CN.md +319 -0
  5. package/bin/smooth-ssh-mcp-codex +43 -0
  6. package/dist/audit.d.ts +23 -0
  7. package/dist/audit.js +140 -0
  8. package/dist/audit.js.map +1 -0
  9. package/dist/auth.d.ts +8 -0
  10. package/dist/auth.js +19 -0
  11. package/dist/auth.js.map +1 -0
  12. package/dist/doctor.d.ts +27 -0
  13. package/dist/doctor.js +169 -0
  14. package/dist/doctor.js.map +1 -0
  15. package/dist/forwardManager.d.ts +49 -0
  16. package/dist/forwardManager.js +141 -0
  17. package/dist/forwardManager.js.map +1 -0
  18. package/dist/init.d.ts +21 -0
  19. package/dist/init.js +80 -0
  20. package/dist/init.js.map +1 -0
  21. package/dist/inventory.d.ts +4 -0
  22. package/dist/inventory.js +262 -0
  23. package/dist/inventory.js.map +1 -0
  24. package/dist/mcpServer.d.ts +8 -0
  25. package/dist/mcpServer.js +403 -0
  26. package/dist/mcpServer.js.map +1 -0
  27. package/dist/operations.d.ts +167 -0
  28. package/dist/operations.js +1240 -0
  29. package/dist/operations.js.map +1 -0
  30. package/dist/policy.d.ts +21 -0
  31. package/dist/policy.js +470 -0
  32. package/dist/policy.js.map +1 -0
  33. package/dist/redaction.d.ts +2 -0
  34. package/dist/redaction.js +64 -0
  35. package/dist/redaction.js.map +1 -0
  36. package/dist/runner.d.ts +24 -0
  37. package/dist/runner.js +90 -0
  38. package/dist/runner.js.map +1 -0
  39. package/dist/server.d.ts +9 -0
  40. package/dist/server.js +130 -0
  41. package/dist/server.js.map +1 -0
  42. package/dist/sessionManager.d.ts +77 -0
  43. package/dist/sessionManager.js +195 -0
  44. package/dist/sessionManager.js.map +1 -0
  45. package/dist/sshArgs.d.ts +24 -0
  46. package/dist/sshArgs.js +135 -0
  47. package/dist/sshArgs.js.map +1 -0
  48. package/dist/stateStore.d.ts +27 -0
  49. package/dist/stateStore.js +99 -0
  50. package/dist/stateStore.js.map +1 -0
  51. package/dist/types.d.ts +95 -0
  52. package/dist/types.js +2 -0
  53. package/dist/types.js.map +1 -0
  54. package/docs/mcp-client.example.json +15 -0
  55. package/examples/hosts.example.yaml +79 -0
  56. package/package.json +58 -0
@@ -0,0 +1,135 @@
1
+ import { createHash } from "node:crypto";
2
+ import { chmodSync, mkdirSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ export function buildSshArgs(host, options) {
5
+ const args = [
6
+ ...baseSshOptions(host, options.controlDir, options.timeoutSeconds ?? host.policy.maxCommandSeconds)
7
+ ];
8
+ if (options.batchMode !== false && !host.passwordEnv) {
9
+ args.push("-o", "BatchMode=yes");
10
+ }
11
+ if (options.forceTty) {
12
+ args.push("-tt");
13
+ }
14
+ args.push("--", targetForHost(host));
15
+ if (options.command !== undefined) {
16
+ args.push(options.command);
17
+ }
18
+ return args;
19
+ }
20
+ export function buildSshControlArgs(host, options) {
21
+ const args = [
22
+ "-O",
23
+ options.controlCommand,
24
+ "-S",
25
+ controlPathForHost(host, options.controlDir),
26
+ "-o",
27
+ "BatchMode=yes",
28
+ "-o",
29
+ "NumberOfPasswordPrompts=0",
30
+ "-o",
31
+ "ConnectTimeout=1"
32
+ ];
33
+ if (host.port)
34
+ args.push("-p", String(host.port));
35
+ if (host.identityFile)
36
+ args.push("-i", host.identityFile);
37
+ if (host.proxyJump)
38
+ args.push("-J", host.proxyJump);
39
+ args.push("--", targetForHost(host));
40
+ return args;
41
+ }
42
+ export function buildScpArgs(host, options) {
43
+ validatePathArg(options.localPath, "localPath");
44
+ validateRemotePathArg(options.remotePath);
45
+ const args = [
46
+ "-o",
47
+ "ControlMaster=auto",
48
+ "-o",
49
+ "ControlPersist=10m",
50
+ "-o",
51
+ `ControlPath=${controlPathForHost(host, options.controlDir)}`,
52
+ "-o",
53
+ "ServerAliveInterval=10",
54
+ "-o",
55
+ "ServerAliveCountMax=3"
56
+ ];
57
+ if (host.port)
58
+ args.push("-P", String(host.port));
59
+ if (host.identityFile)
60
+ args.push("-i", host.identityFile);
61
+ if (host.proxyJump)
62
+ args.push("-J", host.proxyJump);
63
+ const remote = `${targetForHost(host)}:${options.remotePath}`;
64
+ if (options.direction === "upload") {
65
+ args.push("--", options.localPath, remote);
66
+ }
67
+ else {
68
+ args.push("--", remote, options.localPath);
69
+ }
70
+ return args;
71
+ }
72
+ export function controlPathForHost(host, controlDir) {
73
+ mkdirSync(controlDir, { recursive: true, mode: 0o700 });
74
+ chmodSync(controlDir, 0o700);
75
+ const fingerprint = createHash("sha256")
76
+ .update(JSON.stringify({
77
+ id: host.id,
78
+ hostname: host.hostname,
79
+ sshConfigHost: host.sshConfigHost,
80
+ user: host.user,
81
+ port: host.port,
82
+ identityFile: host.identityFile,
83
+ passwordEnv: host.passwordEnv,
84
+ proxyJump: host.proxyJump
85
+ }))
86
+ .digest("hex")
87
+ .slice(0, 16);
88
+ const safeId = host.id.replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 24);
89
+ return join(controlDir, `${safeId}-${fingerprint}.sock`);
90
+ }
91
+ export function targetForHost(host) {
92
+ const targetHost = host.sshConfigHost ?? host.hostname;
93
+ return host.user ? `${host.user}@${targetHost}` : targetHost;
94
+ }
95
+ function baseSshOptions(host, controlDir, timeoutSeconds) {
96
+ const args = [
97
+ "-o",
98
+ `ConnectTimeout=${timeoutSeconds}`,
99
+ "-o",
100
+ "ServerAliveInterval=10",
101
+ "-o",
102
+ "ServerAliveCountMax=3",
103
+ "-o",
104
+ "ControlMaster=auto",
105
+ "-o",
106
+ "ControlPersist=10m",
107
+ "-o",
108
+ `ControlPath=${controlPathForHost(host, controlDir)}`
109
+ ];
110
+ if (host.policy.acceptNewHostKey) {
111
+ args.push("-o", "StrictHostKeyChecking=accept-new");
112
+ }
113
+ if (host.port)
114
+ args.push("-p", String(host.port));
115
+ if (host.identityFile)
116
+ args.push("-i", host.identityFile);
117
+ if (host.proxyJump)
118
+ args.push("-J", host.proxyJump);
119
+ return args;
120
+ }
121
+ function validatePathArg(value, label) {
122
+ if (value.includes("\0") || value.includes("\n") || value.includes("\r")) {
123
+ throw new Error(`Invalid ${label}: contains a control character`);
124
+ }
125
+ if (value.startsWith("-")) {
126
+ throw new Error(`Invalid ${label}: leading dash is not allowed`);
127
+ }
128
+ }
129
+ function validateRemotePathArg(value) {
130
+ validatePathArg(value, "remotePath");
131
+ if (/[\s;|&`$<>]/.test(value)) {
132
+ throw new Error("Invalid remotePath: contains whitespace or shell metacharacters");
133
+ }
134
+ }
135
+ //# sourceMappingURL=sshArgs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sshArgs.js","sourceRoot":"","sources":["../src/sshArgs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type { PermissionLevel } from "./types.js";
2
+ export type HostUseReason = "manual" | "probe" | "exec" | "pty" | "upload" | "download" | "forward";
3
+ export type RecentHost = {
4
+ hostId: string;
5
+ lastUsedAt: string;
6
+ useCount: number;
7
+ reason: HostUseReason;
8
+ };
9
+ export type SmoothSshState = {
10
+ selectedHostId?: string;
11
+ recentHosts: RecentHost[];
12
+ hostPermissionLevels: Record<string, PermissionLevel>;
13
+ };
14
+ export declare class StateStore {
15
+ private readonly path?;
16
+ private state;
17
+ constructor(path?: string | undefined);
18
+ getState(): SmoothSshState;
19
+ selectHost(hostId: string, reason?: HostUseReason): SmoothSshState;
20
+ recordHostUse(hostId: string, reason: HostUseReason): void;
21
+ recentHosts(): RecentHost[];
22
+ setPermissionLevel(hostId: string, permissionLevel: PermissionLevel): SmoothSshState;
23
+ permissionLevelFor(hostId: string): PermissionLevel | undefined;
24
+ private load;
25
+ private save;
26
+ }
27
+ export declare function defaultStatePath(): string;
@@ -0,0 +1,99 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+ export class StateStore {
4
+ path;
5
+ state;
6
+ constructor(path) {
7
+ this.path = path;
8
+ this.state = this.load();
9
+ }
10
+ getState() {
11
+ return {
12
+ selectedHostId: this.state.selectedHostId,
13
+ recentHosts: [...this.state.recentHosts],
14
+ hostPermissionLevels: { ...this.state.hostPermissionLevels }
15
+ };
16
+ }
17
+ selectHost(hostId, reason = "manual") {
18
+ this.state.selectedHostId = hostId;
19
+ this.recordHostUse(hostId, reason);
20
+ return this.getState();
21
+ }
22
+ recordHostUse(hostId, reason) {
23
+ const now = new Date().toISOString();
24
+ const existing = this.state.recentHosts.find((entry) => entry.hostId === hostId);
25
+ const next = {
26
+ hostId,
27
+ lastUsedAt: now,
28
+ useCount: (existing?.useCount ?? 0) + 1,
29
+ reason
30
+ };
31
+ this.state.recentHosts = [next, ...this.state.recentHosts.filter((entry) => entry.hostId !== hostId)].slice(0, 20);
32
+ this.save();
33
+ }
34
+ recentHosts() {
35
+ return [...this.state.recentHosts];
36
+ }
37
+ setPermissionLevel(hostId, permissionLevel) {
38
+ this.state.hostPermissionLevels = {
39
+ ...this.state.hostPermissionLevels,
40
+ [hostId]: permissionLevel
41
+ };
42
+ this.save();
43
+ return this.getState();
44
+ }
45
+ permissionLevelFor(hostId) {
46
+ return this.state.hostPermissionLevels[hostId];
47
+ }
48
+ load() {
49
+ if (!this.path || !existsSync(this.path))
50
+ return { recentHosts: [], hostPermissionLevels: {} };
51
+ const parsed = JSON.parse(readFileSync(this.path, "utf8"));
52
+ return {
53
+ selectedHostId: typeof parsed.selectedHostId === "string" ? parsed.selectedHostId : undefined,
54
+ recentHosts: Array.isArray(parsed.recentHosts)
55
+ ? parsed.recentHosts
56
+ .filter((entry) => Boolean(entry) && typeof entry.hostId === "string")
57
+ .map((entry) => ({
58
+ hostId: entry.hostId,
59
+ lastUsedAt: typeof entry.lastUsedAt === "string" ? entry.lastUsedAt : new Date(0).toISOString(),
60
+ useCount: typeof entry.useCount === "number" ? entry.useCount : 1,
61
+ reason: isReason(entry.reason) ? entry.reason : "manual"
62
+ }))
63
+ : [],
64
+ hostPermissionLevels: normalizePermissionLevels(parsed.hostPermissionLevels)
65
+ };
66
+ }
67
+ save() {
68
+ if (!this.path)
69
+ return;
70
+ const resolved = resolve(this.path);
71
+ mkdirSync(dirname(resolved), { recursive: true, mode: 0o700 });
72
+ writeFileSync(resolved, JSON.stringify(this.state, null, 2) + "\n", { encoding: "utf8", mode: 0o600 });
73
+ chmodSync(resolved, 0o600);
74
+ }
75
+ }
76
+ export function defaultStatePath() {
77
+ return process.env.SMOOTH_SSH_MCP_STATE ?? join(process.env.HOME ?? process.cwd(), ".config", "smooth-ssh-mcp", "state.json");
78
+ }
79
+ function isReason(value) {
80
+ return (value === "manual" ||
81
+ value === "probe" ||
82
+ value === "exec" ||
83
+ value === "pty" ||
84
+ value === "upload" ||
85
+ value === "download" ||
86
+ value === "forward");
87
+ }
88
+ function normalizePermissionLevels(value) {
89
+ if (!value || typeof value !== "object" || Array.isArray(value))
90
+ return {};
91
+ const levels = {};
92
+ for (const [hostId, level] of Object.entries(value)) {
93
+ if (level === 1 || level === 2 || level === 3) {
94
+ levels[hostId] = level;
95
+ }
96
+ }
97
+ return levels;
98
+ }
99
+ //# sourceMappingURL=stateStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stateStore.js","sourceRoot":"","sources":["../src/stateStore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,95 @@
1
+ export type Environment = "dev" | "staging" | "prod" | "unknown";
2
+ export type RiskLevel = "low" | "medium" | "high" | "critical";
3
+ export type Operation = "exec" | "pty" | "pty-input" | "upload" | "download" | "forward" | "permission";
4
+ export type PermissionLevel = 1 | 2 | 3;
5
+ export type CommandMode = "shell" | "argv";
6
+ export type CommandAccess = "read" | "write" | "restart" | "firewall" | "destructive" | "unknown";
7
+ export type HostPolicy = {
8
+ allowExec: boolean;
9
+ allowPty: boolean;
10
+ allowUpload: boolean;
11
+ allowDownload: boolean;
12
+ allowForward: boolean;
13
+ acceptNewHostKey: boolean;
14
+ requireConfirmForSudo: boolean;
15
+ requireConfirmForWrite: boolean;
16
+ requireConfirmForProd: boolean;
17
+ permissionLevel: PermissionLevel;
18
+ deniedCommandPatterns: string[];
19
+ maxCommandSeconds: number;
20
+ maxOutputBytes: number;
21
+ };
22
+ export type Host = {
23
+ id: string;
24
+ hostname: string;
25
+ port?: number;
26
+ user?: string;
27
+ identityFile?: string;
28
+ passwordEnv?: string;
29
+ sshConfigHost?: string;
30
+ proxyJump?: string;
31
+ defaultCwd?: string;
32
+ tags: string[];
33
+ environment: Environment;
34
+ riskLevel: Exclude<RiskLevel, "critical">;
35
+ capabilities?: {
36
+ sudo?: boolean;
37
+ docker?: boolean;
38
+ nginx?: boolean;
39
+ systemd?: boolean;
40
+ openwrt?: boolean;
41
+ };
42
+ policy: HostPolicy;
43
+ };
44
+ export type Inventory = {
45
+ hosts: Host[];
46
+ };
47
+ export type PolicyDecision = {
48
+ allowed: boolean;
49
+ confirmationRequired: boolean;
50
+ risk: RiskLevel;
51
+ reasons: string[];
52
+ };
53
+ export type ConfirmationRequired = {
54
+ confirmationRequired: true;
55
+ token: string;
56
+ risk: RiskLevel;
57
+ reason: string;
58
+ hostId: string;
59
+ operation: Operation;
60
+ preview: {
61
+ command?: string;
62
+ stdinHash?: string;
63
+ stdinBytes?: number;
64
+ localPath?: string;
65
+ remotePath?: string;
66
+ ports?: string[];
67
+ };
68
+ expiresAt: string;
69
+ };
70
+ export type Redaction = {
71
+ pattern: string;
72
+ count: number;
73
+ };
74
+ export type RedactedText = {
75
+ text: string;
76
+ redactions: Redaction[];
77
+ truncated: boolean;
78
+ originalBytes: number;
79
+ };
80
+ export type ExecResult = {
81
+ hostId: string;
82
+ commandId: string;
83
+ exitCode: number | null;
84
+ signal?: NodeJS.Signals | null;
85
+ stdout: string;
86
+ stderr: string;
87
+ startedAt: string;
88
+ endedAt: string;
89
+ durationMs: number;
90
+ truncated: boolean;
91
+ redactions: Redaction[];
92
+ diagnostic?: string;
93
+ attempts?: number;
94
+ argv?: string[];
95
+ };
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,15 @@
1
+ {
2
+ "mcpServers": {
3
+ "smooth-ssh": {
4
+ "command": "node",
5
+ "args": [
6
+ "/path/to/smooth-ssh-mcp/dist/server.js",
7
+ "--config",
8
+ "/home/you/.config/smooth-ssh-mcp/hosts.yaml"
9
+ ],
10
+ "env": {
11
+ "SMOOTH_SSH_PASSWORD_PASSWORD_VPS": "set-this-in-your-client-secret-env"
12
+ }
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,79 @@
1
+ hosts:
2
+ - id: lab-linux
3
+ hostname: 192.0.2.10
4
+ user: ubuntu
5
+ port: 22
6
+ identityFile: ~/.ssh/lab.pem
7
+ environment: dev
8
+ tags: [linux, lab]
9
+ capabilities:
10
+ sudo: true
11
+ systemd: true
12
+ policy:
13
+ allowExec: true
14
+ allowPty: true
15
+ allowUpload: true
16
+ allowDownload: true
17
+ allowForward: true
18
+ acceptNewHostKey: false
19
+ permissionLevel: 2
20
+ maxCommandSeconds: 30
21
+ maxOutputBytes: 65536
22
+
23
+ - id: prod-api
24
+ hostname: 203.0.113.10
25
+ user: root
26
+ port: 22
27
+ environment: prod
28
+ riskLevel: high
29
+ tags: [prod, api, nginx]
30
+ policy:
31
+ allowExec: true
32
+ allowPty: true
33
+ allowUpload: false
34
+ allowDownload: false
35
+ allowForward: true
36
+ acceptNewHostKey: false
37
+ permissionLevel: 2
38
+ requireConfirmForProd: true
39
+ requireConfirmForSudo: true
40
+ requireConfirmForWrite: true
41
+ maxCommandSeconds: 20
42
+ maxOutputBytes: 65536
43
+
44
+ - id: password-vps
45
+ hostname: 203.0.113.20
46
+ user: root
47
+ port: 22
48
+ passwordEnv: SMOOTH_SSH_PASSWORD_PASSWORD_VPS
49
+ environment: prod
50
+ riskLevel: high
51
+ tags: [prod, password-env]
52
+ policy:
53
+ allowExec: true
54
+ allowPty: false
55
+ allowUpload: false
56
+ allowDownload: false
57
+ allowForward: false
58
+ acceptNewHostKey: false
59
+ permissionLevel: 2
60
+ requireConfirmForProd: true
61
+ requireConfirmForSudo: true
62
+ requireConfirmForWrite: true
63
+ maxCommandSeconds: 20
64
+ maxOutputBytes: 65536
65
+
66
+ - id: openwrt-gateway
67
+ sshConfigHost: openwrt-gw
68
+ environment: staging
69
+ tags: [openwrt, gateway]
70
+ capabilities:
71
+ openwrt: true
72
+ policy:
73
+ allowExec: true
74
+ allowPty: true
75
+ allowUpload: false
76
+ allowDownload: false
77
+ allowForward: false
78
+ acceptNewHostKey: false
79
+ permissionLevel: 2
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "smooth-ssh-mcp",
3
+ "version": "0.1.1",
4
+ "description": "MCP server for safer, structured OpenSSH operations by AI assistants.",
5
+ "type": "module",
6
+ "bin": {
7
+ "smooth-ssh-mcp": "dist/server.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc -p tsconfig.build.json",
11
+ "dev": "tsx src/server.ts",
12
+ "test": "vitest run",
13
+ "test:cli": "vitest run --config vitest.cli.config.ts",
14
+ "typecheck": "tsc -p tsconfig.json --noEmit",
15
+ "prepack": "npm run build"
16
+ },
17
+ "dependencies": {
18
+ "@modelcontextprotocol/sdk": "^1.29.0",
19
+ "yaml": "^2.8.1",
20
+ "zod": "^3.25.76"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "^24.0.0",
24
+ "tsx": "^4.20.6",
25
+ "typescript": "^5.9.3",
26
+ "vitest": "^3.2.4"
27
+ },
28
+ "engines": {
29
+ "node": ">=20"
30
+ },
31
+ "license": "MIT",
32
+ "homepage": "https://github.com/Da-bai-da/smooth-ssh-mcp#readme",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/Da-bai-da/smooth-ssh-mcp.git"
36
+ },
37
+ "bugs": {
38
+ "url": "https://github.com/Da-bai-da/smooth-ssh-mcp/issues"
39
+ },
40
+ "keywords": [
41
+ "mcp",
42
+ "ssh",
43
+ "openssh",
44
+ "ai",
45
+ "codex",
46
+ "remote-ops"
47
+ ],
48
+ "files": [
49
+ "dist",
50
+ "bin",
51
+ "README.md",
52
+ "README.en.md",
53
+ "README.zh-CN.md",
54
+ "LICENSE",
55
+ "examples",
56
+ "docs"
57
+ ]
58
+ }