Make SSH work for AI tools - manage the agent and keys, diagnose what is broken, fix it, and give your agent remote access, from Claude Code, Cursor, or any MCP client.
@yawlabs/ssh-mcp manages your SSH environment, diagnoses what is broken, fixes it, and gives your agent remote access to anything. It manages the SSH agent, loads keys, diagnoses failures with actionable fix commands, and provides remote operations - all as MCP tools your AI agent can call. Built and maintained by Yaw Labs, MIT licensed, published on npm.
npx -y @yawlabs/ssh-mcp@latestGitHub · npm · Add to Yaw MCP
Anyone whose agent keeps tripping over SSH. AI CLI tools run in subprocesses where SSH is constantly broken: the agent tries to git pull and gets Permission denied (publickey), it tries to SSH into a server and the agent socket is stale, it tries to deploy and the host key changed because the instance was recreated. Every time, the AI has no idea what is wrong and spirals.
That happens across every situation needing SSH keys - Git (clone, pull, push, fetch, submodules, LFS), package managers pulling from private repos, server access over SSH, SCP, SFTP and rsync, port forwarding and SOCKS proxies, deployment tooling like Ansible and Terraform, and any cloud VPS. The README's worked examples are the shape of it:
ssh_git_check reports the key is not registered, ssh_key_list finds the key exists but is not loaded, ssh_key_load loads it, and the re-check succeeds. Then git pull works.ssh_exec fails with host key verification failed, the auto-diagnostics in the error point at ssh_known_hosts_fix, which refreshes the entry, and the command runs.ssh_test reports connection refused, ssh_diagnose shows the agent running and keys loaded but the host unreachable, and the agent concludes sshd is not running or port 22 is blocked rather than guessing.ssh_multi_exec runs one command on many hosts in parallel and returns results per host.ssh_tail reads the last N lines with an optional grep pattern, instead of an agent improvising a pipeline.1. Add the server to your client. No API key and no required env var - ssh-mcp uses the SSH setup you already have. On macOS, Linux and WSL:
{
"mcpServers": {
"ssh": {
"command": "npx",
"args": ["-y", "@yawlabs/ssh-mcp@latest"]
}
}
}On Windows, wrap the call in cmd /c, since Node 20+ cannot spawn .cmd files directly and that is what npx is there:
{
"mcpServers": {
"ssh": {
"command": "cmd",
"args": ["/c", "npx", "-y", "@yawlabs/ssh-mcp@latest"]
}
}
}The @latest tag makes npx re-resolve against the registry on every spawn, so each MCP session uses the newest published version. If you would rather pin and skip auto-updates, install globally with npm install -g @yawlabs/ssh-mcp and set "command": "ssh-mcp" in the client config instead.
2. Restart and approve. Restart Claude Code (or your MCP client) and approve the ssh server when prompted. Then ask it something: "Diagnose my SSH setup and tell me why git pull is failing."
Running Yaw MCP? One click adds it to your local config, and it is then available in every Yaw Terminal session.
SSH environment management - the tools that fix your local setup so everything else stops breaking:
| Tool | What it does |
|---|---|
ssh_agent_ensure | Ensure ssh-agent is running. Starts one if needed and sets env vars for the session. |
ssh_key_list | List all SSH keys in ~/.ssh/ with type, fingerprint and agent status. |
ssh_key_load | Load a key into the running agent, ensuring the agent is started first. |
ssh_config_lookup | Resolve the effective SSH config for a host: hostname, user, port, proxy, identity files. |
ssh_known_hosts_fix | Remove a stale host key and re-scan. Fixes host key verification failures - and it is the one tool here that writes known_hosts. |
ssh_git_check | Test Git-over-SSH auth to GitHub, GitLab, Bitbucket and friends. |
ssh_test | Quick connectivity test with timing and actionable error details. |
ssh_diagnose | Full SSH environment diagnostic: agent, keys, config, known_hosts and connectivity, with exact fix commands for every failure. |
Remote operations - execution over SSH, file work over SFTP:
| Tool | What it does |
|---|---|
ssh_exec | Execute a command on a remote host, returning stdout, stderr and exit code. An optional env param sets per-call environment variables with a POSIX-safe prefix, which works regardless of sshd's AcceptEnv. |
ssh_read_file / ssh_write_file | Read or write a file on a remote host via SFTP. |
ssh_upload / ssh_download | Move a file to or from the remote host via SFTP. |
ssh_ls / ssh_stat | List a directory, or get metadata (size, octal mode, uid/gid, mtime/atime, file/dir/symlink) instead of parsing ls -la. |
ssh_mkdir | Create a directory via SFTP, with recursive: true for mkdir -p behavior. Unlike the other SFTP tools the path may be relative, resolving against the SFTP working directory; ~ is not expanded, because SFTP has no shell. |
ssh_delete | Delete a file or empty directory via SFTP, dispatching unlink or rmdir from the path's own type via lstat, so a symlink is always unlinked and never followed. Recursive delete is intentionally not supported. |
Higher-level operations - wrappers for the patterns agents otherwise rebuild out of ssh_exec:
| Tool | What it does |
|---|---|
ssh_multi_exec | Run a command on multiple hosts in parallel, returning results per host. The same env prefix applies, built once and sent to every host. |
ssh_find | Search for files remotely with structured parameters: name, type, size, depth, and newer for files modified more recently than a reference path. |
ssh_tail | Read the last N lines of a file, optionally filtered by a grep pattern. |
ssh_service_status | Check systemd service status: active, PID, uptime, description. Flags an error only when the unit could not be found or queried, not when an existing unit is intentionally stopped. |
Three behaviors run underneath all of it. Auto-diagnostics: when a remote operation fails, ssh-mcp runs diagnostics automatically and includes the results in the error response, so your agent is told what is wrong and how to fix it without calling ssh_diagnose separately. Connection pooling: the first call to a host opens a connection and later calls reuse it, kept alive for a period after the last use, then closed; the pool caps at a default size you can raise with SSH_MCP_MAX_POOL_SIZE for fan-out workloads, evicting an idle entry when full and rejecting with Connection pool is full when every entry is in use. SSH config support: host aliases, custom ports, usernames, identity files and ProxyJump all come from your ~/.ssh/config automatically, so host: "myserver" is enough, and chained bastion proxies work too.
known_hosts entry does not cover", so a missing ed25519 line does not read as an attack.SSH_MCP_STRICT_HOST_KEY=1, and the connection path never writes to known_hosts - no tool adds an entry as a side effect of connecting. So connecting pins nothing: every connection to a host absent from known_hosts is a "first" use and is accepted, including one where an attacker swapped the key since your last call. Only hosts put there out of band get mismatch protection - by you, by ssh-keyscan, or by ssh_known_hosts_fix, which you can call explicitly to add one.ssh_test and ssh_diagnose use StrictHostKeyChecking=no for their probes, which only run echo SSH_OK - no credentials or data pass through. Real operations always go through the host verifier.SSH_MCP_COMMAND_WHITELIST requires a match, SSH_MCP_COMMAND_BLACKLIST requires no match, and when both are set a command must pass both. Patterns are JavaScript regexes, comma-separated. Enforcement happens before the SSH connection opens, so no remote process is started for a blocked command, and the error names the pattern or env var that rejected the call.ssh_write_file, ssh_upload, ssh_mkdir and ssh_delete never build a shell command string, so a command-shaped regex has nothing to match: SSH_MCP_COMMAND_BLACKLIST="^rm " does not stop ssh_delete. If you must prevent remote mutation, drop those four tools from your MCP client's tool allowlist; these env vars cannot do it.env prefix defeats them. Policy is checked against the full prefixed command, so a whitelist of ^ls blocks any call that uses env. Worse, a substring pattern like ls can be bypassed by a hostile agent passing env: { ATTACK: " ls " } in front of a destructive command. Use the env-prefix-tolerant anchored form below. When a whitelist blocks a call that used env, the error explains that the prefix is why the anchor stopped matching and suggests the tolerant pattern.privateKeyPath or password is used alone, with nothing else offered. With neither given, the agent and one on-disk key are offered together, the way the OpenSSH client does it. Only one on-disk key is ever offered, so if the first readable candidate is the wrong one, authentication rests on the agent's keys. When an agent is configured an encrypted on-disk key is skipped, which on Windows is always in force because the named pipe is assumed unconditionally.# Read-only allowlist: only ls / df / cat / find / tail
SSH_MCP_COMMAND_WHITELIST="^ls( .*)?,^df( .*)?,^cat ,^find ,^tail "
# Block destructive ops even if your agent goes off-script
SSH_MCP_COMMAND_BLACKLIST="^rm ,^shutdown,^reboot,^mkfs,^dd if="
# Env-prefix-tolerant anchor: allow zero or more KEY='value' prefixes
SSH_MCP_COMMAND_WHITELIST="^([A-Za-z_][A-Za-z0-9_]*='[^']*' )*ls( |$)"The same package exposes its internals as a TypeScript API, so the environment-repair and remote-execution logic is reusable from a plain script with no model involved:
import { connect, exec, diagnose, ensureAgent, ConnectionPool } from '@yawlabs/ssh-mcp';
ensureAgent();
const report = diagnose('my-server');
console.log(report.overall); // "ok" | "warning" | "error"
const pool = new ConnectionPool();
await pool.withConnection({ host: 'my-server' }, async (client) => {
const r = await exec(client, 'uptime');
console.log(r.stdout);
});
pool.drain();listSshKeys and checkGitSsh are exported the same way, and diagnose returns a structured report whose checks each carry a status, a name and a message, alongside an overall verdict of ok, warning or error.
It manages your SSH environment, diagnoses what is broken, fixes it, and gives your agent remote access. That splits into environment tools (ensure the agent is running, list and load keys, resolve effective SSH config, repair a stale known_hosts entry, test Git-over-SSH auth), a full diagnostic that returns exact fix commands for every failure, remote operations over SSH and SFTP, and higher-level wrappers for multi-host execution, remote search, log tailing and systemd service status.
Usually not. All connections respect your ~/.ssh/config, so host aliases, ports, usernames, identity files and ProxyJump settings are used automatically. If you pass an explicit privateKeyPath or password it is used alone and nothing else is offered. With neither given, ssh-mcp offers the ssh-agent and one on-disk key together and lets the server pick. Only one on-disk key is ever offered, so if the first readable candidate is the wrong one, authentication rests on the agent's keys; pass privateKeyPath to force a specific key.
For known hosts, yes: a matching key is accepted and a changed key is rejected as MITM protection, with a message that distinguishes a genuine mismatch from a key type your known_hosts entry does not cover. An unknown host is accepted unless SSH_MCP_STRICT_HOST_KEY=1. Read that last branch carefully, because it is trust-always, not trust-on-first-use: the connection path never writes to known_hosts, so connecting pins nothing and every connection to an absent host is a first use. Only hosts added out of band, by you, by ssh-keyscan, or by ssh_known_hosts_fix, get mismatch protection.
Partly. SSH_MCP_COMMAND_WHITELIST and SSH_MCP_COMMAND_BLACKLIST take comma-separated regex patterns and are enforced before the SSH connection opens, so no remote process starts for a blocked command. The scope is the caveat: the policy covers ssh_exec and ssh_multi_exec only. The mutating SFTP tools ssh_write_file, ssh_upload, ssh_mkdir and ssh_delete never build a shell command string, so a blacklist of ^rm does not stop ssh_delete. To prevent remote mutation, drop those four tools from your MCP client's tool allowlist; these env vars cannot do it.
Yes. On Windows ssh-mcp uses the OpenSSH Authentication Agent's named pipe automatically when SSH_AUTH_SOCK is not set, so no SSH_AUTH_SOCK is needed as long as the OpenSSH agent service is running. One wrinkle: ssh_agent_ensure and ssh_diagnose probe that pipe and tell you if the service is down, but remote operations do not, so a stopped agent service shows up as an auth failure rather than an agent-not-running error until you run the diagnostic tools.
Published by Yaw Labs.