Build initial MCP SSH server
This commit is contained in:
95
src/McpSsh.Server/Terminal/TerminalConnection.cs
Normal file
95
src/McpSsh.Server/Terminal/TerminalConnection.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using Renci.SshNet;
|
||||
using Renci.SshNet.Common;
|
||||
using McpSsh.Server.Ssh;
|
||||
|
||||
namespace McpSsh.Server.Terminal;
|
||||
|
||||
public interface ITerminalConnection : IDisposable
|
||||
{
|
||||
bool DataAvailable { get; }
|
||||
|
||||
string ReadAvailable();
|
||||
|
||||
void Write(string input);
|
||||
}
|
||||
|
||||
public interface ITerminalConnectionFactory
|
||||
{
|
||||
ITerminalConnection Create(TerminalStartRequest request);
|
||||
}
|
||||
|
||||
public sealed class SshNetTerminalConnectionFactory : ITerminalConnectionFactory
|
||||
{
|
||||
private const int ShellBufferSize = 16 * 1024;
|
||||
private readonly ISshKeyResolver _keyResolver;
|
||||
|
||||
public SshNetTerminalConnectionFactory(ISshKeyResolver keyResolver)
|
||||
{
|
||||
_keyResolver = keyResolver;
|
||||
}
|
||||
|
||||
public ITerminalConnection Create(TerminalStartRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var keyPath = _keyResolver.ResolveKeyPath(request.KeyPath);
|
||||
var keyFile = string.IsNullOrEmpty(request.KeyPassphrase)
|
||||
? new PrivateKeyFile(keyPath)
|
||||
: new PrivateKeyFile(keyPath, request.KeyPassphrase);
|
||||
var client = new SshClient(request.Host, request.Port, request.Username, keyFile);
|
||||
|
||||
client.Connect();
|
||||
|
||||
var stream = client.CreateShellStream(
|
||||
"xterm-256color",
|
||||
(uint)request.Cols,
|
||||
(uint)request.Rows,
|
||||
width: 0,
|
||||
height: 0,
|
||||
bufferSize: ShellBufferSize);
|
||||
|
||||
return new SshNetTerminalConnection(client, stream, keyFile);
|
||||
}
|
||||
catch (SshAuthenticationException ex)
|
||||
{
|
||||
throw new SshToolException("ssh_authentication_failed", "SSH key authentication failed. Check the username, key path, and key passphrase.", ex);
|
||||
}
|
||||
catch (SshException ex)
|
||||
{
|
||||
throw new SshToolException("ssh_error", ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class SshNetTerminalConnection : ITerminalConnection
|
||||
{
|
||||
private readonly SshClient _client;
|
||||
private readonly ShellStream _stream;
|
||||
private readonly PrivateKeyFile _keyFile;
|
||||
|
||||
public SshNetTerminalConnection(SshClient client, ShellStream stream, PrivateKeyFile keyFile)
|
||||
{
|
||||
_client = client;
|
||||
_stream = stream;
|
||||
_keyFile = keyFile;
|
||||
}
|
||||
|
||||
public bool DataAvailable => _stream.DataAvailable;
|
||||
|
||||
public string ReadAvailable()
|
||||
{
|
||||
return _stream.Read();
|
||||
}
|
||||
|
||||
public void Write(string input)
|
||||
{
|
||||
_stream.Write(input);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_stream.Dispose();
|
||||
_client.Dispose();
|
||||
_keyFile.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user