@bingzi-233/ssh-mcp 1.2.0 → 1.4.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 +65 -73
- package/dist/forward.js +108 -0
- package/dist/index.js +1138 -232
- package/dist/sftp-ops.js +160 -0
- package/package.json +6 -6
package/dist/sftp-ops.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { posix } from "node:path";
|
|
2
|
+
import { createConnection } from "./ssh.js";
|
|
3
|
+
function openSftp(conn) {
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
conn.sftp((err, sftp) => (err ? reject(err) : resolve(sftp)));
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
function classify(mode) {
|
|
9
|
+
const ifmt = mode & 0o170000;
|
|
10
|
+
if (ifmt === 0o040000)
|
|
11
|
+
return "directory";
|
|
12
|
+
if (ifmt === 0o100000)
|
|
13
|
+
return "file";
|
|
14
|
+
if (ifmt === 0o120000)
|
|
15
|
+
return "link";
|
|
16
|
+
return "other";
|
|
17
|
+
}
|
|
18
|
+
function formatMode(mode) {
|
|
19
|
+
const r = (mode & 0o4) ? "r" : "-";
|
|
20
|
+
const w = (mode & 0o2) ? "w" : "-";
|
|
21
|
+
const x = (mode & 0o1) ? "x" : "-";
|
|
22
|
+
return r + w + x;
|
|
23
|
+
}
|
|
24
|
+
function formatPerms(mode) {
|
|
25
|
+
const f = classify(mode);
|
|
26
|
+
const c = f === "directory" ? "d" : f === "link" ? "l" : f === "other" ? "?" : "-";
|
|
27
|
+
return c + formatMode(mode >> 6) + formatMode(mode >> 3) + formatMode(mode);
|
|
28
|
+
}
|
|
29
|
+
export function formatLsLong(e) {
|
|
30
|
+
return `${formatPerms(e.mode)} ${String(e.uid).padStart(5)} ${String(e.gid).padStart(5)} ${String(e.size).padStart(10)} ${new Date(e.mtime * 1000).toISOString().replace("T", " ").replace(/\..*/, "")} ${e.name}`;
|
|
31
|
+
}
|
|
32
|
+
export function formatLsShort(e) {
|
|
33
|
+
return e.name;
|
|
34
|
+
}
|
|
35
|
+
export async function listDirectory(cfg, path) {
|
|
36
|
+
const conn = await createConnection(cfg, 20_000);
|
|
37
|
+
try {
|
|
38
|
+
const sftp = await openSftp(conn);
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
sftp.readdir(path, (err, entries) => {
|
|
41
|
+
conn.end();
|
|
42
|
+
if (err)
|
|
43
|
+
return reject(err);
|
|
44
|
+
resolve(entries.map((e) => ({
|
|
45
|
+
name: e.filename,
|
|
46
|
+
type: classify(e.attrs.mode),
|
|
47
|
+
size: e.attrs.size,
|
|
48
|
+
mode: e.attrs.mode,
|
|
49
|
+
uid: e.attrs.uid,
|
|
50
|
+
gid: e.attrs.gid,
|
|
51
|
+
mtime: e.attrs.mtime,
|
|
52
|
+
atime: e.attrs.atime,
|
|
53
|
+
longname: e.longname,
|
|
54
|
+
})));
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
conn.end();
|
|
60
|
+
throw e;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export async function statPath(cfg, path) {
|
|
64
|
+
const conn = await createConnection(cfg, 20_000);
|
|
65
|
+
try {
|
|
66
|
+
const sftp = await openSftp(conn);
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
sftp.stat(path, (err, s) => {
|
|
69
|
+
conn.end();
|
|
70
|
+
if (err)
|
|
71
|
+
return reject(err);
|
|
72
|
+
resolve({
|
|
73
|
+
type: classify(s.mode),
|
|
74
|
+
size: s.size,
|
|
75
|
+
mode: s.mode,
|
|
76
|
+
uid: s.uid,
|
|
77
|
+
gid: s.gid,
|
|
78
|
+
mtime: s.mtime,
|
|
79
|
+
atime: s.atime,
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
conn.end();
|
|
86
|
+
throw e;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export async function removePath(cfg, path, recursive) {
|
|
90
|
+
const conn = await createConnection(cfg, 20_000);
|
|
91
|
+
try {
|
|
92
|
+
const sftp = await openSftp(conn);
|
|
93
|
+
// 先 stat 确定类型
|
|
94
|
+
const s = await new Promise((resolve, reject) => {
|
|
95
|
+
sftp.stat(path, (err, st) => {
|
|
96
|
+
if (err)
|
|
97
|
+
return reject(err);
|
|
98
|
+
resolve({ type: classify(st.mode) });
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
if (s.type === "directory") {
|
|
102
|
+
if (!recursive)
|
|
103
|
+
throw new Error(`"${path}" 是目录,需要 --recursive 选项`);
|
|
104
|
+
// 递归删除目录内容
|
|
105
|
+
const entries = await new Promise((res, rej) => {
|
|
106
|
+
sftp.readdir(path, (err, e) => (err ? rej(err) : res(e)));
|
|
107
|
+
});
|
|
108
|
+
for (const e of entries) {
|
|
109
|
+
if (e.filename === "." || e.filename === "..")
|
|
110
|
+
continue;
|
|
111
|
+
await removePath(cfg, posix.join(path, e.filename), true);
|
|
112
|
+
}
|
|
113
|
+
await new Promise((res, rej) => {
|
|
114
|
+
sftp.rmdir(path, (err) => (err ? rej(err) : res()));
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
await new Promise((res, rej) => {
|
|
119
|
+
sftp.unlink(path, (err) => (err ? rej(err) : res()));
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
conn.end();
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
conn.end();
|
|
126
|
+
throw e;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
export async function makeDir(cfg, path, parents) {
|
|
130
|
+
const conn = await createConnection(cfg, 20_000);
|
|
131
|
+
try {
|
|
132
|
+
const sftp = await openSftp(conn);
|
|
133
|
+
if (parents) {
|
|
134
|
+
// 逐层创建
|
|
135
|
+
const parts = path.split("/").filter(Boolean);
|
|
136
|
+
let current = path.startsWith("/") ? "/" : "";
|
|
137
|
+
for (const p of parts) {
|
|
138
|
+
current = current ? posix.join(current, p) : p;
|
|
139
|
+
await new Promise((resolve, reject) => {
|
|
140
|
+
sftp.mkdir(current, { mode: 0o755 }, (err) => {
|
|
141
|
+
// 已存在不算错
|
|
142
|
+
if (err && err.code !== 4)
|
|
143
|
+
return reject(err);
|
|
144
|
+
resolve();
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
await new Promise((res, rej) => {
|
|
151
|
+
sftp.mkdir(path, { mode: 0o755 }, (err) => (err ? rej(err) : res()));
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
conn.end();
|
|
155
|
+
}
|
|
156
|
+
catch (e) {
|
|
157
|
+
conn.end();
|
|
158
|
+
throw e;
|
|
159
|
+
}
|
|
160
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bingzi-233/ssh-mcp",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"description": "纯命令行 SSH/SFTP 工具:在多台远程服务器上执行命令、传输大文件(断点续传)。支持 CLI 模式和 MCP stdio 模式。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"ssh-mcp": "dist/index.js"
|
|
@@ -19,13 +19,13 @@
|
|
|
19
19
|
"prepublishOnly": "npm run build"
|
|
20
20
|
},
|
|
21
21
|
"keywords": [
|
|
22
|
-
"
|
|
23
|
-
"model-context-protocol",
|
|
22
|
+
"cli",
|
|
24
23
|
"ssh",
|
|
25
24
|
"sftp",
|
|
25
|
+
"mcp",
|
|
26
|
+
"model-context-protocol",
|
|
26
27
|
"remote",
|
|
27
|
-
"file-transfer"
|
|
28
|
-
"claude"
|
|
28
|
+
"file-transfer"
|
|
29
29
|
],
|
|
30
30
|
"author": "BingZi-233",
|
|
31
31
|
"license": "MIT",
|