Server data from the Official MCP Registry
Modern SSH for AI agents — cloud servers to BusyBox routers, with destructive commands blocked.
About
Modern SSH for AI agents — cloud servers to BusyBox routers, with destructive commands blocked.
Security Report
Valid MCP server (3 strong, 3 medium validity signals). No known CVEs in dependencies. Package registry verified. Imported from the Official MCP Registry.
7 files analyzed · 1 issue found
Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.
Permissions Required
This plugin requests these system permissions. Most are normal for its category.
What You'll Need
Set these up before or after installing:
Environment variable: SSH_PROFILES_FILE
Environment variable: SSH_MCP_LOG_LEVEL
How to Install
Add this to your MCP configuration file:
{
"mcpServers": {
"io-github-hypnosis-ssh-mcp-server": {
"env": {
"SSH_MCP_LOG_LEVEL": "your-ssh-mcp-log-level-here",
"SSH_PROFILES_FILE": "your-ssh-profiles-file-here"
},
"args": [
"-y",
"@hypnosis/ssh-mcp-server"
],
"command": "npx"
}
}
}Documentation
View on GitHubFrom the project's GitHub README.
SSH MCP Server
An MCP server that lets an AI coding agent — Claude Code, Codex, Gemini CLI, Hermes, or anything else that speaks MCP — run commands, move files, and audit live servers over SSH, using the OpenSSH client, keys, and config already on your machine.
Install · Quick start · Tools · Security · Docs · Contributing · Changelog
What it's for
You already ask an assistant about your servers. Without this, it hands you a command to paste, waits for you to paste the output back, and repeats — you become the transport. With it, the assistant reaches the machine itself and gets structured answers back.
The everyday jobs it was built for:
- Find out why something broke. One
ssh_snapshotcall returns services, resources, docker, network and recent errors together, instead of a dozen commands typed one at a time. - Audit a machine you inherited. Disks, listening ports, firewall, pending updates, certificate expiry — batched into one round trip, read-only, with the findings already sorted into critical, warning and fine.
- Work through logs. Tail or search several journals at once, with context lines and a cap that keeps the answer readable.
- Ship files. A file or a whole directory, binary-safe, verified by sha256, put in place by atomic rename — never a half-written file where the old one used to be.
- Start work that outlives the conversation. A migration or a backup keeps running after the call returns; job state lives on the remote disk, so it survives a restart of this server too.
Why not just give the assistant a shell? Because a shell has no brakes and no memory of what it just did. Here every destructive command is checked before it leaves your machine, every transfer says plainly whether it could verify itself, and a broken pipe is reported as "could not check" instead of being passed off as success.
Why this one
It uses the SSH you already have. No bundled SSH implementation, no native bindings, no rebuild per platform. Commands ride the system ssh client, so your keys, your ~/.ssh/config, your jump hosts and your agent forwarding all keep working exactly as they do in a terminal. One shared multiplexed connection per destination means you authenticate once, not once per command.
It still talks to old servers. OpenSSH has moved on; the machines in the rack often have not. Three features have version floors, and missing one degrades a feature instead of refusing the connection — a client from 2010 is still served:
| From version | What it unlocks | Below it |
|---|---|---|
| 5.6 | Shared multiplexed connection (ControlPersist) | Every command opens its own connection |
| 8.4 | Password and passphrase profiles (SSH_ASKPASS_REQUIRE) | Refused — but only for profiles that need a password; key-based profiles are unaffected |
| 9.0 | scp rides the SFTP protocol | Falls back to the classic scp protocol |
It refuses to destroy what cannot be brought back. Two independent checks run before anything reaches the server. The first reads the command text and stops whole-container destruction — wiping a disk, dropping a database, crontab -r, removing a Docker volume, halting the machine. The second catches a recursive delete aimed at the filesystem root, a home directory or a system tree, including when a symlink leads there. Neither is a policy you have to configure, and both step aside for an explicit confirmation marker: this guards against the slip, not against you.
It speaks current MCP. Built on @modelcontextprotocol/sdk 1.30, TypeScript throughout, 2100+ unit tests plus a live suite that runs against real containers rather than mocks.
Requirements
- Node.js 18+
- A system
sshclient onPATH— nothing is bundled. Any OpenSSH will run; see the version table above for what each floor unlocks.
ssh_monitor({ action: "stats", profile: "production" }) reports the client version it found and whether multiplexing is active.
Installation
You do not have to install anything. Every example below launches the server with
npx -y, which fetches the package on first use and keeps it in the npx cache — the -y
answers the prompt npx would otherwise ask before downloading:
npx -y @hypnosis/ssh-mcp-server
Install it globally if you would rather pin a version, work offline, or avoid the extra second npx spends checking the registry:
npm install -g @hypnosis/ssh-mcp-server
Then use ssh-mcp-server as the command in the client config instead of npx.
Quick start
1. Create a profile file
Put it wherever you like. The examples below use ~/.claude/ssh-profiles.json for Claude Code and ~/.codex/ssh-profiles.json for Codex:
{
"profiles": {
"production": {
"host": "server.example.com",
"username": "admin",
"port": 22,
"privateKeyPath": "~/.ssh/your_private_key"
},
"staging": {
"host": "staging.example.com",
"username": "deploy",
"port": 22,
"privateKeyPath": "~/.ssh/your_private_key"
}
}
}
Every call names its profile. There is no profile the server falls back to: each one is a different machine, and a command sent to the wrong machine is not something an error message can undo afterwards. Ask without a name and the answer lists the names to choose from:
ssh_exec({ command: "uptime" })
→ No profile specified. Name one explicitly: production, staging
A profile the server cannot use for SSH — no host, no username, or mode: "local" — is skipped without complaint, and fields it does not recognise are left alone, so the file can be shared with other tools. A profile with a broken field is a different case: it is named along with the field and the value, and its healthy neighbours keep working.
Each profile optionally takes a pathSecurity block that whitelists or blacklists the paths file tools may touch — see docs/security.md.
Keep passwords out of the profiles file
Prefer keys. Where a password — or an encrypted key — is unavoidable, the secret does not belong in the profiles file: that file gets copied, pasted into issues and committed by accident. Point at a secrets file instead, with secretsFile at the top level, per profile, or both:
{
"secretsFile": "~/.config/ssh-mcp/secrets.json",
"profiles": {
"production": {
"host": "server.example.com",
"username": "admin"
},
"appliance": {
"host": "10.0.0.2",
"port": 2222,
"username": "operator",
"secretsFile": "./appliance-secret.json"
}
}
}
The secrets file is keyed by profile name — see secrets.json.example:
{
"production": { "password": "..." },
"staging": { "passphrase": "..." }
}
chmod 600is required. The server refuses to read a secrets file that anyone but you can read, the same waysshrefuses a private key — and says which file and what to run.- A relative path is resolved from the profiles file, not from the working directory the client happened to start the server in.
- A profile whose secrets file is missing, malformed or too permissive is reported as broken instead of quietly logging in without a password.
- A profile named in
secretsFilebut absent from the file is fine — key-based profiles need no entry. passwordandpassphrasewritten directly in a profile still work, so existing setups keep running, but the secrets file wins and a warning is logged.
The password never travels in argv — it reaches ssh through an askpass helper reading one environment variable, so ps does not show it — and it is masked in the logs. Details in docs/security.md.
2. Point your MCP client at it
Claude Code — one command, -s user makes the server available in every project:
claude mcp add ssh -s user \
-e SSH_PROFILES_FILE="$HOME/.claude/ssh-profiles.json" \
-- npx -y @hypnosis/ssh-mcp-server
Or write it into ~/.claude.json by hand:
{
"mcpServers": {
"ssh": {
"command": "npx",
"args": ["-y", "@hypnosis/ssh-mcp-server"],
"env": {
"SSH_PROFILES_FILE": "~/.claude/ssh-profiles.json"
}
}
}
}
Codex CLI — same shape, TOML instead of JSON:
codex mcp add ssh \
--env SSH_PROFILES_FILE="$HOME/.codex/ssh-profiles.json" \
-- npx -y @hypnosis/ssh-mcp-server
Or write it into ~/.codex/config.toml by hand:
[mcp_servers.ssh]
command = "npx"
args = ["-y", "@hypnosis/ssh-mcp-server"]
[mcp_servers.ssh.env]
SSH_PROFILES_FILE = "~/.codex/ssh-profiles.json"
Any other MCP client works too — it needs a command to run and one environment variable.
3. Restart the client
Done — the assistant can now reach your servers. Ask it to run ssh_monitor({ action: "list" }) to see the profile names it loaded, then ssh_monitor({ action: "stats", profile: "production" }): it reports the ssh client it found and whether multiplexing is active.
Tools
18 tools. Full parameters and examples live in docs/tools.md.
Commands and files
| Tool | What it does |
|---|---|
ssh_exec | Run one command or a batch, with the destructive-command guard and optional detach |
ssh_file_read | Read one or several files, text or binary |
ssh_file_write | Write files with atomic rename and optional sha256 verification |
ssh_file_list | List a directory, with optional glob and recursion |
Long-running work — a command that outlives the call
| Tool | What it does |
|---|---|
ssh_job_status | State of a background job: running, finished, or lost |
ssh_job_output | Read accumulated output from a byte offset |
ssh_job_list | List jobs, sweeping finished ones past their TTL |
ssh_job_kill | Signal a job's whole process group |
Job state lives on the remote disk, not in this server's memory — jobs survive a restart of the MCP server itself.
Logs and health
| Tool | What it does |
|---|---|
ssh_log_tail | Last N lines of one or several logs, glob supported |
ssh_log_search | Pattern search across logs |
ssh_snapshot | One-shot health snapshot: services, resources, docker, network, errors |
ssh_monitor | Transport control: stats, reload, test, list, close |
Transfer — binary-safe, atomic, sha256-verified. Details in docs/transfer.md.
| Tool | What it does |
|---|---|
ssh_upload | Upload a file or directory |
ssh_download | Download a file or directory |
For binaries and large files use
ssh_upload/ssh_download— not base64 chunks throughssh_exec, and not a heredoc throughssh_file_write. Heredoc writes corrupt binaries and offer no integrity or atomicity guarantee.
Audit — read-only, batched into one round trip. Details in docs/audit.md.
| Tool | What it does |
|---|---|
ssh_audit_baseline | System, disk, memory, network, ssh, services, docker, firewall, updates |
ssh_tls_check | Certificate expiry, SAN, chain and renewal hook for a domain |
ssh_disk_breakdown | Where the disk went: du top-N, docker, journald, caches |
ssh_service_status | systemctl status plus a journalctl tail for one unit |
Security
Two levels of caution, and the difference between them is whether the loss can be undone:
- A warning is returned for a destructive but recoverable command — dropping a table,
chmod 777, force-removing a container. You see it and decide. - A refusal stops the call before it reaches the server when the loss would be final: destroying the whole container of the data, or reading something that the same command already destroyed.
Both are bypassed by an explicit # CONFIRMED-DESTRUCTIVE marker, so nothing is permanently forbidden — the guard is there to catch the slip.
What it deliberately does not see: a delete and a read split across two separate calls (no state is carried between invocations), and sinks of tools it does not special-case. It is a seatbelt, not a policy engine — the reasoning is written down in docs/decisions/007-refusal-threshold.md (Russian).
Path handling, quoting rules and per-profile path restrictions: docs/security.md.
Configuration
| Variable | What it does | Default |
|---|---|---|
SSH_PROFILES_FILE | Path to the profiles JSON — required | — |
SSH_MCP_LOG_LEVEL | debug, info, warn, error | info |
LOG_LEVEL | Fallback, used only when SSH_MCP_LOG_LEVEL is unset | info |
SSH_MCP_LOG_TIMESTAMP | Timestamps in log lines | true |
SSH_MCP_CONTROL_PERSIST | Seconds a shared connection stays alive after the last command; 0 closes it at once | 600 |
SSH_MCP_CONTROL_DIR | Where control sockets live | ~/.ssh/ssh-mcp |
SSH_MCP_PROFILES_CACHE_TTL | Profile cache TTL, ms | 60000 |
SSH_MCP_PROFILES_WATCH | Reload the profiles file when it changes | true |
The shared connection outlives this process on purpose: closing it on exit would cut the channel another window on the same machine is using.
Documentation
| docs/tools.md | Every tool, every parameter, with examples |
| docs/security.md | Destructive-command guard, path handling, quoting |
| docs/transfer.md | Upload and download in depth |
| docs/audit.md | Audit tools and the recommended pipeline |
| docs/architecture.md | How the project is built, and how to work on it |
| CONTRIBUTING.md | How to set up the lab and what a patch has to prove |
| SECURITY.md | What the server promises, and how to report a vulnerability privately |
| CHANGELOG.md | Release history |
Development
npm install
npm run build # tsc
npx tsc --noEmit # types, plus dead declarations
npm run test:unit # unit tests
npm run lab:up # start the two test containers
npm run test:live # live suite against those containers
The live suite runs against real containers — one BusyBox, one coreutils — because the two disagree quietly, and a mock agrees with whoever wrote it. See docs/architecture.md for the layout.
Contributing
Issues and pull requests are welcome at github.com/hypnosis/ssh-mcp-server.
License
MIT — see LICENSE.
Reviews
No reviews yet
Be the first to review this server!
More Developer Tools MCP Servers
Fetch
Freeby Modelcontextprotocol · Developer Tools
Web content fetching and conversion for efficient LLM usage
Toleno
Freeby Toleno · Developer Tools
Toleno Network MCP Server — Manage your Toleno mining account with Claude AI using natural language.
mcp-creator-python
Freeby mcp-marketplace · Developer Tools
Create, build, and publish Python MCP servers to PyPI — conversationally.
MarkItDown
Freeby Microsoft · Content & Media
Convert files (PDF, Word, Excel, images, audio) to Markdown for LLM consumption
MCP Marketplace
Freeby mcp-marketplace · Developer Tools
Search and install MCP servers from inside your AI client.
FinAgent
Freeby mcp-marketplace · Finance
Free stock data and market news for any MCP-compatible AI assistant.
