Skip to main content
Glama
samuelcoc
by samuelcoc

mcp-ssh-toolkit (Python)

MCP (Model Context Protocol) server to execute commands via SSH on named servers, defined in a servers.json file.

The focus here is on being practical and secure:

  • Configuration per server and per group (groups).

  • Hot reload of servers.json (no need to restart the MCP on every change).

  • Support for policies (policy) to allow/block commands via regex.

  • Support for passwords without putting them in the JSON (via passwordEnv, passwordCommand, or passwordKeyring).

Configuration via agent (LLM)

Once the MCP is active on your host (opencode / Claude Desktop / etc.), you can ask the LLM agent to configure everything for you by calling the tools:

  • ssh_add_server to add/update servers

  • ssh_reload (if you want to force a reload, although hot reload exists)

  • ssh_list / ssh_info to validate what has been configured

The recommendation is to pass secrets via env vars and only save references like passwordEnv in servers.json.

Related MCP server: SSH MCP Server

Files

  • mcp_ssh_server.py: entrypoint compatible with the MCP server via stdio.

  • mcp_ssh/: modular MCP code (main.py, config.py, models.py, errors.py, policy.py, audit.py, ssh_exec.py, tools.py, handlers.py).

  • web_admin_server.py: entrypoint compatible with the local web panel.

  • web_admin/: MVC structure of the web panel (model.py, view.py, controller.py, app.py).

  • web_admin_ui.html: HTML template for the web panel.

  • servers.json: your local configuration (do not commit).

  • servers.example.json: ready-to-use example (can be committed).

  • USAGE.txt: quick summary.

  • tests/test_mcp_ssh_server.py: basic unit tests.

Requirements

  • Python >= 3.10

  • OpenSSH in PATH (for key/agent auth) or paramiko (for password auth)

Optional:

  • paramiko (when using passwords): pip install paramiko

  • keyring (when using passwordKeyring): pip install keyring

How to run

1) Run manually (stdio)

python mcp_ssh_server.py --config servers.json

Optional (package mode):

python -m mcp_ssh.main --config servers.json

You can also use the env var:

  • MCP_SSH_CONFIG=... (if you don't pass --config)

Local web panel (admin)

If you want a friendly interface to manage servers.json, execute commands, and check history:

python web_admin_server.py --config servers.json --host 127.0.0.1 --port 8787

Optional (package mode):

python -m web_admin.app --config servers.json --host 127.0.0.1 --port 8787

Then open in your browser:

http://127.0.0.1:8787

The panel uses the same servers.json and the same audit log file configured in logging.file.

2) Use in opencode

Edit your user's opencode config file (e.g., C:\Users\YOUR_USER\.config\opencode\opencode.json) and add a local MCP.

Option A (Anaconda):

{
  "mcp": {
    "mcp-ssh": {
      "type": "local",
      "command": [
        "C:\\Users\\SEU_USUARIO\\anaconda3\\python.exe",
        "C:\\Users\\SEU_USUARIO\\Desktop\\mcp-ssh-python\\mcp_ssh_server.py",
        "--config",
        "C:\\Users\\SEU_USUARIO\\Desktop\\mcp-ssh-python\\servers.json"
      ],
      "enabled": true
    }
  }
}

Option B (Python “normal” in PATH):

{
  "mcp": {
    "mcp-ssh": {
      "type": "local",
      "command": [
        "python",
        "C:\\Users\\SEU_USUARIO\\Desktop\\mcp-ssh-python\\mcp_ssh_server.py",
        "--config",
        "C:\\Users\\SEU_USUARIO\\Desktop\\mcp-ssh-python\\servers.json"
      ],
      "enabled": true
    }
  }
}

On Windows, if you use the launcher, it usually works with py (e.g., replace python with py).

Restart opencode after changing opencode.json.

3) Use in Claude Desktop (Anthropic)

Claude Desktop supports MCP via config (look for claude_desktop_config.json). On Windows, it is usually located at:

  • %APPDATA%\Claude\claude_desktop_config.json (Windows)

Example (Python in PATH):

{
  "mcpServers": {
    "mcp-ssh": {
      "command": "python",
      "args": [
        "C:\\Users\\SEU_USUARIO\\Desktop\\mcp-ssh-python\\mcp_ssh_server.py",
        "--config",
        "C:\\Users\\SEU_USUARIO\\Desktop\\mcp-ssh-python\\servers.json"
      ]
    }
  }
}

Example (Anaconda):

{
  "mcpServers": {
    "mcp-ssh": {
      "command": "C:\\Users\\SEU_USUARIO\\anaconda3\\python.exe",
      "args": [
        "C:\\Users\\SEU_USUARIO\\Desktop\\mcp-ssh-python\\mcp_ssh_server.py",
        "--config",
        "C:\\Users\\SEU_USUARIO\\Desktop\\mcp-ssh-python\\servers.json"
      ]
    }
  }
}

Tip: if you need to pass passwords via env vars (passwordEnv), configure the env vars in the Claude Desktop process (or start Claude Desktop from a terminal that already has the env vars set, if applicable).

Configuration (servers.json)

Structure (v1):

{
  "version": 1,

  "defaults": {
    "user": "ubuntu",
    "port": 22,
    "identityFile": "~/.ssh/id_ed25519",
    "strictHostKeyChecking": "accept-new|yes|no",
    "knownHostsFile": "~/.ssh/known_hosts",
    "extraArgs": ["-o", "BatchMode=yes"]
  },

  "policy": {
    "allow": ["^uptime$"],
    "deny": ["(?i)\\brm\\s+-rf\\b"]
  },

  "defaultServer": "nome-do-servidor",

  "groups": {
    "prod": ["prod-web", "prod-db"],
    "staging": ["staging"]
  },

  "servers": {
    "nome-do-servidor": {
      "host": "10.0.0.10",
      "port": 22,
      "user": "ubuntu",
      "identityFile": "~/.ssh/id_ed25519",

      "strictHostKeyChecking": "accept-new|yes|no",
      "knownHostsFile": "~/.ssh/known_hosts",
      "extraArgs": ["-o", "BatchMode=yes"],

      "passwordEnv": "SSH_PASSWORD_ENVVAR",
      "passwordCommand": ["op", "read", "op://vault/item/field"],
      "passwordKeyring": {"service": "mcp-ssh", "username": "ubuntu"},

      "policy": {
        "allow": ["^(uptime|whoami)$"],
        "deny": ["(?i)\\bshutdown\\b"]
      }
    }
  }
}

Policy (allow/deny): decision model

The policy can exist at the root (policy) and also per server (servers.<name>.policy). The model is:

  • deny always blocks: if ANY regex in deny matches, the command is denied.

  • allow is an allowlist: if at least one regex exists in allow, the command is only allowed if it matches at least one.

  • If allow is empty/missing, the default is "allow all" (except what is denied by deny).

  • Root + server are merged (accumulated): allow_final = allow_global + allow_servidor, deny_final = deny_global + deny_servidor.

Security: remote command and shell

  • ssh_exec sends command as a string to the remote host. In practice, this means that the command is interpreted on the remote side (usually by a shell), so metacharacters like ;, &&, |, >, <, $(), backticks, etc., can alter what actually executes.

  • This is different from passwordCommand, which runs locally without shell=True (therefore, it does not suffer from local shell interpretation).

If you want a more "hardened" mode, the recommendation is:

  • Prefer policy.allow with anchored regex (e.g., ^(uptime|whoami)$) to allow only simple commands.

  • (Optional) Block metacharacters via policy.deny.

Example of deny to block common metacharacters (adjust according to your needs):

{
  "policy": {
    "deny": ["[;&|><`$()\\n\\r]"]
  }
}

Authentication (without exposing password)

Recommended:

  • SSH Key (identityFile) / ssh-agent (uses OpenSSH)

For server with password (uses paramiko):

  • passwordEnv: gets the password from an env var

  • passwordCommand: runs a command and uses the stdout as a password (without shell=True)

  • passwordKeyring: reads from the OS keyring

Avoid:

  • password in plain text in the JSON

Hot reload (don't restart always)

The MCP reloads servers.json automatically when the file changes (via mtime).

Additionally, there is:

  • Tool ssh_reload: forces an immediate reload.

Audit logging (executed commands)

By default, the audit log is enabled and records executed commands (ssh_exec / ssh_exec_parallel) with time and server. The format is JSON Lines (.jsonl), 1 event per line.

To explicitly disable:

  • "logging": {"enabled": false}

  • or env var: MCP_SSH_AUDIT_LOG_DISABLE=1

Example:

{
  "logging": {
    "enabled": true,
    "file": "~/.mcp-ssh-toolkit/audit.jsonl",
    "format": "jsonl",
    "includeCommand": true,
    "includeResult": true,
    "includeStdout": false,
    "includeStderr": false,
    "logTests": false
  }
}

You can also point to the file via env var:

  • MCP_SSH_AUDIT_LOG_FILE=/path/to/audit.jsonl

Parallel upload/delete log (file ops)

Remote uploads and deletions generate a separate log (file_ops.jsonl) to facilitate operational auditing.

Config in servers.json:

{
  "fileOpsLogging": {
    "enabled": true,
    "file": "~/.mcp-ssh-toolkit/file_ops.jsonl",
    "format": "jsonl"
  }
}

Env vars:

  • MCP_SSH_FILEOPS_LOG_FILE=/path/to/file_ops.jsonl

  • MCP_SSH_FILEOPS_LOG_DISABLE=1

Available tools

  • ssh_list: lists servers/groups/defaults/policy

  • ssh_info: shows sanitized config of a server (without secrets)

  • ssh_test: tests connection/auth (server or group)

  • ssh_exec: executes on server or group (sequential)

  • ssh_exec_parallel: executes on group (parallel)

  • ssh_upload: sends local file to remote via SFTP (server or group)

  • ssh_delete: deletes remote file/directory via SFTP (server or group)

  • ssh_add_server: adds/updates server and optionally includes it in groups

  • ssh_reload: reloads config from disk

Usage examples

Execute on a server

{"server":"kali-192.168.1.33","command":"cat /etc/os-release && uname -a","timeout_ms":30000}

Execute on a group

{"group":"lab","command":"uptime"}

Execute on group (parallel)

{"group":"lab","command":"uname -a","max_parallel":8}

Test deny/allow policy

  • If you configure policy.deny with (?i)\brm\s+-rf\b and try:

{"server":"kali-192.168.1.33","command":"rm -rf /tmp/test"}

The MCP should return error -32602 informing that the command was blocked by the policy.

Remote upload and deletion

Upload to a server:

{
  "server": "autopago-target",
  "local_path": "C:/tmp/app.tar.gz",
  "remote_path": "/tmp/app.tar.gz",
  "overwrite": true,
  "make_dirs": true
}

Delete remote file:

{
  "server": "autopago-target",
  "remote_path": "/tmp/app.tar.gz"
}

Delete remote directory recursively:

{
  "server": "autopago-target",
  "remote_path": "/tmp/build-dir",
  "recursive": true,
  "missing_ok": true
}

Tool ssh_add_server (add without restarting)

Example: adds srv1, puts it in the prod group, and sets it as default:

{
  "server": "srv1",
  "host": "10.0.0.10",
  "port": 22,
  "user": "ubuntu",
  "identityFile": "~/.ssh/id_ed25519",
  "groups": ["prod"],
  "setDefault": true
}

Note:

  • By default, ssh_add_server does not accept password in plain text.

  • If you want to enable this in the lab, run with:

    • MCP_SSH_ALLOW_PLAINTEXT_PASSWORD=1

Tests

python -m unittest discover -s tests -p "test*.py"

Quick syntax check (optional):

python -m py_compile mcp_ssh_server.py web_admin_server.py

Troubleshooting

  • "ssh executable not found": install/enable OpenSSH on Windows.

  • Password via env doesn't work: the env var must exist in the process that starts the MCP/opencode.

  • Config changed and didn't reflect: use ssh_reload (or check file permissions/mtime).

A
license - permissive license
Not graded
quality - not tested
D
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    A Model Context Protocol server implementation that enables secure remote command execution via SSH, with features for managing and using SSH credentials.
    5
    9
    10
    MIT
  • A
    license
    B
    quality
    D
    maintenance
    A server that enables remote command execution over SSH through the Model Context Protocol (MCP), supporting both password and private key authentication.
    1
    9
    2
    MIT
  • F
    license
    A
    quality
    D
    maintenance
    A local Model Context Protocol server that allows LLMs to securely execute shell commands on remote Linux and Windows systems via SSH connections.
    6
    29
    2
  • A
    license
    Not graded
    quality
    A
    maintenance
    A Model Context Protocol server that allows LLMs to securely execute shell commands on remote Linux and Windows systems via SSH. It supports password and key-based authentication, command timeouts, and sudo elevation for administrative tasks.
    49,165
    631
    MIT

View all related MCP servers

Related MCP Connectors

  • An authenticated remote MCP server for user-owned devices and one-shot capability invocation.

  • Personal assistant MCP server with search, execute, packages, jobs, secrets, and integrations.

  • A comprehensive Model Context Protocol (MCP) server that enables AI assistants to interact with yo…

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/samuelcoc/mcp-ssh-toolkit'

If you have feedback or need assistance with the MCP directory API, please join our Discord server