124 lines
7.8 KiB
C#
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);
|
|
}
|
|
}
|