claude-ssh-mcp 1.0.0__py3-none-any.whl
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.
- claude_ssh_mcp/__init__.py +237 -0
- claude_ssh_mcp-1.0.0.dist-info/METADATA +112 -0
- claude_ssh_mcp-1.0.0.dist-info/RECORD +7 -0
- claude_ssh_mcp-1.0.0.dist-info/WHEEL +5 -0
- claude_ssh_mcp-1.0.0.dist-info/entry_points.txt +2 -0
- claude_ssh_mcp-1.0.0.dist-info/licenses/LICENSE +21 -0
- claude_ssh_mcp-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
claude-ssh-mcp - MCP addon for Claude Desktop providing SSH capabilities.
|
|
4
|
+
|
|
5
|
+
This package automatically installs Node.js and runs the npm package.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
import subprocess
|
|
11
|
+
import shutil
|
|
12
|
+
import platform
|
|
13
|
+
import urllib.request
|
|
14
|
+
import tarfile
|
|
15
|
+
import zipfile
|
|
16
|
+
import tempfile
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
__version__ = "1.0.0"
|
|
20
|
+
|
|
21
|
+
# Node.js version to install if not found
|
|
22
|
+
NODE_VERSION = "20.11.0"
|
|
23
|
+
|
|
24
|
+
def get_node_paths():
|
|
25
|
+
"""Get paths where Node.js might be installed."""
|
|
26
|
+
home = Path.home()
|
|
27
|
+
paths = []
|
|
28
|
+
|
|
29
|
+
if platform.system() == "Windows":
|
|
30
|
+
paths = [
|
|
31
|
+
home / ".claude-ssh-mcp" / "node",
|
|
32
|
+
Path(os.environ.get("PROGRAMFILES", "C:\\Program Files")) / "nodejs",
|
|
33
|
+
Path(os.environ.get("LOCALAPPDATA", "")) / "Programs" / "node",
|
|
34
|
+
]
|
|
35
|
+
else:
|
|
36
|
+
paths = [
|
|
37
|
+
home / ".claude-ssh-mcp" / "node",
|
|
38
|
+
Path("/usr/local/bin"),
|
|
39
|
+
Path("/usr/bin"),
|
|
40
|
+
home / ".local" / "bin",
|
|
41
|
+
home / ".nvm" / "versions" / "node" / f"v{NODE_VERSION}" / "bin",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
return paths
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def find_node():
|
|
48
|
+
"""Find Node.js executable."""
|
|
49
|
+
# Check if node is in PATH
|
|
50
|
+
node_cmd = "node.exe" if platform.system() == "Windows" else "node"
|
|
51
|
+
node_path = shutil.which(node_cmd)
|
|
52
|
+
if node_path:
|
|
53
|
+
return node_path
|
|
54
|
+
|
|
55
|
+
# Check common installation paths
|
|
56
|
+
for base_path in get_node_paths():
|
|
57
|
+
if platform.system() == "Windows":
|
|
58
|
+
node_exe = base_path / "node.exe"
|
|
59
|
+
else:
|
|
60
|
+
node_exe = base_path / "node"
|
|
61
|
+
|
|
62
|
+
if node_exe.exists():
|
|
63
|
+
return str(node_exe)
|
|
64
|
+
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def find_npx():
|
|
69
|
+
"""Find npx executable."""
|
|
70
|
+
npx_cmd = "npx.cmd" if platform.system() == "Windows" else "npx"
|
|
71
|
+
npx_path = shutil.which(npx_cmd)
|
|
72
|
+
if npx_path:
|
|
73
|
+
return npx_path
|
|
74
|
+
|
|
75
|
+
# Check relative to node
|
|
76
|
+
node_path = find_node()
|
|
77
|
+
if node_path:
|
|
78
|
+
node_dir = Path(node_path).parent
|
|
79
|
+
if platform.system() == "Windows":
|
|
80
|
+
npx_exe = node_dir / "npx.cmd"
|
|
81
|
+
else:
|
|
82
|
+
npx_exe = node_dir / "npx"
|
|
83
|
+
|
|
84
|
+
if npx_exe.exists():
|
|
85
|
+
return str(npx_exe)
|
|
86
|
+
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def get_node_download_url():
|
|
91
|
+
"""Get the Node.js download URL for the current platform."""
|
|
92
|
+
system = platform.system().lower()
|
|
93
|
+
machine = platform.machine().lower()
|
|
94
|
+
|
|
95
|
+
if machine in ("x86_64", "amd64"):
|
|
96
|
+
arch = "x64"
|
|
97
|
+
elif machine in ("arm64", "aarch64"):
|
|
98
|
+
arch = "arm64"
|
|
99
|
+
elif machine in ("i386", "i686", "x86"):
|
|
100
|
+
arch = "x86"
|
|
101
|
+
else:
|
|
102
|
+
arch = "x64" # Default
|
|
103
|
+
|
|
104
|
+
if system == "windows":
|
|
105
|
+
return f"https://nodejs.org/dist/v{NODE_VERSION}/node-v{NODE_VERSION}-win-{arch}.zip"
|
|
106
|
+
elif system == "darwin":
|
|
107
|
+
return f"https://nodejs.org/dist/v{NODE_VERSION}/node-v{NODE_VERSION}-darwin-{arch}.tar.gz"
|
|
108
|
+
else: # Linux
|
|
109
|
+
return f"https://nodejs.org/dist/v{NODE_VERSION}/node-v{NODE_VERSION}-linux-{arch}.tar.xz"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def install_nodejs():
|
|
113
|
+
"""Download and install Node.js."""
|
|
114
|
+
install_dir = Path.home() / ".claude-ssh-mcp" / "node"
|
|
115
|
+
install_dir.mkdir(parents=True, exist_ok=True)
|
|
116
|
+
|
|
117
|
+
url = get_node_download_url()
|
|
118
|
+
print(f"Downloading Node.js from {url}...")
|
|
119
|
+
|
|
120
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
121
|
+
tmpdir = Path(tmpdir)
|
|
122
|
+
|
|
123
|
+
# Download
|
|
124
|
+
if url.endswith(".zip"):
|
|
125
|
+
archive_path = tmpdir / "node.zip"
|
|
126
|
+
elif url.endswith(".tar.xz"):
|
|
127
|
+
archive_path = tmpdir / "node.tar.xz"
|
|
128
|
+
else:
|
|
129
|
+
archive_path = tmpdir / "node.tar.gz"
|
|
130
|
+
|
|
131
|
+
urllib.request.urlretrieve(url, archive_path)
|
|
132
|
+
print("Extracting...")
|
|
133
|
+
|
|
134
|
+
# Extract
|
|
135
|
+
if url.endswith(".zip"):
|
|
136
|
+
with zipfile.ZipFile(archive_path, 'r') as zf:
|
|
137
|
+
zf.extractall(tmpdir)
|
|
138
|
+
elif url.endswith(".tar.xz"):
|
|
139
|
+
import lzma
|
|
140
|
+
with lzma.open(archive_path) as xz:
|
|
141
|
+
with tarfile.open(fileobj=xz) as tar:
|
|
142
|
+
tar.extractall(tmpdir)
|
|
143
|
+
else:
|
|
144
|
+
with tarfile.open(archive_path, "r:gz") as tar:
|
|
145
|
+
tar.extractall(tmpdir)
|
|
146
|
+
|
|
147
|
+
# Find extracted directory
|
|
148
|
+
extracted = None
|
|
149
|
+
for item in tmpdir.iterdir():
|
|
150
|
+
if item.is_dir() and item.name.startswith("node-"):
|
|
151
|
+
extracted = item
|
|
152
|
+
break
|
|
153
|
+
|
|
154
|
+
if not extracted:
|
|
155
|
+
raise RuntimeError("Failed to find extracted Node.js directory")
|
|
156
|
+
|
|
157
|
+
# Move to install location
|
|
158
|
+
if platform.system() == "Windows":
|
|
159
|
+
# On Windows, copy the contents directly
|
|
160
|
+
for item in extracted.iterdir():
|
|
161
|
+
dest = install_dir / item.name
|
|
162
|
+
if dest.exists():
|
|
163
|
+
if dest.is_dir():
|
|
164
|
+
shutil.rmtree(dest)
|
|
165
|
+
else:
|
|
166
|
+
dest.unlink()
|
|
167
|
+
shutil.move(str(item), str(dest))
|
|
168
|
+
else:
|
|
169
|
+
# On Unix, copy bin, lib, etc.
|
|
170
|
+
for item in extracted.iterdir():
|
|
171
|
+
dest = install_dir / item.name
|
|
172
|
+
if dest.exists():
|
|
173
|
+
if dest.is_dir():
|
|
174
|
+
shutil.rmtree(dest)
|
|
175
|
+
else:
|
|
176
|
+
dest.unlink()
|
|
177
|
+
shutil.move(str(item), str(dest))
|
|
178
|
+
|
|
179
|
+
# Verify installation
|
|
180
|
+
if platform.system() == "Windows":
|
|
181
|
+
node_exe = install_dir / "node.exe"
|
|
182
|
+
else:
|
|
183
|
+
node_exe = install_dir / "bin" / "node"
|
|
184
|
+
|
|
185
|
+
if not node_exe.exists():
|
|
186
|
+
raise RuntimeError(f"Node.js installation failed - {node_exe} not found")
|
|
187
|
+
|
|
188
|
+
print(f"Node.js installed to {install_dir}")
|
|
189
|
+
return str(node_exe)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def ensure_nodejs():
|
|
193
|
+
"""Ensure Node.js is installed, installing it if necessary."""
|
|
194
|
+
node_path = find_node()
|
|
195
|
+
if node_path:
|
|
196
|
+
return node_path
|
|
197
|
+
|
|
198
|
+
print("Node.js not found. Installing...")
|
|
199
|
+
return install_nodejs()
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def run_npx(*args):
|
|
203
|
+
"""Run npx with the given arguments."""
|
|
204
|
+
npx_path = find_npx()
|
|
205
|
+
|
|
206
|
+
if not npx_path:
|
|
207
|
+
# Ensure Node.js is installed
|
|
208
|
+
node_path = ensure_nodejs()
|
|
209
|
+
node_dir = Path(node_path).parent
|
|
210
|
+
|
|
211
|
+
if platform.system() == "Windows":
|
|
212
|
+
npx_path = str(node_dir / "npx.cmd")
|
|
213
|
+
else:
|
|
214
|
+
npx_path = str(node_dir / "npx")
|
|
215
|
+
|
|
216
|
+
# Run npx
|
|
217
|
+
cmd = [npx_path] + list(args)
|
|
218
|
+
return subprocess.call(cmd)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def main():
|
|
222
|
+
"""Main entry point."""
|
|
223
|
+
# Ensure Node.js is available
|
|
224
|
+
ensure_nodejs()
|
|
225
|
+
|
|
226
|
+
# Run the npm package
|
|
227
|
+
sys.exit(run_npx("-y", "claude-ssh-mcp"))
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def install():
|
|
231
|
+
"""Install command - adds to Claude Desktop config."""
|
|
232
|
+
ensure_nodejs()
|
|
233
|
+
sys.exit(run_npx("-y", "claude-ssh-mcp"))
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
if __name__ == "__main__":
|
|
237
|
+
main()
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: claude-ssh-mcp
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: MCP addon for Claude Desktop - SSH into servers, run commands, transfer files
|
|
5
|
+
Author-email: MaraBank <mb08112009@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/MaraBank/mcp-ssh-server
|
|
8
|
+
Project-URL: Repository, https://github.com/MaraBank/mcp-ssh-server
|
|
9
|
+
Project-URL: Issues, https://github.com/MaraBank/mcp-ssh-server/issues
|
|
10
|
+
Keywords: mcp,ssh,claude,claude-desktop,sftp,remote,server
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Requires-Python: >=3.8
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# claude-ssh-mcp
|
|
27
|
+
|
|
28
|
+
[](https://pypi.org/project/claude-ssh-mcp/)
|
|
29
|
+
[](https://www.npmjs.com/package/claude-ssh-mcp)
|
|
30
|
+
[](https://opensource.org/licenses/MIT)
|
|
31
|
+
|
|
32
|
+
MCP addon for **Claude Desktop** — gives Claude SSH access to your servers.
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
**With pip (auto-installs Node.js if needed):**
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install claude-ssh-mcp
|
|
40
|
+
claude-ssh-mcp
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Or with npm (if you have Node.js):**
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npx -y claude-ssh-mcp
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
That's it. Restart Claude Desktop and you're ready.
|
|
50
|
+
|
|
51
|
+
## Usage
|
|
52
|
+
|
|
53
|
+
Just talk to Claude naturally:
|
|
54
|
+
|
|
55
|
+
- *"Connect to 185.91.118.4 as root with password mypass, call it production"*
|
|
56
|
+
- *"Run `ls -la /var/www` on production"*
|
|
57
|
+
- *"Upload C:\Users\me\app.zip to /tmp/app.zip on production"*
|
|
58
|
+
- *"Download /var/log/error.log from production"*
|
|
59
|
+
- *"Connect to 10.0.0.5 as deploy with key at C:\Users\me\.ssh\id_rsa, name it staging"*
|
|
60
|
+
- *"Transfer /var/log/app.log from production to /tmp/app.log on staging"*
|
|
61
|
+
- *"List my servers"*
|
|
62
|
+
- *"Remove staging"*
|
|
63
|
+
|
|
64
|
+
## Features
|
|
65
|
+
|
|
66
|
+
- **Auto-install** — one command adds it to Claude Desktop
|
|
67
|
+
- **Auto-installs Node.js** — pip version handles everything
|
|
68
|
+
- **Servers are saved** — connect once, reconnect by name
|
|
69
|
+
- **SSH keepalive** — connections stay alive during long sessions
|
|
70
|
+
- **Auto-reconnect** — recovers from network interruptions
|
|
71
|
+
- **Auto-update** — checks for updates every 2 hours
|
|
72
|
+
|
|
73
|
+
## Saved Servers
|
|
74
|
+
|
|
75
|
+
When you tell Claude to connect, the server is saved to `~/.mcp-ssh/servers.json`. Next time just say *"connect to production"*.
|
|
76
|
+
|
|
77
|
+
Edit manually if you prefer:
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"production": {
|
|
82
|
+
"host": "185.91.118.4",
|
|
83
|
+
"port": 22,
|
|
84
|
+
"username": "root",
|
|
85
|
+
"password": "mypass"
|
|
86
|
+
},
|
|
87
|
+
"staging": {
|
|
88
|
+
"host": "10.0.0.5",
|
|
89
|
+
"port": 22,
|
|
90
|
+
"username": "deploy",
|
|
91
|
+
"privateKeyPath": "C:\\Users\\me\\.ssh\\id_rsa"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Tools
|
|
97
|
+
|
|
98
|
+
| Tool | What it does |
|
|
99
|
+
|------|--------------|
|
|
100
|
+
| `ssh_connect` | Connect to a server (new or saved) |
|
|
101
|
+
| `ssh_disconnect` | Close a connection |
|
|
102
|
+
| `ssh_list_sessions` | Show all connections and saved servers |
|
|
103
|
+
| `ssh_remove_server` | Delete a saved server |
|
|
104
|
+
| `ssh_execute` | Run a shell command |
|
|
105
|
+
| `ssh_upload` | Upload a local file (SFTP) |
|
|
106
|
+
| `ssh_download` | Download a remote file (SFTP) |
|
|
107
|
+
| `ssh_transfer` | Copy a file between two servers |
|
|
108
|
+
| `ssh_list_files` | List remote directory contents |
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
MIT — free and open source.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
claude_ssh_mcp/__init__.py,sha256=klN6MQI6WPTxvqI0IzXoMHotqItBB15cpj9BG9vLE5o,6663
|
|
2
|
+
claude_ssh_mcp-1.0.0.dist-info/licenses/LICENSE,sha256=wFclISLKRFSih_TFiMzSwthYku2whTPgUd_sJ6DWarE,1065
|
|
3
|
+
claude_ssh_mcp-1.0.0.dist-info/METADATA,sha256=7RRfMn2hkz5vwOKiOm3I_SewC1ldK11gJks3zEhBFFk,3603
|
|
4
|
+
claude_ssh_mcp-1.0.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
5
|
+
claude_ssh_mcp-1.0.0.dist-info/entry_points.txt,sha256=oheaU_3JumlDh127EFyhFHWC65R4hhwoUGTvCoa-Yl0,55
|
|
6
|
+
claude_ssh_mcp-1.0.0.dist-info/top_level.txt,sha256=vZvGJ6s7bHvnW8vbR0gXnZwX_iZnHfKCsZUt08IN4P0,15
|
|
7
|
+
claude_ssh_mcp-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MaraBank
|
|
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 @@
|
|
|
1
|
+
claude_ssh_mcp
|