Skip to main content
Glama
cygnussystems

cygnus-ssh-mcp

Official

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault

No arguments

Instructions

Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.

This server publishes no instructions, or was last inspected before Glama recorded them.

Capabilities

Features and capabilities supported by this server

Protocol revision2025-11-25

CapabilityDetails
tools
{
  "listChanged": true
}
logging
{}
prompts
{
  "listChanged": false
}
resources
{
  "subscribe": false,
  "listChanged": false
}
extensions
{
  "io.modelcontextprotocol/ui": {}
}
experimental
{}

Tools

Functions exposed to the LLM to take actions

NameDescription
list_toolsA

Retrieves a list of all available tools on this MCP server, along with their descriptions.

Returns: A list of dictionaries, where each dictionary contains the 'name' and 'description' of an available tool.

ssh_conn_is_connectedA

Check whether there is a WORKING SSH connection - a real round trip to the host (a few milliseconds normally, at most ~5s), not just a cached flag. A connection that died while idle (e.g. dropped by a firewall overnight) is detected, dropped, and reported as False.

Returns: bool: True if the connection works, False otherwise - then reconnect with ssh_conn_connect(host_name=...).

ssh_conn_connectA

Establish an SSH connection using a pre-configured host. The host can be specified by its 'user@hostname' key or by its alias.

Beyond the three fully-supported platforms (Linux, macOS, Windows), any other SSH target that responds to a basic shell command connects too, reported as os_type='flex' (e.g. FreeBSD/OPNsense/pfSense-style routers, or other unrecognized POSIX kernels). For 'linux' and 'flex' connections, a one-time capability probe checks the specific shell/coreutils features this server depends on (some embedded/BusyBox-based devices lack GNU extensions like find -printf or tar --strip-components) - the result is returned as 'capabilities' (raw probe results) and, if anything's missing, 'capability_warnings' (plain-English gaps). Tools that need a missing capability fail with a clear error naming what's missing, rather than a cryptic remote command failure - nothing is silently degraded.

Returns: Dictionary with connection status and detailed system information. 'capabilities'/'capability_warnings' are included for 'linux'/'flex' connections only (empty/omitted for macOS/Windows, which are already fully supported and not probed). Two different "os_version" fields: connection.os_version is a short platform/distro identifier used internally (e.g. 'debian', 'centos', 'windows_server_2016', or 'unknown_linux'/'unknown_windows' if not recognized; None on macOS), while system.os_version is the human-readable version string reported by the OS itself (e.g. '12 (bookworm)', '14.8.9').

Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_conn_add_hostA

Add a new host configuration to the host configuration TOML file. Despite the name, this does NOT update an existing entry - if the user@host key already exists, this returns an error response ({'status': 'error', ...}, not a raised exception) rather than overwriting it; use ssh_host_update for that instead.

Before calling this, check 'ssh_host_list' - the host you want may already be configured, in which case you can call 'ssh_conn_connect' directly without adding anything.

Authentication requires either a password OR a keyfile (or both):

  • Password authentication: Provide password

  • Key-based authentication: Provide keyfile (and optionally key_passphrase if the key is encrypted)

If using key-only authentication and sudo operations are needed, you must explicitly provide sudo_password unless the server has passwordless sudo configured.

Warn the user that credentials will be visible to the LLM and that it would be better for the user to add the host directly in the host configuration file. That said, never read this file yourself (directly or via any file/shell tool) to look up, verify, or copy existing hosts' credentials - ssh_host_list, ssh_conn_add_host, ssh_host_update, ssh_host_remove, and ssh_host_use_config are the only tools you should need for host management, and none of them ever expose a stored password/passphrase back to you. This tool always adds to whichever config file is currently active (the server's default unless ssh_host_use_config was called to switch to an alternate one - check ssh_host_list's config_path if unsure).

The host config file is ~/.mcp_ssh_hosts.toml if it exists, otherwise ./mcp_ssh_hosts.toml in the server's working directory - it stores every host's password, sudo password, and key passphrase in plaintext, which is exactly why the tools above exist instead of editing it by hand. The configuration is stored under a ["user@host"] key.

Optional fields:

  • alias: A short name for connecting (e.g., 'prod' instead of 'deploy@production.example.com')

  • description: A text description of what the host is for

Returns: On success: {'status': 'success', 'message', 'key', 'host', 'user', 'port', 'auth_method' ('key' or 'password'), and 'alias'/'description'/'keyfile' if provided}. On failure (missing auth, duplicate host key, or duplicate alias): {'status': 'error', 'error': <message>} - for a duplicate host key, also includes 'existing_config' (the current host/user/port/alias/description) so you can decide whether to use it as-is via ssh_conn_connect instead.

ssh_host_updateA

Update one or more fields of an existing host configuration - this is the safe way to rotate a password, change a port, or adjust other settings without ever needing to read or hand-edit the host configuration TOML file (which stores every host's credentials in plaintext - never read it directly; this tool, ssh_conn_add_host, ssh_host_remove, and ssh_host_list cover everything you should need).

Only the fields you pass are changed - any parameter left at its default (omitted/None) keeps its current value. To clear a field entirely (e.g. drop a password when switching a host to key-only auth), pass an empty string "" rather than omitting it. user/host themselves can't be changed this way (that changes the 'user@host' key identity) - remove and re-add instead if you need that.

Prefer this over ssh_host_remove + ssh_conn_add_host for adjusting an existing host: remove+re-add loses every field you don't explicitly resupply, since ssh_conn_add_host has no knowledge of the entry it just deleted.

host_name may be either the 'user@hostname' key or a configured alias - resolved the same way as ssh_conn_connect.

Warn the user that any new password/passphrase value passed here will be visible to the LLM, the same caveat as ssh_conn_add_host.

Returns: On success: {'status': 'success', 'message', 'key', 'updated_fields' (list of field names that were actually changed)}. On failure (the update would leave neither a password nor a keyfile set, or a duplicate alias): {'status': 'error', 'error': <message>} - not a raised exception.

Raises: SshError: If host_name doesn't resolve to any configured host (tried as both key and alias).

ssh_conn_statusA

Get essential SSH connection status information.

Returns: Dictionary containing basic connection status (user, working directory, OS type)

Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_conn_host_infoA

Get detailed SSH connection status and system information.

Returns: Dictionary containing full connection status and detailed system info including hardware, memory, disk usage, and more. For a 'linux' or 'flex' (non-Linux/macOS/Windows) connection, also includes 'capabilities' (probed GNU/BusyBox-coreutils feature support) and 'capability_warnings' (plain-English list of confirmed gaps) - see ssh_conn_connect's docstring for what these mean.

Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_host_use_configA

Switch which host configuration file ssh_host_list, ssh_conn_connect, ssh_conn_add_host, ssh_host_update, and ssh_host_remove all operate against, for the rest of this session (or until you call this again) - not just for one call. This is the same "one active thing at a time" model ssh_conn_connect uses for SSH connections, applied to host configuration files instead; the two are completely independent (switching config files doesn't affect any current SSH connection, and vice versa).

Use this when you want to browse or use hosts from a different TOML file than the server's default - e.g. a separate list of hosts for a different environment/project. The alternate file must already exist and be a valid host configuration TOML file (see ssh_conn_add_host's docstring for the format) - this deliberately does NOT auto-create a missing file the way the server's own default config file is created on first run, since an LLM-supplied path with a typo should fail loudly rather than silently create a stray file somewhere.

Omit config_path (or pass "") to switch back to the server's original default configuration file.

ssh_host_list's response always includes config_path showing whichever file is currently active, so you can check before mutating anything with ssh_conn_add_host/ssh_host_update/ssh_host_remove.

Returns: On success: {'status': 'success', 'message', 'config_path', 'is_default' (bool), 'host_count'}. On failure (path doesn't exist, path is a directory, or the file fails to parse as valid host config TOML): {'status': 'error', 'error': <message>} - not a raised exception.

ssh_host_listA

List all configured SSH hosts with their aliases and descriptions. This is the ONLY correct way to see what hosts are configured - never read the host configuration TOML file directly (ssh_conn_add_host's docstring names its path). That file stores every host's password, sudo password, and key passphrase in plaintext; this tool deliberately omits all of that and returns only the fields below. If you need to add, change, or remove a host, use ssh_conn_add_host, ssh_host_update, or ssh_host_remove instead of editing the file - between those and ssh_host_use_config, there is no legitimate reason to open it.

Lists hosts from whichever config file is currently active - the server's default unless ssh_host_use_config was called to switch to an alternate file. The returned config_path always shows which one that is.

Returns: Dictionary with: - hosts: List of host information dictionaries, each containing: - key: The 'user@host' key - alias: Optional short name for the host - description: Optional description of the host - config_path: The host configuration file this list came from

ssh_host_removeA

Remove a host configuration from the host configuration TOML file (see ssh_conn_add_host's docstring for its exact path and why you should never read it directly - use ssh_host_list to see what's configured instead).

To change a host's password/port/etc. rather than deleting it, use ssh_host_update instead - removing and re-adding loses every field you don't explicitly resupply.

Returns: Dictionary with operation status

ssh_host_disconnectA

Disconnect the current SSH connection if one exists.

Use this when you want to explicitly close the current SSH connection before connecting to a different host or when you're done with SSH operations.

Returns: Dictionary with disconnection status

Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_conn_verify_sudoA

Check whether elevated access is available on the remote system, without running any privileged command. Call this before using use_sudo=True on other tools if you're not sure elevation will succeed.

On Linux/macOS: probes whether sudo is available and whether it needs a password (via a passwordless sudo -n check, then a password-based check if that fails). Tools called afterward with use_sudo=True will use whichever mode this detected.

On Windows: there is no per-command elevation - checks whether the current SSH session itself is already running as Administrator. If it's not, no tool call can become elevated; you must reconnect as an Administrator account instead.

Returns: Dictionary with: - available (bool): True if sudo/elevation can be used at all (passwordless OR password-based on Linux/macOS; same as passwordless on Windows, since there's no separate password-based mode there) - passwordless (bool): True if no password is needed (passwordless sudo, or an already-elevated Windows session) - requires_password (bool): True if sudo works but needs a password (always False on Windows)

Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_task_statusA

Check the liveness of a background task by PID (from ssh_task_launch, or any other real remote PID - e.g. the pid field from ssh_cmd_run). This does a live check on the remote host every call, not a cached lookup, so it's safe to call repeatedly.

Returns: {'pid', 'status', 'running' (bool, True iff status=='running'), 'timestamp'}. status is one of: - 'running': the process currently exists. - 'exited': the process is gone - it either completed or was killed; there's no way to distinguish which, or recover its exit code, from this tool alone. - 'invalid': the given pid isn't a valid positive integer. - 'error': the liveness check itself failed (e.g. connection issue) - this does NOT mean the process exited, just that its status is unknown.

ssh_task_killA

Terminate a background task (launched via ssh_task_launch, or any other real remote PID) by sending a signal to its PID.

If force=True and the process doesn't exit after wait_seconds, it will be forcibly killed with SIGKILL (signal 9).

With use_sudo=True on a 'linux'/'flex' connection with a BusyBox-style ps that doesn't support -o pgid= (check ssh_conn_connect/ ssh_conn_host_info's capabilities), this raises a clear error instead of running - there is no clean fallback, since killing just the captured PID (rather than its whole process group) can leave the sudo'd command's real child process(es) running as orphans, exactly the failure mode this check exists to prevent. Not an issue with use_sudo=False.

Returns: {'pid', 'result', 'signal', 'force_kill_used', 'timestamp'}. result is one of: - 'killed': confirmed terminated (by the initial signal or the force-kill fallback - see force_kill_used to tell which). Terminal - nothing left to check. - 'already_exited': the process was already gone before any signal was sent. Terminal. - 'failed_to_kill': still running after both the signal and force-kill attempt (or after the signal alone, if force=False). Not terminal - the process is still alive; consider retrying or investigating why it won't die. - 'invalid_pid': pid wasn't a valid positive integer - no signal was sent. - 'error': the kill attempt itself failed unexpectedly (e.g. connection issue) - the process's actual state is unknown, not necessarily still running.

`force_kill_used` is True iff the SIGKILL fallback was actually attempted
(the initial signal alone was not enough to end the process), regardless of
whether the fallback itself succeeded - False if the initial signal alone
was sufficient, the process was already gone, `pid` was invalid, or
`force=False` so no fallback was ever attempted.
ssh_cmd_runA

Execute a command on the remote host and BLOCK until it completes, an io_timeout (silence), a wait_timeout (elapsed cap), or a runtime_timeout (hard cap) occurs.

Timeout semantics (read this before choosing values):

  • io_timeout and wait_timeout firing do NOT mean the remote command stopped - it genuinely keeps running. Monitoring is handed off to a background thread so output/exit code continue to be collected; the response has status='io_timeout'/'wait_timeout', still_running=true, and an id/pid you can use with ssh_cmd_check_status(handle_id=...) to poll again later, or ssh_cmd_output(handle_id=...) to read output collected so far (including output produced after this call returned). Do not rerun the command. You can also still decide to end it early with ssh_cmd_kill(handle_id=...) at any point after either of these fires.

  • io_timeout fires only on SILENCE (no output for N seconds) - a command that keeps producing output never triggers it, however long it runs.

  • wait_timeout fires after N seconds of TOTAL elapsed wait, regardless of activity - use this if you want to check in periodically on a long-running command even while it's actively producing output, rather than being blocked until completion.

  • runtime_timeout is the only knob that DOES attempt to terminate the remote command - a hard safety ceiling, not a UX mechanism. Set it generously (much longer than the command should ever realistically take).

  • A single call never blocks longer than the server's wait cap (default 50s, configurable via --max-wait / MCP_SSH_MAX_WAIT), because most MCP clients abort a tool call at ~60s and the id/pid handoff would be lost. Longer waits are clamped: the response is a normal status='wait_timeout' handoff with wait_capped=true. For anything that may take more than about a minute, ssh_task_launch is usually the better tool.

  • For commands that should survive you disconnecting/reconnecting entirely (not just this call returning), use ssh_task_launch instead - it runs fully detached from this SSH session, whereas a command started here (even after surviving io_timeout/wait_timeout) is still tied to the current connection's lifetime.

You can access command history using 'ssh_cmd_history' to see previous commands and their output. Commands run this way always appear there with origin='user' - pass include_internal=False to ssh_cmd_history to hide unrelated internal plumbing from other tools (e.g. ssh_file_write's sudo dance).

Windows targets: commands run under cmd.exe (CMD syntax), not PowerShell. For PowerShell, run it explicitly: powershell -NoProfile -Command "...".

Working directory: each call is an independent remote process (like a GitHub Actions step or Ansible task, not a continuous shell) - nothing is remembered between calls, including 'cd'. Running ssh_cmd_run("cd /var/log") does NOT affect a later ssh_cmd_run("ls"); it will still list the login directory. Use absolute paths, chain "cd dir && command" within one call, or pass the cwd parameter to run this specific call in a specific directory (Linux/macOS only for now).

Returns: Dictionary containing command output, status, and metadata. The handle identifier is returned here as 'id', but every other tool that accepts it (ssh_cmd_check_status, ssh_cmd_kill, ssh_cmd_output) names the same parameter handle_id - pass this value there. output (stdout) and stderr are always two SEPARATE fields, never interleaved into one combined stream - a command that succeeds can still have written to stderr (warnings, progress meters, non-fatal messages), so check stderr even on status='success'.

Each stream is returned inline up to its most recent ~32 KB. `output_truncated`
and `stderr_truncated` are always present; when true, the response also has
line counts and an `output_note` saying how to read the rest with
ssh_cmd_output(handle_id=..., start_line=...), or that the earliest lines were
dropped (the server keeps ~2 MB per stream per command). For very large output,
redirect it to a file and read that instead.

`status` is one of:
- 'success': command completed with exit code 0. `exit_code`, `output`, `stderr` populated.
- 'command_failed': completed with a non-zero exit code. `exit_code`, `output`, `stderr` populated.
- 'cwd_not_found': the `cwd` parameter didn't exist on the remote host - the
  command was NOT executed at all (fails closed).
- 'io_timeout': no output within `io_timeout` seconds - remote command was NOT
  killed, still_running=true. Poll with ssh_cmd_check_status(handle_id=...).
- 'wait_timeout': `wait_timeout` elapsed regardless of activity - remote command was
  NOT killed, still_running=true. Poll with ssh_cmd_check_status(handle_id=...).
- 'runtime_timeout': `runtime_timeout` exceeded - an attempt was made to kill
  the remote command (see ssh_cmd_check_status's `'killed'` status to confirm).
- 'sudo_required': `use_sudo=True` but elevation isn't available (see
  ssh_conn_verify_sudo before retrying).
- 'busy': another ssh_cmd_run is still WAITING in the foreground on this
  connection - only one call can wait at a time. A command that was already
  handed off (io_timeout/wait_timeout) keeps running in the background and does
  NOT block new ssh_cmd_run calls; several remote commands can be running at once.
- 'error': unexpected failure (e.g. connection dropped).
ssh_cmd_killA

Terminate a currently running command by its handle ID (the handle_id field - also returned as id - from ssh_cmd_run's response).

This tool is specifically for killing commands started with ssh_cmd_run - it looks up handle_id in this connection's command history, not by raw PID. For background tasks launched with ssh_task_launch, use ssh_task_kill with the PID instead. Raises an error if handle_id doesn't exist in history (e.g. from a previous connection - handles don't survive reconnects) or has no associated PID.

If force=True and the process doesn't exit after wait_seconds, it will be forcibly killed with SIGKILL (signal 9).

Returns: {'handle_id', 'pid', 'result', 'signal', 'force_kill_used', 'timestamp'}. result is one of: - 'not_running': the command was already confirmed not running before any signal was sent (checked first, via a live PID status check) - nothing to do. - 'killed': confirmed terminated (by the signal or the force-kill fallback - see force_kill_used to tell which). - 'already_exited': the process was gone by the time the signal landed. - 'failed_to_kill': still running after the signal (and force-kill, if force=True) - not terminal, the process is still alive. - 'invalid_pid': the command's tracked PID wasn't a valid positive integer. - 'error': the kill attempt itself failed unexpectedly - the process's real state is unknown.

`force_kill_used` is True iff the SIGKILL fallback was actually attempted
(the initial signal alone was not enough), regardless of whether the
fallback itself succeeded - False if the initial signal alone was
sufficient, the process was already gone, or `force=False`.

Note: after a successful kill (`'killed'` or `'already_exited'`, or the early
`'not_running'` case), a later `ssh_cmd_check_status` call for this same
`handle_id` will report status `'killed'`.
ssh_cmd_check_statusA

Wait for the specified duration, then check the status of a command started with ssh_cmd_run. Call this repeatedly - it's designed to be polled - after ssh_cmd_run returns status='io_timeout' or 'wait_timeout' (the remote command is still genuinely running in both cases - background monitoring keeps collecting its output/exit code), until you get a TERMINAL status below. Do not rerun the original command while polling.

Terminal status values (nothing left to wait for, stop polling):

  • 'completed': confirmed finished, exit_code is populated - including for commands that survived an io_timeout/wait_timeout, since background monitoring keeps watching for the real exit code.

  • 'killed': the remote process was confirmed terminated (e.g. runtime_timeout killed it, or a prior ssh_cmd_kill call found it already gone) - exit_code is not known.

  • 'completed_exit_code_unknown': rare fallback - monitoring stopped without a confirmed exit code (should only really happen from before background monitoring existed, or after an unexpected error) and a live check now confirms the remote process is no longer running. Only its output (via ssh_cmd_output) is available, not its exit code.

Non-terminal status values (keep polling):

  • 'running': still being actively monitored, not yet finished.

  • 'unknown_still_running': rare fallback (same caveat as 'completed_exit_code_unknown') where a live check confirms the remote command is still actually running. Not a failure - call this tool again to keep checking.

Other:

  • 'not_found': the handle_id doesn't exist (may be from a previous connection - handles don't survive reconnects, but background task PIDs from ssh_task_launch do).

  • 'unknown': the handle exists but its metadata is missing from history (rare, internal inconsistency) - treat like 'not_found'.

Fallback behavior: if handle_id doesn't match any ssh_cmd_run handle, this tool also tries treating it as a raw background-task PID (same as ssh_task_status) and, if that succeeds, returns {'pid', 'status': 'running'/'exited'/'invalid'/'error', 'is_background_task': True, ...} instead - a DIFFERENT status vocabulary than the one above. Prefer ssh_task_status directly for PIDs to avoid ambiguity.

Returns: {'handle_id', 'waited_seconds', 'status', 'exit_code', 'pid', 'output_available', 'output_lines', 'timestamp'}, plus 'next_step' (guidance text) when status is non-terminal.

For an OPERATION handle (the handle_id of an 'in_progress' response from a
long-running tool such as ssh_archive_extract or ssh_file_transfer): returns
`{'handle_id', 'status', 'tool', 'operation', 'started', 'ended', ...}` with status
'running', 'completed' (plus 'result': the tool's full normal result) or 'failed'
(plus 'error'). The wait ends early as soon as the operation finishes. While
running it also returns 'elapsed_seconds' and 'progress': the current 'stage'
(e.g. 'uploading archive', 'extracting on host'), 'bytes_done'/'bytes_total'/
'percent' for transfers, counts such as 'files_searched', and 'last_update' /
'seconds_since_update' - if those stop advancing for a long time, the operation
may be stuck rather than just slow.
ssh_cmd_outputA

Retrieve captured output from a command started with ssh_cmd_run, identified by its handle_id. Useful after an io_timeout/wait_timeout to see progress so far - including output produced after that call returned, since background monitoring keeps collecting it - to page through output a response didn't include in full (ssh_cmd_run returns at most the last ~32 KB of each stream inline and says so via output_truncated/output_note), or to re-inspect an earlier command's output without rerunning it.

stdout and stderr are captured in separate buffers, not interleaved - stream picks which one to retrieve.

The server keeps up to ~2 MB of each stream per command; beyond that the EARLIEST lines are dropped. Asking for a dropped line with start_line gives an error naming the first line still available. For very large output, redirect it to a file (or use ssh_task_launch) and read that instead.

Raises an error if handle_id doesn't exist (e.g. from a previous connection - handles don't survive reconnects).

Returns: A plain list of output lines from the selected stream - not a dict. For total line counts and whether anything was dropped, use ssh_cmd_check_status (output_lines) or ssh_cmd_history.

ssh_cmd_clear_historyA

Clear the command history for the current SSH connection, including finished long-running operations (ones still running stay, so they can still be polled).

Returns: Dictionary with operation status

ssh_cmd_historyB

Retrieve command execution history with optional output snippets.

Returns: List of dictionaries containing command history, ordered from oldest to newest by default. Each entry contains: - id: Command handle ID - command: Executed command - exit_code: Exit status - start_time: Execution start timestamp - end_time: Execution end timestamp - origin: 'user' for a directly user-requested command, or an internal-plumbing label ('tool_internal', 'connection_probe', 'sudo_probe') for a helper command issued by another tool - parent_tool: Name of the MCP tool that triggered this command, when origin != 'user' - output: Stdout snippet (if include_output=True) - stderr is NOT included here, even for a failed command; see the separate 'stderr' field - stderr: Stderr snippet (if include_output=True) - often the more useful stream for a failed command; retrieved the same way ssh_cmd_output(stream='stderr') would

ssh_task_launchA

Launch a command in the background and return its PID immediately, without waiting for it to complete.

Prefer this over ssh_cmd_run for commands that will take a long time or may be quiet for extended periods: package installs, container/image pulls, large downloads, backups, compilation. It avoids holding a blocking tool call open and the ambiguity of io_timeout - the PID it returns survives reconnects and can be checked anytime with ssh_task_status(pid), and stdout_log/stderr_log can be read with ssh_file_read while the task is still running.

Output is redirected to files (see stdout_log/stderr_log), not captured in memory - read the log files to see progress or final output.

Windows targets: the command runs under cmd.exe (CMD syntax); for PowerShell use powershell -NoProfile -Command "...".

On Linux/macOS the launch FAILS with an error (nothing is started) if a log file can't be created, or, with use_sudo, if sudo itself fails - so a returned PID always means the task was really started. With use_sudo, logs in a directory only root can write are created and written as root.

Returns: {'command', 'pid', 'start_time', 'stdout_log', 'stderr_log'}. Both are the paths the output really goes to on the remote host (read them with ssh_file_read), or None for a stream that's discarded - both are None if log_output=False, and stdout_log is None if you passed only stderr_log. With neither passed, the default is /tmp/task-<pid>.log on Linux/macOS (both streams in one file), and C:\Windows\Temp\task-<pid>.log plus task-<pid>_err.log on Windows.

Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_dir_mkdirA

Create a directory on the remote system.

Parent-directory creation and mode behavior differ by platform/sudo:

  • Linux/macOS, use_sudo=False (default): uses SFTP mkdir, which is NOT recursive - this FAILS if the parent directory doesn't already exist. mode applies to the created directory.

  • Linux/macOS, use_sudo=True: uses mkdir -p -m <mode>, which DOES create any missing parent directories.

  • Windows: always creates missing parent directories (New-Item -Force, regardless of use_sudo, which has no effect on Windows anyway). The mode parameter is IGNORED entirely on Windows - there's no equivalent to Unix octal permissions there.

Returns: {'status': 'success', 'path', 'mode' (octal string - reflects the requested mode even on Windows, where it was actually ignored), 'message', 'connection'} on success. Raises an exception on failure (e.g. missing parent directory without sudo on Linux/macOS).

Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_dir_removeA

Remove a directory on the remote system - this is the simple rmdir/Remove-Item equivalent. There is NO dry-run/preview mode here (unlike ssh_dir_delete below) - it acts immediately. If recursive=False (default) and the directory is not empty, this RAISES an exception rather than returning an error dict - it does not partially delete anything.

For a safer recursive delete with a preview step, use ssh_dir_delete instead, which defaults to dry_run=True and returns a graceful {'status': 'error', ...} on failure instead of raising.

Returns: {'status': 'success', 'path', 'recursive', 'message', 'connection'} on success. Raises an exception on failure (non-empty directory with recursive=False, path not found, permission denied, etc.) rather than returning an error dict.

Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_dir_list_files_basicA

List the immediate contents of a directory (via SFTP - not recursive, no metadata). For recursive listing with size/permissions/type/etc., or to filter by filename pattern, use ssh_dir_list_advanced or ssh_dir_search_glob instead.

Returns: List of bare filenames (strings) directly inside path - not full paths, not recursive, and no indication of which entries are files vs. directories (use ssh_file_stat on an entry, or ssh_dir_list_advanced, if you need that).

Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_file_statA

Get status information about a file or directory (via SFTP stat - works the same way on all platforms, no shell command involved).

On Windows, mode/uid/gid come from the SFTP subsystem's own cross-platform attribute reporting, not real Windows ACLs/ownership - Windows has no equivalent concept, so these values (e.g. uid/gid of 0) are not meaningful there and should not be relied on to reason about actual Windows permissions/ownership. type/size/atime/mtime are unaffected and accurate on all platforms.

Returns: {'exists': True, 'path', 'type' ('file'/'directory'/'symlink'/'unknown'), 'mode' (octal string, e.g. "0o40755"), 'uid', 'gid', 'size' (bytes), 'atime', 'mtime'} when the path exists. atime/mtime are raw numeric Unix timestamps (seconds since epoch, as returned by SFTP), not formatted date strings - convert with datetime.fromtimestamp() if you need a readable date. If the path does NOT exist (or stat failed for another reason, e.g. permission denied), returns {'exists': False, 'path', 'error'} instead - this is a normal, non-exceptional return value, not a raised error. If the check itself fails (permission denied, no SFTP on this host, ...), exists is None (unknown), not False, with the reason in error.

Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_file_readA

Read file contents directly via SFTP.

This tool reads raw bytes from the remote file using SFTP and decodes them on the client side. Unlike command-based file reading (cat, Get-Content), SFTP completely bypasses shell and console encoding issues.

Why use this instead of ssh_cmd_run with cat/Get-Content?

  • Works correctly with Unicode on ALL platforms including Windows

  • Bypasses Windows PowerShell's OEM code page encoding problem

  • More efficient for binary-safe file transfer

  • No shell escaping issues with special characters in content

Returns: Dictionary with: - success: True if file was read successfully - content: The file contents as a string - size: Number of bytes read - encoding: The encoding used to decode the content

Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_file_find_lines_with_patternA

Search for a pattern in a remote file and return matching lines with their line numbers. Use this to find WHERE a pattern occurs before using ssh_file_get_context_around_line to see surrounding lines, or ssh_file_replace_line/ssh_file_delete_line_by_content to edit (those require an exact, unique line match - this tool helps you find that exact line first).

Regex flavor differs by platform when regex=True: POSIX extended regex (grep -E) on Linux/macOS - avoid PCRE-only syntax like \d, use [0-9] or [[:digit:]] instead; Python's re module on Windows (matched locally after an SFTP read, not via PowerShell - see below). When regex=False (default), the pattern is matched as a literal fixed string on every platform.

On Windows, this reads the whole file via SFTP and matches locally in Python, rather than shelling out to PowerShell/Select-String - matched line content could otherwise come back corrupted for non-ASCII text, since Windows' console encodes stdout in its OEM code page rather than UTF-8 (the same problem ssh_file_read's SFTP approach avoids).

Returns: {'total_matches': int, 'matches': [{'line_number': int, 'content': str}, ...]}. No matches is not an error - total_matches is 0 and matches is []. On failure (e.g. file not found, permission denied), an 'error' key is present instead.

Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_file_get_context_around_lineA

Get lines before and after a line, to see it in context before editing. Like the line-editing tools (ssh_file_replace_line and siblings), match_line must match exactly one line in the file (whitespace-trimmed, literal text, not a pattern) - use ssh_file_find_lines_with_pattern first if you're not sure the line is unique.

Reads the file via SFTP and matches locally (same as ssh_file_find_lines_with_pattern) - Unicode/non-ASCII content is safe on all platforms, including Windows.

Returns: On a unique match: {'match_found': True, 'match_line_number': int, 'context_block': [{'line_number': int, 'content': str}, ...]} (the matched line plus context lines before/after). If the line isn't found, or matches more than once: {'match_found': False, 'error': str} - for a multi-match error, also includes 'matches' (every matching line, so you can pick a more specific match_line).

Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_file_replace_lineA

Replace a line in a file with a new line. match_line must match EXACTLY ONE line in the file (whitespace-trimmed, literal text - not a pattern); if it matches zero lines or more than one, the operation fails with a descriptive error rather than guessing which line you meant. Use ssh_file_find_lines_with_pattern first if you're not sure the line is unique.

PARAMETERS:

  • file_path: Path to the file to modify

  • match_line: Exact line content to match and replace (whitespace-trimmed)

  • new_line: New line to insert in place of the match

  • use_sudo: Use sudo for the operation (default: false)

  • force: No longer needed (kept for compatibility) - use_sudo reads root-only files with sudo (default: false)

RETURNS: On success: {'success': True, 'lines_written': 1} (or, in the rare edge case where new_line is identical to the matched line, {'success': True, 'message': 'No changes needed...'} instead - nothing to write). On failure (match not found, match not unique, file not found/unreadable): {'success': False, 'error': str} - not a raised exception. Note: unlike some other file tools, this does NOT return file_path in the response.

EXAMPLES: Example 1: Replace a commented line with an active configuration

{
  "file_path": "/etc/ssh/sshd_config",
  "match_line": "#ClientAliveInterval 0",
  "new_line": "ClientAliveInterval 300"
}

Note: To delete a line entirely, use the dedicated ssh_file_delete_line_by_content tool instead. To replace/insert MULTIPLE lines in one call, use ssh_file_replace_line_multi instead.

Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_file_replace_line_multiA

Replace a line in a file with one or more new lines (or delete it, with an empty list). Use this instead of ssh_file_replace_line when you need to insert more than one line, or delete a line without using the separate ssh_file_delete_line_by_content tool.

match_line must match EXACTLY ONE line in the file (whitespace-trimmed, literal text - not a pattern); if it matches zero lines or more than one, the operation fails with a descriptive error rather than guessing. Use ssh_file_find_lines_with_pattern first if you're not sure the line is unique.

PARAMETERS:

  • file_path: Path to the file to modify

  • match_line: Exact line content to match and replace (whitespace-trimmed)

  • new_lines: List of new lines to insert in place of the match

    • To replace with multiple lines: use ["first line", "second line", ...]

    • To delete the line entirely: use [] (empty list)

    • To replace with an empty line: use [""]

  • use_sudo: Use sudo for the operation (default: false)

  • force: No longer needed (kept for compatibility) - use_sudo reads root-only files with sudo (default: false)

RETURNS: On success: {'success': True, 'lines_written': <len(new_lines)>} (or, in the rare edge case where the result is byte-identical to the original file, {'success': True, 'message': 'No changes needed...'} instead). On failure (match not found, match not unique, file not found/unreadable): {'success': False, 'error': str} - not a raised exception. Note: does NOT return file_path in the response.

EXAMPLES: Example 1: Replace a line with multiple lines

{
  "file_path": "/etc/hosts",
  "match_line": "127.0.0.1 localhost",
  "new_lines": ["127.0.0.1 localhost", "127.0.0.1 myhost.local"]
}

Example 2: Delete a line entirely

{
  "file_path": "/etc/nginx/nginx.conf",
  "match_line": "# server_tokens off;",
  "new_lines": []
}

Example 3: Replace with an empty line

{
  "file_path": "/etc/ssh/sshd_config",
  "match_line": "PermitRootLogin yes",
  "new_lines": [""]
}
Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_file_transferA

Transfer a single FILE between the local machine (running this MCP server) and the remote host, via SFTP. Both local_path and remote_path must be file paths, not directories - for whole-directory transfers, use ssh_dir_transfer instead. For remote-to-remote copies (no local machine involved), use ssh_file_copy instead.

Caveat: use_sudo=True on download/upload stages a copy through /tmp/ using Unix shell commands (mv/chmod/rm) - this only works against Linux/macOS remote hosts. Windows has no per-command sudo concept anyway (see ssh_conn_verify_sudo), so use_sudo=True against a Windows connection raises immediately instead of attempting these Unix commands - connect as Administrator instead.

Returns: {'operation' (human-readable description of what happened), 'success': True, 'local_path', 'remote_path', 'sudo', 'connection'}. Raises an exception on failure rather than returning a success: False dict.

Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_dir_transferA

Transfer directories between local and remote systems.

Uses archive-based transfer for efficiency:

  • Upload: Archives locally, transfers, extracts on remote

  • Download: Archives on remote, transfers, extracts locally

Archive format is automatically selected based on remote OS:

  • Linux/macOS: tar.gz

  • Windows: zip

Where the files end up (check files_location in the result):

  • upload: the CONTENTS of local_path are placed directly in remote_path (local /x/proj/a.txt -> remote_path/a.txt)

  • download: the remote folder itself is placed inside local_path (remote /srv/proj/a.txt -> local_path/proj/a.txt)

What it returns (a dictionary): - success: whether the transfer succeeded - operation: 'upload' or 'download' - local_path, remote_path: as given - files_location: the folder where the transferred files now are - archive_format: 'tar.gz' or 'zip' - files_transferred: number of regular files (folders not counted) - directories: number of subfolders (the top folder itself not counted) - payload_bytes: total size of the transferred files - archive_bytes: size of the compressed archive actually sent - bytes_transferred: same as archive_bytes (kept for compatibility)

Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_file_insert_lines_after_matchA

Insert one or more new lines immediately after a matching line. match_line must match EXACTLY ONE line in the file (whitespace-trimmed, literal text - not a pattern); if it matches zero lines or more than one, the operation fails with a descriptive error rather than guessing. Use ssh_file_find_lines_with_pattern first if you're not sure the line is unique.

PARAMETERS:

  • file_path: Path to the file to modify

  • match_line: Exact line content to match (whitespace-trimmed)

  • lines_to_insert: List of lines to insert after the match

    • To insert multiple lines: use ["first line", "second line", ...]

    • To insert a single line: use ["line to insert"]

    • To insert an empty line: use [""]

  • use_sudo: Use sudo for the operation (default: false)

  • force: No longer needed (kept for compatibility) - use_sudo reads root-only files with sudo (default: false)

RETURNS: On success: {'success': True, 'lines_inserted': <len(lines_to_insert)>} (note: the key is lines_inserted here, vs. lines_written on ssh_file_replace_line/ssh_file_replace_line_multi). On failure (match not found, match not unique, file not found/unreadable): {'success': False, 'error': str}

  • not a raised exception. Does NOT return file_path in the response.

EXAMPLES: Example 1: Insert configuration lines after a marker

{
  "file_path": "/etc/nginx/nginx.conf",
  "match_line": "http {",
  "lines_to_insert": ["    server_tokens off;", "    client_max_body_size 20M;"]
}

Example 2: Add a new host entry after localhost

{
  "file_path": "/etc/hosts",
  "match_line": "127.0.0.1 localhost",
  "lines_to_insert": ["192.168.1.10 myserver.local"]
}
Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_file_delete_line_by_contentA

Delete a line by its exact content. match_line must match EXACTLY ONE line in the file (whitespace-trimmed, literal text - not a pattern); if it matches zero lines or more than one, the operation fails with a descriptive error rather than guessing which line(s) to delete. Use ssh_file_find_lines_with_pattern first if you're not sure the line is unique. (Equivalent to ssh_file_replace_line_multi(new_lines=[]), provided as a clearer-named shortcut.)

Returns: On success: {'success': True} (no count field - only ever deletes the one matched line). On failure (match not found, match not unique, file not found/unreadable): {'success': False, 'error': str} - not a raised exception.

Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_file_copyA

Copy a file on the remote host (source and destination are both remote paths - for local<->remote transfers use ssh_file_transfer instead).

If append_timestamp=True, a timestamp is inserted before the destination's file extension in the format %Y%m%dT%H%M%S, e.g. destination_path="/etc/hosts.bak" becomes /etc/hosts.20260704T153045.bak - not appended after the extension, and not configurable to a different format.

Returns: On success: {'success': True, 'copied_to': <actual destination path used, including the timestamp if applied>}. On failure (source not found, permission error): {'success': False, 'error': str} - not a raised exception.

Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_file_writeA

Create a new file, or overwrite/append to an existing one, with the given content (works the same whether or not the file already exists - append=True on a nonexistent file just creates it). Handles special characters and multi-line content properly.

mode (Unix permission bits) is applied via a chmod command after writing - this only works on Linux/macOS. On Windows there's no equivalent, so mode is silently ignored there (same convention as ssh_dir_mkdir's mode parameter).

Returns: On success: {'success': True, 'file_path', 'bytes_written' (int), 'mode' (octal string, or None if not set OR if the connection is Windows - the response never echoes back a mode value that wasn't actually applied), 'append' (bool, echoes the parameter), 'connection'}. On failure (e.g. parent directory missing and create_dirs=False, write error): {'success': False, 'file_path', 'error'} - not a raised exception.

Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_file_moveA

Move or rename a file or directory (works for both - whichever source is).

If destination already exists: fails cleanly with overwrite=False (default); with overwrite=True, it's replaced (mv -f/Move-Item -Force). If source doesn't exist, also fails cleanly rather than raising.

Returns: {'success': True, 'message': str} on success, or {'success': False, 'message': str} on failure (source not found, destination exists and overwrite=False, permission error) - not a raised exception.

Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_dir_search_globA

Recursively search for files (or directories, with include_dirs=True) matching a filename glob pattern (e.g. *.log) - matches by NAME only, not content; use ssh_dir_search_files_content instead to search inside files. For metadata beyond just path/type (size, mtime, permissions), use ssh_dir_list_advanced instead.

max_depth uses standard find -maxdepth semantics: this is the same on both Linux/macOS and Windows despite the different underlying commands. max_depth=1 means the given path itself plus its immediate children only (not grandchildren); omit max_depth for unlimited recursion.

On a 'linux'/'flex' connection with a limited BusyBox-style find (no -printf - check ssh_conn_connect/ssh_conn_host_info's capabilities), this raises a clear error instead of running. Fallback: ssh_dir_list_files_basic (non-recursive filenames, match the pattern yourself) - SFTP-based and unaffected by this, at the cost of one call per directory level.

Returns: List of {'path': str, 'type': str} - type is a single-character code (f=file, d=directory, l=symlink) from the underlying find/stat output, not a spelled-out word.

Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_dir_calc_sizeA

Calculate the total size of a directory recursively: the sum of the sizes of all regular files under it (directories' own sizes are not counted - same on every platform).

On a 'linux' connection whose find lacks -printf (check ssh_conn_connect/ ssh_conn_host_info's capabilities), this raises a clear error instead of running. Fallback: ssh_cmd_run("du -sk <path>") - kilobytes, and it includes directory overhead, but works on most BusyBox builds.

Returns: {'path', 'size_bytes' (int), 'size_human' (e.g. "1.23 MB", "512.00 KB", "3.50 GB" - 2 decimal places, binary/1024-based units)}.

Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_dir_deleteA

Delete a directory and all its contents recursively. dry_run DEFAULTS TO True - calling this with no arguments other than path only PREVIEWS what would be deleted and deletes NOTHING; you must explicitly pass dry_run=False to actually delete. Refuses to delete a small set of critical paths outright (root, home directory, C:\Windows, C:\Users, etc.) regardless of dry_run.

For a simpler non-recursive removal without the preview step, see ssh_dir_remove.

Returns: {'status': 'success'/'error', 'deleted_items': [str, ...] (paths that were/would be removed, depth-first order)}, plus 'dry_run': True ONLY when this was a preview (check for this key to tell a preview apart from a real deletion - it's absent, not False, on actual deletions) and 'error' (string) on failure. Also fails (with 'error' set) if path is a recognized critical directory.

Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_dir_batch_delete_filesA

Recursively find and delete files matching a filename glob pattern (same pattern syntax as ssh_dir_search_glob, e.g. *.tmp) under a directory. dry_run DEFAULTS TO True - calling this with no arguments other than path/pattern only PREVIEWS which files would be deleted and deletes NOTHING; you must explicitly pass dry_run=False to actually delete.

On a 'linux'/'flex' connection with a BusyBox-style xargs that doesn't support -0 (check ssh_conn_connect/ssh_conn_host_info's capabilities), this raises a clear error instead of running. Fallback: ssh_cmd_run with find <path> -name '<pattern>' -exec rm -f {} + instead - portable, and still safe with spaces in filenames.

Returns: {'status': 'success'/'error', 'deleted_files': [str, ...] (matching file paths that were/would be deleted)}, plus 'dry_run': True ONLY when this was a preview (check for this key to tell a preview apart from a real deletion) and 'error' (string) on failure. Note the key is deleted_files here, vs. deleted_items on ssh_dir_delete.

Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_dir_list_advancedA

List contents of a directory recursively with full metadata (size, permissions, ownership, modification time) - use this instead of ssh_dir_list_files_basic (which only returns bare filenames, non-recursive) when you need more than just names, or ssh_dir_search_glob when you only need to filter by filename pattern without full metadata (that tool is also faster for large trees).

max_depth uses standard find -maxdepth semantics: max_depth=1 means path itself plus its immediate children only; omit for unlimited recursion.

On a 'linux'/'flex' connection with a limited BusyBox-style find (no -printf - check ssh_conn_connect/ssh_conn_host_info's capabilities), this raises a clear error instead of running. Fallback: ssh_dir_list_files_basic (non-recursive filenames) plus ssh_file_stat per entry for metadata - both are SFTP-based and unaffected by this, at the cost of one call per directory level.

Returns: List of {'path', 'type', 'size_bytes' (int), 'modified_time' (float Unix timestamp), 'permissions' (string, e.g. "755"), 'user', 'group'}. type is a spelled-out word here ('file'/'directory'/'symlink'/'pipe'/'socket'/ 'block'/'character') - note this differs from ssh_dir_search_glob, which returns single-character type codes ('f'/'d'/'l') for the same concept. On Windows, permissions is always the literal placeholder "0" (Windows has no Unix-style permission bits) and group is always "unknown" - user still reflects the real file owner there.

Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_dir_search_files_contentA

Recursively search file CONTENTS for a pattern under a directory (unlike ssh_dir_search_glob/ssh_file_find_lines_with_pattern, which match filenames or search within a single already-known file respectively).

Regex flavor differs by platform when regex=True: POSIX extended regex (grep -E) on Linux/macOS - avoid PCRE-only syntax like \d, use [0-9] or [[:digit:]] instead; Python's re module on Windows. When regex=False (default), the pattern is matched as a literal fixed string.

On Windows the whole search runs over SFTP (directory listings + raw file reads, matched locally), so non-ASCII file names and content are handled correctly.

Returns: List of {'file': str, 'line': int, 'content': str} - one entry per matching line, across all files under dir_path. Empty list if nothing matches (not an error) - but only when every file could be searched.

If some files or subdirectories could NOT be searched (unreadable, or on
Windows larger than 10 MB), the result is instead
`{'status': 'incomplete', 'matches': [...same entries...], 'skipped': [{'path',
'reason'}, ...], 'skipped_count': int, 'note': str}` - so "no matches" is never
claimed for files that weren't actually searched. A missing or unreadable
`dir_path` itself is an error.

Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_dir_copyA

Copy a directory recursively (remote-to-remote; for local<->remote use ssh_dir_transfer instead).

overwrite controls what happens if destination_path already exists:

  • True: the entire existing destination directory is deleted first, then a fresh copy is made.

  • False (default): does NOT block the copy - it copies into the existing destination as-is, merging with whatever's already there, and any file that shares a name with a source file gets silently overwritten anyway. This does NOT behave like the "fail if destination exists" semantics of ssh_file_move/ssh_file_copy's own overwrite parameter.

Returns: {'status': 'success'/'error', 'files_copied' (int), 'bytes_copied' (int), 'destination_path'} (plus 'message' on error). Note: files_copied/ bytes_copied are computed by counting the destination directory's TOTAL contents after the copy, not just the files newly copied in this call - if merging into a non-empty destination, these counts include pre-existing files too.

Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_archive_createA

Create a compressed archive from a directory.

IMPORTANT cross-platform caveat: on Windows, tar.gz/tar are NOT natively available - requesting either one silently creates a .zip file instead (via Compress-Archive), and archive_path's extension is auto-corrected to .zip if needed. The only way to tell this happened is the returned format field saying 'zip' even though you asked for tar.gz/tar. An archive created this way on Windows can only be extracted with ssh_archive_extract on another Windows host - Linux/macOS's extractor only recognizes .tar/.tar.gz/.tgz files, not .zip. There is no cross-platform-portable archive format currently available through this tool.

Returns: On success: {'status': 'success', 'success': True, 'archive_created' (the actual path used, which may differ from archive_path if the extension was corrected on Windows), 'format' (the ACTUAL format used - 'tar.gz'/'tar' on Linux/macOS, always 'zip' on Windows), 'size_bytes'}. On failure: {'status': 'error', 'message': str} - not a raised exception.

Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.
ssh_archive_extractA

Extract an archive to a directory. The accepted format is platform-specific and determined purely by archive_path's file extension (not by inspecting the archive's actual content):

  • Linux/macOS: .tar.gz, .tgz, or .tar only. A .zip file (e.g. created by ssh_archive_create on Windows) will fail here with an unsupported-format error.

  • Windows: .zip only. A .tar/.tar.gz file (e.g. created by ssh_archive_create on Linux/macOS) will fail here the same way. There is no cross-platform-portable archive format currently available through this tool - archives must be created and extracted on the same OS family.

overwrite=False (default) does not fail outright if some files already exist at the destination - on Linux/macOS it extracts everything else and just logs a warning about the skipped files (no per-file detail returned); on Windows the behavior follows Expand-Archive's own overwrite handling.

On a 'linux'/'flex' connection with a BusyBox-style tar (check ssh_conn_connect/ssh_conn_host_info's capabilities), this can raise a clear error instead of running - unlike the wrong-format/extraction-error cases below, which return an error dict, this is a raised exception. Two independent gaps: missing --strip-components blocks extraction entirely (no clean single-tool fallback - extract without stripping via ssh_cmd_run, the archive's own top-level directory will remain, then relocate its contents up one level yourself); missing --keep-old-files only blocks when overwrite isn't explicitly True - passing overwrite=True avoids that one specifically.

Returns: On success: {'status': 'success', 'success': True, 'destination_path', 'files_extracted' (number of files), 'directories' (number of folders), 'extracted_files' (the first 50 file paths, relative to destination_path), 'extracted_files_truncated' (True if there are more), 'note' (how to list them all, and whether existing files were kept because overwrite=False)}. On failure: {'status': 'error', 'message': str, 'extracted_files': []} - not a raised exception.

Long-running calls: if this takes longer than the server's wait cap (default 50s), it
returns {"status": "in_progress", "handle_id": ...} instead of the result - the work keeps
running. Get the result by polling ssh_cmd_check_status(handle_id=...), which returns this
tool's normal response once finished. Do not call it again meanwhile. Only one such
operation runs at a time; another one started meanwhile fails with a 'busy' error.

Prompts

Interactive templates invoked by user choice

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription

No resources

TDQS

A4.1/5.0

Scored across 46 tools

Disambiguation4/5

Most tools target clearly distinct resource+action pairs across well-separated namespaces (conn, host, file, dir, cmd, task, archive). However, a few clusters overlap: ssh_dir_remove vs ssh_dir_delete vs ssh_dir_batch_delete_files all remove directories/files, and ssh_file_replace_line_multi vs ssh_file_delete_line_by_content are near-subsets of each other. Descriptions do disambiguate these, but boundaries require careful reading.

Naming Consistency4/5

The ssh_<namespace>_<verb>_<noun> pattern is broadly consistent across file/dir/cmd/task/archive tools. Deviations exist: host management is split between the ssh_conn_ prefix (ssh_conn_add_host) and the ssh_host_ prefix (ssh_host_update, ssh_host_remove, ssh_host_list), and ssh_file_delete_line_by_content doesn't mirror the ssh_file_replace_line structure. Still readable and mostly predictable.

Tool Count3/5

46 tools is well above the comfortable range and pushes into heavy territory. For a full-featured remote-shell server spanning connection, host, file, directory, command, task, and archive domains most tools earn their place, but the surface could be consolidated (e.g. three directory-deletion tools, two line-replacement tools).

Completeness5/5

Lifecycle coverage is exemplary: connection and host CRUD, file read/write/move/copy/stat plus line-level editing, directory list/mkdir/remove/delete/copy/search/size/transfer, command run/kill/status/output/history, background task launch/status/kill, and archive create/extract. Almost no obvious gaps for an SSH automation server.

Maintenance

ActivityMaintained
ResponsivenessNo issues