SSH and SFTP MCP server for remote command execution, file operations, and host key management
Strong tool naming (verb_noun pattern throughout) and comprehensive descriptions (avg 180 chars). All 14 tools have clear, actionable descriptions explaining WHAT, WHEN, and consequences. Input schemas present with typed parameters and descriptions. However, output schemas are not explicitly documented in the visible code, responses are marshaled to JSON but structure is inferred rather than declared. Error handling is present but generic (toolErr pattern). Missing tool annotations (readOnlyHint, destructiveHint) despite clear risk classifications. ssh_session_send has complex parameter interactions (raw vs wait_idle_ms mutually exclusive) documented in description but not in schema constraints.
Access embedded skill guidance docs. Use action=list to see available skills, action=read with skill=<slug> to fetch a specific doc. Prefer reading skill:// resources directly if your client supports them.
List the contents of a directory on a remote host via SFTP.
Read a file from a remote host via SFTP. Returns UTF-8 text; binary files are base64-encoded with binary=true in the output.
Write content to a file on a remote host via SFTP. Pass binary=true and base64-encode binary content. Use mode (octal string, e.g. '0644') to set permissions.
Inspect or re-trust a changed SSH host key after a server rebuild. Call without confirm to preview the current and new key fingerprints. Call with confirm=true and expected_fingerprint=<new_fingerprint> to rewrite the known_hosts entry. Requires allow_known_hosts_update: true on the host. Only use after a human has verified the new fingerprint OUT-OF-BAND — e.g. against the hosting provider's console, a config-management record, or a call to whoever rebuilt the host. This tool's own preview scan cannot detect a man-in-the-middle: it is a plain, unverified connection by design (there is no stored key to check against yet), so a network attacker positioned during the scan could present a forged key that looks self-consistent across repeated previews. Do not treat a human simply clicking 'confirm' as sufficient — the fingerprint must have been checked against an independent source first.
Output schemas not explicitly documented. Responses are JSON-marshaled but structure is inferred from code rather than declared in tool definitions. LLMs cannot reliably predict response fields.
Tool annotations missing. ssh_session_send, sftp_write, ssh_job_cancel, ssh_accept_host_key are marked WRITE/destructive but lack readOnlyHint/destructiveHint annotations in schema.
Parameter constraints not formalized in schema. ssh_session_send documents 'raw' and 'wait_idle_ms' as mutually exclusive in description only; should be enforced via oneOf or explicit validation with clear error messages.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 61 | 2025-06-18+ | v2 |
Return what rootcanal can do on a specific host: SSH, SFTP, allowed SFTP path prefixes, session idle timeout, and terminal/output settings.
Cancel a running detached job by job_id. Sends SIGTERM to the remote process.
Poll the status and output tail of a detached job started with ssh_run_once(detach=true). Returns running state, elapsed time, exit code, and stdout/stderr tails.
List all pre-declared SSH hosts with their non-sensitive metadata (name, address, user, auth type, SFTP status). Credentials and key paths are never included.
Execute a single command on a pre-declared host via SSH exec channel (no PTY). Returns stdout, stderr, and exit_code. Use detach=true to run in background and get a job_id. Use this for one-shot reads (df, ls, docker inspect, cat) instead of open/send/close. Requires a POSIX-compatible remote shell.
Close an open shell session and release its resources.
List all currently open shell sessions with their host and timing metadata.
Open a persistent interactive shell session on a pre-declared host. Returns a session_id for use with ssh_session_send and ssh_session_close.
Write input to an open shell session's stdin and wait for the command to complete (marker-based). Send empty input to continue waiting for an in-flight command. Use wait_idle_ms for raw/REPL mode.
Error handling is generic. toolErr() pattern returns errors without recovery guidance. E.g., 'host not found' should suggest 'Call ssh_list_hosts() to see available hosts.'
get_skill tool naming is generic. 'get_skill' with action=list|read is overloaded. Consider split_tools: list_skills (returns all slugs) and read_skill (fetches body by slug) for clarity.