@bingzi-233/ssh-mcp 1.3.0 → 1.5.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 +42 -46
- package/dist/forward.js +108 -0
- package/dist/index.js +1343 -2
- package/dist/ops.js +470 -0
- package/dist/sftp-ops.js +160 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ssh-mcp
|
|
2
2
|
|
|
3
|
-
纯命令行 SSH/SFTP
|
|
3
|
+
纯命令行 SSH/SFTP 工具:远程执行命令、批量操作、大文件传输(断点续传)、端口转发、健康检查、配置对比、快照打包、tail -f、watch 等 27 个子命令。CLI + MCP 双模式。
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@bingzi-233/ssh-mcp)
|
|
6
6
|
[](https://nodejs.org)
|
|
@@ -12,26 +12,16 @@
|
|
|
12
12
|
## CLI 快速上手
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
# 安装
|
|
16
15
|
npm i -g @bingzi-233/ssh-mcp
|
|
17
16
|
|
|
18
|
-
#
|
|
19
|
-
ssh-mcp
|
|
20
|
-
|
|
21
|
-
#
|
|
22
|
-
ssh-mcp
|
|
23
|
-
|
|
24
|
-
#
|
|
25
|
-
ssh-mcp
|
|
26
|
-
|
|
27
|
-
# 上传文件(支持断点续传)
|
|
28
|
-
ssh-mcp upload -s prod-web -l ./dist.tar.gz -r /tmp/dist.tar.gz
|
|
29
|
-
|
|
30
|
-
# 下载文件
|
|
31
|
-
ssh-mcp download -s prod-web -r /var/log/app.log -l ./logs/app.log
|
|
32
|
-
|
|
33
|
-
# 传输进度
|
|
34
|
-
ssh-mcp transfer-status
|
|
17
|
+
ssh-mcp list-servers # 列出服务器
|
|
18
|
+
ssh-mcp run-command -s prod-web -c "df -h /" # 执行命令
|
|
19
|
+
ssh-mcp batch --servers web1,web2,web3 -c "uptime" # 批量执行
|
|
20
|
+
ssh-mcp upload -s prod-web -l ./dist.tar.gz -r /tmp/ # 上传文件
|
|
21
|
+
ssh-mcp health -s prod-web # 健康检查
|
|
22
|
+
ssh-mcp ls -s prod-web -p /var/log # 列出目录
|
|
23
|
+
ssh-mcp tail-f -s prod-web -p /var/log/app.log # 追尾日志
|
|
24
|
+
ssh-mcp forward -s prod-web -L 8080:192.168.1.5:80 # 端口转发
|
|
35
25
|
```
|
|
36
26
|
|
|
37
27
|
## 命令一览
|
|
@@ -40,13 +30,23 @@ ssh-mcp transfer-status
|
|
|
40
30
|
|---|---|
|
|
41
31
|
| `list-servers` | 列出所有已配置的服务器 |
|
|
42
32
|
| `run-command` | 在远程服务器上执行命令 |
|
|
43
|
-
| `
|
|
44
|
-
| `close-session` |
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
47
|
-
| `
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
33
|
+
| `batch` | 在多台服务器上批量执行同一命令 |
|
|
34
|
+
| `open-session` / `close-session` / `list-sessions` | 长连接会话管理 |
|
|
35
|
+
| `upload` / `download` | 大文件传输(后台任务,断点续传) |
|
|
36
|
+
| `transfer-status` / `cancel-transfer` | 传输进度与取消 |
|
|
37
|
+
| `forward` / `list-forwards` / `close-forward` | SSH 端口转发(本地/远程) |
|
|
38
|
+
| `ls` / `stat` | 列出远程目录、查看文件信息 |
|
|
39
|
+
| `rm` / `mkdir` | 删除远程文件/目录、创建远程目录 |
|
|
40
|
+
| `health` | 一键健康检查(OS/磁盘/内存/负载/CPU) |
|
|
41
|
+
| `cert-info` | 查看 SSL/TLS 证书信息 |
|
|
42
|
+
| `copy-between` | 服务器间直传文件(不经本地中转) |
|
|
43
|
+
| `diff-servers` | 对比两台服务器同一文件差异 |
|
|
44
|
+
| `exec-script` | 上传脚本执行并自动清理 |
|
|
45
|
+
| `snapshot` | 远程目录 tar.gz 打包下载 |
|
|
46
|
+
| `tail-f` / `stop-tail` / `list-tails` | 持续追踪远程文件(SFTP) |
|
|
47
|
+
| `watch` / `stop-watch` / `list-watches` | 定时重复执行命令并高亮差异 |
|
|
48
|
+
| `curl` | 从远程服务器发起 HTTP 请求 |
|
|
49
|
+
| `env` | 收集远程服务器环境信息 |
|
|
50
50
|
|
|
51
51
|
每个子命令运行 `ssh-mcp <子命令> --help` 查看详细用法。
|
|
52
52
|
|
|
@@ -56,24 +56,15 @@ ssh-mcp transfer-status
|
|
|
56
56
|
|
|
57
57
|
```json
|
|
58
58
|
{
|
|
59
|
-
"security": {
|
|
60
|
-
"blocked_patterns": []
|
|
61
|
-
},
|
|
59
|
+
"security": { "blocked_patterns": [] },
|
|
62
60
|
"servers": [
|
|
63
61
|
{
|
|
64
62
|
"name": "prod-web",
|
|
65
|
-
"description": "生产环境
|
|
63
|
+
"description": "生产环境",
|
|
66
64
|
"host": "192.168.1.10",
|
|
67
65
|
"port": 22,
|
|
68
66
|
"username": "deploy",
|
|
69
67
|
"privateKeyPath": "~/.ssh/id_rsa"
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
"name": "db",
|
|
73
|
-
"description": "数据库服务器",
|
|
74
|
-
"host": "db.example.com",
|
|
75
|
-
"username": "admin",
|
|
76
|
-
"password": "your-password"
|
|
77
68
|
}
|
|
78
69
|
]
|
|
79
70
|
}
|
|
@@ -81,16 +72,24 @@ ssh-mcp transfer-status
|
|
|
81
72
|
|
|
82
73
|
鉴权优先级:私钥 → 密码 → ssh-agent。修改配置无需重启。
|
|
83
74
|
|
|
84
|
-
##
|
|
75
|
+
## 端口转发
|
|
85
76
|
|
|
86
77
|
```bash
|
|
87
|
-
|
|
88
|
-
ssh-mcp
|
|
89
|
-
|
|
90
|
-
|
|
78
|
+
# 本地转发:本机 8080 → 经 SSH 服务器 → 内网 192.168.1.5:80
|
|
79
|
+
ssh-mcp forward -s prod-web -L 8080:192.168.1.5:80
|
|
80
|
+
|
|
81
|
+
# 远程转发:SSH 服务器 9000 → 回传到本机 localhost:3000
|
|
82
|
+
ssh-mcp forward -s prod-web -R 9000:127.0.0.1:3000
|
|
83
|
+
|
|
84
|
+
ssh-mcp list-forwards
|
|
85
|
+
ssh-mcp close-forward -i f1
|
|
91
86
|
```
|
|
92
87
|
|
|
93
|
-
|
|
88
|
+
## 批量执行
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
ssh-mcp batch --servers prod-web,prod-api,prod-worker -c "systemctl status nginx"
|
|
92
|
+
```
|
|
94
93
|
|
|
95
94
|
## 安全策略
|
|
96
95
|
|
|
@@ -101,10 +100,7 @@ ssh-mcp close-session -s $SID
|
|
|
101
100
|
以 MCP stdio 服务运行(供 Claude Code 等 AI 客户端调用):
|
|
102
101
|
|
|
103
102
|
```bash
|
|
104
|
-
# 手动注册
|
|
105
103
|
claude mcp add ssh -- npx -y @bingzi-233/ssh-mcp --mcp
|
|
106
|
-
|
|
107
|
-
# 或通过插件安装
|
|
108
104
|
/plugin marketplace add BingZi-233/ssh-mcp
|
|
109
105
|
/plugin install ssh-mcp@bingzi-plugins
|
|
110
106
|
```
|
package/dist/forward.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { createServer, Socket } from "node:net";
|
|
2
|
+
import { createConnection } from "./ssh.js";
|
|
3
|
+
const forwards = new Map();
|
|
4
|
+
/** 存活的本地 net.Server / SSH 连接,用于关闭 */
|
|
5
|
+
const resources = new Map();
|
|
6
|
+
let counter = 0;
|
|
7
|
+
function track(id, conn, server) {
|
|
8
|
+
resources.set(id, { conn, server });
|
|
9
|
+
conn.on("close", () => {
|
|
10
|
+
const f = forwards.get(id);
|
|
11
|
+
if (f)
|
|
12
|
+
f.state = "stopped";
|
|
13
|
+
server?.close();
|
|
14
|
+
});
|
|
15
|
+
conn.on("error", () => {
|
|
16
|
+
const f = forwards.get(id);
|
|
17
|
+
if (f)
|
|
18
|
+
f.state = "stopped";
|
|
19
|
+
server?.close();
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 启动端口转发。
|
|
24
|
+
*
|
|
25
|
+
* type=local: 监听本机 localPort,流量经 SSH 服务器转发到 remoteHost:remotePort。
|
|
26
|
+
* type=remote: SSH 服务器监听 remotePort,流量经本机转发到 localHost:localPort。
|
|
27
|
+
*/
|
|
28
|
+
export async function startForward(cfg, type, localHost, localPort, remoteHost, remotePort) {
|
|
29
|
+
const conn = await createConnection(cfg, 20_000);
|
|
30
|
+
const id = `f${++counter}`;
|
|
31
|
+
if (type === "local") {
|
|
32
|
+
await new Promise((resolve, reject) => {
|
|
33
|
+
const server = createServer((socket) => {
|
|
34
|
+
conn.forwardOut(localHost, localPort, remoteHost, remotePort, (err, stream) => {
|
|
35
|
+
if (err) {
|
|
36
|
+
socket.destroy();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
socket.pipe(stream).pipe(socket);
|
|
40
|
+
stream.on("error", () => socket.destroy());
|
|
41
|
+
socket.on("error", () => stream.destroy());
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
server.on("error", reject);
|
|
45
|
+
server.listen(localPort, localHost, () => {
|
|
46
|
+
server.removeListener("error", reject);
|
|
47
|
+
track(id, conn, server);
|
|
48
|
+
resolve();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
// remote: SSH 服务器暴露端口,转发回本机
|
|
54
|
+
await new Promise((resolve, reject) => {
|
|
55
|
+
conn.forwardIn(remoteHost, remotePort, (err) => {
|
|
56
|
+
if (err)
|
|
57
|
+
reject(err);
|
|
58
|
+
else {
|
|
59
|
+
track(id, conn);
|
|
60
|
+
resolve();
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
// 处理来自远程的入站连接
|
|
65
|
+
conn.on("tcp connection", (info, accept) => {
|
|
66
|
+
const stream = accept();
|
|
67
|
+
const sock = new Socket();
|
|
68
|
+
sock.connect(remotePort, localHost, () => {
|
|
69
|
+
sock.pipe(stream).pipe(sock);
|
|
70
|
+
stream.on("error", () => sock.destroy());
|
|
71
|
+
sock.on("error", () => stream.destroy());
|
|
72
|
+
});
|
|
73
|
+
sock.on("error", () => stream.end());
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
const fwd = {
|
|
77
|
+
id,
|
|
78
|
+
server: cfg.name,
|
|
79
|
+
type,
|
|
80
|
+
localHost,
|
|
81
|
+
localPort,
|
|
82
|
+
remoteHost,
|
|
83
|
+
remotePort,
|
|
84
|
+
state: "running",
|
|
85
|
+
createdAt: Date.now(),
|
|
86
|
+
};
|
|
87
|
+
forwards.set(id, fwd);
|
|
88
|
+
return fwd;
|
|
89
|
+
}
|
|
90
|
+
export function getForward(id) {
|
|
91
|
+
return forwards.get(id);
|
|
92
|
+
}
|
|
93
|
+
export function listForwards() {
|
|
94
|
+
return [...forwards.values()];
|
|
95
|
+
}
|
|
96
|
+
export function closeForward(id) {
|
|
97
|
+
const fwd = forwards.get(id);
|
|
98
|
+
if (!fwd || fwd.state === "stopped")
|
|
99
|
+
return false;
|
|
100
|
+
fwd.state = "stopped";
|
|
101
|
+
const res = resources.get(id);
|
|
102
|
+
if (res) {
|
|
103
|
+
res.server?.close();
|
|
104
|
+
res.conn.end();
|
|
105
|
+
resources.delete(id);
|
|
106
|
+
}
|
|
107
|
+
return true;
|
|
108
|
+
}
|