diff --git a/src/mcpdotnet/Protocol/Transport/StdioServerTransport.cs b/src/mcpdotnet/Protocol/Transport/StdioServerTransport.cs index 30045ff..17585ff 100644 --- a/src/mcpdotnet/Protocol/Transport/StdioServerTransport.cs +++ b/src/mcpdotnet/Protocol/Transport/StdioServerTransport.cs @@ -19,11 +19,13 @@ public sealed class StdioServerTransport : TransportBase, IServerTransport { private readonly string _serverName; private readonly ILogger _logger; - private readonly JsonSerializerOptions _jsonOptions; + + private readonly JsonSerializerOptions _jsonOptions = JsonSerializerOptionsExtensions.DefaultOptions; + private readonly TextReader _stdin = Console.In; + private readonly TextWriter _stdout = Console.Out; + private Task? _readTask; private CancellationTokenSource? _shutdownCts; - private readonly TextReader _input; - private readonly TextWriter _output; private string EndpointName => $"Server (stdio) ({_serverName})"; @@ -59,62 +61,12 @@ public sealed class StdioServerTransport : TransportBase, IServerTransport /// /// public StdioServerTransport(string serverName, ILoggerFactory? loggerFactory = null) - : this(serverName ?? throw new ArgumentNullException(nameof(serverName)), Console.In, Console.Out, loggerFactory) - { - } - - /// - /// Initializes a new instance of the class using the specified - /// and streams. - /// - /// The server options. - /// The reader, from which all input is read. - /// The writer, to which all output is written. - /// Optional logger factory used for logging employed by the transport. - /// is or contains a null name. - /// is . - /// is . - /// - /// - /// By default, no logging is performed. If a is supplied, it must not log - /// to , as that will interfere with the transport's output. - /// - /// - public StdioServerTransport(McpServerOptions serverOptions, TextReader input, TextWriter output, ILoggerFactory? loggerFactory = null) - : this(GetServerName(serverOptions), input, output, loggerFactory) - { - } - - /// - /// Initializes a new instance of the class using the specified - /// and streams. - /// - /// The name of the server. - /// The reader, from which all input is read. - /// The writer, to which all output is written. - /// Optional logger factory used for logging employed by the transport. - /// is . - /// is . - /// is . - /// - /// - /// By default, no logging is performed. If a is supplied, it must not log - /// to , as that will interfere with the transport's output. - /// - /// - public StdioServerTransport(string serverName, TextReader input, TextWriter output, ILoggerFactory? loggerFactory = null) : base(loggerFactory) { Throw.IfNull(serverName); - Throw.IfNull(input); - Throw.IfNull(output); - + _serverName = serverName; - _input = input; - _output = output; - _logger = (ILogger?)loggerFactory?.CreateLogger() ?? NullLogger.Instance; - _jsonOptions = JsonSerializerOptionsExtensions.DefaultOptions; } /// @@ -149,8 +101,8 @@ public sealed class StdioServerTransport : TransportBase, IServerTransport var json = JsonSerializer.Serialize(message, _jsonOptions); _logger.TransportSendingMessage(EndpointName, id, json); - await _output.WriteLineAsync(json.AsMemory(), cancellationToken).ConfigureAwait(false); - await _output.FlushAsync(cancellationToken).ConfigureAwait(false); + await _stdout.WriteLineAsync(json.AsMemory(), cancellationToken).ConfigureAwait(false); + await _stdout.FlushAsync(cancellationToken).ConfigureAwait(false); _logger.TransportSentMessage(EndpointName, id); } @@ -178,7 +130,7 @@ public sealed class StdioServerTransport : TransportBase, IServerTransport { _logger.TransportWaitingForMessage(EndpointName); - var reader = _input; + var reader = _stdin; var line = await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false); if (line == null) { diff --git a/tests/mcpdotnet.Tests/Transport/StdioServerTransportTests.cs b/tests/mcpdotnet.Tests/Transport/StdioServerTransportTests.cs index 3ce7705..46037fd 100644 --- a/tests/mcpdotnet.Tests/Transport/StdioServerTransportTests.cs +++ b/tests/mcpdotnet.Tests/Transport/StdioServerTransportTests.cs @@ -47,16 +47,6 @@ public class StdioServerTransportTests Assert.Throws("serverName", () => new StdioServerTransport(new McpServerOptions() { ServerInfo = new() { Name = null!, Version = "" } })); } - [Fact] - public void Constructor_Throws_For_Null_Streams() - { - Assert.Throws("input", () => new StdioServerTransport(_serverOptions, null!, Console.Out)); - Assert.Throws("input", () => new StdioServerTransport("name", null!, Console.Out)); - - Assert.Throws("output", () => new StdioServerTransport("name", Console.In, null!)); - Assert.Throws("output", () => new StdioServerTransport(_serverOptions, Console.In, null!)); - } - [Fact] public async Task StartListeningAsync_Should_Set_Connected_State() { @@ -70,21 +60,33 @@ public class StdioServerTransportTests [Fact] public async Task SendMessageAsync_Should_Send_Message() { - var input = new StringReader(""); - var output = new StringWriter(); + TextReader oldIn = Console.In; + TextWriter oldOut = Console.Out; + try + { + var output = new StringWriter(); - await using var transport = new StdioServerTransport(_serverOptions, input, output, NullLoggerFactory.Instance); - await transport.StartListeningAsync(); + Console.SetIn(new StringReader("")); + Console.SetOut(output); - var message = new JsonRpcRequest { Method = "test", Id = RequestId.FromNumber(44) }; + await using var transport = new StdioServerTransport(_serverOptions, NullLoggerFactory.Instance); + await transport.StartListeningAsync(); + + var message = new JsonRpcRequest { Method = "test", Id = RequestId.FromNumber(44) }; - await transport.SendMessageAsync(message); + await transport.SendMessageAsync(message); - var result = output.ToString()?.Trim(); - var expected = JsonSerializer.Serialize(message, JsonSerializerOptionsExtensions.DefaultOptions); + var result = output.ToString()?.Trim(); + var expected = JsonSerializer.Serialize(message, JsonSerializerOptionsExtensions.DefaultOptions); - Assert.Equal(expected, result); + Assert.Equal(expected, result); + } + finally + { + Console.SetOut(oldOut); + Console.SetIn(oldIn); + } } [Fact] @@ -113,19 +115,29 @@ public class StdioServerTransportTests var message = new JsonRpcRequest { Method = "test", Id = RequestId.FromNumber(44) }; var json = JsonSerializer.Serialize(message, JsonSerializerOptionsExtensions.DefaultOptions); - var input = new StringReader(json); - var output = new StringWriter(); + TextReader oldIn = Console.In; + TextWriter oldOut = Console.Out; + try + { + Console.SetIn(new StringReader(json)); + Console.SetOut(new StringWriter()); - await using var transport = new StdioServerTransport(_serverOptions, input, output); - await transport.StartListeningAsync(); + await using var transport = new StdioServerTransport(_serverOptions); + await transport.StartListeningAsync(); - var canRead = await transport.MessageReader.WaitToReadAsync(); + var canRead = await transport.MessageReader.WaitToReadAsync(); - Assert.True(canRead, "Nothing to read here from transport message reader"); - Assert.True(transport.MessageReader.TryPeek(out var readMessage)); - Assert.NotNull(readMessage); - Assert.IsType(readMessage); - Assert.Equal(44, ((JsonRpcRequest)readMessage).Id.AsNumber); + Assert.True(canRead, "Nothing to read here from transport message reader"); + Assert.True(transport.MessageReader.TryPeek(out var readMessage)); + Assert.NotNull(readMessage); + Assert.IsType(readMessage); + Assert.Equal(44, ((JsonRpcRequest)readMessage).Id.AsNumber); + } + finally + { + Console.SetOut(oldOut); + Console.SetIn(oldIn); + } } [Fact]