| 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): 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} 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.
|