SSH MCP Server
The SSH MCP Server enables comprehensive remote server administration via SSH, allowing secure command execution, file transfer, interactive sessions, and operation history management.
Profile & Connection Management: List configured SSH profiles, connect/disconnect from remote servers, and check connection status including uptime.
Command Execution: Execute remote commands, with PTY support for interactive programs and automatic prompt responses (e.g., sudo passwords). Dangerous commands (e.g.,
rm -rf,mkfs,reboot) require explicit confirmation before execution.Persistent Shell Sessions: Start, send input to, read output from, and close persistent PTY shell sessions (up to 5 concurrent).
File Operations (SFTP): Upload, download, list directories, read (with optional offset/line-limit), and write remote files.
Operation History & Undo: View a history of all operations during a session and undo reversible file operations (write, upload, download) by record ID.
Security: SSH key-only authentication, host fingerprint verification, local sandbox directory restrictions for file transfers, destructive command detection, and full audit logging with secret redaction.
Provides tools for remote administration of Linux servers via SSH, including command execution, persistent shell sessions, SFTP file transfers, and a safety layer for detecting destructive commands.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@SSH MCP ServerList the files in /var/www/html on the staging profile"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
SSH MCP Server
MCP (Model Context Protocol) server for remote server administration via SSH. Supports multiple profiles, remote command execution, interactive commands (PTY), persistent shell sessions, file transfer (SFTP), destructive command detection with audit logging, and operation history with undo capabilities.
Architecture
General Overview
graph TB
Client["Claude Desktop<br/>(or any MCP client)"]
Server["SSH MCP Server"]
MCP["MCP SDK<br/>(stdio)"]
Router["Tool Router<br/>(index.ts)"]
Security["Security Module<br/>- Dangerous cmd detection<br/>- Audit logging"]
SSH["SSH Client (ssh2)"]
Exec["exec()<br/>- Commands<br/>- cat (read)"]
Interactive["exec() + PTY<br/>- Interactive commands<br/>- Auto prompt response"]
Shell["shell() + PTY<br/>- Persistent sessions<br/>- REPLs / multi-step"]
SFTP["SFTP (lazy init)<br/>- upload / download<br/>- ls / write / readdir"]
Remote["Remote Server<br/>(Linux/Unix)"]
Client -->|"stdio (JSON-RPC)"| Server
Server --- MCP
MCP --> Router
Router --> Security
Router --> SSH
SSH --- Exec
SSH --- Interactive
SSH --- Shell
SSH --- SFTP
SSH -->|"SSH (TCP :22)"| RemoteConnection and Execution Flow
flowchart TD
Start([Start]) --> ListProfiles["ssh_list_profiles<br/>View available profiles"]
ListProfiles --> Connect["ssh_connect<br/>(profile name)"]
Connect -->|Invalid or missing key| Error["Error: privateKeyPath missing<br/>or wrong passphrase"]
Connect -->|OK| Active["Active Connection<br/>(1 profile at a time)"]
Active --> ExecBranch["ssh_exec<br/>(command)"]
Active --> InteractiveBranch["ssh_exec_interactive<br/>(command + auto-responses)"]
Active --> ShellBranch["Shell Sessions<br/>start / send / read / close"]
Active --> SFTPBranch["SFTP operations<br/>upload / download<br/>ls / read / write"]
Active --> StatusBranch["ssh_status<br/>ssh_disconnect"]
ExecBranch --> DangerCheck{"Dangerous<br/>command?<br/>(regex)"}
InteractiveBranch --> DangerCheck
DangerCheck -->|No| Execute["Execute command"]
DangerCheck -->|Yes| ConfirmCheck{"confirm:<br/>true?"}
ConfirmCheck -->|Yes| Execute
ConfirmCheck -->|No| Warning["WARNING<br/>(not executed)"]
ShellBranch -->|"send (raw:false)"| DangerCheck
ShellBranch -->|"send (raw:true)"| Execute
Execute --> Audit["audit.log"]
SFTPBranch --> Audit
Warning --> AuditSecurity Flow (Dangerous Commands)
flowchart LR
Input["Command received"] --> Check["isDangerousCommand()<br/>(16 regex patterns)"]
Check -->|Safe| Exec["Execute command"]
Check -->|Dangerous| Confirm{"confirm: true?"}
Confirm -->|Yes| Exec
Confirm -->|No| Warn["WARNING returned<br/>(command NOT executed)"]
Exec --> Log["audit.log"]
Warn --> Log
subgraph Detected Patterns
P1["rm -rf /"]
P2["mkfs.*"]
P3["dd if="]
P4["reboot / shutdown / halt"]
P5["chmod 777 / chown -R"]
P6["fork bomb"]
P7["systemctl stop/disable"]
P8["killall / iptables -F"]
endProject Structure
s01_ssh_mcp/
├── src/
│ ├── index.ts # SSHMCPServer class — tool router, SSH logic, interactive/shell handlers
│ ├── tools.ts # MCP tool definitions (17 tools, JSON schemas)
│ ├── profiles.ts # Profile loading + private key file read and passphrase injection from env
│ ├── security.ts # Dangerous command detection + AuditLogger (secret redaction, 0o600 perms)
│ ├── types.ts # Interfaces: SSHProfile, AuditEntry, PromptResponse, ShellSession, CommandRecord, ReverseInfo
│ ├── utils.ts # Pure utilities: formatUptime, padRight, escapeShellArg, stripAnsi
│ └── validation.ts # Input validation: requireString, optionalString/Boolean/Number, clampTimeout
├── dist/ # Compiled output (generated by tsc)
├── profiles.json # SSH server configuration (not versioned)
├── profiles.json.example # Profile template (included in npm package)
├── .env # Optional key passphrases (not versioned)
├── audit.log # Audit log (generated at runtime)
├── package.json
└── tsconfig.jsonSetup
1. Server Profiles
Copy profiles.json.example → profiles.json and fill in your values. The file must not be versioned (it is in .gitignore).
Required fields:
Field | Description |
| Server IP or hostname |
| SSH port (usually |
| SSH username |
| Path to the private key file. Supports |
| SHA-256 host fingerprint (see below) |
Optional fields:
Field | Description | Default |
| Local directory where downloads/uploads are allowed. Supports | Process working directory |
{
"production": {
"host": "192.168.1.100",
"port": 22,
"username": "deploy",
"privateKeyPath": "~/.ssh/id_ed25519_production",
"hostFingerprint": "SHA256:AbCdEfGhIjKlMnOpQrStUvWxYz0123456789abcd",
"localSandboxDir": "~/mcp-downloads"
}
}Get the host fingerprint:
ssh-keyscan -t ed25519 HOST 2>/dev/null | ssh-keygen -lf -
# Output: 256 SHA256:AbCd... host (ED25519)
# Copy the "SHA256:..." part into hostFingerprintImportant: Password authentication has been removed. Only SSH key authentication is supported. Make sure the remote server has the matching public key in
~/.ssh/authorized_keysand enforcePasswordAuthentication noinsshd_config.
2. Passphrases (optional)
If your private keys are protected with a passphrase, define them in .env:
SSH_PASSPHRASE_PRODUCTION=your_passphrase
SSH_PASSPHRASE_STAGING=your_passphraseFormat: SSH_PASSPHRASE_<PROFILE_NAME_UPPERCASE>. Omit the variable if the key has no passphrase.
3. Build and Run
npm install
npm run build
npm start4. MCP Configuration (Claude Desktop)
Option A: Using npx (recommended)
No local installation required — just add to your Claude Desktop config (claude_desktop_config.json):
{
"mcpServers": {
"ssh": {
"command": "npx",
"args": ["-y", "s01-ssh-mcp"],
"env": {
"SSH_PASSPHRASE_PRODUCTION": "your_passphrase_if_any",
"SSH_PASSPHRASE_STAGING": "your_passphrase_if_any"
}
}
}
}Option B: Local installation
{
"mcpServers": {
"ssh": {
"command": "node",
"args": ["/path/to/s01_ssh_mcp/dist/index.js"],
"env": {
"SSH_PASSPHRASE_PRODUCTION": "your_passphrase_if_any",
"SSH_PASSPHRASE_STAGING": "your_passphrase_if_any"
}
}
}
}Note: You can optionally set
SSH_PROFILES_PATHinenvto point to aprofiles.jsonin a different location.
Available Tools
Tool | Description | Requires Connection |
| List configured profiles (includes | No |
| Connect to an SSH profile | No |
| Close the active SSH connection (closes all shell sessions) | Yes |
| Connection status (profile, host, uptime) | Yes |
| Execute a remote command | Yes |
| Execute interactive command with PTY and auto-response to prompts | Yes |
| Start a persistent interactive shell session with PTY | Yes |
| Send input to an active shell session | Yes |
| Read accumulated output from a shell session buffer | Yes |
| Close a shell session and release resources | Yes |
| Upload a local file to the server (SFTP) | Yes |
| Download a file from the server (SFTP) | Yes |
| List a remote directory (SFTP) | Yes |
| Read remote file contents (supports partial reading with offset/limit) | Yes |
| Write content to a remote file (SFTP) | Yes |
| View operation history for the active connection | Yes |
| Revert a specific operation by record ID | Yes |
Tool Parameters
Tool | Parameters | Required |
|
| Yes |
|
|
|
|
|
|
|
| No |
|
|
|
|
|
|
|
|
|
|
| Both |
|
| Both |
|
| No |
|
|
|
|
| Both |
|
| No |
|
|
|
Operation History & Undo
Every operation executed during an active connection is recorded in memory. This allows reviewing what was done and reverting specific operations.
Reversibility by Operation
Operation | Reversible | Undo Strategy |
| Yes | Restores previous content. If file didn't exist, deletes it |
| Yes | Restores previous remote content. If file didn't exist, deletes it |
| Yes | Restores previous local content. If file didn't exist locally, deletes it |
| No | Recorded but not auto-reversible |
| No | Recorded but not auto-reversible |
| N/A | Read-only, nothing to revert |
| N/A | Read-only, nothing to revert |
| N/A | Cannot revert input sent to an interactive shell |
The history is cleared on ssh_connect and ssh_disconnect.
Security
Security Model
This server implements layered security. Each layer has defined boundaries:
Layer | Protects Against | Limitations |
SSH key auth | Password brute-force | Does not protect if the key is stolen |
Host verification (MITM) | Connecting to impersonated servers | Requires correct |
Local sandbox | Arbitrary local file read/write/delete via | Does not apply to remote operations |
Dangerous command detection | Accidental destructive commands | Advisory layer, NOT a security barrier. Bypassable via obfuscation, variables, |
Audit log redaction | Common secret patterns (password=, token=, Bearer) in logs | Does not detect all possible secret formats |
Defense in depth recommendation: The server is only as secure as the remote SSH user. Configure the user with minimal necessary permissions, use restrictive
sudoers, and keepPasswordAuthentication noinsshd_config.
Note on
raw: trueinssh_shell_send: This mode sends input directly to the shell without any dangerous command validation. This is intentional for advanced use cases (control sequences, internal REPLs). Use with caution.
History & Undo Limitations
History and undo capability are lost when the MCP server restarts (history lives in memory).
Binary files (detected via null byte check) are excluded from undo backup entirely — undo is UTF-8 only.
Undo is not atomic: if the process dies mid-write during restore, the file may be truncated.
Previous content of files larger than 512 KB is not stored — undo will return an error in that case.
History keeps a maximum of 100 entries. Oldest entries are automatically evicted.
Profile Cache
profiles.json is read and validated at server startup. Changes to the file require restarting the MCP server to take effect.
Destructive Command Detection
The following patterns are intercepted and require confirm: true to execute. This applies to ssh_exec, ssh_exec_interactive, and ssh_shell_send (when raw: false):
Note: This detection is an advisory layer, not a security barrier. Do not rely on it as the sole protection against destructive actions.
Pattern | Reason |
| Recursive rm on system root |
| Mass file deletion |
| Filesystem formatting |
| Direct disk write |
| Server state control |
| Runlevel change |
| Insecure permissions on root |
| Mass ownership change |
| Direct device write |
| Fork bomb |
| System service shutdown |
| Mass process termination |
| Firewall rules flush |
Audit Log
All operations are logged to audit.log with the format:
[timestamp] [profile] [tool] [parameters] [RESULT: ok|error]Example:
[2026-03-04T10:30:00.000Z] [production] [ssh_exec] [ls -la /var/log] [RESULT: ok]
[2026-03-04T10:31:00.000Z] [production] [ssh_upload] [./app.tar.gz -> /tmp/app.tar.gz] [RESULT: ok]Technical Details
MCP Transport: stdio (JSON-RPC over stdin/stdout)
SSH Connection: One active connection at a time. Attempting to connect to another profile without disconnecting raises an error. Keepalive: 30s interval, max 3 retries, 20s ready timeout.
Host Verification: Every
ssh_connectverifies the remote fingerprint viahostHash: "sha256"+hostVerifier. Converts the hex fingerprint from ssh2 to base64 and compares to theSHA256:<base64>value stored in the profile. Connection is rejected if they don't match.Connection Cleanup: SSH
closeandendevents triggercleanupState()on unexpected disconnects, clearing all sessions and SFTP state. Intentional disconnects are guarded against double-cleanup.SFTP: Lazy initialization — created on first file operation use and reused thereafter. Auto-invalidated if the SFTP subsystem closes or errors (
sftp.on('close'/'error')).Interactive Exec: Uses
exec()withpty: truefor commands requiring interactive input. Supports auto-response to prompts via regex matching. User-provided regex patterns are validated withsafe-regex2before compilation to prevent ReDoS. Settle timeout (2s) detects command completion; global timeout (default 30s) prevents hangs.Shell Sessions: Uses
shell()with PTY for persistent interactive terminals. Up to 5 concurrent sessions stored in aMap<string, ShellSession>. Auto-close after 5 minutes of inactivity. Buffer capped at 1MB. All sessions are closed onssh_disconnect. ANSI escape codes are stripped from output.Exec Command Timeout: All internal
ssh execcalls (includingcatfor file reads) race against a 30s timeout. stdout and stderr are capped at 1MB with per-chunk truncation during accumulation. Non-zero exit codes without stderr resolve with an[exit code: N]annotation instead of rejecting (preservesgrep/diffbehavior). Usescat -- pathandrm -f -- pathto protect against filenames starting with-.Timeout Clamping: User-provided timeouts for
ssh_exec_interactive,ssh_shell_send, andssh_shell_readare clamped to a minimum of 1s and a maximum of 5 minutes.Input Validation: Centralized in
validation.ts. All tool arguments go through typed helpers (requireString,optionalBoolean, etc.) instead of uncheckedanycasts.File Reading: Uses
ssh exec cat(not SFTP) for text files. Supports partial reading viaoffset(start line, 1-based) andlimit(max lines) usingsed -n.File Writing: Uses SFTP
createWriteStreamfor large file support.Argument Escaping: Shell escaping with single quotes to prevent command injection.
Audit Logging: Non-blocking — log write failures are silently ignored to avoid disrupting operations. Common secret patterns (
password=,token=,Bearer, etc.) are redacted before writing. Log file created with0o600permissions (owner read/write only). Responses markedsensitive: trueare logged as[REDACTED]. All tool operations are audited, includingssh_ls. Graceful shutdown viabeforeExithandler flushes pending writes.Profile Cache:
profiles.jsonis read and fully validated once at startup (all required fields + key file readable). Changes require restarting the MCP server.Operation History: All operations are recorded in memory during the active connection. File operations (
ssh_write_file,ssh_upload) capture previous content before modifying, enabling undo.ssh_downloadpreserves pre-existing local files vialocal_file_restoreundo type. Binary files (null byte detection) are excluded from undo backup. History capped at 100 entries.previousContentnot stored if > 512KB. History is cleared on connect/disconnect.
License
This project is licensed under the MIT License.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/d3v1an/ssh-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server