Files
mcp-ssh/src/McpSsh.Server/Tools/SshTools.cs
2026-05-24 15:31:59 -07:00

124 lines
7.8 KiB
C#

using System.ComponentModel;
using McpSsh.Server.Sftp;
using McpSsh.Server.Ssh;
using McpSsh.Server.Terminal;
using ModelContextProtocol.Server;
namespace McpSsh.Server.Tools;
[McpServerToolType]
public sealed class SshTools(SshExecService sshExecService,
TerminalSessionManager terminalSessionManager,
SftpService sftpService)
{
private readonly SshExecService _sshExecService = sshExecService;
private readonly TerminalSessionManager _terminalSessionManager = terminalSessionManager;
private readonly SftpService _sftpService = sftpService;
[McpServerTool(Name = "ssh_exec", Destructive = false)]
[Description("Execute a single command over SSH using key-based authentication.")]
public Task<SshExecResult> ExecuteAsync(
[Description("Remote hostname or IP address. OpenSSH aliases are not supported in this vertical slice.")] string host,
[Description("Remote SSH username.")] string username,
[Description("Command to execute on the remote host.")] string command,
[Description("Remote SSH port. Defaults to 22.")] int? port = null,
[Description("Optional remote working directory.")] string? cwd = null,
[Description("Optional local private key path. Defaults to ~/.ssh/id_ed25519, ~/.ssh/id_ecdsa, then ~/.ssh/id_rsa.")] string? keyPath = null,
[Description("Optional private key passphrase. Use only with trusted MCP clients.")] string? keyPassphrase = null,
[Description("Timeout in seconds. Defaults to 30 and is capped at 300.")] int? timeoutSeconds = null,
CancellationToken cancellationToken = default)
{
return _sshExecService.ExecuteAsync(host, username, command, port, cwd, keyPath, keyPassphrase, timeoutSeconds, cancellationToken);
}
[McpServerTool(Name = "ssh_terminal_start", Destructive = false)]
[Description("Start a persistent SSH PTY shell session using key-based authentication.")]
public Task<TerminalStartResult> StartTerminalAsync(
[Description("Remote hostname or IP address. OpenSSH aliases are not supported in this vertical slice.")] string host,
[Description("Remote SSH username.")] string username,
[Description("Terminal columns. Defaults to 120.")] int? cols = null,
[Description("Terminal rows. Defaults to 40.")] int? rows = null,
[Description("Remote SSH port. Defaults to 22.")] int? port = null,
[Description("Optional local private key path. Defaults to ~/.ssh/id_ed25519, ~/.ssh/id_ecdsa, then ~/.ssh/id_rsa.")] string? keyPath = null,
[Description("Optional private key passphrase. Use only with trusted MCP clients.")] string? keyPassphrase = null,
[Description("Idle timeout in seconds. Defaults to 900.")] int? idleTimeoutSeconds = null,
CancellationToken cancellationToken = default)
{
return _terminalSessionManager.StartAsync(host, username, cols, rows, port, keyPath, keyPassphrase, idleTimeoutSeconds, cancellationToken);
}
[McpServerTool(Name = "ssh_terminal_write", Destructive = false)]
[Description("Write input to an active SSH terminal session.")]
public TerminalWriteResult WriteTerminal(
[Description("Terminal session ID returned by terminal_start.")] string sessionId,
[Description("Input to write to the terminal. Include newline characters when submitting commands.")] string input)
{
return _terminalSessionManager.Write(sessionId, input);
}
[McpServerTool(Name = "ssh_terminal_read", Destructive = false)]
[Description("Read buffered output from an active SSH terminal session.")]
public TerminalReadResult ReadTerminal(
[Description("Terminal session ID returned by terminal_start.")] string sessionId,
[Description("Maximum output characters to read. Defaults to 12000.")] int? maxBytes = null)
{
return _terminalSessionManager.Read(sessionId, maxBytes);
}
[McpServerTool(Name = "ssh_terminal_stop", Destructive = false)]
[Description("Stop and remove an SSH terminal session.")]
public TerminalStopResult StopTerminal(
[Description("Terminal session ID returned by terminal_start.")] string sessionId)
{
return _terminalSessionManager.Stop(sessionId);
}
[McpServerTool(Name = "sftp_list", Destructive = false)]
[Description("List remote directory contents over SFTP.")]
public Task<SftpListResult> ListSftpAsync(
[Description("Remote hostname or IP address. OpenSSH aliases are not supported in this vertical slice.")] string host,
[Description("Remote SSH username.")] string username,
[Description("Remote directory path to list.")] string remotePath,
[Description("Remote SSH port. Defaults to 22.")] int? port = null,
[Description("Optional local private key path. Defaults to ~/.ssh/id_ed25519, ~/.ssh/id_ecdsa, then ~/.ssh/id_rsa.")] string? keyPath = null,
[Description("Optional private key passphrase. Use only with trusted MCP clients.")] string? keyPassphrase = null,
CancellationToken cancellationToken = default)
{
return _sftpService.ListAsync(host, username, remotePath, port, keyPath, keyPassphrase, cancellationToken);
}
[McpServerTool(Name = "sftp_get", Destructive = false)]
[Description("Download a remote file using SFTP, silently falling back to SCP when SFTP is unavailable.")]
public Task<SftpTransferResult> GetSftpAsync(
[Description("Remote hostname or IP address. OpenSSH aliases are not supported in this vertical slice.")] string host,
[Description("Remote SSH username.")] string username,
[Description("Remote file path to download.")] string remotePath,
[Description("Local destination path. Must stay within the server working directory.")] string localPath,
[Description("Remote SSH port. Defaults to 22.")] int? port = null,
[Description("Optional local private key path. Defaults to ~/.ssh/id_ed25519, ~/.ssh/id_ecdsa, then ~/.ssh/id_rsa.")] string? keyPath = null,
[Description("Optional private key passphrase. Use only with trusted MCP clients.")] string? keyPassphrase = null,
[Description("Overwrite an existing local file. Defaults to false.")] bool? overwrite = null,
[Description("Maximum download size in bytes. Defaults to 104857600.")] long? maxBytes = null,
CancellationToken cancellationToken = default)
{
return _sftpService.GetAsync(host, username, remotePath, localPath, port, keyPath, keyPassphrase, overwrite, maxBytes, cancellationToken);
}
[McpServerTool(Name = "sftp_put", Destructive = false)]
[Description("Upload a local file using SFTP, silently falling back to SCP when SFTP is unavailable.")]
public Task<SftpTransferResult> PutSftpAsync(
[Description("Remote hostname or IP address. OpenSSH aliases are not supported in this vertical slice.")] string host,
[Description("Remote SSH username.")] string username,
[Description("Local file path to upload. Must stay within the server working directory.")] string localPath,
[Description("Remote destination file path.")] string remotePath,
[Description("Remote SSH port. Defaults to 22.")] int? port = null,
[Description("Optional local private key path. Defaults to ~/.ssh/id_ed25519, ~/.ssh/id_ecdsa, then ~/.ssh/id_rsa.")] string? keyPath = null,
[Description("Optional private key passphrase. Use only with trusted MCP clients.")] string? keyPassphrase = null,
[Description("Overwrite an existing remote file. Defaults to false.")] bool? overwrite = null,
[Description("Maximum upload size in bytes. Defaults to 104857600.")] long? maxBytes = null,
CancellationToken cancellationToken = default)
{
return _sftpService.PutAsync(host, username, localPath, remotePath, port, keyPath, keyPassphrase, overwrite, maxBytes, cancellationToken);
}
}