Remove TextReader/Writer parameterization from StdioServerTransport (#96)

It had been added only for tests.
This commit is contained in:
Stephen Toub 2025-03-17 13:30:37 -04:00 • committed by GitHub
commit a0afc28ad6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 50 additions and 86 deletions

View file

@ -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
/// </para>
/// </remarks>
public StdioServerTransport(string serverName, ILoggerFactory? loggerFactory = null)
: this(serverName ?? throw new ArgumentNullException(nameof(serverName)), Console.In, Console.Out, loggerFactory)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="StdioServerTransport"/> class using the specified
/// <paramref name="input"/> and <paramref name="output"/> streams.
/// </summary>
/// <param name="serverOptions">The server options.</param>
/// <param name="input">The reader, from which all input is read.</param>
/// <param name="output">The writer, to which all output is written.</param>
/// <param name="loggerFactory">Optional logger factory used for logging employed by the transport.</param>
/// <exception cref="ArgumentNullException"><paramref name="serverOptions"/> is <see langword="null"/> or contains a null name.</exception>
/// <exception cref="ArgumentNullException"><paramref name="input"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="output"/> is <see langword="null"/>.</exception>
/// <remarks>
/// <para>
/// By default, no logging is performed. If a <paramref name="loggerFactory"/> is supplied, it must not log
/// to <see cref="Console.Out"/>, as that will interfere with the transport's output.
/// </para>
/// </remarks>
public StdioServerTransport(McpServerOptions serverOptions, TextReader input, TextWriter output, ILoggerFactory? loggerFactory = null)
: this(GetServerName(serverOptions), input, output, loggerFactory)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="StdioServerTransport"/> class using the specified
/// <paramref name="input"/> and <paramref name="output"/> streams.
/// </summary>
/// <param name="serverName">The name of the server.</param>
/// <param name="input">The reader, from which all input is read.</param>
/// <param name="output">The writer, to which all output is written.</param>
/// <param name="loggerFactory">Optional logger factory used for logging employed by the transport.</param>
/// <exception cref="ArgumentNullException"><paramref name="serverName"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="input"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="output"/> is <see langword="null"/>.</exception>
/// <remarks>
/// <para>
/// By default, no logging is performed. If a <paramref name="loggerFactory"/> is supplied, it must not log
/// to <see cref="Console.Out"/>, as that will interfere with the transport's output.
/// </para>
/// </remarks>
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<StdioClientTransport>() ?? NullLogger.Instance;
_jsonOptions = JsonSerializerOptionsExtensions.DefaultOptions;
}
/// <inheritdoc/>
@ -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)
{

View file

@ -47,16 +47,6 @@ public class StdioServerTransportTests
Assert.Throws<ArgumentNullException>("serverName", () => new StdioServerTransport(new McpServerOptions() { ServerInfo = new() { Name = null!, Version = "" } }));
}
[Fact]
public void Constructor_Throws_For_Null_Streams()
{
Assert.Throws<ArgumentNullException>("input", () => new StdioServerTransport(_serverOptions, null!, Console.Out));
Assert.Throws<ArgumentNullException>("input", () => new StdioServerTransport("name", null!, Console.Out));
Assert.Throws<ArgumentNullException>("output", () => new StdioServerTransport("name", Console.In, null!));
Assert.Throws<ArgumentNullException>("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<JsonRpcRequest>(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<JsonRpcRequest>(readMessage);
Assert.Equal(44, ((JsonRpcRequest)readMessage).Id.AsNumber);
}
finally
{
Console.SetOut(oldOut);
Console.SetIn(oldIn);
}
}
[Fact]