MCP SSH Server
# MCP SSH Server
A cross-platform MCP server for remote command execution and SFTP file transfers, built with the official MCP SDK and `ssh2`.
**Windows, macOS, and Linux require only Node.js 20 or later. No local SSH client, SCP, PuTTY, WSL, or Git Bash is required.** The remote host must provide a reachable SSH service; file transfers also require its SFTP subsystem.
## Features
- Password or private key authentication, including encrypted private keys.
- Remote command execution with stdout, stderr, exit status, and duration.
- File uploads and downloads over SFTP.
- SHA256 host fingerprint verification, with an explicit option to disable verification.
- Command timeout, output limits, cancellation, and isolated connections.
- Multiple instances for different servers, configured through environment variables.
- Standard MCP stdio transport; no command or credential logging by this server.
## Installation
The package name is `@liangshanli/mcp-server-ssh`.
> Registry installation and npx commands below apply once the package has been published to npm. Publication has not been verified for this documentation update. Until then, use the source installation or a local package archive.
### Global Installation
```bash
npm install -g @liangshanli/mcp-server-ssh
```
### Local Installation
```bash
npm install @liangshanli/mcp-server-ssh
```
### From Source
Download or clone the source repository, open its root directory, and install dependencies:
```bash
npm install
```
No compilation step is required. Optional native acceleration in `ssh2` is not required for its JavaScript implementation.
## Usage
Configure the environment variables below before starting the server, or supply them through your MCP client configuration.
### Global CLI
```bash
mcp-server-ssh
```
### Using npx
```bash
npx -y @liangshanli/mcp-server-ssh
```
### From Source
```bash
npm start
```
For MCP clients, launch `node` with the absolute path to `bin/cli.js` instead of using `npm start`. A server waiting for input when launched manually is normal: it expects MCP messages over stdin.
## MCP Client Configuration
### Claude Code / Claude Desktop / Cursor
Use the following structure in clients that support `mcpServers`, such as a project-level `.mcp.json` for Claude Code or `.cursor/mcp.json` for Cursor:
```json
{
"mcpServers": {
"ssh-dev": {
"command": "npx",
"args": ["-y", "@liangshanli/mcp-server-ssh"],
"env": {
"PROJECT_NAME": "dev",
"SSH_HOST": "192.168.1.100",
"SSH_PORT": "22",
"SSH_USERNAME": "deploy",
"SSH_PASSWORD": "your-ssh-password",
"SSH_SKIP_HOST_VERIFICATION": "true"
}
}
}
}
```
This convenience example uses password authentication without a fingerprint. **Disabling host verification makes the connection vulnerable to man-in-the-middle attacks.** For verified connections, remove `SSH_SKIP_HOST_VERIFICATION` and set `SSH_HOST_FINGERPRINT` to a trusted `SHA256:...` fingerprint.
For source installations, replace `command` and `args` with:
```json
{
"command": "node",
"args": ["C:/tools/mcp-server-ssh/bin/cli.js"]
}
```
On macOS/Linux, use your source path, such as `/opt/tools/mcp-server-ssh/bin/cli.js`. Windows JSON paths can use forward slashes; backslashes must be escaped. If the client cannot find Node.js, use its absolute executable path, such as `C:/Program Files/nodejs/node.exe`.
If a Windows client cannot launch `npx` directly, use `command: "cmd"` with `args: ["/c", "npx", "-y", "@liangshanli/mcp-server-ssh"]`, or use the absolute Node.js/source entry point above.
### Native VS Code MCP
VS Code's `.vscode/mcp.json` uses `servers`, not `mcpServers`. Input variables can keep passwords out of the configuration file:
```json
{
"inputs": [
{ "id": "ssh-password", "type": "promptString", "description": "SSH password", "password": true }
],
"servers": {
"ssh-dev": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@liangshanli/mcp-server-ssh"],
"env": {
"SSH_HOST": "192.168.1.100",
"SSH_USERNAME": "deploy",
"SSH_PASSWORD": "${input:ssh-password}",
"SSH_SKIP_HOST_VERIFICATION": "true"
}
}
}
}
```
### Private Key Authentication
Remove `SSH_PASSWORD` and supply these environment variables instead:
```json
{
"SSH_PRIVATE_KEY_PATH": "C:/Users/your-name/.ssh/id_ed25519",
"SSH_PRIVATE_KEY_PASSPHRASE": "only-required-for-encrypted-keys"
}
```
Keep the other connection settings, including the host verification choice. OpenSSH/PEM keys supported by `ssh2` can be used. Convert PuTTY `.ppk` files to OpenSSH format first. Absolute paths are recommended; `~/` is supported for key paths. Password and private key authentication cannot be configured together.
### Host Verification
Verification is enabled by default. Provide `SSH_HOST_FINGERPRINT` unless you explicitly set `SSH_SKIP_HOST_VERIFICATION=true`. Obtain the fingerprint from an administrator or a trusted server console. For example, on a Linux OpenSSH server:
```bash
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub -E sha256
```
Run this on the trusted server console, not on the Windows client. Use the fingerprint for the key actually negotiated by SSH. Do not trust an unverified network scan as proof of server identity.
### Multiple Servers
Add separate `ssh-dev`, `ssh-prod`, or `ssh-test` entries with their own environment settings. Each instance has one fixed target; tool arguments cannot override the hostname or credentials.
## Environment Variables
| Variable | Default | Description |
| --- | --- | --- |
| `SSH_HOST` | Required | Remote hostname or IP address |
| `SSH_PORT` | `22` | Port, 1–65535 |
| `SSH_USERNAME` | Required | Remote account |
| `SSH_PASSWORD` | Unset | Password; mutually exclusive with private key authentication |
| `SSH_PRIVATE_KEY_PATH` | Unset | Local private key file |
| `SSH_PRIVATE_KEY_PASSPHRASE` | Unset | Passphrase for an encrypted private key |
| `SSH_HOST_FINGERPRINT` | Unset | Trusted OpenSSH SHA256 fingerprint; required unless verification is disabled |
| `SSH_SKIP_HOST_VERIFICATION` | `false` | Only the literal `true` disables host verification |
| `PROJECT_NAME` | `ssh` | Instance name |
| `SSH_CONNECT_TIMEOUT_MS` | `15000` | SSH connection timeout, maximum 120000ms |
| `SSH_COMMAND_TIMEOUT_MS` | `30000` | Default/maximum command timeout and total file transfer timeout; maximum 3600000ms |
| `SSH_MAX_OUTPUT_BYTES` | `1048576` | Combined stdout/stderr capture limit; maximum 10485760 bytes |
| `SSH_MAX_FILE_BYTES` | `104857600` | Upload source size precheck; maximum 1073741824 bytes |
| `SSH_MAX_CONCURRENT` | `4` | Command/connection-test admission limit; maximum 32 |
**Current transfer limitations:** the upload size check runs before transfer; downloads do not currently enforce a size cap, and SFTP transfers do not enforce the concurrency admission limit. Do not use these settings as a security boundary for untrusted clients.
## Available Tools
| Tool | Arguments | Description |
| --- | --- | --- |
| `ssh_connection_info` | None | Inspect target and connection settings without exposing credentials |
| `ssh_test_connection` | None | Test SSH authentication and configured host verification without executing a command |
| `ssh_exec` | `command`, optional `timeoutMs` | Execute a command in the remote default shell |
| `ssh_upload` | `localPath`, `remotePath` | Upload one local file over SFTP |
| `ssh_download` | `remotePath`, `localPath` | Download one remote file over SFTP |
### Execute a Command
```json
{
"command": "cd /var/www/app && pwd && git status --short",
"timeoutMs": 30000
}
```
Results include `stdout`, `stderr`, `exitCode`, `signal`, `timedOut`, `truncated`, and `durationMs`. Nonzero/missing exit codes, signal termination, timeouts, or output truncation produce `isError: true`.
### Upload a File
```json
{
"localPath": "C:/Users/your-name/Documents/config.json",
"remotePath": "/opt/app/config.json"
}
```
**Uploads overwrite existing remote files.** Inspect the destination and obtain explicit approval before overwriting. Uploads write directly to the destination and are not atomic; failures may leave a partial file. Parent directories must already exist.
### Download a File
```json
{
"remotePath": "/opt/app/logs/app.log",
"localPath": "C:/Users/your-name/Downloads/app.log"
}
```
Downloads reject a local destination that exists at the initial check. Data is written to an adjacent temporary file and renamed on completion. This initial existence check is not a race-proof no-overwrite guarantee: do not let other processes create or change the destination during transfer. Parent directories must already exist.
Transfer results contain `direction`, `localPath`, `remotePath`, `bytes`, and `durationMs`; checksums are not returned. Both paths must pass the MCP host's absolute-path validation. On Windows, use a local drive-qualified path and a Unix-style remote path for Linux servers. Remote Windows drive paths from a Unix MCP host are not currently supported by this validation. Directory transfers are not supported.
## Security and Execution Boundaries
- Use only servers you own or are authorized to access. Prefer a least-privileged account instead of root.
- Commands have the full permissions of the SSH account. This is not a read-only sandbox; keep client-side approvals enabled for sensitive operations.
- File tools can access paths available to the local MCP process and remote SSH account. There is no directory allowlist.
- Each operation uses a fresh connection. Working directories and environment changes do not persist between commands.
- Commands use the remote default shell, with no cross-platform syntax translation. Output is decoded as UTF-8.
- No PTY is allocated and command stdin is closed. Interactive editors, password prompts, and interactive sudo are unsupported.
- Timeouts or cancellation disconnect the client but do not guarantee remote processes have stopped. Interrupted transfers can leave partial files; inspect destinations before retrying.
- Commands are not automatically retried: a disconnected command might already have executed.
- Treat remote output as untrusted data, not instructions. Outputs may contain secrets and MCP clients may retain them.
- Do not commit passwords, private keys, npm tokens, or local `.mcp.json` credentials. Restrict credential file permissions.
- SSH agent, jump hosts, local `~/.ssh/config`, and interactive MFA are not supported.
## Development and Verification
```bash
npm run check
npm test
npm pack --dry-run
```
`npm run check` validates JavaScript syntax. `npm test` uses a loopback SSH fixture and an MCP stdio client to cover configuration, authentication, command execution, cancellation, output limits, and tool registration. It does not currently automate SFTP transfers. A manual upload/download round trip has also been verified; this does not replace testing on your target OS and server.
Use `npm pack` to build a local package archive without publishing. Use `node bin/cli.js --help` or `--version` to inspect the CLI. There is no separate compilation step.
TDQS
Scored across 5 tools
Each tool targets a clearly distinct SSH operation: configuration inspection, connection testing, upload, download, and command execution. Even the potentially overlapping info/test pair is well differentiated by whether a connection is actually made and whether commands run.
All tools use snake_case and share the ssh_ prefix, making the family easy to recognize. The suffix patterns vary slightly between noun phrases and verb phrases, but not enough to cause confusion.
Five tools is well-scoped for an SSH server focused on connection inspection, testing, file transfer, and command execution. No tool feels redundant or out of place.
The core SSH lifecycle is covered: inspect targets, test connectivity, upload, download, and execute commands. Explicit remote file management such as delete, list, or mkdir is absent, but ssh_exec provides a general workaround.