mcp-remote-ssh 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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mohammadfaiz Bawa
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,209 @@
1
+ Metadata-Version: 2.4
2
+ Name: mcp-remote-ssh
3
+ Version: 0.1.0
4
+ Summary: MCP server for remote SSH operations -- persistent sessions, structured command execution, SFTP file transfer, and port forwarding for AI agents.
5
+ Author: Mohammadfaiz Bawa
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/faizbawa/mcp-remote-ssh
8
+ Project-URL: Repository, https://github.com/faizbawa/mcp-remote-ssh
9
+ Project-URL: Issues, https://github.com/faizbawa/mcp-remote-ssh/issues
10
+ Project-URL: Changelog, https://github.com/faizbawa/mcp-remote-ssh/blob/main/CHANGELOG.md
11
+ Keywords: mcp,ssh,remote,sftp,paramiko,ai,model-context-protocol,cursor,claude
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: System :: Networking
21
+ Classifier: Topic :: System :: Systems Administration
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: fastmcp<4,>=3.0
26
+ Requires-Dist: paramiko>=3.4
27
+ Requires-Dist: click>=8.0
28
+ Requires-Dist: loguru>=0.7
29
+ Dynamic: license-file
30
+
31
+ # mcp-remote-ssh
32
+
33
+ MCP server for remote SSH operations. Gives AI agents persistent SSH sessions, structured command output, SFTP file transfer, and SSH port forwarding -- with native password and key-based authentication.
34
+
35
+ ## Why this exists
36
+
37
+ Existing SSH MCP servers each solve part of the problem but none combine all of:
38
+
39
+ - **Password + key auth** -- connect to any host, whether it uses passwords, SSH keys, or an agent
40
+ - **Dual execution model** -- one-shot `exec_command()` with real exit codes *and* persistent interactive shells for long-running workflows
41
+ - **SFTP** -- read, write, upload, download files properly instead of piping through a PTY
42
+ - **Port forwarding** -- SSH tunnels for accessing remote services (databases, web UIs, VNC, etc.)
43
+ - **Structured output** -- `stdout`, `stderr`, and `exit_code` as separate fields, not screen scrapes
44
+
45
+ ## Installation
46
+
47
+ ```bash
48
+ # Via uv (recommended)
49
+ uv tool install mcp-remote-ssh
50
+
51
+ # Via pip
52
+ pip install mcp-remote-ssh
53
+
54
+ # From source
55
+ git clone https://github.com/faizbawa/mcp-remote-ssh.git
56
+ cd mcp-remote-ssh
57
+ uv sync
58
+ ```
59
+
60
+ ## Configuration
61
+
62
+ ### Cursor / Claude Desktop
63
+
64
+ Add to your MCP client configuration:
65
+
66
+ ```json
67
+ {
68
+ "mcpServers": {
69
+ "remote-ssh": {
70
+ "command": "uvx",
71
+ "args": ["mcp-remote-ssh"]
72
+ }
73
+ }
74
+ }
75
+ ```
76
+
77
+ Or if running from source:
78
+
79
+ ```json
80
+ {
81
+ "mcpServers": {
82
+ "remote-ssh": {
83
+ "command": "uv",
84
+ "args": ["run", "--directory", "/path/to/mcp-remote-ssh", "mcp-remote-ssh"]
85
+ }
86
+ }
87
+ }
88
+ ```
89
+
90
+ ## Tools (18 total)
91
+
92
+ ### Connection management
93
+
94
+ | Tool | Description |
95
+ |---|---|
96
+ | `ssh_connect` | Connect to a host (password, key, or agent auth). Returns `session_id`. |
97
+ | `ssh_list_sessions` | List all active sessions with status |
98
+ | `ssh_close_session` | Close a session and release all resources |
99
+
100
+ ### Structured execution (one-shot, clean output)
101
+
102
+ | Tool | Description |
103
+ |---|---|
104
+ | `ssh_execute` | Run a command, returns `{stdout, stderr, exit_code}` |
105
+ | `ssh_sudo_execute` | Run a command with sudo elevation |
106
+
107
+ ### Interactive shell (persistent state)
108
+
109
+ | Tool | Description |
110
+ |---|---|
111
+ | `ssh_shell_open` | Open a persistent interactive shell (`invoke_shell`) |
112
+ | `ssh_shell_send` | Send text to the shell (with optional Enter) |
113
+ | `ssh_shell_read` | Read current shell buffer (poll for output) |
114
+ | `ssh_shell_send_control` | Send Ctrl+C, Ctrl+D, etc. |
115
+ | `ssh_shell_wait` | Wait for a pattern or output to stabilize |
116
+
117
+ ### File transfer (SFTP)
118
+
119
+ | Tool | Description |
120
+ |---|---|
121
+ | `ssh_upload_file` | Upload a local file to the remote host |
122
+ | `ssh_download_file` | Download a remote file to local machine |
123
+ | `ssh_read_remote_file` | Read a text file on the remote host |
124
+ | `ssh_write_remote_file` | Write text to a remote file (create or append) |
125
+ | `ssh_list_remote_dir` | List directory contents with metadata |
126
+
127
+ ### Port forwarding
128
+
129
+ | Tool | Description |
130
+ |---|---|
131
+ | `ssh_forward_port` | Create an SSH tunnel (local port -> remote port) |
132
+ | `ssh_list_forwards` | List active port forwards for a session |
133
+ | `ssh_close_forward` | Close a specific port forward |
134
+
135
+ ## Quick start
136
+
137
+ ```text
138
+ # Connect with password
139
+ ssh_connect(host="myserver.example.com", username="admin", password="secret")
140
+ → {"session_id": "a1b2c3d4", "connected": true, ...}
141
+
142
+ # Run a structured command
143
+ ssh_execute(session_id="a1b2c3d4", command="df -h /")
144
+ → {"stdout": "Filesystem Size Used ...", "stderr": "", "exit_code": 0}
145
+
146
+ # Open a persistent shell for interactive work
147
+ ssh_shell_open(session_id="a1b2c3d4")
148
+ ssh_shell_send(session_id="a1b2c3d4", data="cd /opt && make -j$(nproc)")
149
+ ssh_shell_wait(session_id="a1b2c3d4", pattern="$ ", timeout=600)
150
+
151
+ # Transfer files via SFTP
152
+ ssh_upload_file(session_id="a1b2c3d4", local_path="config.yaml", remote_path="/etc/app/config.yaml")
153
+ ssh_read_remote_file(session_id="a1b2c3d4", remote_path="/var/log/app.log")
154
+
155
+ # Set up an SSH tunnel
156
+ ssh_forward_port(session_id="a1b2c3d4", remote_port=5432, local_port=15432)
157
+ # Now connect to localhost:15432 to reach the remote PostgreSQL
158
+ ```
159
+
160
+ ## Architecture
161
+
162
+ ```
163
+ src/mcp_remote_ssh/
164
+ ├── __init__.py # CLI entry point (click)
165
+ ├── lifespan.py # FastMCP lifespan (session cleanup)
166
+ ├── session.py # SSHSession, SessionStore, PortForward
167
+ └── server/
168
+ ├── __init__.py # FastMCP app + tool registration
169
+ ├── helpers.py # get_session, require_connected, require_shell
170
+ ├── connection.py # ssh_connect, ssh_list_sessions, ssh_close_session
171
+ ├── execute.py # ssh_execute, ssh_sudo_execute
172
+ ├── shell.py # ssh_shell_open/send/read/control/wait
173
+ ├── sftp.py # upload, download, read, write, list_dir
174
+ └── forward.py # forward_port, list_forwards, close_forward
175
+ ```
176
+
177
+ ### Key design decisions
178
+
179
+ - **[Paramiko](https://www.paramiko.org/)** for SSH -- mature, pure-Python, supports password auth, SFTP, channels, and port forwarding natively
180
+ - **[FastMCP](https://github.com/PrefectHQ/fastmcp)** for MCP protocol
181
+ - **Dual execution model** -- `exec_command()` for structured one-shot commands (returns exit codes), `invoke_shell()` for persistent interactive sessions
182
+ - **Async wrappers** -- all blocking Paramiko calls run in `run_in_executor` to avoid blocking the event loop
183
+ - **Shell buffer management** -- interactive shell keeps a 500KB rolling buffer for `shell_read` polling
184
+
185
+ ## Transport options
186
+
187
+ ```bash
188
+ # stdio (default, for Cursor/Claude Desktop)
189
+ mcp-remote-ssh
190
+
191
+ # SSE
192
+ mcp-remote-ssh --transport sse --host 0.0.0.0 --port 9810
193
+
194
+ # Streamable HTTP
195
+ mcp-remote-ssh --transport streamable-http --host 0.0.0.0 --port 9810
196
+ ```
197
+
198
+ ## Dependencies
199
+
200
+ | Package | Purpose |
201
+ |---|---|
202
+ | [FastMCP](https://github.com/PrefectHQ/fastmcp) | MCP protocol handling |
203
+ | [Paramiko](https://www.paramiko.org/) | SSH2 protocol (connections, channels, SFTP) |
204
+ | [Click](https://click.palletsprojects.com/) | CLI interface |
205
+ | [Loguru](https://loguru.readthedocs.io/) | Logging |
206
+
207
+ ## License
208
+
209
+ MIT
@@ -0,0 +1,179 @@
1
+ # mcp-remote-ssh
2
+
3
+ MCP server for remote SSH operations. Gives AI agents persistent SSH sessions, structured command output, SFTP file transfer, and SSH port forwarding -- with native password and key-based authentication.
4
+
5
+ ## Why this exists
6
+
7
+ Existing SSH MCP servers each solve part of the problem but none combine all of:
8
+
9
+ - **Password + key auth** -- connect to any host, whether it uses passwords, SSH keys, or an agent
10
+ - **Dual execution model** -- one-shot `exec_command()` with real exit codes *and* persistent interactive shells for long-running workflows
11
+ - **SFTP** -- read, write, upload, download files properly instead of piping through a PTY
12
+ - **Port forwarding** -- SSH tunnels for accessing remote services (databases, web UIs, VNC, etc.)
13
+ - **Structured output** -- `stdout`, `stderr`, and `exit_code` as separate fields, not screen scrapes
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ # Via uv (recommended)
19
+ uv tool install mcp-remote-ssh
20
+
21
+ # Via pip
22
+ pip install mcp-remote-ssh
23
+
24
+ # From source
25
+ git clone https://github.com/faizbawa/mcp-remote-ssh.git
26
+ cd mcp-remote-ssh
27
+ uv sync
28
+ ```
29
+
30
+ ## Configuration
31
+
32
+ ### Cursor / Claude Desktop
33
+
34
+ Add to your MCP client configuration:
35
+
36
+ ```json
37
+ {
38
+ "mcpServers": {
39
+ "remote-ssh": {
40
+ "command": "uvx",
41
+ "args": ["mcp-remote-ssh"]
42
+ }
43
+ }
44
+ }
45
+ ```
46
+
47
+ Or if running from source:
48
+
49
+ ```json
50
+ {
51
+ "mcpServers": {
52
+ "remote-ssh": {
53
+ "command": "uv",
54
+ "args": ["run", "--directory", "/path/to/mcp-remote-ssh", "mcp-remote-ssh"]
55
+ }
56
+ }
57
+ }
58
+ ```
59
+
60
+ ## Tools (18 total)
61
+
62
+ ### Connection management
63
+
64
+ | Tool | Description |
65
+ |---|---|
66
+ | `ssh_connect` | Connect to a host (password, key, or agent auth). Returns `session_id`. |
67
+ | `ssh_list_sessions` | List all active sessions with status |
68
+ | `ssh_close_session` | Close a session and release all resources |
69
+
70
+ ### Structured execution (one-shot, clean output)
71
+
72
+ | Tool | Description |
73
+ |---|---|
74
+ | `ssh_execute` | Run a command, returns `{stdout, stderr, exit_code}` |
75
+ | `ssh_sudo_execute` | Run a command with sudo elevation |
76
+
77
+ ### Interactive shell (persistent state)
78
+
79
+ | Tool | Description |
80
+ |---|---|
81
+ | `ssh_shell_open` | Open a persistent interactive shell (`invoke_shell`) |
82
+ | `ssh_shell_send` | Send text to the shell (with optional Enter) |
83
+ | `ssh_shell_read` | Read current shell buffer (poll for output) |
84
+ | `ssh_shell_send_control` | Send Ctrl+C, Ctrl+D, etc. |
85
+ | `ssh_shell_wait` | Wait for a pattern or output to stabilize |
86
+
87
+ ### File transfer (SFTP)
88
+
89
+ | Tool | Description |
90
+ |---|---|
91
+ | `ssh_upload_file` | Upload a local file to the remote host |
92
+ | `ssh_download_file` | Download a remote file to local machine |
93
+ | `ssh_read_remote_file` | Read a text file on the remote host |
94
+ | `ssh_write_remote_file` | Write text to a remote file (create or append) |
95
+ | `ssh_list_remote_dir` | List directory contents with metadata |
96
+
97
+ ### Port forwarding
98
+
99
+ | Tool | Description |
100
+ |---|---|
101
+ | `ssh_forward_port` | Create an SSH tunnel (local port -> remote port) |
102
+ | `ssh_list_forwards` | List active port forwards for a session |
103
+ | `ssh_close_forward` | Close a specific port forward |
104
+
105
+ ## Quick start
106
+
107
+ ```text
108
+ # Connect with password
109
+ ssh_connect(host="myserver.example.com", username="admin", password="secret")
110
+ → {"session_id": "a1b2c3d4", "connected": true, ...}
111
+
112
+ # Run a structured command
113
+ ssh_execute(session_id="a1b2c3d4", command="df -h /")
114
+ → {"stdout": "Filesystem Size Used ...", "stderr": "", "exit_code": 0}
115
+
116
+ # Open a persistent shell for interactive work
117
+ ssh_shell_open(session_id="a1b2c3d4")
118
+ ssh_shell_send(session_id="a1b2c3d4", data="cd /opt && make -j$(nproc)")
119
+ ssh_shell_wait(session_id="a1b2c3d4", pattern="$ ", timeout=600)
120
+
121
+ # Transfer files via SFTP
122
+ ssh_upload_file(session_id="a1b2c3d4", local_path="config.yaml", remote_path="/etc/app/config.yaml")
123
+ ssh_read_remote_file(session_id="a1b2c3d4", remote_path="/var/log/app.log")
124
+
125
+ # Set up an SSH tunnel
126
+ ssh_forward_port(session_id="a1b2c3d4", remote_port=5432, local_port=15432)
127
+ # Now connect to localhost:15432 to reach the remote PostgreSQL
128
+ ```
129
+
130
+ ## Architecture
131
+
132
+ ```
133
+ src/mcp_remote_ssh/
134
+ ├── __init__.py # CLI entry point (click)
135
+ ├── lifespan.py # FastMCP lifespan (session cleanup)
136
+ ├── session.py # SSHSession, SessionStore, PortForward
137
+ └── server/
138
+ ├── __init__.py # FastMCP app + tool registration
139
+ ├── helpers.py # get_session, require_connected, require_shell
140
+ ├── connection.py # ssh_connect, ssh_list_sessions, ssh_close_session
141
+ ├── execute.py # ssh_execute, ssh_sudo_execute
142
+ ├── shell.py # ssh_shell_open/send/read/control/wait
143
+ ├── sftp.py # upload, download, read, write, list_dir
144
+ └── forward.py # forward_port, list_forwards, close_forward
145
+ ```
146
+
147
+ ### Key design decisions
148
+
149
+ - **[Paramiko](https://www.paramiko.org/)** for SSH -- mature, pure-Python, supports password auth, SFTP, channels, and port forwarding natively
150
+ - **[FastMCP](https://github.com/PrefectHQ/fastmcp)** for MCP protocol
151
+ - **Dual execution model** -- `exec_command()` for structured one-shot commands (returns exit codes), `invoke_shell()` for persistent interactive sessions
152
+ - **Async wrappers** -- all blocking Paramiko calls run in `run_in_executor` to avoid blocking the event loop
153
+ - **Shell buffer management** -- interactive shell keeps a 500KB rolling buffer for `shell_read` polling
154
+
155
+ ## Transport options
156
+
157
+ ```bash
158
+ # stdio (default, for Cursor/Claude Desktop)
159
+ mcp-remote-ssh
160
+
161
+ # SSE
162
+ mcp-remote-ssh --transport sse --host 0.0.0.0 --port 9810
163
+
164
+ # Streamable HTTP
165
+ mcp-remote-ssh --transport streamable-http --host 0.0.0.0 --port 9810
166
+ ```
167
+
168
+ ## Dependencies
169
+
170
+ | Package | Purpose |
171
+ |---|---|
172
+ | [FastMCP](https://github.com/PrefectHQ/fastmcp) | MCP protocol handling |
173
+ | [Paramiko](https://www.paramiko.org/) | SSH2 protocol (connections, channels, SFTP) |
174
+ | [Click](https://click.palletsprojects.com/) | CLI interface |
175
+ | [Loguru](https://loguru.readthedocs.io/) | Logging |
176
+
177
+ ## License
178
+
179
+ MIT
@@ -0,0 +1,61 @@
1
+ [project]
2
+ name = "mcp-remote-ssh"
3
+ version = "0.1.0"
4
+ description = "MCP server for remote SSH operations -- persistent sessions, structured command execution, SFTP file transfer, and port forwarding for AI agents."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ requires-python = ">=3.10"
8
+ keywords = ["mcp", "ssh", "remote", "sftp", "paramiko", "ai", "model-context-protocol", "cursor", "claude"]
9
+ classifiers = [
10
+ "Development Status :: 4 - Beta",
11
+ "Intended Audience :: Developers",
12
+ "Intended Audience :: System Administrators",
13
+ "Programming Language :: Python :: 3",
14
+ "Programming Language :: Python :: 3.10",
15
+ "Programming Language :: Python :: 3.11",
16
+ "Programming Language :: Python :: 3.12",
17
+ "Programming Language :: Python :: 3.13",
18
+ "Topic :: System :: Networking",
19
+ "Topic :: System :: Systems Administration",
20
+ ]
21
+ dependencies = [
22
+ "fastmcp>=3.0,<4",
23
+ "paramiko>=3.4",
24
+ "click>=8.0",
25
+ "loguru>=0.7",
26
+ ]
27
+
28
+ [[project.authors]]
29
+ name = "Mohammadfaiz Bawa"
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/faizbawa/mcp-remote-ssh"
33
+ Repository = "https://github.com/faizbawa/mcp-remote-ssh"
34
+ Issues = "https://github.com/faizbawa/mcp-remote-ssh/issues"
35
+ Changelog = "https://github.com/faizbawa/mcp-remote-ssh/blob/main/CHANGELOG.md"
36
+
37
+ [project.scripts]
38
+ mcp-remote-ssh = "mcp_remote_ssh:main"
39
+
40
+ [dependency-groups]
41
+ dev = [
42
+ "pytest>=9.0",
43
+ "pytest-asyncio>=1.0",
44
+ "pytest-cov>=7.0",
45
+ "pytest-mock>=3.15",
46
+ ]
47
+
48
+ [tool.uv]
49
+ package = true
50
+
51
+ [tool.ruff]
52
+ line-length = 120
53
+ indent-width = 4
54
+
55
+ lint.select = ["E", "F", "B", "W", "I", "N", "UP", "S", "BLE", "C4", "EM", "ISC"]
56
+ lint.ignore = ["EM102"]
57
+ lint.fixable = ["ALL"]
58
+ lint.per-file-ignores."tests/**/*.py" = ["S101", "S106"]
59
+
60
+ format.quote-style = "single"
61
+ format.indent-style = "space"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,37 @@
1
+ import asyncio
2
+ import sys
3
+ from pathlib import Path
4
+
5
+ import click
6
+ from loguru import logger
7
+
8
+ try:
9
+ LOG_DIR = Path.home() / '.mcp_remote_ssh'
10
+ LOG_DIR.mkdir(exist_ok=True)
11
+ logger.add(LOG_DIR / 'log.log', rotation='10 MB')
12
+ except Exception as e: # noqa: BLE001
13
+ logger.error(f'Failed to set up logger directory: {e}')
14
+
15
+ if sys.platform == 'win32':
16
+ asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
17
+
18
+
19
+ @click.command()
20
+ @click.option('--transport', type=click.Choice(['stdio', 'sse', 'streamable-http']), default='stdio')
21
+ @click.option('--host', default='0.0.0.0', help='Host to bind to for SSE or Streamable HTTP transport') # noqa: S104
22
+ @click.option('--port', default=9810, help='Port to listen on for SSE or Streamable HTTP transport')
23
+ def main(
24
+ transport: str,
25
+ host: str,
26
+ port: int,
27
+ ) -> None:
28
+ from mcp_remote_ssh.server import mcp
29
+
30
+ if transport == 'stdio':
31
+ asyncio.run(mcp.run_async(transport=transport))
32
+ elif transport in ('sse', 'streamable-http'):
33
+ asyncio.run(mcp.run_async(transport=transport, host=host, port=port))
34
+
35
+
36
+ if __name__ == '__main__':
37
+ main()
@@ -0,0 +1,33 @@
1
+ from __future__ import annotations
2
+
3
+ from contextlib import asynccontextmanager
4
+ from dataclasses import dataclass
5
+ from typing import TYPE_CHECKING, AsyncIterator
6
+
7
+ from loguru import logger
8
+
9
+ from mcp_remote_ssh.session import SessionStore
10
+
11
+ if TYPE_CHECKING:
12
+ from fastmcp import FastMCP
13
+
14
+
15
+ @dataclass
16
+ class LifespanContext:
17
+ sessions: SessionStore
18
+
19
+
20
+ @asynccontextmanager
21
+ async def lifespan(app: FastMCP) -> AsyncIterator[LifespanContext]:
22
+ ctx = LifespanContext(sessions=SessionStore())
23
+ try:
24
+ yield ctx
25
+ finally:
26
+ for info in ctx.sessions.list_all():
27
+ sid = info['session_id']
28
+ session = ctx.sessions.get(sid)
29
+ errors = session.close()
30
+ if errors:
31
+ logger.warning(f'Session {sid} cleanup errors: {errors}')
32
+ else:
33
+ logger.debug(f'Cleaned up session {sid}')
@@ -0,0 +1,9 @@
1
+ from fastmcp import FastMCP
2
+
3
+ from mcp_remote_ssh.lifespan import LifespanContext, lifespan
4
+
5
+ __all__ = ['mcp']
6
+
7
+ mcp = FastMCP('mcp-remote-ssh', lifespan=lifespan)
8
+
9
+ from mcp_remote_ssh.server import connection, execute, forward, sftp, shell # noqa: E402, F401
@@ -0,0 +1,97 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from typing import Any
5
+
6
+ import paramiko
7
+ from fastmcp import Context
8
+ from loguru import logger
9
+
10
+ from mcp_remote_ssh.server import mcp
11
+ from mcp_remote_ssh.server.helpers import get_session, get_store
12
+ from mcp_remote_ssh.session import SSHSession
13
+
14
+
15
+ @mcp.tool()
16
+ async def ssh_connect(
17
+ ctx: Context,
18
+ host: str,
19
+ username: str = 'root',
20
+ password: str = '',
21
+ key_path: str = '',
22
+ port: int = 22,
23
+ timeout: int = 60,
24
+ ) -> dict[str, Any]:
25
+ """Connect to a remote host via SSH. Returns a session_id for use with all
26
+ other tools. Supports password and key-based authentication.
27
+
28
+ Args:
29
+ host: Hostname or IP address of the remote server.
30
+ username: SSH username (default: root).
31
+ password: Password for authentication. Leave empty for key-based auth.
32
+ key_path: Path to SSH private key file. Leave empty for password auth.
33
+ port: SSH port (default: 22).
34
+ timeout: Connection timeout in seconds (default: 60).
35
+
36
+ Returns:
37
+ Session info dict with session_id, host, and connection status.
38
+ """
39
+ store = get_store(ctx)
40
+ session = SSHSession(host=host, username=username, port=port)
41
+ session.client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # noqa: S507
42
+
43
+ connect_kwargs: dict[str, Any] = {
44
+ 'hostname': host,
45
+ 'port': port,
46
+ 'username': username,
47
+ 'timeout': timeout,
48
+ 'allow_agent': False,
49
+ 'look_for_keys': False,
50
+ }
51
+ if key_path:
52
+ connect_kwargs['key_filename'] = key_path
53
+ connect_kwargs['look_for_keys'] = True
54
+ elif password:
55
+ connect_kwargs['password'] = password
56
+ else:
57
+ connect_kwargs['allow_agent'] = True
58
+ connect_kwargs['look_for_keys'] = True
59
+
60
+ loop = asyncio.get_running_loop()
61
+ await loop.run_in_executor(None, lambda: session.client.connect(**connect_kwargs))
62
+
63
+ await store.add(session)
64
+ logger.info(f'Connected to {username}@{host}:{port} as session {session.session_id}')
65
+ return session.summary()
66
+
67
+
68
+ @mcp.tool()
69
+ async def ssh_list_sessions(ctx: Context) -> list[dict[str, Any]]:
70
+ """List all active SSH sessions with their connection status and details.
71
+
72
+ Returns:
73
+ List of session info dicts.
74
+ """
75
+ store = get_store(ctx)
76
+ return store.list_all()
77
+
78
+
79
+ @mcp.tool()
80
+ async def ssh_close_session(ctx: Context, session_id: str) -> str:
81
+ """Close an SSH session and release all its resources (shell, SFTP,
82
+ port forwards). WARNING: this kills any running processes in the session.
83
+
84
+ Args:
85
+ session_id: The session ID returned by ssh_connect.
86
+
87
+ Returns:
88
+ Confirmation message.
89
+ """
90
+ store = get_store(ctx)
91
+ session = get_session(ctx, session_id)
92
+ errors = session.close()
93
+ await store.remove(session_id)
94
+ logger.info(f'Closed session {session_id} to {session.host}')
95
+ if errors:
96
+ return f'Session {session_id} closed with warnings: {"; ".join(errors)}'
97
+ return f'Session {session_id} to {session.host} closed.'