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.
- package/LICENSE +21 -0
- package/README.en.md +319 -0
- package/README.md +32 -0
- package/README.zh-CN.md +319 -0
- package/bin/smooth-ssh-mcp-codex +43 -0
- package/dist/audit.d.ts +23 -0
- package/dist/audit.js +140 -0
- package/dist/audit.js.map +1 -0
- package/dist/auth.d.ts +8 -0
- package/dist/auth.js +19 -0
- package/dist/auth.js.map +1 -0
- package/dist/doctor.d.ts +27 -0
- package/dist/doctor.js +169 -0
- package/dist/doctor.js.map +1 -0
- package/dist/forwardManager.d.ts +49 -0
- package/dist/forwardManager.js +141 -0
- package/dist/forwardManager.js.map +1 -0
- package/dist/init.d.ts +21 -0
- package/dist/init.js +80 -0
- package/dist/init.js.map +1 -0
- package/dist/inventory.d.ts +4 -0
- package/dist/inventory.js +262 -0
- package/dist/inventory.js.map +1 -0
- package/dist/mcpServer.d.ts +8 -0
- package/dist/mcpServer.js +403 -0
- package/dist/mcpServer.js.map +1 -0
- package/dist/operations.d.ts +167 -0
- package/dist/operations.js +1240 -0
- package/dist/operations.js.map +1 -0
- package/dist/policy.d.ts +21 -0
- package/dist/policy.js +470 -0
- package/dist/policy.js.map +1 -0
- package/dist/redaction.d.ts +2 -0
- package/dist/redaction.js +64 -0
- package/dist/redaction.js.map +1 -0
- package/dist/runner.d.ts +24 -0
- package/dist/runner.js +90 -0
- package/dist/runner.js.map +1 -0
- package/dist/server.d.ts +9 -0
- package/dist/server.js +130 -0
- package/dist/server.js.map +1 -0
- package/dist/sessionManager.d.ts +77 -0
- package/dist/sessionManager.js +195 -0
- package/dist/sessionManager.js.map +1 -0
- package/dist/sshArgs.d.ts +24 -0
- package/dist/sshArgs.js +135 -0
- package/dist/sshArgs.js.map +1 -0
- package/dist/stateStore.d.ts +27 -0
- package/dist/stateStore.js +99 -0
- package/dist/stateStore.js.map +1 -0
- package/dist/types.d.ts +95 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/docs/mcp-client.example.json +15 -0
- package/examples/hosts.example.yaml +79 -0
- package/package.json +58 -0
package/dist/sshArgs.js
ADDED
|
@@ -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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"}
|
package/dist/types.d.ts
ADDED
|
@@ -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 @@
|
|
|
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
|
+
}
|