Skip to main content
Glama
vait90
by vait90

SSH MCP Server (paramiko)

Paramiko-based SSH MCP server that can run commands on remote machines and move files over SFTP. It is available on two transports, selectable with an environment variable / flag:

  • http – MCP streamable-http endpoint on the /mcp path (this is where Cherry Studio connects), plus a documented OpenAPI/Swagger interface (/docs, /openapi.json).

  • stdio – classic MCP stdio transport (for local launch / docker exec).

IMPORTANT about ports: 2222 is the MCP server port that Cherry Studio connects to. This is NOT the remote machine's SSH port! The remote machine's SSH port is usually 22 (SSH_PORT). So: Cherry Studio → http://<host-IP>:2222/mcp → MCP server → paramiko → the remote machine's SSH port 22.


Available MCP tools

Stateless tools — simple, one-off operations

Tool

Description

ssh_test

Tests the connection and authentication to a remote machine.

ssh_execute

Runs a SINGLE shell command in a fresh connection (stdout / stderr / exit code). No memory: the cd / export do not persist to the next call, and it cannot interactively prompt.

ssh_upload

Uploads a local file to the remote machine over SFTP.

ssh_download

Downloads a file from the remote machine over SFTP.

Stateful, interactive session tools — live shell

These keep a live shell open, where the state persists across calls (directory changes after cd, export-ed variables, handling interactive prompts: sudo password, apt [Y/n], etc.).

Tool

Description

ssh_open_session

Step 1 – opens a new interactive shell and returns a session_id.

ssh_send

Step 2 – sends text (a command or a prompt response) to the session. The session_id must always be passed.

ssh_read

Step 3 (optional) – reads further output without sending anything (for slow/long-running commands).

ssh_close_session

Step 4 – closes the session. Always close it when you are done.

ssh_list_sessions

Lists the open sessions (host, user, idle time), for example if a session_id was lost.

The tool descriptions (docstrings) intentionally contain a very detailed, plain-English "USE THIS WHEN..." guide so that the consuming model clearly knows when and how to use each tool.

Every tool's parameters (host, port, username, password, private_key, private_key_path, passphrase, timeout) can be specified:

  • per call, individually, or

  • as defaults in the .env file (SSH_* variables). Anything not given in the call is taken from the SSH_* environment variables.

Supported authentication: password and key (inline PEM or file path, optionally with a passphrase). Unknown host keys are automatically accepted by the server (AutoAddPolicy) so that automation works smoothly.


Related MCP server: SSH MCP Server

Stateless vs. stateful (interactive) use

Which one when?

  • A single, standalone command (e.g. ls, uptime, df -h) → ssh_execute. Every call opens a fresh connection, runs one command, and closes. No memory: the cd and export do not survive to the next call, and it cannot respond to interactive prompts.

  • Anything interactive or multi-step (state retention after cd/export, entering a sudo password, answering apt [Y/n], sequential commands that build on one another) → interactive session: ssh_open_sessionssh_sendssh_readssh_close_session.

  1. ssh_open_session → you get a session_id back (and the login banner / first prompt in initial_output).

  2. ssh_send → you type a command or respond to a prompt. The session_id must be passed in every call. It also sends an Enter by default.

  3. ssh_read (optional) → collect further output for slow / long-running commands without sending anything.

  4. ssh_close_session → once you are done, close the session.

With ssh_list_sessions you can view open sessions at any time (host, user, idle time), for example if a session_id was lost.

Examples (via REST endpoints)

Opening a session:

curl -X POST http://localhost:2222/api/ssh/session/open \
  -H "Content-Type: application/json" \
  -d '{"host":"192.168.1.100","username":"user","password":"secret"}'
# -> {"ok":true,"session_id":"<ID>", "initial_output":"...prompt..."}

Changing directory, which persists (stateful):

curl -X POST http://localhost:2222/api/ssh/session/send \
  -H "Content-Type: application/json" \
  -d '{"session_id":"<ID>","input":"cd /var/log && pwd"}'
# a következő ssh_send már a /var/log-ban futna

Sudo command + responding to the password prompt:

# 1) elindítod a sudo parancsot
curl -X POST http://localhost:2222/api/ssh/session/send \
  -H "Content-Type: application/json" \
  -d '{"session_id":"<ID>","input":"sudo apt-get update"}'
# 2) a kimenetben megjelenik a "[sudo] password for user:" prompt -> beküldöd a jelszót
curl -X POST http://localhost:2222/api/ssh/session/send \
  -H "Content-Type: application/json" \
  -d '{"session_id":"<ID>","input":"my_sudo_password"}'

Responding to apt [Y/n]:

curl -X POST http://localhost:2222/api/ssh/session/send \
  -H "Content-Type: application/json" \
  -d '{"session_id":"<ID>","input":"sudo apt-get install htop","read_timeout":5}'
# amikor jön a "Do you want to continue? [Y/n]" kérdés:
curl -X POST http://localhost:2222/api/ssh/session/send \
  -H "Content-Type: application/json" \
  -d '{"session_id":"<ID>","input":"Y"}'

Closing the session:

curl -X POST http://localhost:2222/api/ssh/session/close \
  -H "Content-Type: application/json" \
  -d '{"session_id":"<ID>"}'

Timeout / idleness / errors: every session operation opportunistically closes sessions that have been idle longer than the SSH_SESSION_IDLE_TIMEOUT (default 600 seconds), as well as those whose channel has died. At most SSH_MAX_SESSIONS (default 20) sessions can be open at the same time — reaching the limit yields a clear error message. If a session_id no longer exists, the response tells you exactly what to do (open a new session, or check with ssh_list_sessions).


Project structure

ssh-mcp-server/
├── app/
│   ├── __init__.py
│   ├── ssh_ops.py     # paramiko SSH/SFTP műveletek (közös logika)
│   └── server.py      # MCP tool-ok + FastAPI/OpenAPI + transport választás
├── requirements.txt
├── Dockerfile
├── docker-compose.yml # 2222:2222 publikálás
├── .env.example
└── README.md

Prerequisites

cd ssh-mcp-server
cp .env.example .env
# szerkeszd a .env-et: add meg a távoli gép adatait (SSH_HOST, SSH_USERNAME, stb.)

Build and start (HTTP mode)

docker compose up -d --build

This starts the server in HTTP mode and publishes the 2222 port to the host (ports: "2222:2222").

Verification

curl http://localhost:2222/health
# {"status":"ok","service":"ssh-mcp-server","mcp_endpoint":"/mcp"}
  • Swagger UI (in a browser): http://localhost:2222/docs

  • OpenAPI JSON: http://localhost:2222/openapi.json

  • MCP endpoint (Cherry Studio): http://<host-IP>:2222/mcp

Shutdown

docker compose down

2. HTTP mode manually (without Docker, for development)

python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
export TRANSPORT=http HOST=0.0.0.0 PORT=2222
python -m app.server

3. stdio mode

With the server running in a container, using docker exec:

docker exec -i -e TRANSPORT=stdio ssh-mcp-server python -m app.server

Or directly, without Docker:

TRANSPORT=stdio python -m app.server

4. Cherry Studio integration

The container runs on your laptop in Docker, and Cherry Studio uses the host IP and port 2222.

  1. Start the server: docker compose up -d --build

  2. Find the IP address of the machine (host) where the Docker is running:

    • Linux: hostname -I → e.g. 192.168.1.50

    • If Cherry Studio is running on the same machine, localhost / 127.0.0.1 is fine too.

  3. Cherry Studio → SettingsMCP ServersAdd / New server.

  4. Enter the following:

    • Type: Streamable HTTP (if not available, use SSE / HTTP)

    • URL / Endpoint: http://<host-IP>:2222/mcp

      • e.g. http://192.168.1.50:2222/mcp

      • same machine: http://localhost:2222/mcp

  5. Save and enable it. Cherry Studio then loads the ssh_test, ssh_execute, ssh_upload, ssh_download tools.

If you are connecting from a remote machine, make sure that port 2222 is reachable (allow it in the firewall) and that Docker is listening on 0.0.0.0 (default).

B) stdio mode

If Cherry Studio expects a stdio MCP server (it launches a command):

  • Command: docker

  • Arguments:

exec -i -e TRANSPORT=stdio ssh-mcp-server python -m app.server

(For this, the sf-mcp-server container must be running — docker compose up -d.)


5. .env configuration

Variable

Description

Default

TRANSPORT

http or stdio

http

HOST

MCP HTTP bind address

0.0.0.0

PORT

MCP HTTP port (reached by Cherry Studio)

2222

SSH_HOST

Remote machine address

SSH_PORT

SSH port of the remote machine

22

SSH_USERNAME

SSH username

SSH_PASSWORD

SSH password (or use a key)

SSH_PRIVATE_KEY

Inline PEM private key

SSH_PRIVATE_KEY_PATH

Path to the private key file (inside the container)

SSH_PASSPHRASE

Passphrase for the private key

SSH_TIMEOUT

Connection timeout (in seconds)

15

SSH_SESSION_IDLE_TIMEOUT

Automatically close idle interactive sessions after this many seconds (0 = none)

600

SSH_MAX_SESSIONS

Maximum number of open interactive sessions at once

20

Key-based authentication in Docker

Mount the keys into the container and set the path. In docker-compose.yml, uncomment the volumes line:

    volumes:
      - ./keys:/keys:ro

then in .env:

SSH_PRIVATE_KEY_PATH=/keys/id_ed25519

6. REST endpoints for testing (OpenAPI)

In HTTP mode, in addition to the Cherry Studio MCP endpoint, REST endpoints are also available — these perform the same SSH operations and can be easily used with curl / from the Swagger UI:

Method

Path

Operation

GET

/health

Status

GET

/

Server info

POST

/api/ssh/test

Connection test

POST

/api/ssh/execute

Command execution

POST

/api/ssh/upload

File upload (SFTP)

POST

/api/ssh/download

File download (SFTP)

POST

/api/ssh/session/open

Open interactive session (step 1)

POST

/api/ssh/session/send

Send input to the session (step 2)

POST

/api/ssh/session/read

Read output without sending (step 3)

POST

/api/ssh/session/close

Close the session (step 4)

GET

/api/ssh/session/list

List open sessions

Example (stateless single-command run):

curl -X POST http://localhost:2222/api/ssh/execute \
  -H "Content-Type: application/json" \
  -d '{"host":"192.168.1.100","username":"user","password":"secret","command":"uname -a"}'

Security notes

  • Secrets are never in the code — everything is read from .env / call parameters.

  • The .env file is excluded by .dockerignore and typically also .gitignore — do not commit it to version control.

  • The server uses AutoAddPolicy (automatic acceptance of unknown host keys). Convenient on closed networks; in stricter environments, it is advisable to use known host keys.

  • Expose the 2222 MCP port only on a trusted network.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI assistants to securely execute commands, transfer files, and manage port forwarding on remote servers via SSH.
    91 npm
    37
    Apache 2.0
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables remote server management via SSH, including command execution, file transfer (SFTP), and interactive shell sessions, with support for multiple hosts.
    -
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI agents to securely execute commands on remote hosts via SSH and SFTP, with persistent shells, file transfers, screenshots, and an audit log.
    1
    MIT