ssh-agent-workspace 1.0.4 → 1.0.6
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 +471 -313
- package/dist/__tests__/SSHManager.test.js +59 -37
- package/dist/__tests__/SSHManager.test.js.map +1 -1
- package/dist/__tests__/SessionManager.test.js +53 -51
- package/dist/__tests__/SessionManager.test.js.map +1 -1
- package/dist/__tests__/StorageManager.test.js +74 -74
- package/dist/__tests__/StorageManager.test.js.map +1 -1
- package/dist/__tests__/ansi.test.js +33 -33
- package/dist/__tests__/ansi.test.js.map +1 -1
- package/dist/__tests__/security.test.js +54 -54
- package/dist/__tests__/security.test.js.map +1 -1
- package/dist/__tests__/validation.test.js +17 -17
- package/dist/__tests__/validation.test.js.map +1 -1
- package/dist/core/HostSecurityManager.d.ts.map +1 -1
- package/dist/core/HostSecurityManager.js +9 -9
- package/dist/core/HostSecurityManager.js.map +1 -1
- package/dist/core/SSHManager.d.ts +1 -1
- package/dist/core/SSHManager.d.ts.map +1 -1
- package/dist/core/SSHManager.js +31 -31
- package/dist/core/SSHManager.js.map +1 -1
- package/dist/core/SessionManager.d.ts +4 -4
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +14 -14
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/StorageManager.d.ts.map +1 -1
- package/dist/core/StorageManager.js +11 -11
- package/dist/core/StorageManager.js.map +1 -1
- package/dist/core/TmuxManager.d.ts +3 -3
- package/dist/core/TmuxManager.d.ts.map +1 -1
- package/dist/core/TmuxManager.js +16 -16
- package/dist/core/TmuxManager.js.map +1 -1
- package/dist/core/ToolConfigManager.d.ts.map +1 -1
- package/dist/core/ToolConfigManager.js +10 -10
- package/dist/core/ToolConfigManager.js.map +1 -1
- package/dist/index.js +35 -35
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +4 -4
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +41 -41
- package/dist/server.js.map +1 -1
- package/dist/tools/backup.d.ts +3 -3
- package/dist/tools/backup.d.ts.map +1 -1
- package/dist/tools/backup.js +86 -49
- package/dist/tools/backup.js.map +1 -1
- package/dist/tools/connect.d.ts +4 -4
- package/dist/tools/connect.d.ts.map +1 -1
- package/dist/tools/connect.js +54 -42
- package/dist/tools/connect.js.map +1 -1
- package/dist/tools/connection_status.d.ts +4 -4
- package/dist/tools/connection_status.d.ts.map +1 -1
- package/dist/tools/connection_status.js +23 -16
- package/dist/tools/connection_status.js.map +1 -1
- package/dist/tools/db_query.d.ts +3 -3
- package/dist/tools/db_query.d.ts.map +1 -1
- package/dist/tools/db_query.js +130 -78
- package/dist/tools/db_query.js.map +1 -1
- package/dist/tools/deploy.d.ts +3 -3
- package/dist/tools/deploy.d.ts.map +1 -1
- package/dist/tools/deploy.js +103 -53
- package/dist/tools/deploy.js.map +1 -1
- package/dist/tools/disconnect.d.ts +3 -3
- package/dist/tools/disconnect.d.ts.map +1 -1
- package/dist/tools/disconnect.js +16 -19
- package/dist/tools/disconnect.js.map +1 -1
- package/dist/tools/exec.d.ts +3 -3
- package/dist/tools/exec.d.ts.map +1 -1
- package/dist/tools/exec.js +40 -45
- package/dist/tools/exec.js.map +1 -1
- package/dist/tools/group_exec.d.ts +3 -3
- package/dist/tools/group_exec.d.ts.map +1 -1
- package/dist/tools/group_exec.js +60 -44
- package/dist/tools/group_exec.js.map +1 -1
- package/dist/tools/health_check.d.ts +3 -3
- package/dist/tools/health_check.d.ts.map +1 -1
- package/dist/tools/health_check.js +27 -23
- package/dist/tools/health_check.js.map +1 -1
- package/dist/tools/host_security.d.ts +1 -1
- package/dist/tools/host_security.d.ts.map +1 -1
- package/dist/tools/host_security.js +30 -36
- package/dist/tools/host_security.js.map +1 -1
- package/dist/tools/index.d.ts +23 -23
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +23 -23
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/interrupt.d.ts +3 -3
- package/dist/tools/interrupt.d.ts.map +1 -1
- package/dist/tools/interrupt.js +18 -18
- package/dist/tools/interrupt.js.map +1 -1
- package/dist/tools/list_hosts.d.ts.map +1 -1
- package/dist/tools/list_hosts.js +5 -7
- package/dist/tools/list_hosts.js.map +1 -1
- package/dist/tools/list_sessions.d.ts +1 -1
- package/dist/tools/list_sessions.js +4 -4
- package/dist/tools/list_sessions.js.map +1 -1
- package/dist/tools/read_output.d.ts +3 -3
- package/dist/tools/read_output.d.ts.map +1 -1
- package/dist/tools/read_output.js +17 -17
- package/dist/tools/read_output.js.map +1 -1
- package/dist/tools/reconnect_to_tmux.d.ts +4 -4
- package/dist/tools/reconnect_to_tmux.d.ts.map +1 -1
- package/dist/tools/reconnect_to_tmux.js +44 -37
- package/dist/tools/reconnect_to_tmux.js.map +1 -1
- package/dist/tools/send_input.d.ts +3 -3
- package/dist/tools/send_input.d.ts.map +1 -1
- package/dist/tools/send_input.js +18 -18
- package/dist/tools/send_input.js.map +1 -1
- package/dist/tools/sftp_download.d.ts +3 -3
- package/dist/tools/sftp_download.d.ts.map +1 -1
- package/dist/tools/sftp_download.js +41 -27
- package/dist/tools/sftp_download.js.map +1 -1
- package/dist/tools/sftp_list.d.ts +3 -3
- package/dist/tools/sftp_list.d.ts.map +1 -1
- package/dist/tools/sftp_list.js +35 -26
- package/dist/tools/sftp_list.js.map +1 -1
- package/dist/tools/sftp_upload.d.ts +3 -3
- package/dist/tools/sftp_upload.d.ts.map +1 -1
- package/dist/tools/sftp_upload.js +43 -29
- package/dist/tools/sftp_upload.js.map +1 -1
- package/dist/tools/ssh_tunnel.d.ts +3 -3
- package/dist/tools/ssh_tunnel.d.ts.map +1 -1
- package/dist/tools/ssh_tunnel.js +130 -76
- package/dist/tools/ssh_tunnel.js.map +1 -1
- package/dist/tools/sync.d.ts +3 -3
- package/dist/tools/sync.d.ts.map +1 -1
- package/dist/tools/sync.js +103 -61
- package/dist/tools/sync.js.map +1 -1
- package/dist/tools/tail_log.d.ts +3 -3
- package/dist/tools/tail_log.d.ts.map +1 -1
- package/dist/tools/tail_log.js +38 -26
- package/dist/tools/tail_log.js.map +1 -1
- package/dist/tools/tools_config.d.ts +1 -1
- package/dist/tools/tools_config.d.ts.map +1 -1
- package/dist/tools/tools_config.js +25 -35
- package/dist/tools/tools_config.js.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/ansi.js +1 -1
- package/dist/utils/ansi.js.map +1 -1
- package/dist/utils/logger.d.ts +1 -1
- package/dist/utils/logger.js +2 -2
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/security.d.ts +1 -1
- package/dist/utils/security.js +3 -3
- package/dist/utils/security.js.map +1 -1
- package/dist/utils/ssh.d.ts +2 -2
- package/dist/utils/ssh.js +5 -5
- package/dist/utils/ssh.js.map +1 -1
- package/dist/utils/sshConfig.d.ts +1 -1
- package/dist/utils/sshConfig.d.ts.map +1 -1
- package/dist/utils/sshConfig.js +21 -21
- package/dist/utils/sshConfig.js.map +1 -1
- package/dist/utils/validation.d.ts +1 -1
- package/dist/utils/validation.js +3 -3
- package/dist/utils/validation.js.map +1 -1
- package/docs/SECURITY.md +213 -213
- package/docs/TOOLS.md +425 -425
- package/package.json +59 -48
- package/vitest.config.ts +10 -10
package/README.md
CHANGED
|
@@ -1,313 +1,471 @@
|
|
|
1
|
-
# ssh-agent-workspace
|
|
2
|
-
|
|
3
|
-
<p align="left">
|
|
4
|
-
<img src="https://img.shields.io/badge/Node.js-≥18-339933?logo=node.js" alt="Node.js ≥18">
|
|
5
|
-
<img src="https://img.shields.io/badge/MCP-Server-orange" alt="MCP">
|
|
6
|
-
<img src="https://img.shields.io/badge/Tools-25-blue" alt="25 tools">
|
|
7
|
-
<img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT">
|
|
8
|
-
<img src="https://img.shields.io/badge/platform-linux%20%7C%20macos%20%7C%20windows-lightgrey" alt="platform">
|
|
9
|
-
</p>
|
|
10
|
-
|
|
11
|
-
**Stateful persistent workspace for AI agents over SSH.**
|
|
12
|
-
|
|
13
|
-
Unlike traditional SSH MCP servers that execute every command in a fresh shell, SSH Agent Workspace provides a tmux-backed workspace that survives multiple commands, SSH reconnects, MCP restarts, and network interruptions. Your working directory, environment variables, shell history, and running processes remain intact.
|
|
14
|
-
|
|
15
|
-
```
|
|
16
|
-
Traditional SSH MCP
|
|
17
|
-
|
|
18
|
-
AI
|
|
19
|
-
└─ ssh exec channel
|
|
20
|
-
└─ command
|
|
21
|
-
└─ state lost every time
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
SSH Agent Workspace
|
|
25
|
-
|
|
26
|
-
AI
|
|
27
|
-
└─ persistent tmux workspace
|
|
28
|
-
├─ cwd persists
|
|
29
|
-
├─ env persists
|
|
30
|
-
├─ shell history persists
|
|
31
|
-
├─ running processes persist
|
|
32
|
-
└─ auto recovery
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
## Why This Exists
|
|
38
|
-
|
|
39
|
-
Most SSH MCP servers use exec channels. Every command starts from scratch:
|
|
40
|
-
|
|
41
|
-
```
|
|
42
|
-
❌ No persistent cwd — cd /var/www before every command
|
|
43
|
-
❌ No persistent env vars — re-export forever
|
|
44
|
-
❌ Interactive programs break — vim, htop, docker attach don't work
|
|
45
|
-
❌ State disappears after reconnect — start over from nothing
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
SSH Agent Workspace treats SSH as a **persistent workspace** instead of a command runner. Give your AI agent a real terminal that stays alive.
|
|
49
|
-
|
|
50
|
-
---
|
|
51
|
-
|
|
52
|
-
## Core Features
|
|
53
|
-
|
|
54
|
-
- **Stateful workspaces** — Persistent tmux-backed sessions. cwd, env, history survive everything.
|
|
55
|
-
- **Automatic recovery** — Reconnect to existing sessions after SSH drops or MCP restarts.
|
|
56
|
-
- **Runtime reconfiguration** — Enable/disable tools, update per-host security policies without restart.
|
|
57
|
-
- **Deterministic output** — Prompt sentinel-based execution. No sleep-based output guessing.
|
|
58
|
-
|
|
59
|
-
---
|
|
60
|
-
|
|
61
|
-
## Quick Start
|
|
62
|
-
|
|
63
|
-
### Install
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
npm install -g ssh-agent-workspace
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
Or from source:
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
git clone https://github.com/ShiroNexo/ssh-agent-workspace.git
|
|
73
|
-
cd ssh-agent-workspace
|
|
74
|
-
npm install && npm run build
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
### Setup Your SSH Config
|
|
78
|
-
|
|
79
|
-
Hosts must be defined in `~/.ssh/config`:
|
|
80
|
-
|
|
81
|
-
```
|
|
82
|
-
Host prod
|
|
83
|
-
HostName 10.0.0.5
|
|
84
|
-
User deploy
|
|
85
|
-
IdentityFile ~/.ssh/id_ed25519
|
|
86
|
-
|
|
87
|
-
Host staging
|
|
88
|
-
HostName 10.0.0.10
|
|
89
|
-
User deploy
|
|
90
|
-
IdentityFile ~/.ssh/id_ed25519
|
|
91
|
-
|
|
92
|
-
Host internal
|
|
93
|
-
HostName 172.16.0.50
|
|
94
|
-
User admin
|
|
95
|
-
ProxyJump bastion
|
|
96
|
-
|
|
97
|
-
Host bastion
|
|
98
|
-
HostName jump.example.com
|
|
99
|
-
User jumpuser
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### Try It
|
|
103
|
-
|
|
104
|
-
```
|
|
105
|
-
> connect host=prod
|
|
106
|
-
→ { session_id: "sess_abc", tmux_session: "mcp_prod_x1y2z3" }
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
Your agent now has a persistent workspace on `prod`. Running `cd /var/www` once means the agent stays there for every subsequent command.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
<
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
>
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
1
|
+
# ssh-agent-workspace
|
|
2
|
+
|
|
3
|
+
<p align="left">
|
|
4
|
+
<img src="https://img.shields.io/badge/Node.js-≥18-339933?logo=node.js" alt="Node.js ≥18">
|
|
5
|
+
<img src="https://img.shields.io/badge/MCP-Server-orange" alt="MCP">
|
|
6
|
+
<img src="https://img.shields.io/badge/Tools-25-blue" alt="25 tools">
|
|
7
|
+
<img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT">
|
|
8
|
+
<img src="https://img.shields.io/badge/platform-linux%20%7C%20macos%20%7C%20windows-lightgrey" alt="platform">
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
**Stateful persistent workspace for AI agents over SSH.**
|
|
12
|
+
|
|
13
|
+
Unlike traditional SSH MCP servers that execute every command in a fresh shell, SSH Agent Workspace provides a tmux-backed workspace that survives multiple commands, SSH reconnects, MCP restarts, and network interruptions. Your working directory, environment variables, shell history, and running processes remain intact.
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
Traditional SSH MCP
|
|
17
|
+
|
|
18
|
+
AI
|
|
19
|
+
└─ ssh exec channel
|
|
20
|
+
└─ command
|
|
21
|
+
└─ state lost every time
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
SSH Agent Workspace
|
|
25
|
+
|
|
26
|
+
AI
|
|
27
|
+
└─ persistent tmux workspace
|
|
28
|
+
├─ cwd persists
|
|
29
|
+
├─ env persists
|
|
30
|
+
├─ shell history persists
|
|
31
|
+
├─ running processes persist
|
|
32
|
+
└─ auto recovery
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Why This Exists
|
|
38
|
+
|
|
39
|
+
Most SSH MCP servers use exec channels. Every command starts from scratch:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
❌ No persistent cwd — cd /var/www before every command
|
|
43
|
+
❌ No persistent env vars — re-export forever
|
|
44
|
+
❌ Interactive programs break — vim, htop, docker attach don't work
|
|
45
|
+
❌ State disappears after reconnect — start over from nothing
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
SSH Agent Workspace treats SSH as a **persistent workspace** instead of a command runner. Give your AI agent a real terminal that stays alive.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Core Features
|
|
53
|
+
|
|
54
|
+
- **Stateful workspaces** — Persistent tmux-backed sessions. cwd, env, history survive everything.
|
|
55
|
+
- **Automatic recovery** — Reconnect to existing sessions after SSH drops or MCP restarts.
|
|
56
|
+
- **Runtime reconfiguration** — Enable/disable tools, update per-host security policies without restart.
|
|
57
|
+
- **Deterministic output** — Prompt sentinel-based execution. No sleep-based output guessing.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Quick Start
|
|
62
|
+
|
|
63
|
+
### Install
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm install -g ssh-agent-workspace
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Or from source:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
git clone https://github.com/ShiroNexo/ssh-agent-workspace.git
|
|
73
|
+
cd ssh-agent-workspace
|
|
74
|
+
npm install && npm run build
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Setup Your SSH Config
|
|
78
|
+
|
|
79
|
+
Hosts must be defined in `~/.ssh/config`:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
Host prod
|
|
83
|
+
HostName 10.0.0.5
|
|
84
|
+
User deploy
|
|
85
|
+
IdentityFile ~/.ssh/id_ed25519
|
|
86
|
+
|
|
87
|
+
Host staging
|
|
88
|
+
HostName 10.0.0.10
|
|
89
|
+
User deploy
|
|
90
|
+
IdentityFile ~/.ssh/id_ed25519
|
|
91
|
+
|
|
92
|
+
Host internal
|
|
93
|
+
HostName 172.16.0.50
|
|
94
|
+
User admin
|
|
95
|
+
ProxyJump bastion
|
|
96
|
+
|
|
97
|
+
Host bastion
|
|
98
|
+
HostName jump.example.com
|
|
99
|
+
User jumpuser
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Try It
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
> connect host=prod
|
|
106
|
+
→ { session_id: "sess_abc", tmux_session: "mcp_prod_x1y2z3" }
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Your agent now has a persistent workspace on `prod`. Running `cd /var/www` once means the agent stays there for every subsequent command.
|
|
110
|
+
|
|
111
|
+
### Add to Your MCP Client
|
|
112
|
+
|
|
113
|
+
<details>
|
|
114
|
+
<summary><b>OpenCode</b></summary>
|
|
115
|
+
|
|
116
|
+
Add to `~/.config/opencode/opencode.json`:
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"$schema": "https://opencode.ai/config.json",
|
|
121
|
+
"mcp": {
|
|
122
|
+
"workspace": {
|
|
123
|
+
"type": "local",
|
|
124
|
+
"command": ["npx", "-y", "ssh-agent-workspace"]
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Or via CLI:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
opencode mcp add workspace -- npx -y ssh-agent-workspace
|
|
134
|
+
```
|
|
135
|
+
</details>
|
|
136
|
+
|
|
137
|
+
<details>
|
|
138
|
+
<summary><b>Claude Code</b></summary>
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
claude mcp add workspace -- npx -y ssh-agent-workspace
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Or add to `~/.config/claude-code/claude_code_config.json` or project `.mcp.json`:
|
|
145
|
+
|
|
146
|
+
```json
|
|
147
|
+
{
|
|
148
|
+
"mcpServers": {
|
|
149
|
+
"workspace": {
|
|
150
|
+
"command": "npx",
|
|
151
|
+
"args": ["-y", "ssh-agent-workspace"],
|
|
152
|
+
"autoApprove": [
|
|
153
|
+
"mcp__workspace__connect",
|
|
154
|
+
"mcp__workspace__exec",
|
|
155
|
+
"mcp__workspace__send_input",
|
|
156
|
+
"mcp__workspace__read_output",
|
|
157
|
+
"mcp__workspace__list_hosts",
|
|
158
|
+
"mcp__workspace__list_sessions",
|
|
159
|
+
"mcp__workspace__sftp_upload",
|
|
160
|
+
"mcp__workspace__sftp_download",
|
|
161
|
+
"mcp__workspace__sftp_list"
|
|
162
|
+
]
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
> **Tip:** The `autoApprove` block lets the agent use those tools without asking permission each time. Add or remove tools based on your comfort level.
|
|
169
|
+
</details>
|
|
170
|
+
|
|
171
|
+
<details>
|
|
172
|
+
<summary><b>Cursor</b></summary>
|
|
173
|
+
|
|
174
|
+
Go to `Cursor Settings` → `MCP` → `New MCP Server`. Use this config:
|
|
175
|
+
|
|
176
|
+
```json
|
|
177
|
+
{
|
|
178
|
+
"mcpServers": {
|
|
179
|
+
"workspace": {
|
|
180
|
+
"command": "npx",
|
|
181
|
+
"args": ["-y", "ssh-agent-workspace"]
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
</details>
|
|
187
|
+
|
|
188
|
+
<details>
|
|
189
|
+
<summary><b>Codex (OpenAI)</b></summary>
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
codex mcp add workspace -- npx -y ssh-agent-workspace
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Or add to `~/.codex/config.toml`:
|
|
196
|
+
|
|
197
|
+
```toml
|
|
198
|
+
[mcp_servers.workspace]
|
|
199
|
+
command = "npx"
|
|
200
|
+
args = ["-y", "ssh-agent-workspace"]
|
|
201
|
+
```
|
|
202
|
+
</details>
|
|
203
|
+
|
|
204
|
+
<details>
|
|
205
|
+
<summary><b>Windsurf</b></summary>
|
|
206
|
+
|
|
207
|
+
Add to `~/.codeium/windsurf/mcp_config.json`:
|
|
208
|
+
|
|
209
|
+
```json
|
|
210
|
+
{
|
|
211
|
+
"mcpServers": {
|
|
212
|
+
"workspace": {
|
|
213
|
+
"command": "npx",
|
|
214
|
+
"args": ["-y", "ssh-agent-workspace"]
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
</details>
|
|
220
|
+
|
|
221
|
+
<details>
|
|
222
|
+
<summary><b>Copilot / VS Code</b></summary>
|
|
223
|
+
|
|
224
|
+
```json
|
|
225
|
+
{
|
|
226
|
+
"mcpServers": {
|
|
227
|
+
"workspace": {
|
|
228
|
+
"command": "npx",
|
|
229
|
+
"args": ["-y", "ssh-agent-workspace"]
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
</details>
|
|
235
|
+
|
|
236
|
+
<details>
|
|
237
|
+
<summary><b>Gemini CLI</b></summary>
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
gemini mcp add workspace npx -y ssh-agent-workspace
|
|
241
|
+
```
|
|
242
|
+
</details>
|
|
243
|
+
|
|
244
|
+
<details>
|
|
245
|
+
<summary><b>Cline</b></summary>
|
|
246
|
+
|
|
247
|
+
```json
|
|
248
|
+
{
|
|
249
|
+
"mcpServers": {
|
|
250
|
+
"workspace": {
|
|
251
|
+
"command": "npx",
|
|
252
|
+
"args": ["-y", "ssh-agent-workspace"]
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
</details>
|
|
258
|
+
|
|
259
|
+
<details>
|
|
260
|
+
<summary><b>Qoder</b></summary>
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
qodercli mcp add workspace -- npx -y ssh-agent-workspace
|
|
264
|
+
```
|
|
265
|
+
</details>
|
|
266
|
+
|
|
267
|
+
> **Using npx** means no global install needed. npx auto-downloads the latest version. If you installed globally (`npm install -g ssh-agent-workspace`), replace `"npx"` / `"-y"` / `"ssh-agent-workspace"` with `"ssh-agent-workspace"` as the command directly.
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Key Features
|
|
272
|
+
|
|
273
|
+
### Workspace Persistence
|
|
274
|
+
|
|
275
|
+
Every session is a **dedicated tmux session** on the remote host. Your agent's working directory, environment variables, shell history, and running processes persist across commands, disconnections, and server restarts.
|
|
276
|
+
|
|
277
|
+
```
|
|
278
|
+
connect → tmux workspace created with PS1='__MCP_PROMPT__> '
|
|
279
|
+
│
|
|
280
|
+
exec "cd /var/www" → prompt wait → output returned → cwd is now /var/www
|
|
281
|
+
exec "docker ps" → runs in /var/www, no need to cd again
|
|
282
|
+
exec "vim app.js" → vim opens and stays running in tmux
|
|
283
|
+
|
|
284
|
+
(SSH drops, MCP restarts...)
|
|
285
|
+
|
|
286
|
+
reconnect_to_tmux → same tmux session, same cwd, vim still open
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Deterministic Output
|
|
290
|
+
|
|
291
|
+
Instead of `sleep 3 && capture`, every `exec` call polls `tmux capture-pane` until the exact prompt string `__MCP_PROMPT__>` appears. No race conditions, no false positives from output that looks like a prompt.
|
|
292
|
+
|
|
293
|
+
```
|
|
294
|
+
Send command → grace interval → poll every 250ms → prompt detected → capture → return output
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Auto-Recovery
|
|
298
|
+
|
|
299
|
+
On server start, `ssh-agent-workspace` scans all configured hosts for `mcp_*` tmux sessions and automatically reconnects. Disable with `MCP_SSH_RESTORE_SESSIONS=false`.
|
|
300
|
+
|
|
301
|
+
### Runtime Reconfiguration
|
|
302
|
+
|
|
303
|
+
| Tool | What it does |
|
|
304
|
+
|---|---|
|
|
305
|
+
| `tools_config` | Enable/disable any tool at runtime. Persistent. `tools_config` itself can never be disabled. |
|
|
306
|
+
| `host_security` | Set per-host read-only, command allowlist/denylist at runtime. Overrides global env vars per host. |
|
|
307
|
+
|
|
308
|
+
No restart needed. Changes apply immediately.
|
|
309
|
+
|
|
310
|
+
### Three-Layer Security
|
|
311
|
+
|
|
312
|
+
| Layer | Scope | Mechanism |
|
|
313
|
+
|---|---|---|
|
|
314
|
+
| **Global** | All hosts | `MCP_SSH_READONLY`, `MCP_SSH_ALLOWED_HOSTS`, `MCP_SSH_DENYLIST_COMMANDS` |
|
|
315
|
+
| **Per-Host** | Individual host | `host_security` tool: `readonly`, `allow_commands`, `deny_commands` |
|
|
316
|
+
| **Per-Operation** | Single command/query | SQL keyword blocklist, path sanitization, shell escaping |
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## Tools (25)
|
|
321
|
+
|
|
322
|
+
<details>
|
|
323
|
+
<summary><b>Workspace (9 tools)</b></summary>
|
|
324
|
+
|
|
325
|
+
| Tool | Description |
|
|
326
|
+
|---|---|
|
|
327
|
+
| `connect` | Create persistent tmux workspace on remote host |
|
|
328
|
+
| `reconnect_to_tmux` | Reattach to existing workspace after disconnect |
|
|
329
|
+
| `exec` | Run command, wait for prompt, return output |
|
|
330
|
+
| `send_input` | Inject raw input (non-blocking) |
|
|
331
|
+
| `read_output` | Capture pane tail |
|
|
332
|
+
| `interrupt` | Ctrl-C / Ctrl-D signal |
|
|
333
|
+
| `disconnect` | Close session. Optionally kill tmux or keep alive |
|
|
334
|
+
| `list_hosts` | List `~/.ssh/config` aliases |
|
|
335
|
+
| `list_sessions` | List active workspaces |
|
|
336
|
+
</details>
|
|
337
|
+
|
|
338
|
+
<details>
|
|
339
|
+
<summary><b>File Transfer (3 tools)</b></summary>
|
|
340
|
+
|
|
341
|
+
| Tool | Description |
|
|
342
|
+
|---|---|
|
|
343
|
+
| `sftp_upload` | Upload file to remote |
|
|
344
|
+
| `sftp_download` | Download file from remote |
|
|
345
|
+
| `sftp_list` | List remote directory |
|
|
346
|
+
</details>
|
|
347
|
+
|
|
348
|
+
<details>
|
|
349
|
+
<summary><b>Monitoring (3 tools)</b></summary>
|
|
350
|
+
|
|
351
|
+
| Tool | Description |
|
|
352
|
+
|---|---|
|
|
353
|
+
| `connection_status` | SSH liveness + tmux existence |
|
|
354
|
+
| `health_check` | CPU / RAM / Disk / Load / Uptime |
|
|
355
|
+
| `tail_log` | Log tail with optional follow |
|
|
356
|
+
</details>
|
|
357
|
+
|
|
358
|
+
<details>
|
|
359
|
+
<summary><b>DevOps (6 tools)</b></summary>
|
|
360
|
+
|
|
361
|
+
| Tool | Description |
|
|
362
|
+
|---|---|
|
|
363
|
+
| `deploy` | Upload → backup → chmod → chown → restart |
|
|
364
|
+
| `backup` | tar.gz archive → download → cleanup |
|
|
365
|
+
| `sync` | Rsync-lite via SFTP (bidirectional, dry-run) |
|
|
366
|
+
| `ssh_tunnel_open` | Local port forward or SOCKS5 proxy |
|
|
367
|
+
| `ssh_tunnel_list` | List active tunnels |
|
|
368
|
+
| `ssh_tunnel_close` | Close tunnel, free port |
|
|
369
|
+
</details>
|
|
370
|
+
|
|
371
|
+
<details>
|
|
372
|
+
<summary><b>Cluster & Queries (2 tools)</b></summary>
|
|
373
|
+
|
|
374
|
+
| Tool | Description |
|
|
375
|
+
|---|---|
|
|
376
|
+
| `group_exec` | Run command across multiple workspaces (parallel/sequential) |
|
|
377
|
+
| `db_query` | Read-only MySQL / PostgreSQL / MongoDB via SSH |
|
|
378
|
+
</details>
|
|
379
|
+
|
|
380
|
+
<details>
|
|
381
|
+
<summary><b>Runtime Config (2 tools)</b></summary>
|
|
382
|
+
|
|
383
|
+
| Tool | Description |
|
|
384
|
+
|---|---|
|
|
385
|
+
| `tools_config` | Enable/disable tools at runtime |
|
|
386
|
+
| `host_security` | Per-host read-only, command allow/denylist |
|
|
387
|
+
</details>
|
|
388
|
+
|
|
389
|
+
**Full reference:** [`docs/TOOLS.md`](docs/TOOLS.md) | **Security:** [`docs/SECURITY.md`](docs/SECURITY.md)
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## Usage Examples
|
|
394
|
+
|
|
395
|
+
```
|
|
396
|
+
> connect host=prod
|
|
397
|
+
→ session_id: "sess_abc", tmux_session: "mcp_prod_x1y2z3"
|
|
398
|
+
|
|
399
|
+
> exec session_id=sess_abc command="cd /var/www && docker ps"
|
|
400
|
+
→ { output: "3 containers running" }
|
|
401
|
+
|
|
402
|
+
> exec session_id=sess_abc command="ls"
|
|
403
|
+
→ { output: "..." } (cwd is still /var/www)
|
|
404
|
+
|
|
405
|
+
> group_exec session_ids=["sess_abc","sess_def"] command="uptime"
|
|
406
|
+
→ [{ host: "prod", output: "up 14 days" }, { host: "staging", output: "up 3 days" }]
|
|
407
|
+
|
|
408
|
+
> health_check session_id=sess_abc
|
|
409
|
+
→ { cpu: 12%, memory: 45%, disk: [{ "/": 56% }], uptime: "14 days" }
|
|
410
|
+
|
|
411
|
+
> db_query session_id=sess_abc type=mysql database=mydb query="SELECT COUNT(*) FROM users"
|
|
412
|
+
→ [{ "COUNT(*)": 15423 }]
|
|
413
|
+
|
|
414
|
+
> deploy session_id=sess_abc files=[{"local":"dist/app.js","remote":"/var/www/app.js"}] backup=true chmod="755" restart_service="nginx"
|
|
415
|
+
|
|
416
|
+
> host_security action=set host=prod readonly=true
|
|
417
|
+
→ Host 'prod' locked to read-only
|
|
418
|
+
|
|
419
|
+
> tools_config disable backup
|
|
420
|
+
→ Tool 'backup' disabled. Removed from MCP tool list.
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## Configuration
|
|
426
|
+
|
|
427
|
+
### Environment Variables
|
|
428
|
+
|
|
429
|
+
| Variable | Default | Description |
|
|
430
|
+
|---|---|---|
|
|
431
|
+
| `LOG_LEVEL` | `info` | `trace`, `debug`, `info`, `warn`, `error` |
|
|
432
|
+
| `MCP_SSH_READONLY` | `false` | Block all write operations globally |
|
|
433
|
+
| `MCP_SSH_ALLOWED_HOSTS` | `(all)` | Comma-separated host whitelist |
|
|
434
|
+
| `MCP_SSH_DENYLIST_COMMANDS` | `(none)` | Global command blocklist |
|
|
435
|
+
| `MCP_SSH_RESTORE_SESSIONS` | `true` | Auto-restore workspaces on startup |
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## Project Structure
|
|
440
|
+
|
|
441
|
+
```
|
|
442
|
+
src/
|
|
443
|
+
├── core/
|
|
444
|
+
│ ├── SessionManager.ts # Session registry + persistence
|
|
445
|
+
│ ├── SSHManager.ts # SSH2 connections, SFTP, proxy-jump
|
|
446
|
+
│ ├── TmuxManager.ts # Tmux operations (create, capture, signal)
|
|
447
|
+
│ ├── StorageManager.ts # Persistent session storage (JSON)
|
|
448
|
+
│ ├── ToolConfigManager.ts # Runtime tool enable/disable
|
|
449
|
+
│ └── HostSecurityManager.ts # Per-host security policies
|
|
450
|
+
├── tools/ # 25 MCP tool handlers
|
|
451
|
+
└── utils/ # Security, SSH config parsing, logging, validation
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
## Troubleshooting
|
|
457
|
+
|
|
458
|
+
```bash
|
|
459
|
+
ssh-add -l # Verify SSH agent key
|
|
460
|
+
ssh <alias> # Test manual login
|
|
461
|
+
chmod 600 ~/.ssh/id_* # Fix key permissions
|
|
462
|
+
|
|
463
|
+
# Clean stale workspaces on remote
|
|
464
|
+
tmux ls | grep '^mcp_' | awk -F: '{print $1}' | xargs -I{} tmux kill-session -t {}
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
---
|
|
468
|
+
|
|
469
|
+
## License
|
|
470
|
+
|
|
471
|
+
MIT
|