using System.Diagnostics;
using System.Text.Json;
using Svrnty.MCP.Gateway.Core.Interfaces;
using Svrnty.MCP.Gateway.Core.Models;
namespace Svrnty.MCP.Gateway.Infrastructure.Transport;
///
/// Server transport implementation using stdio (standard input/output).
/// Launches a process and communicates via stdin/stdout.
///
public class StdioServerTransport : IServerTransport
{
private readonly string _command;
private readonly string[] _args;
private Process? _process;
private StreamWriter? _stdin;
private StreamReader? _stdout;
private bool _isConnected;
public bool IsConnected => _isConnected;
public StdioServerTransport(string command, string[] args)
{
_command = command ?? throw new ArgumentNullException(nameof(command));
_args = args ?? Array.Empty();
}
public async Task ConnectAsync(CancellationToken cancellationToken = default)
{
if (_isConnected)
{
return;
}
_process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = _command,
Arguments = string.Join(" ", _args),
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
_process.Start();
_stdin = _process.StandardInput;
_stdout = _process.StandardOutput;
_isConnected = true;
await Task.CompletedTask;
}
public async Task SendRequestAsync(GatewayRequest request, CancellationToken cancellationToken = default)
{
if (!_isConnected || _stdin == null || _stdout == null)
{
throw new InvalidOperationException("Transport is not connected");
}
// Serialize request to JSON
var jsonRequest = JsonSerializer.Serialize(request);
await _stdin.WriteLineAsync(jsonRequest);
await _stdin.FlushAsync();
// Read response from stdout
var jsonResponse = await _stdout.ReadLineAsync();
if (string.IsNullOrEmpty(jsonResponse))
{
return new GatewayResponse
{
Success = false,
Error = "Empty response from server"
};
}
// Deserialize response
var response = JsonSerializer.Deserialize(jsonResponse);
return response ?? new GatewayResponse
{
Success = false,
Error = "Failed to deserialize response"
};
}
public async Task DisconnectAsync(CancellationToken cancellationToken = default)
{
if (!_isConnected)
{
return;
}
_stdin?.Close();
_stdout?.Close();
if (_process != null && !_process.HasExited)
{
_process.Kill();
await _process.WaitForExitAsync(cancellationToken);
}
_process?.Dispose();
_process = null;
_stdin = null;
_stdout = null;
_isConnected = false;
}
public void Dispose()
{
DisconnectAsync().GetAwaiter().GetResult();
}
}