SSH MCP server for AI agents — remote commands, file transfer, log search and server audits through OpenSSH, with destructive-command checks. Works with Claude Code, Codex, Gemini CLI and any MCP client.
This SSH MCP server demonstrates strong tool design with clear naming conventions (verb-based: ssh_audit_baseline, ssh_exec, ssh_file_read), comprehensive descriptions (averaging 180-220 chars per tool), and well-structured input schemas with type definitions and parameter descriptions. Most tools include enum constraints and validation guidance. However, there are gaps in output schema documentation, minimal error recovery guidance, and some parameter descriptions lack format/constraint clarity. The server follows good composition patterns (single responsibility per tool) and includes helpful risk annotations (READ_ONLY, WRITE, IRREVERSIBLE). Tool-to-tool chaining is well-supported through consistent profile parameter usage. Key weaknesses: ssh_file_write and some log tools lack detailed output schema documentation; error messages from the source code show limited recovery guidance; a few parameters (like 'top_n', 'unit') could benefit from stricter validation descriptions.
Reports how a machine is set up: sshd, firewall, pending updates, failed services, docker, listening ports and disk, each section marked CRITICAL, WARNING or OK. Reads only, in one round trip instead of a dozen commands. For load and health at this moment rather than settings, use ssh_snapshot.
Finds what filled a disk: free space per filesystem, the largest directories under each path given, and what docker, journald and package caches hold. Reads only, nothing is deleted. For how full the disks are at all, ssh_snapshot answers in one line.
Runs one command or a list of them on a server, each with its own exit code, stdout and stderr. Work measured in minutes belongs in detach, not in a longer timeout. Reach for it last — files, logs, transfers, health and jobs each have a tool that batches the round trips and parses the answer.
List files in a directory on the remote server
Read a file from the remote server
ssh_file_write lacks detailed description and output schema documentation. Description is only 'Write one or more files to a remote server' (48 chars), below the 80-char baseline for parameter-heavy tools. No documented return value structure (success/failure per file, bytes written, etc.).
ssh_log_tail and ssh_log_search have minimal descriptions (67-72 chars). ssh_log_tail description is 'Tail a log file on the remote server', does not explain return format, line ordering, or when to use it vs ssh_job_output. Missing guidance on context: 'For recent errors only, use this; for historical analysis, use ssh_log_search with a time window.'
Parameter descriptions lack explicit format/constraint guidance. 'path' appears in multiple tools (ssh_file_read, ssh_file_list, ssh_log_tail, ssh_log_search) with identical minimal descriptions. Should include: 'Absolute path (e.g. /var/log/syslog), relative paths not supported' or 'Glob patterns supported: *.log, /var/log/**/app.log'. Currently LLMs cannot validate format before sending.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-22 | C | 66 | 2025-06-18+ | v2 |
Write one or more files to a remote server
Stops a detached job and everything it started — the signal reaches the whole process group. A job that had already finished is reported as gone, not as a refusal.
Lists the detached jobs on a machine with their state, jobs started with sudo included — for when an id was not kept. Ids and states only; for what a job printed, use ssh_job_output.
Returns what a detached job has written so far, stdout and stderr together, from a byte offset you choose. For whether the job is still running rather than what it printed, ssh_job_status answers in one line.
Reports the state of a detached job, with the last lines it wrote so you can see where it got to. lost means no exit code was left behind, not that the work failed — ssh_job_output still has the output.
Search log files on the remote server
Tail a log file on the remote server
Reports one systemd unit: whether it is loaded, active and enabled, with the tail of its journal. A machine without systemd comes back as NOT CHECKED, never as a stopped service — that would read as an outage which is not there. For every failed unit at once, ssh_audit_baseline names them.
Checks the TLS certificate a domain serves — days left, whether the name matches a SAN, the issuer and whether renewal is configured — with the handshake made from the server itself, so it sees what that machine sees, including hosts closed to the outside. A null field means the check could not run, not that the certificate is bad. Run it per domain, once ssh_audit_baseline has named the sites.
Error handling and recovery guidance is implicit rather than explicit. Tool descriptions mention failures (e.g., ssh_job_status: 'lost means no exit code was left behind, not that the work failed') but do not guide LLM recovery actions. When a tool fails, there is no 'try this next' guidance in the output or description.
Output schemas are documented in code (audit-output.ts references show BASELINE_OUTPUT_SCHEMA, TLS_CHECK_OUTPUT_SCHEMA, etc.) but not visible in the tool definition source provided. From the code snippet, tool definitions reference external schema objects but do not inline schema details. This makes it impossible to verify output field names, types, and whether required chaining IDs are present.
ssh_file_write input schema description says 'Array of file objects to write' but does not specify the structure of each file object (required fields: path, contents? permissions? encoding?). LLMs cannot construct valid input without seeing the object schema.
Numeric parameter constraints are missing or vague. ssh_disk_breakdown 'top_n' has no min/max stated; ssh_service_status 'log_lines' defaults to 50 but no bounds documented; ssh_exec 'timeout' is 30000ms but no upper limit mentioned. Unbounded numbers risk LLMs passing absurd values (e.g. top_n=999999).
Tool annotations present (READS_REMOTE visible in audit-tool.ts) but incomplete coverage. Not all READ_ONLY tools are consistently annotated, and WRITE tools could benefit from more explicit destructiveHint annotations to guide LLM safety reasoning.