@pyrokine/mcp-ssh 1.1.0 → 1.1.2
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 +81 -72
- package/README_zh.md +81 -73
- package/dist/file-ops.js +36 -20
- package/dist/index.js +24 -9
- package/dist/session-manager.d.ts +62 -51
- package/dist/session-manager.js +201 -168
- package/dist/ssh-config.js +2 -2
- package/package.json +1 -1
- package/src/file-ops.ts +602 -577
- package/src/index.ts +971 -948
- package/src/session-manager.ts +986 -945
- package/src/ssh-config.ts +185 -185
- package/src/types.ts +89 -89
- package/tsconfig.json +7 -2
package/dist/index.js
CHANGED
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
17
17
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
18
|
-
import { CallToolRequestSchema, ListToolsRequestSchema
|
|
19
|
-
import { sessionManager } from './session-manager.js';
|
|
18
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
20
19
|
import * as fileOps from './file-ops.js';
|
|
21
|
-
import {
|
|
20
|
+
import { sessionManager } from './session-manager.js';
|
|
21
|
+
import { parseProxyJump, parseSSHConfig } from './ssh-config.js';
|
|
22
22
|
// 创建 MCP Server
|
|
23
23
|
const server = new Server({
|
|
24
24
|
name: 'ssh-mcp-pro',
|
|
@@ -133,6 +133,9 @@ const tools = [
|
|
|
133
133
|
|
|
134
134
|
适用场景: SSH 以 root 登录,但需要以其他用户(如 caros)执行命令。
|
|
135
135
|
|
|
136
|
+
默认加载目标用户的 shell 配置以获取环境变量(su -c 创建非交互式 shell,不会自动执行 rc 文件)。
|
|
137
|
+
支持 bash(.bashrc)、zsh(.zshrc) 及其他 shell(.profile)。
|
|
138
|
+
|
|
136
139
|
示例: ssh_exec_as_user(alias="server", command="whoami", targetUser="caros")`,
|
|
137
140
|
inputSchema: {
|
|
138
141
|
type: 'object',
|
|
@@ -141,6 +144,7 @@ const tools = [
|
|
|
141
144
|
command: { type: 'string', description: '要执行的命令' },
|
|
142
145
|
targetUser: { type: 'string', description: '目标用户名' },
|
|
143
146
|
timeout: { type: 'number', description: '超时(毫秒)' },
|
|
147
|
+
loadProfile: { type: 'boolean', description: '是否加载 .bashrc(默认 true)' },
|
|
144
148
|
},
|
|
145
149
|
required: ['alias', 'command', 'targetUser'],
|
|
146
150
|
},
|
|
@@ -396,7 +400,11 @@ rsync 可实现增量传输,对大目录同步效率更高。
|
|
|
396
400
|
type: 'object',
|
|
397
401
|
properties: {
|
|
398
402
|
ptyId: { type: 'string', description: 'PTY 会话 ID' },
|
|
399
|
-
mode: {
|
|
403
|
+
mode: {
|
|
404
|
+
type: 'string',
|
|
405
|
+
enum: ['screen', 'raw'],
|
|
406
|
+
description: '输出模式:screen(当前屏幕)或 raw(原始流),默认 screen',
|
|
407
|
+
},
|
|
400
408
|
clear: { type: 'boolean', description: '(仅 raw 模式) 读取后是否清空缓冲区,默认 true' },
|
|
401
409
|
},
|
|
402
410
|
required: ['ptyId'],
|
|
@@ -607,7 +615,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
607
615
|
username: user,
|
|
608
616
|
password: args.password,
|
|
609
617
|
privateKeyPath: keyPath,
|
|
610
|
-
alias: args.alias ||
|
|
618
|
+
alias: args.alias ||
|
|
619
|
+
args.configHost,
|
|
611
620
|
env: args.env,
|
|
612
621
|
keepaliveInterval: args.keepaliveInterval,
|
|
613
622
|
jumpHost,
|
|
@@ -655,7 +664,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
655
664
|
break;
|
|
656
665
|
}
|
|
657
666
|
case 'ssh_exec_as_user': {
|
|
658
|
-
const execResult = await sessionManager.execAsUser(args.alias, args.command, args.targetUser, {
|
|
667
|
+
const execResult = await sessionManager.execAsUser(args.alias, args.command, args.targetUser, {
|
|
668
|
+
timeout: args.timeout,
|
|
669
|
+
loadProfile: args.loadProfile,
|
|
670
|
+
});
|
|
659
671
|
result = execResult;
|
|
660
672
|
break;
|
|
661
673
|
}
|
|
@@ -688,8 +700,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
688
700
|
success: false,
|
|
689
701
|
error: err.message,
|
|
690
702
|
});
|
|
691
|
-
if (stopOnError)
|
|
703
|
+
if (stopOnError) {
|
|
692
704
|
break;
|
|
705
|
+
}
|
|
693
706
|
}
|
|
694
707
|
}
|
|
695
708
|
result = {
|
|
@@ -841,7 +854,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
841
854
|
success: true,
|
|
842
855
|
forwardId,
|
|
843
856
|
type: 'local',
|
|
844
|
-
message: `Local forward: ${args.localHost ||
|
|
857
|
+
message: `Local forward: ${args.localHost ||
|
|
858
|
+
'127.0.0.1'}:${args.localPort} -> ${args.remoteHost}:${args.remotePort}`,
|
|
845
859
|
};
|
|
846
860
|
break;
|
|
847
861
|
}
|
|
@@ -851,7 +865,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
851
865
|
success: true,
|
|
852
866
|
forwardId,
|
|
853
867
|
type: 'remote',
|
|
854
|
-
message: `Remote forward: ${args.remoteHost ||
|
|
868
|
+
message: `Remote forward: ${args.remoteHost ||
|
|
869
|
+
'127.0.0.1'}:${args.remotePort} -> ${args.localHost}:${args.localPort}`,
|
|
855
870
|
};
|
|
856
871
|
break;
|
|
857
872
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - 会话持久化
|
|
9
9
|
*/
|
|
10
10
|
import { Client, ClientChannel, SFTPWrapper } from 'ssh2';
|
|
11
|
-
import {
|
|
11
|
+
import { ExecOptions, ExecResult, PersistedSession, PortForwardInfo, PtyOptions, PtySessionInfo, SSHConnectionConfig, SSHSessionInfo } from './types.js';
|
|
12
12
|
interface SSHSession {
|
|
13
13
|
client: Client;
|
|
14
14
|
config: SSHConnectionConfig;
|
|
@@ -37,23 +37,10 @@ export declare class SessionManager {
|
|
|
37
37
|
private maxReconnectAttempts;
|
|
38
38
|
private defaultPtyBufferSize;
|
|
39
39
|
constructor(persistPath?: string);
|
|
40
|
-
private ensurePersistDir;
|
|
41
|
-
/**
|
|
42
|
-
* 生成连接别名
|
|
43
|
-
*/
|
|
44
|
-
private generateAlias;
|
|
45
40
|
/**
|
|
46
41
|
* 建立 SSH 连接
|
|
47
42
|
*/
|
|
48
43
|
connect(config: SSHConnectionConfig): Promise<string>;
|
|
49
|
-
/**
|
|
50
|
-
* 通过跳板机转发连接
|
|
51
|
-
*/
|
|
52
|
-
private forwardConnection;
|
|
53
|
-
/**
|
|
54
|
-
* 检查连接是否存活
|
|
55
|
-
*/
|
|
56
|
-
private isAlive;
|
|
57
44
|
/**
|
|
58
45
|
* 重新连接
|
|
59
46
|
*/
|
|
@@ -74,26 +61,20 @@ export declare class SessionManager {
|
|
|
74
61
|
* 列出所有会话
|
|
75
62
|
*/
|
|
76
63
|
listSessions(): SSHSessionInfo[];
|
|
77
|
-
/**
|
|
78
|
-
* 转义 shell 参数(使用单引号方式)
|
|
79
|
-
*/
|
|
80
|
-
private escapeShellArg;
|
|
81
64
|
/**
|
|
82
65
|
* 执行命令
|
|
83
66
|
*/
|
|
84
67
|
exec(alias: string, command: string, options?: ExecOptions): Promise<ExecResult>;
|
|
85
|
-
/**
|
|
86
|
-
* 校验用户名(只允许字母、数字、下划线、连字符)
|
|
87
|
-
*/
|
|
88
|
-
private isValidUsername;
|
|
89
|
-
/**
|
|
90
|
-
* 校验环境变量名(只允许字母、数字、下划线,不能以数字开头)
|
|
91
|
-
*/
|
|
92
|
-
private isValidEnvKey;
|
|
93
68
|
/**
|
|
94
69
|
* 以其他用户身份执行命令
|
|
70
|
+
* @param loadProfile 是否加载用户的 shell 配置(默认 true)。
|
|
71
|
+
* su -c 创建非交互式 shell,不会自动执行 rc 文件,
|
|
72
|
+
* 但大多数用户的环境变量设置在 rc 文件中,因此默认加载。
|
|
73
|
+
* 支持 bash(.bashrc)、zsh(.zshrc) 及其他 shell(.profile)。
|
|
95
74
|
*/
|
|
96
|
-
execAsUser(alias: string, command: string, targetUser: string, options?: ExecOptions
|
|
75
|
+
execAsUser(alias: string, command: string, targetUser: string, options?: ExecOptions & {
|
|
76
|
+
loadProfile?: boolean;
|
|
77
|
+
}): Promise<ExecResult>;
|
|
97
78
|
/**
|
|
98
79
|
* 使用 sudo 执行命令
|
|
99
80
|
*/
|
|
@@ -102,18 +83,10 @@ export declare class SessionManager {
|
|
|
102
83
|
* 获取 SFTP 客户端
|
|
103
84
|
*/
|
|
104
85
|
getSftp(alias: string): Promise<SFTPWrapper>;
|
|
105
|
-
/**
|
|
106
|
-
* 持久化会话信息
|
|
107
|
-
*/
|
|
108
|
-
private persistSessions;
|
|
109
86
|
/**
|
|
110
87
|
* 加载持久化的会话信息(仅用于显示,不自动重连)
|
|
111
88
|
*/
|
|
112
89
|
loadPersistedSessions(): PersistedSession[];
|
|
113
|
-
/**
|
|
114
|
-
* 生成 PTY 会话 ID
|
|
115
|
-
*/
|
|
116
|
-
private generatePtyId;
|
|
117
90
|
/**
|
|
118
91
|
* 启动持久化 PTY 会话
|
|
119
92
|
*/
|
|
@@ -122,10 +95,6 @@ export declare class SessionManager {
|
|
|
122
95
|
* 向 PTY 写入数据
|
|
123
96
|
*/
|
|
124
97
|
ptyWrite(ptyId: string, data: string): boolean;
|
|
125
|
-
/**
|
|
126
|
-
* 从终端仿真器获取当前屏幕内容
|
|
127
|
-
*/
|
|
128
|
-
private getScreenContent;
|
|
129
98
|
/**
|
|
130
99
|
* 读取 PTY 输出
|
|
131
100
|
* @param mode 'screen' 返回当前屏幕内容,'raw' 返回原始 ANSI 流
|
|
@@ -155,10 +124,6 @@ export declare class SessionManager {
|
|
|
155
124
|
* 关闭所有 PTY 会话
|
|
156
125
|
*/
|
|
157
126
|
ptyCloseAll(): number;
|
|
158
|
-
/**
|
|
159
|
-
* 生成端口转发 ID
|
|
160
|
-
*/
|
|
161
|
-
private generateForwardId;
|
|
162
127
|
/**
|
|
163
128
|
* 创建本地端口转发
|
|
164
129
|
* 本地监听 localHost:localPort,转发到远程 remoteHost:remotePort
|
|
@@ -169,6 +134,60 @@ export declare class SessionManager {
|
|
|
169
134
|
* 远程监听 remoteHost:remotePort,转发到本地 localHost:localPort
|
|
170
135
|
*/
|
|
171
136
|
forwardRemote(alias: string, remotePort: number, localHost: string, localPort: number, remoteHost?: string): Promise<string>;
|
|
137
|
+
/**
|
|
138
|
+
* 关闭端口转发
|
|
139
|
+
*/
|
|
140
|
+
forwardClose(forwardId: string): boolean;
|
|
141
|
+
/**
|
|
142
|
+
* 列出所有端口转发
|
|
143
|
+
*/
|
|
144
|
+
forwardList(): PortForwardInfo[];
|
|
145
|
+
private ensurePersistDir;
|
|
146
|
+
/**
|
|
147
|
+
* 生成连接别名
|
|
148
|
+
*/
|
|
149
|
+
private generateAlias;
|
|
150
|
+
/**
|
|
151
|
+
* 通过跳板机转发连接
|
|
152
|
+
*/
|
|
153
|
+
private forwardConnection;
|
|
154
|
+
/**
|
|
155
|
+
* 检查连接是否存活
|
|
156
|
+
*/
|
|
157
|
+
private isAlive;
|
|
158
|
+
/**
|
|
159
|
+
* 转义 shell 参数(使用单引号方式)
|
|
160
|
+
*/
|
|
161
|
+
private escapeShellArg;
|
|
162
|
+
/**
|
|
163
|
+
* 校验用户名(只允许字母、数字、下划线、连字符)
|
|
164
|
+
*/
|
|
165
|
+
private isValidUsername;
|
|
166
|
+
/**
|
|
167
|
+
* 校验环境变量名(只允许字母、数字、下划线,不能以数字开头)
|
|
168
|
+
*/
|
|
169
|
+
private isValidEnvKey;
|
|
170
|
+
/**
|
|
171
|
+
* 根据用户 shell 类型生成加载配置文件的命令
|
|
172
|
+
* bash → .bashrc, zsh → .zshrc, 其他 → .profile
|
|
173
|
+
*/
|
|
174
|
+
private getLoadProfileCommand;
|
|
175
|
+
/**
|
|
176
|
+
* 持久化会话信息
|
|
177
|
+
*/
|
|
178
|
+
private persistSessions;
|
|
179
|
+
/**
|
|
180
|
+
* 生成 PTY 会话 ID
|
|
181
|
+
*/
|
|
182
|
+
private generatePtyId;
|
|
183
|
+
/**
|
|
184
|
+
* 从终端仿真器获取当前屏幕内容
|
|
185
|
+
*/
|
|
186
|
+
private getScreenContent;
|
|
187
|
+
/**
|
|
188
|
+
* 生成端口转发 ID
|
|
189
|
+
*/
|
|
190
|
+
private generateForwardId;
|
|
172
191
|
/**
|
|
173
192
|
* 确保 SSH session 有共享的 tcp connection dispatcher
|
|
174
193
|
* 所有 remote forward 共用一个 dispatcher,根据 destIP/destPort 路由
|
|
@@ -180,14 +199,6 @@ export declare class SessionManager {
|
|
|
180
199
|
* @param alias - session 的 map key
|
|
181
200
|
*/
|
|
182
201
|
private removeTcpDispatcherIfEmpty;
|
|
183
|
-
/**
|
|
184
|
-
* 关闭端口转发
|
|
185
|
-
*/
|
|
186
|
-
forwardClose(forwardId: string): boolean;
|
|
187
|
-
/**
|
|
188
|
-
* 列出所有端口转发
|
|
189
|
-
*/
|
|
190
|
-
forwardList(): PortForwardInfo[];
|
|
191
202
|
}
|
|
192
203
|
export declare const sessionManager: SessionManager;
|
|
193
204
|
export {};
|