using Microsoft.Extensions.Logging; using ModelContextProtocol.Protocol; namespace ModelContextProtocol.Server; /// /// Provides an implemented via "stdio" (standard input/output). /// public sealed class StdioServerTransport : StreamServerTransport { /// /// Initializes a new instance of the class. /// /// The server options. /// Optional logger factory used for logging employed by the transport. /// is or contains a null name. public StdioServerTransport(McpServerOptions serverOptions, ILoggerFactory? loggerFactory = null) : this(GetServerName(serverOptions), loggerFactory: loggerFactory) { } /// /// Initializes a new instance of the class. /// /// The name of the server. /// Optional logger factory used for logging employed by the transport. /// is . public StdioServerTransport(string serverName, ILoggerFactory? loggerFactory = null) : base(new CancellableStdinStream(Console.OpenStandardInput()), new BufferedStream(Console.OpenStandardOutput()), serverName ?? throw new ArgumentNullException(nameof(serverName)), loggerFactory) { } private static string GetServerName(McpServerOptions serverOptions) { Throw.IfNull(serverOptions); return serverOptions.ServerInfo?.Name ?? McpServer.DefaultImplementation.Name; } // Neither WindowsConsoleStream nor UnixConsoleStream respect CancellationTokens or cancel any I/O on Dispose. // WindowsConsoleStream will return an EOS on Ctrl-C, but that is not the only reason the shutdownToken may fire. private sealed class CancellableStdinStream(Stream stdinStream) : Stream { public override bool CanRead => true; public override bool CanSeek => false; public override bool CanWrite => false; public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => stdinStream.ReadAsync(buffer, offset, count, cancellationToken).WaitAsync(cancellationToken); #if NET public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) { ValueTask vt = stdinStream.ReadAsync(buffer, cancellationToken); return vt.IsCompletedSuccessfully ? vt : new(vt.AsTask().WaitAsync(cancellationToken)); } #endif // The McpServer shouldn't call flush on the stdin Stream, but it doesn't need to throw just in case. public override void Flush() { } public override long Length => throw new NotSupportedException(); public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); public override void SetLength(long value) => throw new NotSupportedException(); public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); } }