@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # ssh-mcp
2
2
 
3
- 纯命令行 SSH/SFTP 工具:在多台远程服务器上执行命令、传输大文件(断点续传)。支持 CLI 模式和 MCP stdio 模式(`--mcp`)。
3
+ 纯命令行 SSH/SFTP 工具:远程执行命令、批量操作、大文件传输(断点续传)、端口转发、健康检查、配置对比、快照打包、tail -f、watch 等 27 个子命令。CLI + MCP 双模式。
4
4
 
5
5
  [![NPM](https://img.shields.io/npm/v/@bingzi-233/ssh-mcp?color=CB3837&logo=npm)](https://www.npmjs.com/package/@bingzi-233/ssh-mcp)
6
6
  [![Node](https://img.shields.io/node/v/@bingzi-233/ssh-mcp?color=339933&logo=nodedotjs)](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 --help
20
-
21
- # 列出服务器
22
- ssh-mcp list-servers
23
-
24
- # 执行远程命令
25
- ssh-mcp run-command -s prod-web -c "df -h /"
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
- | `open-session` | 打开长连接会话(复用 TCP 连接) |
44
- | `close-session` | 关闭长连接会话 |
45
- | `list-sessions` | 列出当前所有长连接会话 |
46
- | `upload` | 上传文件到远程(后台任务,断点续传) |
47
- | `download` | 从远程下载文件(后台任务,断点续传) |
48
- | `transfer-status` | 查看传输进度 |
49
- | `cancel-transfer` | 取消传输 |
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": "生产环境 Web 服务器",
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
- SID=$(ssh-mcp open-session -s prod-web)
88
- ssh-mcp run-command -s prod-web --session $SID -c "hostname"
89
- ssh-mcp run-command -s prod-web --session $SID -c "uptime"
90
- ssh-mcp close-session -s $SID
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
- 复用 TCP 连接,省去重复握手和认证。注意每条命令仍在独立 channel 中执行,不保留工作目录。
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
  ```
@@ -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
+ }