General September 7, 2026

SSHand

Open MCP server giving AI agents SSH access to remote Linux/Unix machines. Includes shell commands, file read/write, and SFTP transfers. Works with Claude, ChatGPT, Cursor, VS Code, and any MCP-compatible client.

PythonArtificial Intelligence + Machine Learning
SSHand

SSHand

PyPI: https://pypi.org/project/sshand/

Introduction

I kept running into the same wall while working on my homelab. I would be in a debugging session, get stuck, resign to my AI overlords, and every time something needed to happen on the actual machine, I had to leave the conversation, open a terminal, run the command myself, and paste the output back. The obvious solution would be to install one of the existing SSH MCP servers. However, almost all of them were thin Node wrappers around ssh2 or node-ssh, published by accounts with no history behind them, which meant I was being asked to hand full shell credentials to code I had no reason to trust. The handful that looked reputable exposed a single run_command tool and nothing else, no file transfer, no host inventory, and no auth beyond a plaintext password sitting in a config file. Nothing covered what I actually needed, so I wrote it myself.

SSHand is a Python MCP server built on Paramiko and FastMCP that exposes 11 tools over a registered inventory of hosts. It works with Claude Desktop, ChatGPT, Cursor, VS Code, the OpenAI SDK, and any other MCP-compatible client.

Tools

The Model Context Protocol is a specification that lets a client expose a set of typed tools to a language model, so the model can call them directly instead of the user relaying output by hand. I split SSHand into 11 of them, grouped into four jobs:

  • Host management: ssh_add_host, ssh_list_hosts, ssh_remove_host, ssh_test_connection
  • Execution: ssh_run_command (with optional cwd and sudo password handling)
  • Files: ssh_read_file, ssh_write_file, ssh_list_directory, ssh_upload_file, ssh_download_file
  • Introspection: ssh_get_local_info (which reports the OS and path style of the local machine so the agent stops guessing at path separators)
  • I initially wanted v0.2.0 to have twenty more tools. I quickly cut that back, because every tool I add is another thing the model has to read before it picks one, and ssh_run_command already covers most of what people reach for. The rest exist so the agent doesn't have to build a sed one-liner when it wants to edit a config file.

    Authentication

    I assumed supporting three auth methods would be a weekend of work. It was not. Key files were easy. Passwords were easy until I had to make sure a prompt never blocked the stdio stream the MCP client was reading from. SSH agent support was where I lost most of my time, since Windows routes agent communication through a named pipe rather than a Unix socket, and encrypted keys need a passphrase handed back at exactly the right moment in the handshake.

    I ended up writing a separate platform_utils module just for the Windows detection and fallback path, and a PassphraseProvider class so the passphrase lookup is resolved lazily instead of being held in memory from startup. The host inventory itself lives at ~/.sshand/hosts.yaml, outside the repo by default, and repo-local inventories are gitignored. Agent auth means a user can skip storing credentials entirely, which is the option I would want if I were installing someone elses SSH server.

    Setup

    The first version shipped with a README and some YAML examples. After giving it another try, I realized that nobody reads the README, so I built an interactive terminal wizard with Rich and Questionary. It walks through three steps: adding a host, testing the connection, then printing a ready-to-paste config snippet for whichever client the user is on. Connection testing happens before the config is written, so a bad key or a wrong username surfaces in the wizard instead of as a silent tool failure inside the users agent three days later.

    This is also the part where I leaned on AI coding agents the most. TUI work is fiddly, annoying, largely mechanical, and I do not think my time was well spent hand-tuning Rich layouts and Questionary prompt validators, so I described the flow I wanted and iterated on what came back. I leaned on the Claude MCP-creation MCP Server (ironic, I know) to ensure adherence to MCP specification. I wrote the SSH and auth layer myself, with AI for errors, bug fixes, and looking for oversights in safety.

    Testing

    I decided early to test against real SSH connections instead of mocking Paramiko. This makes the suite slower, and it means tests need a reachable server to run against, which is a real cost. But mocks only ever verify that I called my own wrapper correctly, and every bug that actually bit me during development was a timeout, a partial auth failure, or an agent handshake quirk, none of which a mock would have produced. 28 tests cover command execution, file operations, host management, and all three auth paths, and a failure in that suite is a failure I actually care about.

    Reflection

    The thing that changed for me here was the gap between writing code and shipping it. A script that works on my machine is done when it runs. Something published to PyPI is not done until the defaults are safe for someone who never reads the docs, the errors say what went wrong, and the setup path works on an OS I do not personally use. That is most of the work, and none of it is the interesting part. This was also my first real project where I relied heavily on AI coding agents for development rather than only debugging, and I was pleasantly surprised

    I also spent longer than I want to admit deciding on a name. SSHand is small and describes what it does, and I applied the same rule to the API, ssh_run_command over executeRemoteCommand, which kept the whole surface predictable enough that an agent can guess a tool name and be right.