
bvisible/mcp-ssh-manager
Developer toolsYou give your agents a shell, and you see what they do with it.
Overview
You give your agents a shell, and you see what they do with it.
README
You give your agents a shell,and you see what they do with it.
An MCP server for SSH. Let Claude Code, Claude Desktop and OpenAI Codex (or any MCP client) work on your real servers — run commands, move files, query databases, take backups, check health — and watch over their shoulder while they do it.
Twenty seconds, unedited — watch full size. Real SSH, real SFTP, three throwaway hosts on the loopback.
The problem, in one line
An MCP SSH server is the most dangerous tool you can hand an agent: a shell on machines that matter. Today, when your agent runs something on production, you find out afterwards — if you find out at all.
This one lets you watch, and lets you say no. It is also, on the boring days, simply a very good way to let an assistant do the server work you were going to do yourself.
Choose your workspace
Desktop application (4.0.1): one installation with its own runtime,
terminal, file browser and server settings. No separate Node.js or npm
installation. Download it:
| Platform | File | Signature |
|---|---|---|
| macOS, Apple silicon / Intel | …-arm64.dmg / …-x64.dmg |
Signed and notarized by Apple — opens normally |
| Windows x64 / arm64 | …-setup.exe |
Not code-signed in 4.0.x. Windows shows “Windows protected your PC”: More info → Run anyway. Check the file against the SHA-256 in release-manifest-win32.json on the release (ssh-manager app does it for you). Signing comes in a later release. |
| Linux x64 | .deb, .AppImage |
Unsigned, as Linux packages usually are |
Already using npm / the CLI? npm update -g mcp-ssh-manager brings 4.0.1,
and the V4 engine runs without an interface, from your existing .env, TOML or
process variables — no automatic import, no forced wizard, no approval prompt
unless you enable one per server. The one behaviour that tightens is SSH
host-key verification; read that section of the
upgrade and rollback guide before upgrading. To stay on 3.8
for now, pin it: npm install -g mcp-ssh-manager@3.
From the engine to the application in one step. With a V4 engine installed,
ssh-manager app downloads the desktop application of the engine’s own version,
checks it against the release (size and SHA-256 from the release manifest, and
on macOS Apple’s signature and notarization), installs it and opens it — or just
opens it when it is already there. The browser interface of ssh-manager control
offers the same as one button. Your agents keep using the npm engine; the
application is the window on top of it. Nothing is downloaded unless you ask.
Without npm at all. The application carries the engine too, and your AI
tools can start that one: Options → AI tools lists Claude Code, Claude Desktop
and Codex, says which ssh-manager each one starts (npm’s, the application’s, or
none yet) and switches it in one click — no Node, no terminal. The same tab, in
ssh-manager control, shows npm users which version each tool really runs.
A switch rewrites that tool’s one ssh-manager entry, keeps its environment
variables, and takes effect when the tool next starts; switching back is the
same click.
Updates. The application checks for a new version at launch and every six hours and says so where you will see it: a mark on the sidebar, Options → Updates (the running version, the one on offer, a Check now button, release notes) and, once per version, a notification. Downloading is a click, and installing is a second one (Restart to update) or happens when you quit. The npm engine updates with npm, on its own schedule. A change to the interface reaches npm users with the next npm release and application users with the next application release.
Up and running with npm
npm install -g mcp-ssh-manager # or: brew tap bvisible/mcp-ssh-manager https://github.com/bvisible/mcp-ssh-manager && brew install ssh-manager
ssh-manager server add # guided: host, user, key or password
claude mcp add ssh-manager mcp-ssh-manager
That’s it. Restart Claude Code and ask it something.
Then just ask
No syntax to learn. Your assistant already knows how to use the 37 tools; you talk to it in your own words.
| You say | What happens |
|---|---|
| “Why is production slow right now?” | CPU, memory, disk, load average and the top processes, in one round trip |
| “Back up the database before I deploy.” | A compressed, timestamped MySQL/PostgreSQL/MongoDB dump with a retention policy |
| “Tail the nginx error log on staging.” | Live output, streamed as it happens |
| “Push ./dist to production:/var/www and restart nginx.” | The files out, then the restart — with ownership, permissions and a rollback copy handled |
| “Which of my servers is running out of disk?” | Every server in the group, checked in parallel |
| “Open a tunnel to the production database.” | Local port forward, so your GUI client just connects |
“Run docker ps on everything tagged production.” |
One command, the whole group, sequential or parallel |
There is a CLI too, for when you’d rather do it yourself: ssh-manager with no arguments opens this.
Watch over its shoulder — the control plane
New in 4.0.0. The application is opt-in: with no vault and no approval enabled, the engine keeps working headlessly, exactly as 3.8.x. See the migration guide for host-key verification and recovery behaviour.
ssh-manager control # prints a local URL — or `ssh-manager app` for the desktop app
One thing in two wrappers. ssh-manager control runs the whole interface in a browser tab with nothing extra to install; the desktop builds for macOS, Windows and Linux are that same page with the engine inside them, so they need neither Node nor the npm package.
The desktop build adds the two things a browser tab cannot do:
- It lives in the menu bar. The icon carries a count when something is waiting, and its menu lists what is blocked on you, which shells and commands are open right now, and your servers — so “is anything waiting on me?” costs a glance instead of a window switch. Closing the window leaves it there rather than quitting.
- It posts real system notifications, with the application’s own identity, so macOS and Windows show and route them properly. Destructive requests stay on screen instead of fading after four seconds. macOS asks your permission the first time it needs to.
Stop it before it runs
The agent pauses and waits. You see the machine, the user, the tool, and the command in full — wrapped, never truncated, because half a command is how you approve the wrong thing. A desktop notification fires when something is waiting, because the request that goes unseen is the one that times out and is denied.
Approval is switched on from the interface, per server — not from a file:
Servers → the server → Approval → never (default) | destructive | always
Approval is stored in the vault and cannot be overridden by .env or process
variables. Editing connection details preserves it. This prevents accidental
configuration overrides from disabling the gate; it is not a security boundary
against a process that already controls your local user account or vault files.
If you had SSH_SERVER_*_APPROVAL set in a file, the engine says
so on every start rather than quietly leaving you unprotected.
The destructive list is deliberately short. A prompt that cries wolf gets clicked through without being read, which is worse than no prompt at all: systemctl restart does not interrupt you, systemctl stop does.
When a request requires approval, a timeout, unreachable socket or disconnected control plane denies it. If nothing is listening, start ssh-manager control or the desktop app before retrying. Servers with approval disabled keep working headlessly.
See what it’s doing, while it does it
Output goes through a terminal emulator, not a `` tag — what your agent ran looks exactly like what you’d have seen had you typed it yourself, colours and all. Each stream keeps a bounded scrollback, so opening the screen mid-command shows what came before instead of starting blank.
Nothing here touches the disk. A stream can carry secrets — a config being catted, a token echoed by a deploy script — so it lives in memory, capped, and disappears with the window.
Both filesystems, side by side
Drag a file across and it moves directly between your machine and the server through the control plane. The browser never holds the bytes — which is faster, and the only way a multi-gigabyte file works at all.
Health, when you ask for it
Nothing is probed in the background. Each check is an SSH handshake, and a dashboard that connects to every production box on a timer is worse than no dashboard. The button is the whole scheduling policy. Thresholds are yours to set, and crossing one is said in words rather than encoded in a colour.
A shell on any machine
Pick a machine, get a shell — the same one an agent gets, with your keyboard on it. Useful for the ordinary reasons, and for the one people ask about most: running an agent on the server rather than pointing one at it.
Including this machine. The desktop application opens a shell on your own computer too, with your login shell and everything you have configured in it, so you are not switching to a different window to run the local half of the same job. Open as many as you like, on any mix of machines: each is a tab across the top and an entry in the rail, and switching away leaves it running.
Beside it, the commands you would rather not retype, saved per server or for all of them, and picked from a list inside any shell.
And the rest of it
On the same page: an interactive shell on any server, saved commands you pick from a list instead of retyping, groups you can run one command across, the audit trail of what agents ran and what you decided, and your known host keys.
Every screenshot above is the desktop build; each has a browser twin in docs/images/. See ROADMAP-V4.md for what is built and what isn’t.
Safe by default
Other SSH MCP servers hand the agent a shell and wish you luck. This one assumes you’d like some say in the matter.
Decide how far it can go — per server
| Mode | What the agent can do |
|---|---|
unrestricted (default) |
Everything. Same as any other SSH MCP server, zero overhead. |
readonly |
Mutating tools are refused outright — no deploy, no upload, no sudo, no database import. Reads still work. |
restricted |
Every command must match an allow pattern and no deny pattern. Anything else is refused before it reaches the host. |
SSH_SERVER_PROD_MODE=readonly
SSH_SERVER_STAGING_MODE=restricted
SSH_SERVER_STAGING_ALLOW_PATTERNS=^systemctl (status|restart) myapp$;^tail -n \d+ /var/log/
SSH_SERVER_PROD_AUDIT_LOG=~/.ssh-manager/audit.jsonl
This is a second layer under Claude Code’s autoApprove, which is all-or-nothing per tool: once ssh_execute is approved, anything goes. Useful when you’re sharing the MCP with a third-party agent, a CI bot, or working on a client’s machine. Full reference in docs/SECURITY_MODES.md.
Credentials out of the clear-text .env
ssh-manager vault import # take what you already have, encrypted
ssh-manager vault list # see it, without ever printing a secret
ssh-manager vault add prod # add one interactively
ssh-manager vault backup FILE # a copy that survives a new machine
AES-256-GCM, master key in your OS keychain (a 0600 file where there is none — Windows, CI, containers). Only secret values are encrypted: hosts, users and modes stay readable, so the file can still be inspected and diffed. GCM is authenticated, so a tampered vault fails loudly instead of handing a wrong password to a production server.
The key belongs to this machine. A vault copied to a new laptop is ciphertext nobody can open — vault backup writes a copy encrypted under a passphrase you choose, which is what makes the move survivable. Your .env is never modified, not by import, not by the interface, and a setup half in each is a normal state rather than a broken one. Upgrading changes nothing on its own: see docs/MIGRATION.md.
The unglamorous parts, which are the ones that bite
- Your sudo password never reaches the remote command line. It travels on the SSH channel’s stdin, so it isn’t visible in
ps, in/proc//cmdline, or in anauditdtrail — unlike theecho "$pass" | sudo -Spattern common in this category (#34). - Every shell argument is quoted through one central helper, with a test that drives 340 builder × argument × payload combinations through a real shell to prove none of them execute.
- Read-only SQL is enforced, not suggested.
ssh_db_queryrefuses anything that isn’t aSELECT. - Vulnerabilities are published, not buried. SECURITY.md has the reporting process and every advisory already fixed — including the six in v3.8.6, among them a command injection on the machine running the server, triggered by any SSH server it connects to.
- Reproducible installs. The lockfile is committed, CI installs with
npm ci, and a test enforces that every dependency resolves to registry.npmjs.org with an integrity hash and no unreviewed install scripts.
Questions people ask
Can I let an AI agent SSH into my servers safely?
Yes, if you decide how far it can go. Per server you can make it read-only, allow only commands that match a pattern, or hold every request (or only the destructive ones) until you approve it in the control plane; credentials can live in an encrypted vault instead of a clear-text .env; and every command and decision is logged. See Safe by default.
How do I use Claude Code or Codex with SSH?
npm install -g mcp-ssh-manager, add a server with ssh-manager server add, then claude mcp add ssh-manager mcp-ssh-manager. Codex is set up here. Then ask in plain words: “why is production slow?”, “tail the nginx error log on staging”.
Does it work with Cursor, Windsurf, Cline or another MCP client? It is a standard stdio MCP server, so any MCP client can run it. Claude Code, Claude Desktop and Codex are the clients documented and tested here. For another one, add what it expects, for example:
{ "mcpServers": { "ssh-manager": { "command": "npx", "args": ["-y", "mcp-ssh-manager"] } } }
Do I need Node.js? With npm, yes (Node 18 or later). The desktop application carries its own runtime and engine: install it and connect your AI tools from Options → AI tools, with no Node and no npm.
How is this different from other SSH MCP servers?
Most hand the agent a shell. This one lets you choose per server how far it goes, can hold requests until you approve them, keeps an audit trail, and has a desktop app and a browser interface to see it all. It also runs without any of that, as a plain engine configured from a .env or a TOML file, if that is what you want.
Where are my passwords and keys kept?
Key files stay where they are. Saved passwords and passphrases can go into the encrypted vault (AES-256-GCM, with the key in your operating system’s keychain) rather than a .env.
Is it free? Yes, it is MIT-licensed. The source is here, the package is on npm and the desktop apps are on the releases page.
Reference
What’s new
4.0.1 — the application tells you about updates. Options → Updates, a mark on the sidebar and a notification once per version, so a new release no longer goes unnoticed in a menu you never open; a tidier Servers toolbar; the interface is built when the package is made rather than committed (what you install is byte for byte the same); and the npm, registry and GitHub descriptions say what V4 is. No change to the engine’s behaviour.
4.0.0 — the control plane. See what an agent is running on your servers, with its output, and stop it before it runs. A desktop application for macOS, Windows and Linux, installed from the engine with ssh-manager app; an encrypted vault for credentials; human approval per server; a shell on your own machine alongside the remote ones. Everything new is opt-in — an npm install upgraded from 3.8.x keeps behaving as 3.8.x, which a test installs from the registry and proves. The Windows application is not code-signed in this release. What changes and how to go back.
v3.8.7 — a security release; upgrade. Two advisories fixed: a partial server name or an alias escaped readonly and restricted mode (GHSA-mq9f-5385-rjf4), and ssh_backup_restore’s backupId ran commands on the server (GHSA-2wjh-xm9c-7qf9). See the CHANGELOG.
v3.8.6 — a security release; upgrade. Six advisories fixed. The default on-error hook let any SSH server you connect to run a command on your machine; restricted mode only checked how a line started; ssh_db_query could write; host keys were never compared. See the CHANGELOG.
v3.8.5 — a security release. Three command-injection advisories fixed, one of which defeated readonly mode. Upgrade if you use ssh_backup_*, ssh_db_dump, ssh_service_status or ssh_tail.
- RCE bypassing
readonly/restricted(GHSA-m793-whw6-f537) —ssh_service_statusandssh_tailare read-only, so they stay enabled on servers you locked down, and neither quoted its arguments nor consulted the policy layer. A service name likenginx; id > /tmp/pwnedexecuted. - RCE through
ssh_db_dump(GHSA-796j-h5q5-jx6p) — thestatrun after the dump interpolated the output path raw. The v3.6.7 patch had stopped one line short. - RCE through every
ssh_backup_*tool (GHSA-qwwm-vrm9-4mw8) —backup-manager.jshad zero shell escaping across its nine builders whiledatabase-manager.jshad 95.
Quoting now lives in one module so “did this builder quote its inputs?” has a single answer.
Full changelog, every release back to v1.0.0 →
Validation before a release
The V4 testing guide describes the offline suites, real npm 3.8.5 → V4 → rollback checks, browser flows, and platform release gates. Use the final V4 test guide to review the actual npm package or desktop candidate with a disposable profile before publication.
Install
npx -y mcp-ssh-managerConfiguration
{ "mcpServers": { "ssh-manager": { "command": "npx", "args": ["-y", "mcp-ssh-manager"] } } }