name: ssh-manager
Use this skill when the user asks to:
Do NOT use for: fully automated unattended batch operations, configuration management at scale (use Ansible/Salt for that), or any operation the user has not explicitly approved.
The ssh-manager MCP plugin handles SSH connections, key management, and authentication internally. No local SSH/sshpass detection is needed.
Step 1 — List configured servers:
Call ssh_list_servers to show the user all available servers:
Configured servers:
1. my-vps (root@192.168.1.100:22) key auth DigitalOcean droplet
2. staging-box (deploy@staging.example.com:2222) key auth Staging environment
3. db-server (admin@10.0.0.5:22) password PostgreSQL host
Ask the user to pick one server by name.
If no servers are configured (ssh_list_servers returns empty), tell the user: "No servers found in ssh-manager config. Add a server first via your ssh-manager configuration."
Step 2 — Select operations:
Ask the user which operations to perform (multi-select). The full list:
Also ask: Log lines to show: [20 / 50 / 100]
Display a summary:
── VPS Inspection Plan (ssh-manager) ────────
Server: my-vps
Config: key auth
Services: System health, Nginx, Docker, SSL
Log lines: 50
Step 1/10 — Health: ssh_health_check(detailed=true)
Step 2/10 — Nginx: ssh_execute("nginx -t 2>&1")
Step 3/10 — Nginx: ls -la /etc/nginx/sites-enabled/
Step 4/10 — Nginx: ssh_tail(/var/log/nginx/error.log, lines=50, follow=false)
Step 5/10 — Docker: ssh_execute("docker ps -a")
Step 6/10 — Docker: ssh_execute("docker info --format '{{.Containers}} containers'")
Step 7/10 — SSL: ssh_execute("...openssl...")
Step 8/10 — Firewall: ssh_execute("ufw status verbose")
Step 9/10 — Process: ssh_process_manager(action=list, sortBy=cpu, limit=10)
Step 10/10 — Custom: ssh_execute("your command")
─────────────────────────────────────────────
Proceed? [y/N]
Authorization rules:
[Step N/M — Group: {service}] Description: {tool_name}({params})[DESTRUCTIVE] and ALWAYS require explicit confirmation regardless of any prior approval.Destructive operations list (always require extra confirmation):
ssh_db_importssh_db_dump (writes file on remote filesystem)ssh_process_manager action=killssh_alert_setup action=setssh_execute_sudo (elevated privileges, can modify anything)Per-step flow:
[Step N/M — Group: {service}] Description and tool invocation[DESTRUCTIVE] if applicablevps-session-{server}-{date}.txt) in real time, including tool name, params, and timestampssh_history to log the command in-memory for in-session recallIf a non-destructive tool fails or returns an error, note it and move on — do not abort the sequence. If a DESTRUCTIVE operation (ssh_db_import, ssh_execute_sudo, ssh_process_manager kill) fails or returns a partial error, STOP immediately, report the failure to the user with full error details, and ask whether to continue or abort. Do NOT silently move on from a failed destructive operation — the server may be in an inconsistent state.
── Complete ──────────────────────────────
Steps run: 8/10
Skipped: 2
Anomalies: Nginx error log has warnings
─────────────────────────────────────────
After the summary, offer follow-up options:
ssh_execute)| Category | ssh-manager tool | Notes |
|---|---|---|
| System health overview | ssh_health_check |
detailed=true for full metrics |
| CPU detail | ssh_monitor type=cpu |
|
| Memory detail | ssh_monitor type=memory |
|
| Disk detail | ssh_monitor type=disk |
|
| Network connections | ssh_monitor type=network |
|
| Service status | ssh_service_status |
pass array of service names |
| Process top list | ssh_process_manager action=list |
sortBy=cpu or memory |
| Process kill | ssh_process_manager action=kill |
DESTRUCTIVE |
| Nginx config test | ssh_execute |
nginx -t 2>&1 |
| Nginx enabled sites | ssh_execute |
ls -la /etc/nginx/sites-enabled/ |
| Nginx error log | ssh_tail |
file=/var/log/nginx/error.log, follow=false |
| Docker containers | ssh_execute |
docker ps -a |
| Docker info | ssh_execute |
docker info |
| Database list | ssh_db_list |
type per engine |
| Database tables | ssh_db_list with database param |
|
| Database query | ssh_db_query |
SQL SELECT only (enforced); MongoDB find() |
| Database dump | ssh_db_dump |
DESTRUCTIVE |
| Database import | ssh_db_import |
DESTRUCTIVE |
| Systemd failed | ssh_execute |
systemctl list-units --failed --no-pager |
| Systemd running | ssh_execute |
systemctl list-units --state=running --no-pager |
| Firewall | ssh_execute |
ufw status verbose or iptables -L -n |
| SSL certs | ssh_execute |
find /etc/letsencrypt/live/ ... openssl x509 ... |
| Cron jobs | ssh_execute |
crontab -l and ls -la /etc/cron.d/ |
| Auth log | ssh_tail |
file=/var/log/auth.log (or secure), follow=false |
| Syslog | ssh_tail |
file=/var/log/syslog (or messages), follow=false |
| Journalctl errors | ssh_execute |
journalctl -p err -n {N} --no-pager |
| Server alerts | ssh_alert_setup |
action: set (DESTRUCTIVE) / get / check |
| Custom command | ssh_execute |
any shell command |
Replace {N} with the user's log lines selection (20, 50, or 100).
Note: If a command requires root privileges and the SSH user is not root, use ssh_execute_sudo instead of ssh_execute. Common cases: ufw status, journalctl, openssl on /etc/letsencrypt/, systemctl on some systems.
ufw status verbose
journalctl -p err -n {N} --no-pager
openssl x509 -in /etc/letsencrypt/live/*/cert.pem -noout -dates -subject
nginx -t 2>&1
ls -la /etc/nginx/sites-enabled/
Use ssh_tail — not raw exec.
docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}'
docker info --format '{{.Containers}} containers, {{.Images}} images'
systemctl list-units --failed --no-pager
systemctl list-units --state=running --no-pager
ufw status verbose 2>/dev/null || iptables -L -n --line-numbers 2>/dev/null
command -v openssl >/dev/null 2>&1 && find /etc/letsencrypt/live/ -name 'cert.pem' -exec openssl x509 -in {} -noout -dates -subject \; 2>/dev/null || echo "openssl not found"
crontab -l 2>/dev/null; ls -la /etc/cron.d/ 2>/dev/null
journalctl -p err -n {N} --no-pager 2>/dev/null
ssh_list_servers returns only one server, ALWAYS ask the user to explicitly confirm the server name before proceeding. No auto-selection, no defaulting to the first result, no inferring from context. Every connection must be explicitly confirmed by the user.[DESTRUCTIVE]. No exceptions.ssh_history for in-session command recall AND maintain a local session log file (vps-session-{server}-{date}.txt) for export.ssh_list_servers — never ask the user to type host/port/user manually. If no servers are configured, tell the user to add them to ssh-manager config.ssh_db_query enforces this). MongoDB uses find() with collection parameter.ssh_tail with follow=false to get lines back directly. Use the grep param for filtering.ssh_health_check (structured JSON) over manual ssh_execute commands.ssh_service_status over manual systemctl status commands.ssh_monitor with the appropriate type over manual top/free/df commands.ssh_execute commands with user-provided input (file paths, grep patterns, command arguments), ALWAYS single-quote the interpolated values. Never concatenate raw user input into a shell command without quoting. Example: ssh_execute("tail -50 '/var/log/nginx/error.log'") not ssh_execute("tail -50 " + userInput). If the input contains single quotes, reject it and ask the user to provide a safe value..git folder first. Run a guard check (e.g., ssh_execute("test -d '{path}/.git' && echo ok || echo no")) and only proceed with the git command if the check returns ok. If .git is absent, report it to the user and abort the git operation. Never run git pull, git status, git log, git checkout, or any other git subcommand against a directory that is not a git repository.Errors come from tool responses, not raw SSH. The plugin handles ConnectTimeout, StrictHostKeyChecking, and most connection-layer issues internally.
| Tool response | Likely cause | Action |
|---|---|---|
server "{name}" not found |
Server name does not match ssh_list_servers output |
Show ssh_list_servers again; ask user to pick an exact name |
permission denied / auth error |
Auth type mismatch in server config | Ask user to verify auth method (key vs password) in their ssh-manager config |
connection refused / timeout |
Server unreachable, wrong host/port, or SSH daemon down | Report to user; suggest checking server availability independently |
command not found: {name} |
Remote tool not installed | Note it and continue with next step |
tool is blocked on readonly server |
Server in readonly/restricted mode | Inform user; skip destructive steps |
required parameter missing |
Bug or incomplete tool spec | Re-check parameters against tool spec; do not fall back to raw ssh_execute for destructive tools as it bypasses plugin safety guards |