mcp-ssh-toolkit
Enables remote command execution and server management for Ubuntu systems via SSH, featuring support for group operations, regex-based security policies, and secure credential handling.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-ssh-toolkitcheck the disk usage on all servers in the prod group"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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, orpasswordKeyring).
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_serverto add/update serversssh_reload(if you want to force a reload, although hot reload exists)ssh_list/ssh_infoto 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 viastdio.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.10OpenSSH in
PATH(for key/agent auth) orparamiko(for password auth)
Optional:
paramiko(when using passwords):pip install paramikokeyring(when usingpasswordKeyring):pip install keyring
How to run
1) Run manually (stdio)
python mcp_ssh_server.py --config servers.jsonOptional (package mode):
python -m mcp_ssh.main --config servers.jsonYou 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 8787Optional (package mode):
python -m web_admin.app --config servers.json --host 127.0.0.1 --port 8787Then open in your browser:
http://127.0.0.1:8787The 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:
denyalways blocks: if ANY regex indenymatches, the command is denied.allowis an allowlist: if at least one regex exists inallow, the command is only allowed if it matches at least one.If
allowis empty/missing, the default is "allow all" (except what is denied bydeny).Root + server are merged (accumulated):
allow_final = allow_global + allow_servidor,deny_final = deny_global + deny_servidor.
Security: remote command and shell
ssh_execsendscommandas 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 withoutshell=True(therefore, it does not suffer from local shell interpretation).
If you want a more "hardened" mode, the recommendation is:
Prefer
policy.allowwith 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 varpasswordCommand: runs a command and uses the stdout as a password (withoutshell=True)passwordKeyring: reads from the OS keyring
Avoid:
passwordin 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.jsonlMCP_SSH_FILEOPS_LOG_DISABLE=1
Available tools
ssh_list: lists servers/groups/defaults/policyssh_info: shows sanitized config of a server (without secrets)ssh_test: tests connection/auth (server or group)ssh_exec: executes onserverorgroup(sequential)ssh_exec_parallel: executes ongroup(parallel)ssh_upload: sends local file to remote via SFTP (serverorgroup)ssh_delete: deletes remote file/directory via SFTP (serverorgroup)ssh_add_server: adds/updates server and optionally includes it in groupsssh_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.denywith(?i)\brm\s+-rf\band 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_serverdoes not acceptpasswordin 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.pyTroubleshooting
"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).
This server cannot be installed
Maintenance
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
- AlicenseAqualityCmaintenanceA Model Context Protocol server implementation that enables secure remote command execution via SSH, with features for managing and using SSH credentials.5910MIT
- AlicenseBqualityDmaintenanceA server that enables remote command execution over SSH through the Model Context Protocol (MCP), supporting both password and private key authentication.192MIT
- FlicenseAqualityDmaintenanceA local Model Context Protocol server that allows LLMs to securely execute shell commands on remote Linux and Windows systems via SSH connections.6292
- AlicenseNot gradedqualityAmaintenanceA 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,165631MIT
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…
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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