mcp-ssh-pty 1.1.0 → 1.2.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/README.md +36 -24
- package/dist/cli.js +6 -4
- package/dist/index.js +0 -4
- package/dist/shell-manager.d.ts +16 -1
- package/dist/shell-manager.js +111 -24
- package/dist/ssh-manager.d.ts +15 -0
- package/dist/ssh-manager.js +46 -2
- package/dist/tools.js +25 -5
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ npm install -g mcp-ssh-pty
|
|
|
11
11
|
### Add to Claude Code
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
claude mcp add ssh -- npx -y mcp-ssh-pty
|
|
14
|
+
claude mcp add --transport stdio ssh -- npx -y mcp-ssh-pty
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
## CLI Commands
|
|
@@ -28,7 +28,7 @@ mcp-ssh-pty list --all # Show both levels
|
|
|
28
28
|
### Add server
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
|
-
# Interactive mode
|
|
31
|
+
# Interactive mode
|
|
32
32
|
mcp-ssh-pty add
|
|
33
33
|
|
|
34
34
|
# Save to project level
|
|
@@ -58,22 +58,6 @@ mcp-ssh-pty test my-server
|
|
|
58
58
|
mcp-ssh-pty config
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
```
|
|
62
|
-
? 选择配置级别:
|
|
63
|
-
❯ 📁 项目级别 (已存在)
|
|
64
|
-
🌐 用户级别 (新建)
|
|
65
|
-
|
|
66
|
-
? 选择操作:
|
|
67
|
-
❯ 📋 查看所有服务器
|
|
68
|
-
➕ 添加服务器
|
|
69
|
-
✏️ 编辑服务器
|
|
70
|
-
🗑️ 删除服务器
|
|
71
|
-
🔌 测试连接
|
|
72
|
-
🔄 切换配置级别
|
|
73
|
-
📁 显示配置文件路径
|
|
74
|
-
🚪 退出
|
|
75
|
-
```
|
|
76
|
-
|
|
77
61
|
## Configuration
|
|
78
62
|
|
|
79
63
|
### Config file locations
|
|
@@ -84,8 +68,6 @@ mcp-ssh-pty config
|
|
|
84
68
|
| User | `~/.claude/ssh-servers.json` | Low |
|
|
85
69
|
| Custom | `SSH_MCP_CONFIG_PATH` env | Highest |
|
|
86
70
|
|
|
87
|
-
Project level config overrides user level when exists.
|
|
88
|
-
|
|
89
71
|
### Config format
|
|
90
72
|
|
|
91
73
|
```json
|
|
@@ -104,13 +86,25 @@ Project level config overrides user level when exists.
|
|
|
104
86
|
|
|
105
87
|
## MCP Usage
|
|
106
88
|
|
|
107
|
-
###
|
|
89
|
+
### List Servers
|
|
108
90
|
|
|
109
91
|
```
|
|
110
92
|
ssh({ action: "list" })
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
```json
|
|
97
|
+
[
|
|
98
|
+
{ "name": "local", "connected": false, "type": "built-in" },
|
|
99
|
+
{ "name": "my-server", "connected": false, "type": "configured" }
|
|
100
|
+
]
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Connect
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
ssh({ action: "connect", server: "local" }) # Local shell
|
|
107
|
+
ssh({ action: "connect", server: "my-server" }) # Remote SSH
|
|
114
108
|
```
|
|
115
109
|
|
|
116
110
|
### Command Execution
|
|
@@ -144,6 +138,24 @@ ssh({ read: true })
|
|
|
144
138
|
ssh({ signal: "SIGINT" }) # Ctrl+C
|
|
145
139
|
```
|
|
146
140
|
|
|
141
|
+
### Disconnect
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
ssh({ action: "disconnect" })
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Status
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
ssh({ action: "status" })
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Built-in Servers
|
|
154
|
+
|
|
155
|
+
| Name | Description |
|
|
156
|
+
|------|-------------|
|
|
157
|
+
| `local` | Local shell (uses system default shell) |
|
|
158
|
+
|
|
147
159
|
## License
|
|
148
160
|
|
|
149
161
|
MIT
|
package/dist/cli.js
CHANGED
|
@@ -26,6 +26,9 @@ function formatScope(scope) {
|
|
|
26
26
|
* list 命令
|
|
27
27
|
*/
|
|
28
28
|
async function listServers(options) {
|
|
29
|
+
// 先显示内置服务器
|
|
30
|
+
console.log("=== 内置服务器 ===");
|
|
31
|
+
console.log(` • local (本地 shell)\n`);
|
|
29
32
|
if (options.all) {
|
|
30
33
|
// 显示两个级别的配置
|
|
31
34
|
console.log("=== 项目级别 (local) ===");
|
|
@@ -64,14 +67,13 @@ async function listServers(options) {
|
|
|
64
67
|
const scope = options.local ? "local" : options.global ? "global" : undefined;
|
|
65
68
|
const configManager = getConfigManager(scope);
|
|
66
69
|
const servers = configManager.listServers();
|
|
67
|
-
console.log(
|
|
68
|
-
console.log(
|
|
70
|
+
console.log(`=== ${formatScope(configManager.getScope())} ===`);
|
|
71
|
+
console.log(`路径: ${configManager.getConfigPath()}\n`);
|
|
69
72
|
if (servers.length === 0) {
|
|
70
|
-
console.log("
|
|
73
|
+
console.log("(空)");
|
|
71
74
|
console.log("\n使用 'mcp-ssh-pty add' 添加服务器");
|
|
72
75
|
return;
|
|
73
76
|
}
|
|
74
|
-
console.log("已配置的服务器:\n");
|
|
75
77
|
servers.forEach((server, index) => {
|
|
76
78
|
console.log(` ${index + 1}. ${formatServer(server)}`);
|
|
77
79
|
});
|
package/dist/index.js
CHANGED
|
@@ -32,15 +32,11 @@ async function startServer() {
|
|
|
32
32
|
registerTools(server, sshManager, configManager);
|
|
33
33
|
const transport = new StdioServerTransport();
|
|
34
34
|
await server.connect(transport);
|
|
35
|
-
console.error("SSH MCP Server 已启动");
|
|
36
|
-
console.error(`配置文件路径: ${configManager.getConfigPath()}`);
|
|
37
35
|
process.on("SIGINT", async () => {
|
|
38
|
-
console.error("收到 SIGINT,正在关闭...");
|
|
39
36
|
await sshManager.disconnect();
|
|
40
37
|
process.exit(0);
|
|
41
38
|
});
|
|
42
39
|
process.on("SIGTERM", async () => {
|
|
43
|
-
console.error("收到 SIGTERM,正在关闭...");
|
|
44
40
|
await sshManager.disconnect();
|
|
45
41
|
process.exit(0);
|
|
46
42
|
});
|
package/dist/shell-manager.d.ts
CHANGED
|
@@ -2,19 +2,34 @@ import { Client } from "ssh2";
|
|
|
2
2
|
import { ShellResult, ShellConfig } from "./types.js";
|
|
3
3
|
export declare class ShellManager {
|
|
4
4
|
private shell;
|
|
5
|
+
private localProcess;
|
|
6
|
+
private ptyProcess;
|
|
5
7
|
private outputBuffer;
|
|
6
8
|
private outputLines;
|
|
7
9
|
private lastOutputTime;
|
|
8
10
|
private config;
|
|
11
|
+
private isLocal;
|
|
9
12
|
constructor(config?: Partial<ShellConfig>);
|
|
10
13
|
/**
|
|
11
|
-
*
|
|
14
|
+
* 打开远程 PTY Shell(在 SSH 连接成功后调用)
|
|
12
15
|
*/
|
|
13
16
|
open(client: Client): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* 打开本地 Shell(使用 node-pty 创建真正的 PTY)
|
|
19
|
+
*/
|
|
20
|
+
openLocal(): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* 设置 stream 的事件监听
|
|
23
|
+
*/
|
|
24
|
+
private setupStream;
|
|
14
25
|
/**
|
|
15
26
|
* 检查 shell 是否已打开
|
|
16
27
|
*/
|
|
17
28
|
isOpen(): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* 是否是本地 shell
|
|
31
|
+
*/
|
|
32
|
+
isLocalShell(): boolean;
|
|
18
33
|
/**
|
|
19
34
|
* 发送命令,智能等待完成
|
|
20
35
|
*/
|
package/dist/shell-manager.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as pty from "node-pty";
|
|
1
2
|
const DEFAULT_CONFIG = {
|
|
2
3
|
quickTimeout: 2000,
|
|
3
4
|
maxTimeout: 5000,
|
|
@@ -6,15 +7,18 @@ const DEFAULT_CONFIG = {
|
|
|
6
7
|
};
|
|
7
8
|
export class ShellManager {
|
|
8
9
|
shell = null;
|
|
10
|
+
localProcess = null;
|
|
11
|
+
ptyProcess = null;
|
|
9
12
|
outputBuffer = "";
|
|
10
13
|
outputLines = [];
|
|
11
14
|
lastOutputTime = 0;
|
|
12
15
|
config;
|
|
16
|
+
isLocal = false;
|
|
13
17
|
constructor(config) {
|
|
14
18
|
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
15
19
|
}
|
|
16
20
|
/**
|
|
17
|
-
*
|
|
21
|
+
* 打开远程 PTY Shell(在 SSH 连接成功后调用)
|
|
18
22
|
*/
|
|
19
23
|
async open(client) {
|
|
20
24
|
return new Promise((resolve, reject) => {
|
|
@@ -24,36 +28,104 @@ export class ShellManager {
|
|
|
24
28
|
return;
|
|
25
29
|
}
|
|
26
30
|
this.shell = stream;
|
|
27
|
-
this.
|
|
28
|
-
this.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
31
|
+
this.isLocal = false;
|
|
32
|
+
this.setupStream(stream);
|
|
33
|
+
// 等待初始提示符
|
|
34
|
+
setTimeout(() => {
|
|
35
|
+
resolve();
|
|
36
|
+
}, 500);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 打开本地 Shell(使用 node-pty 创建真正的 PTY)
|
|
42
|
+
*/
|
|
43
|
+
async openLocal() {
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
try {
|
|
46
|
+
// 检测系统默认 shell
|
|
47
|
+
const shellPath = process.env.SHELL || (process.platform === "win32" ? "powershell.exe" : "/bin/sh");
|
|
48
|
+
// 使用 node-pty 创建真正的 PTY
|
|
49
|
+
const ptyProc = pty.spawn(shellPath, [], {
|
|
50
|
+
name: "xterm-256color",
|
|
51
|
+
cols: 120,
|
|
52
|
+
rows: 40,
|
|
53
|
+
cwd: process.cwd(),
|
|
54
|
+
env: process.env,
|
|
44
55
|
});
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
56
|
+
this.ptyProcess = ptyProc;
|
|
57
|
+
this.isLocal = true;
|
|
58
|
+
// 创建统一的 stream 接口
|
|
59
|
+
const dataListeners = [];
|
|
60
|
+
const closeListeners = [];
|
|
61
|
+
const errorListeners = [];
|
|
62
|
+
ptyProc.onData((data) => {
|
|
63
|
+
dataListeners.forEach((l) => l(Buffer.from(data)));
|
|
48
64
|
});
|
|
49
|
-
|
|
50
|
-
|
|
65
|
+
ptyProc.onExit(() => {
|
|
66
|
+
closeListeners.forEach((l) => l());
|
|
51
67
|
});
|
|
68
|
+
const stream = {
|
|
69
|
+
write: (data) => {
|
|
70
|
+
ptyProc.write(data);
|
|
71
|
+
},
|
|
72
|
+
end: () => {
|
|
73
|
+
ptyProc.kill();
|
|
74
|
+
},
|
|
75
|
+
on: ((event, listener) => {
|
|
76
|
+
if (event === "data") {
|
|
77
|
+
dataListeners.push(listener);
|
|
78
|
+
}
|
|
79
|
+
else if (event === "close") {
|
|
80
|
+
closeListeners.push(listener);
|
|
81
|
+
}
|
|
82
|
+
else if (event === "error") {
|
|
83
|
+
errorListeners.push(listener);
|
|
84
|
+
}
|
|
85
|
+
}),
|
|
86
|
+
};
|
|
87
|
+
this.shell = stream;
|
|
88
|
+
this.setupStream(stream);
|
|
52
89
|
// 等待初始提示符
|
|
53
90
|
setTimeout(() => {
|
|
54
91
|
resolve();
|
|
55
92
|
}, 500);
|
|
56
|
-
}
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
reject(new Error(`无法打开本地 shell: ${error instanceof Error ? error.message : String(error)}`));
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 设置 stream 的事件监听
|
|
101
|
+
*/
|
|
102
|
+
setupStream(stream) {
|
|
103
|
+
this.outputBuffer = "";
|
|
104
|
+
this.outputLines = [];
|
|
105
|
+
this.lastOutputTime = Date.now();
|
|
106
|
+
// 监听数据事件
|
|
107
|
+
stream.on("data", (data) => {
|
|
108
|
+
const text = data.toString();
|
|
109
|
+
this.outputBuffer += text;
|
|
110
|
+
this.lastOutputTime = Date.now();
|
|
111
|
+
// 按行分割并存储
|
|
112
|
+
const lines = this.outputBuffer.split("\n");
|
|
113
|
+
// 最后一个可能是不完整的行,保留在 buffer
|
|
114
|
+
this.outputBuffer = lines.pop() || "";
|
|
115
|
+
this.outputLines.push(...lines);
|
|
116
|
+
// 限制缓冲区大小
|
|
117
|
+
if (this.outputLines.length > this.config.maxBufferLines) {
|
|
118
|
+
this.outputLines = this.outputLines.slice(-this.config.maxBufferLines);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
// 监听关闭事件
|
|
122
|
+
stream.on("close", () => {
|
|
123
|
+
this.shell = null;
|
|
124
|
+
this.localProcess = null;
|
|
125
|
+
this.ptyProcess = null;
|
|
126
|
+
});
|
|
127
|
+
stream.on("error", (err) => {
|
|
128
|
+
console.error("Shell error:", err.message);
|
|
57
129
|
});
|
|
58
130
|
}
|
|
59
131
|
/**
|
|
@@ -62,6 +134,12 @@ export class ShellManager {
|
|
|
62
134
|
isOpen() {
|
|
63
135
|
return this.shell !== null;
|
|
64
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* 是否是本地 shell
|
|
139
|
+
*/
|
|
140
|
+
isLocalShell() {
|
|
141
|
+
return this.isLocal;
|
|
142
|
+
}
|
|
65
143
|
/**
|
|
66
144
|
* 发送命令,智能等待完成
|
|
67
145
|
*/
|
|
@@ -140,8 +218,17 @@ export class ShellManager {
|
|
|
140
218
|
this.shell.end();
|
|
141
219
|
this.shell = null;
|
|
142
220
|
}
|
|
221
|
+
if (this.localProcess) {
|
|
222
|
+
this.localProcess.kill();
|
|
223
|
+
this.localProcess = null;
|
|
224
|
+
}
|
|
225
|
+
if (this.ptyProcess) {
|
|
226
|
+
this.ptyProcess.kill();
|
|
227
|
+
this.ptyProcess = null;
|
|
228
|
+
}
|
|
143
229
|
this.outputLines = [];
|
|
144
230
|
this.outputBuffer = "";
|
|
231
|
+
this.isLocal = false;
|
|
145
232
|
}
|
|
146
233
|
/**
|
|
147
234
|
* 获取当前缓冲区行数
|
package/dist/ssh-manager.d.ts
CHANGED
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
import { ServerConfig, ConnectionStatus } from "./types.js";
|
|
2
2
|
import { ShellManager } from "./shell-manager.js";
|
|
3
|
+
export declare const LOCAL_SERVER: ServerConfig;
|
|
3
4
|
export declare class SSHManager {
|
|
4
5
|
private client;
|
|
5
6
|
private currentServer;
|
|
6
7
|
private isConnected;
|
|
7
8
|
private shellManager;
|
|
9
|
+
private isLocalConnection;
|
|
8
10
|
constructor();
|
|
9
11
|
private expandPath;
|
|
12
|
+
/**
|
|
13
|
+
* 检查是否是本地连接
|
|
14
|
+
*/
|
|
15
|
+
static isLocalServer(config: ServerConfig): boolean;
|
|
10
16
|
connect(config: ServerConfig): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* 本地连接
|
|
19
|
+
*/
|
|
20
|
+
private connectLocal;
|
|
21
|
+
/**
|
|
22
|
+
* SSH 远程连接
|
|
23
|
+
*/
|
|
24
|
+
private connectSSH;
|
|
11
25
|
disconnect(): Promise<void>;
|
|
12
26
|
private cleanup;
|
|
13
27
|
/**
|
|
@@ -15,5 +29,6 @@ export declare class SSHManager {
|
|
|
15
29
|
*/
|
|
16
30
|
private registerSensitiveInfo;
|
|
17
31
|
getShellManager(): ShellManager;
|
|
32
|
+
isLocal(): boolean;
|
|
18
33
|
getStatus(): ConnectionStatus;
|
|
19
34
|
}
|
package/dist/ssh-manager.js
CHANGED
|
@@ -4,11 +4,19 @@ import { homedir } from "os";
|
|
|
4
4
|
import { join } from "path";
|
|
5
5
|
import { ShellManager } from "./shell-manager.js";
|
|
6
6
|
import { getSanitizer } from "./sanitizer.js";
|
|
7
|
+
// 内置的本地服务器配置
|
|
8
|
+
export const LOCAL_SERVER = {
|
|
9
|
+
name: "local",
|
|
10
|
+
host: "localhost",
|
|
11
|
+
port: 0,
|
|
12
|
+
username: process.env.USER || process.env.USERNAME || "local",
|
|
13
|
+
};
|
|
7
14
|
export class SSHManager {
|
|
8
15
|
client = null;
|
|
9
16
|
currentServer = null;
|
|
10
17
|
isConnected = false;
|
|
11
18
|
shellManager;
|
|
19
|
+
isLocalConnection = false;
|
|
12
20
|
constructor() {
|
|
13
21
|
this.shellManager = new ShellManager();
|
|
14
22
|
}
|
|
@@ -18,14 +26,46 @@ export class SSHManager {
|
|
|
18
26
|
}
|
|
19
27
|
return path;
|
|
20
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* 检查是否是本地连接
|
|
31
|
+
*/
|
|
32
|
+
static isLocalServer(config) {
|
|
33
|
+
return config.name === "local" || config.host === "local";
|
|
34
|
+
}
|
|
21
35
|
async connect(config) {
|
|
22
36
|
if (this.isConnected) {
|
|
23
37
|
await this.disconnect();
|
|
24
38
|
}
|
|
39
|
+
// 检查是否是本地连接
|
|
40
|
+
if (SSHManager.isLocalServer(config)) {
|
|
41
|
+
return this.connectLocal();
|
|
42
|
+
}
|
|
43
|
+
return this.connectSSH(config);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 本地连接
|
|
47
|
+
*/
|
|
48
|
+
async connectLocal() {
|
|
49
|
+
try {
|
|
50
|
+
await this.shellManager.openLocal();
|
|
51
|
+
this.isConnected = true;
|
|
52
|
+
this.isLocalConnection = true;
|
|
53
|
+
this.currentServer = LOCAL_SERVER;
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
this.cleanup();
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* SSH 远程连接
|
|
62
|
+
*/
|
|
63
|
+
async connectSSH(config) {
|
|
25
64
|
return new Promise((resolve, reject) => {
|
|
26
65
|
this.client = new Client();
|
|
27
66
|
this.client.on("ready", async () => {
|
|
28
67
|
this.isConnected = true;
|
|
68
|
+
this.isLocalConnection = false;
|
|
29
69
|
this.currentServer = config;
|
|
30
70
|
// 注册敏感信息到过滤器
|
|
31
71
|
this.registerSensitiveInfo(config);
|
|
@@ -77,13 +117,14 @@ export class SSHManager {
|
|
|
77
117
|
async disconnect() {
|
|
78
118
|
// 先关闭 shell
|
|
79
119
|
this.shellManager.close();
|
|
80
|
-
if (this.client && this.isConnected) {
|
|
120
|
+
if (this.client && this.isConnected && !this.isLocalConnection) {
|
|
81
121
|
this.client.end();
|
|
82
122
|
}
|
|
83
123
|
this.cleanup();
|
|
84
124
|
}
|
|
85
125
|
cleanup() {
|
|
86
126
|
this.isConnected = false;
|
|
127
|
+
this.isLocalConnection = false;
|
|
87
128
|
this.currentServer = null;
|
|
88
129
|
this.client = null;
|
|
89
130
|
// 清除敏感信息
|
|
@@ -105,11 +146,14 @@ export class SSHManager {
|
|
|
105
146
|
getShellManager() {
|
|
106
147
|
return this.shellManager;
|
|
107
148
|
}
|
|
149
|
+
isLocal() {
|
|
150
|
+
return this.isLocalConnection;
|
|
151
|
+
}
|
|
108
152
|
getStatus() {
|
|
109
153
|
return {
|
|
110
154
|
connected: this.isConnected,
|
|
111
155
|
serverName: this.currentServer?.name || null,
|
|
112
|
-
host: this.currentServer?.host || null,
|
|
156
|
+
host: this.isLocalConnection ? "local" : (this.currentServer?.host || null),
|
|
113
157
|
username: this.currentServer?.username || null,
|
|
114
158
|
};
|
|
115
159
|
}
|
package/dist/tools.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { LOCAL_SERVER } from "./ssh-manager.js";
|
|
2
3
|
import { sanitize } from "./sanitizer.js";
|
|
3
4
|
/**
|
|
4
5
|
* 过滤 ShellResult 中的敏感信息
|
|
@@ -151,10 +152,19 @@ ssh({ signal: "SIGINT" }) # Ctrl+C 停止`,
|
|
|
151
152
|
case "list": {
|
|
152
153
|
const servers = configManager.listServers();
|
|
153
154
|
const status = sshManager.getStatus();
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
155
|
+
// 添加内置的 local 服务器
|
|
156
|
+
const list = [
|
|
157
|
+
{
|
|
158
|
+
name: LOCAL_SERVER.name,
|
|
159
|
+
connected: status.serverName === LOCAL_SERVER.name,
|
|
160
|
+
type: "built-in",
|
|
161
|
+
},
|
|
162
|
+
...servers.map((s) => ({
|
|
163
|
+
name: s.name,
|
|
164
|
+
connected: status.serverName === s.name,
|
|
165
|
+
type: "configured",
|
|
166
|
+
})),
|
|
167
|
+
];
|
|
158
168
|
return {
|
|
159
169
|
content: [{ type: "text", text: JSON.stringify(list, null, 2) }],
|
|
160
170
|
};
|
|
@@ -166,9 +176,19 @@ ssh({ signal: "SIGINT" }) # Ctrl+C 停止`,
|
|
|
166
176
|
isError: true,
|
|
167
177
|
};
|
|
168
178
|
}
|
|
179
|
+
// 检查是否是本地连接
|
|
180
|
+
if (serverName === "local") {
|
|
181
|
+
await sshManager.connect(LOCAL_SERVER);
|
|
182
|
+
return {
|
|
183
|
+
content: [{
|
|
184
|
+
type: "text",
|
|
185
|
+
text: "成功连接到本地 Shell",
|
|
186
|
+
}],
|
|
187
|
+
};
|
|
188
|
+
}
|
|
169
189
|
const serverConfig = configManager.getServer(serverName);
|
|
170
190
|
if (!serverConfig) {
|
|
171
|
-
const available = configManager.listServers().map((s) => s.name);
|
|
191
|
+
const available = ["local", ...configManager.listServers().map((s) => s.name)];
|
|
172
192
|
return {
|
|
173
193
|
content: [{
|
|
174
194
|
type: "text",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-ssh-pty",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "MCP Server for SSH remote command execution with PTY shell support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"@inquirer/prompts": "^8.1.0",
|
|
34
34
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
35
35
|
"commander": "^14.0.2",
|
|
36
|
+
"node-pty": "^1.1.0",
|
|
36
37
|
"ssh2": "^1.16.0",
|
|
37
38
|
"zod": "^3.24.0"
|
|
38
39
|
},
|