mcp-ssh-reloaded 0.1.0__tar.gz
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.
- mcp_ssh_reloaded-0.1.0/LICENSE +7 -0
- mcp_ssh_reloaded-0.1.0/PKG-INFO +197 -0
- mcp_ssh_reloaded-0.1.0/README.md +160 -0
- mcp_ssh_reloaded-0.1.0/pyproject.toml +86 -0
- mcp_ssh_reloaded-0.1.0/setup.cfg +4 -0
- mcp_ssh_reloaded-0.1.0/src/mcp_ssh_reloaded/__init__.py +32 -0
- mcp_ssh_reloaded-0.1.0/src/mcp_ssh_reloaded/__main__.py +150 -0
- mcp_ssh_reloaded-0.1.0/src/mcp_ssh_reloaded/api_types.py +198 -0
- mcp_ssh_reloaded-0.1.0/src/mcp_ssh_reloaded/command_executor.py +1355 -0
- mcp_ssh_reloaded-0.1.0/src/mcp_ssh_reloaded/connection.py +279 -0
- mcp_ssh_reloaded-0.1.0/src/mcp_ssh_reloaded/datastructures.py +101 -0
- mcp_ssh_reloaded-0.1.0/src/mcp_ssh_reloaded/enable.py +115 -0
- mcp_ssh_reloaded-0.1.0/src/mcp_ssh_reloaded/enhanced_executor.py +546 -0
- mcp_ssh_reloaded-0.1.0/src/mcp_ssh_reloaded/error_handler.py +265 -0
- mcp_ssh_reloaded-0.1.0/src/mcp_ssh_reloaded/file_manager.py +414 -0
- mcp_ssh_reloaded-0.1.0/src/mcp_ssh_reloaded/logging_manager.py +299 -0
- mcp_ssh_reloaded-0.1.0/src/mcp_ssh_reloaded/server.py +519 -0
- mcp_ssh_reloaded-0.1.0/src/mcp_ssh_reloaded/services.py +303 -0
- mcp_ssh_reloaded-0.1.0/src/mcp_ssh_reloaded/session_diagnostics.py +340 -0
- mcp_ssh_reloaded-0.1.0/src/mcp_ssh_reloaded/session_manager.py +2328 -0
- mcp_ssh_reloaded-0.1.0/src/mcp_ssh_reloaded/validation.py +237 -0
- mcp_ssh_reloaded-0.1.0/src/mcp_ssh_reloaded.egg-info/PKG-INFO +197 -0
- mcp_ssh_reloaded-0.1.0/src/mcp_ssh_reloaded.egg-info/SOURCES.txt +35 -0
- mcp_ssh_reloaded-0.1.0/src/mcp_ssh_reloaded.egg-info/dependency_links.txt +1 -0
- mcp_ssh_reloaded-0.1.0/src/mcp_ssh_reloaded.egg-info/entry_points.txt +3 -0
- mcp_ssh_reloaded-0.1.0/src/mcp_ssh_reloaded.egg-info/requires.txt +3 -0
- mcp_ssh_reloaded-0.1.0/src/mcp_ssh_reloaded.egg-info/top_level.txt +1 -0
- mcp_ssh_reloaded-0.1.0/tests/test_bug_regressions.py +299 -0
- mcp_ssh_reloaded-0.1.0/tests/test_concurrency.py +120 -0
- mcp_ssh_reloaded-0.1.0/tests/test_enhanced_features.py +141 -0
- mcp_ssh_reloaded-0.1.0/tests/test_env_override.py +146 -0
- mcp_ssh_reloaded-0.1.0/tests/test_integration.py +353 -0
- mcp_ssh_reloaded-0.1.0/tests/test_interactive_pty.py +438 -0
- mcp_ssh_reloaded-0.1.0/tests/test_mikrotik.py +290 -0
- mcp_ssh_reloaded-0.1.0/tests/test_network_devices.py +321 -0
- mcp_ssh_reloaded-0.1.0/tests/test_package_manager_integration.py +507 -0
- mcp_ssh_reloaded-0.1.0/tests/test_prompt_detection.py +75 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright © 2025 Jon Rogers
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mcp-ssh-reloaded
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for SSH sessions management.
|
|
5
|
+
Author: Jon Rogers <devnullvoid>
|
|
6
|
+
Author-email: AmritaConstant <admin@amritabot.com>
|
|
7
|
+
License: Copyright © 2025 Jon Rogers
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
14
|
+
Project-URL: Homepage, https://github.com/AmritaBot/mcp-ssh-reloaded
|
|
15
|
+
Project-URL: Repository, https://github.com/AmritaBot/mcp-ssh-reloaded
|
|
16
|
+
Project-URL: Issues, https://github.com/AmritaBot/mcp-ssh-reloaded/issues
|
|
17
|
+
Keywords: mcp,ssh,fastmcp
|
|
18
|
+
Classifier: Development Status :: 4 - Beta
|
|
19
|
+
Classifier: Environment :: Console
|
|
20
|
+
Classifier: Intended Audience :: Developers
|
|
21
|
+
Classifier: Operating System :: OS Independent
|
|
22
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
23
|
+
Classifier: Programming Language :: Python
|
|
24
|
+
Classifier: Programming Language :: Python :: 3
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
27
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
28
|
+
Classifier: Topic :: System :: Networking
|
|
29
|
+
Classifier: Topic :: System :: Systems Administration
|
|
30
|
+
Requires-Python: <3.14,>=3.10
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
License-File: LICENSE
|
|
33
|
+
Requires-Dist: fastmcp>=3.2.0
|
|
34
|
+
Requires-Dist: paramiko>=3.4.0
|
|
35
|
+
Requires-Dist: pyte>=0.8.0
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# MCP SSH Session
|
|
39
|
+
|
|
40
|
+
An MCP (Model Context Protocol) server that enables AI agents to establish and manage persistent SSH sessions.
|
|
41
|
+
|
|
42
|
+
## Features
|
|
43
|
+
|
|
44
|
+
- **Smart Command Execution** — auto-transitions to async mode if timeout is reached
|
|
45
|
+
- **Persistent Sessions** — SSH connections reused across commands
|
|
46
|
+
- **Async Commands** — non-blocking execution for long-running tasks
|
|
47
|
+
- **SSH Config Support** — reads `~/.ssh/config` for aliases, ports, keys
|
|
48
|
+
- **Multi-host** — manage connections to multiple hosts simultaneously
|
|
49
|
+
- **Network Devices** — enable mode handling for Cisco, Juniper, MikroTik, etc.
|
|
50
|
+
- **Sudo Support** — automatic password handling for Unix/Linux hosts
|
|
51
|
+
- **File Operations** — read/write remote files via SFTP (sudo fallback)
|
|
52
|
+
- **Command Interruption** — send Ctrl+C to stop running commands
|
|
53
|
+
- **Thread-safe** — safe for concurrent operations
|
|
54
|
+
|
|
55
|
+
## Quick Start
|
|
56
|
+
|
|
57
|
+
### Install
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
uvx mcp-ssh-reloaded
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Development
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
uv venv
|
|
67
|
+
source .venv/bin/activate
|
|
68
|
+
uv pip install -e .
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### CLI
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# MCP server (default)
|
|
75
|
+
mcp-ssh-reloaded serve mcp
|
|
76
|
+
|
|
77
|
+
# Direct execution (no MCP)
|
|
78
|
+
mcp-ssh-reloaded exec myserver "uname -a" -u admin
|
|
79
|
+
|
|
80
|
+
# List / close sessions
|
|
81
|
+
mcp-ssh-reloaded list
|
|
82
|
+
mcp-ssh-reloaded close-all
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### MCP Client Config
|
|
86
|
+
|
|
87
|
+
**Claude Code / Desktop** (`~/.claude.json` or `claude_desktop_config.json`):
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"mcpServers": {
|
|
92
|
+
"ssh-session": {
|
|
93
|
+
"type": "stdio",
|
|
94
|
+
"command": "uvx",
|
|
95
|
+
"args": ["mcp-ssh-reloaded", "serve", "mcp"],
|
|
96
|
+
"env": {}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Quick Examples
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
// SSH config alias
|
|
106
|
+
{ "host": "myserver", "command": "uptime" }
|
|
107
|
+
|
|
108
|
+
// Explicit params
|
|
109
|
+
{ "host": "example.com", "username": "user", "command": "ls -la", "port": 2222 }
|
|
110
|
+
|
|
111
|
+
// Network device (Cisco enable mode)
|
|
112
|
+
{ "host": "router", "username": "admin", "enable_password": "secret", "command": "show run" }
|
|
113
|
+
|
|
114
|
+
// Unix with sudo
|
|
115
|
+
{ "host": "server", "username": "ops", "sudo_password": "secret", "command": "systemctl restart nginx" }
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
> **Full API reference:** [API-DOCS.md](./API-DOCS.md) — all types, all methods, all MCP tools, error handling, server config tunables.
|
|
121
|
+
|
|
122
|
+
## SSH Config
|
|
123
|
+
|
|
124
|
+
`~/.ssh/config` is read automatically:
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
Host myserver
|
|
128
|
+
HostName example.com
|
|
129
|
+
User myuser
|
|
130
|
+
Port 2222
|
|
131
|
+
IdentityFile ~/.ssh/id_rsa
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Then use `"host": "myserver"` — the rest is resolved for you.
|
|
135
|
+
|
|
136
|
+
## Credential Hiding (OVRD\_\*)
|
|
137
|
+
|
|
138
|
+
For production environments, store real credentials in env vars so AI agents only see aliases:
|
|
139
|
+
|
|
140
|
+
| Variable | Description |
|
|
141
|
+
| -------------------------- | ----------------------- |
|
|
142
|
+
| `OVRD_{alias}_HOST` | Real hostname or IP |
|
|
143
|
+
| `OVRD_{alias}_PORT` | SSH port |
|
|
144
|
+
| `OVRD_{alias}_USER` | SSH username |
|
|
145
|
+
| `OVRD_{alias}_PASS` | SSH password |
|
|
146
|
+
| `OVRD_{alias}_KEY` | Path to SSH private key |
|
|
147
|
+
| `OVRD_{alias}_SUDO_PASS` | Sudo password |
|
|
148
|
+
| `OVRD_{alias}_ENABLE_PASS` | Enable password |
|
|
149
|
+
|
|
150
|
+
**Example config:**
|
|
151
|
+
|
|
152
|
+
```json
|
|
153
|
+
{
|
|
154
|
+
"mcpServers": {
|
|
155
|
+
"ssh-session": {
|
|
156
|
+
"type": "stdio",
|
|
157
|
+
"command": "uvx",
|
|
158
|
+
"args": ["mcp-ssh-reloaded", "serve", "mcp"],
|
|
159
|
+
"env": {
|
|
160
|
+
"OVRD_prod_db_HOST": "192.168.1.100",
|
|
161
|
+
"OVRD_prod_db_USER": "admin",
|
|
162
|
+
"OVRD_prod_db_PASS": "secret123",
|
|
163
|
+
"OVRD_prod_db_SUDO_PASS": "sudopass"
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The agent uses `"host": "prod_db"` — never sees real IPs or passwords.
|
|
171
|
+
|
|
172
|
+
## How It Works
|
|
173
|
+
|
|
174
|
+
Commands run inside persistent interactive shells:
|
|
175
|
+
|
|
176
|
+
- **Directory persists**: `cd /tmp` stays in `/tmp` for the next command
|
|
177
|
+
- **Env vars persist**: `export FOO=bar` is visible across commands
|
|
178
|
+
- **Prompt detection**: completion detected via captured prompt or idle timeout (2 s)
|
|
179
|
+
- **Session recovery**: stuck shells auto-reset after repeated prompt-detection failures
|
|
180
|
+
|
|
181
|
+
## Docs
|
|
182
|
+
|
|
183
|
+
| Doc | Topic |
|
|
184
|
+
|-----|-------|
|
|
185
|
+
| [API-DOCS.md](./API-DOCS.md) | Full API reference — types, SSHService methods, MCP tools, error model |
|
|
186
|
+
| [docs/ASYNC_COMMANDS.md](./docs/ASYNC_COMMANDS.md) | Smart execution & async command lifecycle |
|
|
187
|
+
| [docs/INTERACTIVE_MODE.md](./docs/INTERACTIVE_MODE.md) | Terminal emulation, screen snapshots, key sending |
|
|
188
|
+
| [docs/SAFETY_PROTECTIONS.md](./docs/SAFETY_PROTECTIONS.md) | Limits, timeouts, session recovery, error handling |
|
|
189
|
+
| [docs/DOCKER.md](./docs/DOCKER.md) | Running via Docker |
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
MIT — see [LICENSE](./LICENSE).
|
|
194
|
+
|
|
195
|
+
## Fork
|
|
196
|
+
|
|
197
|
+
Fork of [devnullvoid/mcp-ssh-session](https://github.com/devnullvoid/mcp-ssh-session) with significant refactoring by [AmritaConstant](https://github.com/AmritaBot).
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# MCP SSH Session
|
|
2
|
+
|
|
3
|
+
An MCP (Model Context Protocol) server that enables AI agents to establish and manage persistent SSH sessions.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Smart Command Execution** — auto-transitions to async mode if timeout is reached
|
|
8
|
+
- **Persistent Sessions** — SSH connections reused across commands
|
|
9
|
+
- **Async Commands** — non-blocking execution for long-running tasks
|
|
10
|
+
- **SSH Config Support** — reads `~/.ssh/config` for aliases, ports, keys
|
|
11
|
+
- **Multi-host** — manage connections to multiple hosts simultaneously
|
|
12
|
+
- **Network Devices** — enable mode handling for Cisco, Juniper, MikroTik, etc.
|
|
13
|
+
- **Sudo Support** — automatic password handling for Unix/Linux hosts
|
|
14
|
+
- **File Operations** — read/write remote files via SFTP (sudo fallback)
|
|
15
|
+
- **Command Interruption** — send Ctrl+C to stop running commands
|
|
16
|
+
- **Thread-safe** — safe for concurrent operations
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
### Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
uvx mcp-ssh-reloaded
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Development
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
uv venv
|
|
30
|
+
source .venv/bin/activate
|
|
31
|
+
uv pip install -e .
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### CLI
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# MCP server (default)
|
|
38
|
+
mcp-ssh-reloaded serve mcp
|
|
39
|
+
|
|
40
|
+
# Direct execution (no MCP)
|
|
41
|
+
mcp-ssh-reloaded exec myserver "uname -a" -u admin
|
|
42
|
+
|
|
43
|
+
# List / close sessions
|
|
44
|
+
mcp-ssh-reloaded list
|
|
45
|
+
mcp-ssh-reloaded close-all
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### MCP Client Config
|
|
49
|
+
|
|
50
|
+
**Claude Code / Desktop** (`~/.claude.json` or `claude_desktop_config.json`):
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"mcpServers": {
|
|
55
|
+
"ssh-session": {
|
|
56
|
+
"type": "stdio",
|
|
57
|
+
"command": "uvx",
|
|
58
|
+
"args": ["mcp-ssh-reloaded", "serve", "mcp"],
|
|
59
|
+
"env": {}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Quick Examples
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
// SSH config alias
|
|
69
|
+
{ "host": "myserver", "command": "uptime" }
|
|
70
|
+
|
|
71
|
+
// Explicit params
|
|
72
|
+
{ "host": "example.com", "username": "user", "command": "ls -la", "port": 2222 }
|
|
73
|
+
|
|
74
|
+
// Network device (Cisco enable mode)
|
|
75
|
+
{ "host": "router", "username": "admin", "enable_password": "secret", "command": "show run" }
|
|
76
|
+
|
|
77
|
+
// Unix with sudo
|
|
78
|
+
{ "host": "server", "username": "ops", "sudo_password": "secret", "command": "systemctl restart nginx" }
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
> **Full API reference:** [API-DOCS.md](./API-DOCS.md) — all types, all methods, all MCP tools, error handling, server config tunables.
|
|
84
|
+
|
|
85
|
+
## SSH Config
|
|
86
|
+
|
|
87
|
+
`~/.ssh/config` is read automatically:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
Host myserver
|
|
91
|
+
HostName example.com
|
|
92
|
+
User myuser
|
|
93
|
+
Port 2222
|
|
94
|
+
IdentityFile ~/.ssh/id_rsa
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Then use `"host": "myserver"` — the rest is resolved for you.
|
|
98
|
+
|
|
99
|
+
## Credential Hiding (OVRD\_\*)
|
|
100
|
+
|
|
101
|
+
For production environments, store real credentials in env vars so AI agents only see aliases:
|
|
102
|
+
|
|
103
|
+
| Variable | Description |
|
|
104
|
+
| -------------------------- | ----------------------- |
|
|
105
|
+
| `OVRD_{alias}_HOST` | Real hostname or IP |
|
|
106
|
+
| `OVRD_{alias}_PORT` | SSH port |
|
|
107
|
+
| `OVRD_{alias}_USER` | SSH username |
|
|
108
|
+
| `OVRD_{alias}_PASS` | SSH password |
|
|
109
|
+
| `OVRD_{alias}_KEY` | Path to SSH private key |
|
|
110
|
+
| `OVRD_{alias}_SUDO_PASS` | Sudo password |
|
|
111
|
+
| `OVRD_{alias}_ENABLE_PASS` | Enable password |
|
|
112
|
+
|
|
113
|
+
**Example config:**
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"mcpServers": {
|
|
118
|
+
"ssh-session": {
|
|
119
|
+
"type": "stdio",
|
|
120
|
+
"command": "uvx",
|
|
121
|
+
"args": ["mcp-ssh-reloaded", "serve", "mcp"],
|
|
122
|
+
"env": {
|
|
123
|
+
"OVRD_prod_db_HOST": "192.168.1.100",
|
|
124
|
+
"OVRD_prod_db_USER": "admin",
|
|
125
|
+
"OVRD_prod_db_PASS": "secret123",
|
|
126
|
+
"OVRD_prod_db_SUDO_PASS": "sudopass"
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The agent uses `"host": "prod_db"` — never sees real IPs or passwords.
|
|
134
|
+
|
|
135
|
+
## How It Works
|
|
136
|
+
|
|
137
|
+
Commands run inside persistent interactive shells:
|
|
138
|
+
|
|
139
|
+
- **Directory persists**: `cd /tmp` stays in `/tmp` for the next command
|
|
140
|
+
- **Env vars persist**: `export FOO=bar` is visible across commands
|
|
141
|
+
- **Prompt detection**: completion detected via captured prompt or idle timeout (2 s)
|
|
142
|
+
- **Session recovery**: stuck shells auto-reset after repeated prompt-detection failures
|
|
143
|
+
|
|
144
|
+
## Docs
|
|
145
|
+
|
|
146
|
+
| Doc | Topic |
|
|
147
|
+
|-----|-------|
|
|
148
|
+
| [API-DOCS.md](./API-DOCS.md) | Full API reference — types, SSHService methods, MCP tools, error model |
|
|
149
|
+
| [docs/ASYNC_COMMANDS.md](./docs/ASYNC_COMMANDS.md) | Smart execution & async command lifecycle |
|
|
150
|
+
| [docs/INTERACTIVE_MODE.md](./docs/INTERACTIVE_MODE.md) | Terminal emulation, screen snapshots, key sending |
|
|
151
|
+
| [docs/SAFETY_PROTECTIONS.md](./docs/SAFETY_PROTECTIONS.md) | Limits, timeouts, session recovery, error handling |
|
|
152
|
+
| [docs/DOCKER.md](./docs/DOCKER.md) | Running via Docker |
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
MIT — see [LICENSE](./LICENSE).
|
|
157
|
+
|
|
158
|
+
## Fork
|
|
159
|
+
|
|
160
|
+
Fork of [devnullvoid/mcp-ssh-session](https://github.com/devnullvoid/mcp-ssh-session) with significant refactoring by [AmritaConstant](https://github.com/AmritaBot).
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "mcp-ssh-reloaded"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "MCP server for SSH sessions management."
|
|
5
|
+
requires-python = ">=3.10, <3.14"
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "Jon Rogers <devnullvoid>" },
|
|
9
|
+
{ name = "AmritaConstant", email = "admin@amritabot.com" },
|
|
10
|
+
]
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
keywords = ["mcp", "ssh", "fastmcp"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Environment :: Console",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Topic :: System :: Networking",
|
|
25
|
+
"Topic :: System :: Systems Administration",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"fastmcp>=3.2.0",
|
|
29
|
+
"paramiko>=3.4.0",
|
|
30
|
+
"pyte>=0.8.0",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
mcp-ssh = "mcp_ssh_reloaded.__main__:main"
|
|
35
|
+
mcp-ssh-reloaded = "mcp_ssh_reloaded.__main__:main"
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://github.com/AmritaBot/mcp-ssh-reloaded"
|
|
39
|
+
Repository = "https://github.com/AmritaBot/mcp-ssh-reloaded"
|
|
40
|
+
Issues = "https://github.com/AmritaBot/mcp-ssh-reloaded/issues"
|
|
41
|
+
|
|
42
|
+
[dependency-groups]
|
|
43
|
+
dev = [
|
|
44
|
+
"pyright>=1.1.411",
|
|
45
|
+
"pytest>=9.0.1",
|
|
46
|
+
"pytest-cov>=7.1.0",
|
|
47
|
+
"ruff>=0.15.21",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[tool.uv]
|
|
51
|
+
package = true
|
|
52
|
+
|
|
53
|
+
[tool.ruff]
|
|
54
|
+
line-length = 88
|
|
55
|
+
target-version = "py310"
|
|
56
|
+
[tool.ruff.lint]
|
|
57
|
+
select = [
|
|
58
|
+
"F", # Pyflakes
|
|
59
|
+
"W", # pycodestyle warnings
|
|
60
|
+
"E", # pycodestyle errors
|
|
61
|
+
"UP", # pyupgrade
|
|
62
|
+
"ASYNC", # flake8-async
|
|
63
|
+
"C4", # flake8-comprehensions
|
|
64
|
+
"T10", # flake8-debugger
|
|
65
|
+
"PYI", # flake8-pyi
|
|
66
|
+
"PT", # flake8-pytest-style
|
|
67
|
+
"Q", # flake8-quotes
|
|
68
|
+
"RUF", # Ruff-specific rules
|
|
69
|
+
"I", # isort
|
|
70
|
+
"PERF", # pylint-performance
|
|
71
|
+
]
|
|
72
|
+
ignore = [
|
|
73
|
+
"E501", # line-too-long
|
|
74
|
+
"UP037", # quoted-annotation
|
|
75
|
+
"RUF001", # ambiguous-unicode-character-string
|
|
76
|
+
"RUF002", # ambiguous-unicode-character-docstring
|
|
77
|
+
"RUF003", # ambiguous-unicode-character-comment
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
[tool.pyright]
|
|
82
|
+
typeCheckingMode = "standard"
|
|
83
|
+
|
|
84
|
+
[[tool.pyright.executionEnvironments]]
|
|
85
|
+
root = "tests"
|
|
86
|
+
typeCheckingMode = "basic"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""MCP SSH Session server — persistent SSH sessions for AI agents."""
|
|
2
|
+
|
|
3
|
+
from .api_types import (
|
|
4
|
+
CommandResult,
|
|
5
|
+
CommandStatus,
|
|
6
|
+
ConnectionParams,
|
|
7
|
+
DeviceFamily,
|
|
8
|
+
ErrorCategory,
|
|
9
|
+
FileContent,
|
|
10
|
+
ServerConfig,
|
|
11
|
+
SessionDiagnostics,
|
|
12
|
+
SessionInfo,
|
|
13
|
+
SSHError,
|
|
14
|
+
)
|
|
15
|
+
from .server import mcp
|
|
16
|
+
from .services import SSHService
|
|
17
|
+
|
|
18
|
+
__version__ = "0.3.0"
|
|
19
|
+
__all__ = [
|
|
20
|
+
"CommandResult",
|
|
21
|
+
"CommandStatus",
|
|
22
|
+
"ConnectionParams",
|
|
23
|
+
"DeviceFamily",
|
|
24
|
+
"ErrorCategory",
|
|
25
|
+
"FileContent",
|
|
26
|
+
"SSHError",
|
|
27
|
+
"SSHService",
|
|
28
|
+
"ServerConfig",
|
|
29
|
+
"SessionDiagnostics",
|
|
30
|
+
"SessionInfo",
|
|
31
|
+
"mcp",
|
|
32
|
+
]
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Entry point for mcp-ssh.
|
|
2
|
+
|
|
3
|
+
Supports multiple modes:
|
|
4
|
+
mcp-ssh serve mcp # MCP stdio server (default)
|
|
5
|
+
mcp-ssh serve http # MCP Streamable HTTP server
|
|
6
|
+
mcp-ssh serve sse # MCP SSE server
|
|
7
|
+
mcp-ssh exec <host> <cmd> # Direct execution (no MCP)
|
|
8
|
+
mcp-ssh list # List active sessions
|
|
9
|
+
mcp-ssh close <host> # Close a session
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
from .api_types import ConnectionParams
|
|
16
|
+
from .services import SSHService
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main(argv: list[str] | None = None) -> None:
|
|
20
|
+
from .logging_manager import get_logger
|
|
21
|
+
|
|
22
|
+
_log = get_logger("cli")
|
|
23
|
+
|
|
24
|
+
if argv is None:
|
|
25
|
+
argv = sys.argv[1:]
|
|
26
|
+
|
|
27
|
+
parser = argparse.ArgumentParser(
|
|
28
|
+
prog="mcp-ssh",
|
|
29
|
+
description="MCP SSH Session server",
|
|
30
|
+
)
|
|
31
|
+
sub = parser.add_subparsers(dest="mode")
|
|
32
|
+
|
|
33
|
+
# serve <sub-mode>
|
|
34
|
+
serve_p = sub.add_parser("serve", help="Start a server")
|
|
35
|
+
serve_sub = serve_p.add_subparsers(dest="serve_mode", required=True)
|
|
36
|
+
serve_sub.add_parser("mcp", help="MCP stdio server (default)")
|
|
37
|
+
|
|
38
|
+
http_p = serve_sub.add_parser("http", help="MCP Streamable HTTP server")
|
|
39
|
+
http_p.add_argument(
|
|
40
|
+
"--host", default="127.0.0.1", help="Listen host (default: 127.0.0.1)"
|
|
41
|
+
)
|
|
42
|
+
http_p.add_argument(
|
|
43
|
+
"--port", type=int, default=8000, help="Listen port (default: 8000)"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
sse_p = serve_sub.add_parser("sse", help="MCP SSE server")
|
|
47
|
+
sse_p.add_argument(
|
|
48
|
+
"--host", default="127.0.0.1", help="Listen host (default: 127.0.0.1)"
|
|
49
|
+
)
|
|
50
|
+
sse_p.add_argument(
|
|
51
|
+
"--port", type=int, default=8000, help="Listen port (default: 8000)"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# exec <host> <command...>
|
|
55
|
+
exec_p = sub.add_parser("exec", help="Execute a command directly")
|
|
56
|
+
exec_p.add_argument("host")
|
|
57
|
+
exec_p.add_argument("command", nargs="+")
|
|
58
|
+
exec_p.add_argument("-u", "--user")
|
|
59
|
+
exec_p.add_argument("-p", "--port", type=int, default=22)
|
|
60
|
+
exec_p.add_argument("-k", "--key")
|
|
61
|
+
exec_p.add_argument("--sudo-password")
|
|
62
|
+
exec_p.add_argument("-t", "--timeout", type=int, default=30)
|
|
63
|
+
|
|
64
|
+
# list
|
|
65
|
+
sub.add_parser("list", help="List active sessions")
|
|
66
|
+
|
|
67
|
+
# close <host>
|
|
68
|
+
close_p = sub.add_parser("close", help="Close a session")
|
|
69
|
+
close_p.add_argument("host")
|
|
70
|
+
close_p.add_argument("-u", "--user")
|
|
71
|
+
close_p.add_argument("-p", "--port", type=int, default=22)
|
|
72
|
+
|
|
73
|
+
# close-all
|
|
74
|
+
sub.add_parser("close-all", help="Close all sessions")
|
|
75
|
+
|
|
76
|
+
args = parser.parse_args(argv)
|
|
77
|
+
|
|
78
|
+
if args.mode is None:
|
|
79
|
+
_run_mcp()
|
|
80
|
+
elif args.mode == "serve":
|
|
81
|
+
if args.serve_mode == "mcp":
|
|
82
|
+
_run_mcp()
|
|
83
|
+
elif args.serve_mode == "http":
|
|
84
|
+
_run_mcp(transport="streamable-http", host=args.host, port=args.port)
|
|
85
|
+
elif args.serve_mode == "sse":
|
|
86
|
+
_run_mcp(transport="sse", host=args.host, port=args.port)
|
|
87
|
+
else:
|
|
88
|
+
_run_mcp()
|
|
89
|
+
elif args.mode == "exec":
|
|
90
|
+
_run_exec(args)
|
|
91
|
+
elif args.mode == "list":
|
|
92
|
+
_run_list()
|
|
93
|
+
elif args.mode == "close":
|
|
94
|
+
_run_close(args)
|
|
95
|
+
elif args.mode == "close-all":
|
|
96
|
+
_run_close_all()
|
|
97
|
+
else:
|
|
98
|
+
_run_mcp()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _run_mcp(transport: str = "stdio", **kwargs: object) -> None:
|
|
102
|
+
from .server import mcp
|
|
103
|
+
|
|
104
|
+
mcp.run(transport=transport, **kwargs) # type: ignore[arg-type]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _run_exec(args) -> None:
|
|
108
|
+
svc = SSHService()
|
|
109
|
+
conn = ConnectionParams(
|
|
110
|
+
host=args.host,
|
|
111
|
+
port=args.port,
|
|
112
|
+
username=args.user,
|
|
113
|
+
key_filename=args.key,
|
|
114
|
+
sudo_password=args.sudo_password,
|
|
115
|
+
)
|
|
116
|
+
cmd = " ".join(args.command)
|
|
117
|
+
result = svc.execute(conn, cmd, timeout=args.timeout)
|
|
118
|
+
if result.stdout:
|
|
119
|
+
sys.stdout.write(result.stdout)
|
|
120
|
+
if result.stderr:
|
|
121
|
+
sys.stderr.write(result.stderr)
|
|
122
|
+
sys.exit(result.exit_code)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _run_list() -> None:
|
|
126
|
+
svc = SSHService()
|
|
127
|
+
sessions = svc.list_sessions()
|
|
128
|
+
if not sessions:
|
|
129
|
+
print("No active SSH sessions")
|
|
130
|
+
return
|
|
131
|
+
for s in sessions:
|
|
132
|
+
print(f" {s.session_key}")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _run_close(args) -> None:
|
|
136
|
+
svc = SSHService()
|
|
137
|
+
svc.close_session(
|
|
138
|
+
ConnectionParams(host=args.host, username=args.user, port=args.port)
|
|
139
|
+
)
|
|
140
|
+
print(f"Closed: {args.user or '(default)'}@{args.host}:{args.port}")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _run_close_all() -> None:
|
|
144
|
+
svc = SSHService()
|
|
145
|
+
svc.close_all()
|
|
146
|
+
print("All SSH sessions closed")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
if __name__ == "__main__":
|
|
150
|
+
main()
|