Persistent, stateful remote SSH sessions for AI agents via Model Context Protocol (MCP)
Strong tool definitions with comprehensive descriptions (avg 250+ chars), explicit input schemas with Zod validation, and tool annotations present. All 7 tools have clear verb-noun naming (ssh_*) and detailed parameter descriptions. Session ID validation via regex pattern enforces safety. However, output schemas are not formally documented in the code, responses are inferred from implementation rather than declared. Error handling guidance is embedded in descriptions but not structured as recovery patterns. Parameter descriptions are excellent but lack some edge-case guidance (e.g., what happens if a session is reaped mid-command).
Close the remote shell and SSH connection, clean its private temporary directory, and invalidate the id. Close a dirty session and call ssh_open for a clean environment.
List allowed OpenSSH Host aliases discovered from the local ssh_config (and explicit allowlist). Returns only safe connection metadata: alias, hostname, user, port, proxy_jump. Never returns private keys, IdentityFile paths, agent sockets, or ProxyCommand. Pass reload=true after editing ~/.ssh/config to re-parse without restarting the MCP server. Use an alias from this list with ssh_open.
Send Ctrl-C to the current foreground process group and wait for the shell protocol to recover. The session is kept only when recovery is confirmed. Returns nothing_to_interrupt when idle.
List all live sessions with host, cwd, state, last exit code, idle time, idle-reap countdown, and connection capacity. Use this to recover valid ids; never invent one.
Open a new persistent remote bash shell for an allowed ssh_config Host alias from ssh_hosts. Each call creates a clean session with a new id. Credentials come only from local OpenSSH configuration/agent; passwords and private keys are never accepted as arguments and must not be read from disk by the model.
Output schemas not formally documented. Tool descriptions explain what is returned (e.g., 'Returns status=running, cwd, last_exit, command_status, duration_ms, and latest lines'), but no JSON Schema or TypeScript interface is visible in the source. LLMs cannot reliably parse unstructured responses or plan downstream tool calls without explicit output type definitions.
ssh_list description is generic ('List all live sessions with host, cwd, state, last exit code, idle time, idle-reap countdown, and connection capacity'). Does not explain when to call it, what 'idle-reap countdown' means, or how to interpret 'connection capacity'. Lacks actionable guidance for LLM decision-making.
Error handling is implicit in descriptions but not structured. No explicit error classification (retryable vs. fatal) or recovery guidance in tool definitions. E.g., ssh_run says 'do not retry a running command' but does not define what error codes or response fields indicate a retryable state vs. a permanent failure.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 65 | 2025-06-18+ | v2 |
Observe the current foreground command and its latest output without starting another command. When status=running, optional wait_sec (default 0) long-polls this MCP call until the command finishes or the wait expires—prefer a positive wait_sec over busy-looping. wait_sec never stops the remote command. Returns the newest lines in chronological order, limited independently for stdout and stderr; lines defaults to 50 and is capped to protect model context. Use after ssh_run returns status=running. When idle, returns immediately with cwd, last_exit, command_status, duration_ms, and the latest lines from the completed command. command_status describes the command outcome separately from shell status. If the session closes during a waiting peek, the response retains the final output and failure details; do not blindly retry commands with possible side effects.
Start a non-interactive command in the same persistent shell identified by id. cwd and environment changes persist. The call waits at most wait_sec (default 10 seconds); if the command is still active it returns status=running without stopping it. Commands have no automatic execution timeout by default. Only an explicitly provided timeout_sec sends Ctrl-C at that deadline. Never retry a running command: poll with ssh_peek(wait_sec=...) so the MCP call blocks until idle or the wait expires; do not spam peeks with wait_sec=0. Call ssh_interrupt to stop it, or open another session for concurrent work. Only one foreground command may run per id. Do not use vim, top, password prompts, or other interactive TUI/input flows.
Parameter 'lines' in ssh_peek has max=1000 but description says 'defaults to 50 and is capped to protect model context'. Does not state the actual cap (1000) or explain why 1000 is safe. Inconsistency between schema constraint and description guidance.
ssh_open parameter 'name' is optional but its purpose is unclear. Description does not explain what 'session name' is used for (display only? lookup key? logging?). If it is purely cosmetic, this should be stated; if it affects behavior, the impact must be documented.