using Renci.SshNet; namespace McpSsh.Server.Ssh; public interface ISshClientFactory { SshClient CreateSshClient(SshConnectionRequest request); SftpClient CreateSftpClient(SshConnectionRequest request); ScpClient CreateScpClient(SshConnectionRequest request); } public sealed record SshConnectionRequest( string Host, string Username, int Port, string? KeyPath, string? KeyPassphrase, TimeSpan? Timeout = null); public sealed class SshNetClientFactory : ISshClientFactory { private readonly ISshKeyResolver _keyResolver; public SshNetClientFactory(ISshKeyResolver keyResolver) { _keyResolver = keyResolver; } public SshClient CreateSshClient(SshConnectionRequest request) { var client = new SshClient(CreateConnectionInfo(request)); ApplyTimeout(client, request.Timeout); return client; } public SftpClient CreateSftpClient(SshConnectionRequest request) { var client = new SftpClient(CreateConnectionInfo(request)); ApplyTimeout(client, request.Timeout); return client; } public ScpClient CreateScpClient(SshConnectionRequest request) { var client = new ScpClient(CreateConnectionInfo(request)); ApplyTimeout(client, request.Timeout); return client; } private ConnectionInfo CreateConnectionInfo(SshConnectionRequest request) { var keyPath = _keyResolver.ResolveKeyPath(request.KeyPath); var keyFile = string.IsNullOrEmpty(request.KeyPassphrase) ? new PrivateKeyFile(keyPath) : new PrivateKeyFile(keyPath, request.KeyPassphrase); var auth = new PrivateKeyAuthenticationMethod(request.Username, keyFile); return new ConnectionInfo(request.Host, request.Port, request.Username, auth); } private static void ApplyTimeout(BaseClient client, TimeSpan? timeout) { if (timeout is { } value) { client.ConnectionInfo.Timeout = value; } } }