1- using System . Diagnostics ;
2- using System . Text . Json ;
1+ using Microsoft . Extensions . Logging ;
2+ using Microsoft . Extensions . Logging . Abstractions ;
33using ModelContextProtocol . Configuration ;
44using ModelContextProtocol . Logging ;
55using ModelContextProtocol . Protocol . Messages ;
66using ModelContextProtocol . Utils ;
77using ModelContextProtocol . Utils . Json ;
8- using Microsoft . Extensions . Logging ;
9- using Microsoft . Extensions . Logging . Abstractions ;
8+ using System . Diagnostics ;
9+ using System . Text ;
10+ using System . Text . Json ;
1011
1112namespace ModelContextProtocol . Protocol . Transport ;
1213
@@ -20,6 +21,8 @@ public sealed class StdioClientTransport : TransportBase, IClientTransport
2021 private readonly ILogger _logger ;
2122 private readonly JsonSerializerOptions _jsonOptions ;
2223 private Process ? _process ;
24+ private StreamWriter ? _stdInWriter ;
25+ private StreamReader ? _stdOutReader ;
2326 private Task ? _readTask ;
2427 private CancellationTokenSource ? _shutdownCts ;
2528 private bool _processStarted ;
@@ -99,6 +102,13 @@ public async Task ConnectAsync(CancellationToken cancellationToken = default)
99102 }
100103 _logger . TransportProcessStarted ( EndpointName , _process . Id ) ;
101104 _processStarted = true ;
105+
106+ // Create streams with explicit UTF-8 encoding to ensure proper Unicode character handling
107+ // This is especially important for non-ASCII characters like Chinese text and emoji
108+ var utf8Encoding = new UTF8Encoding ( false ) ; // No BOM
109+ _stdInWriter = new StreamWriter ( _process . StandardInput . BaseStream , utf8Encoding ) { AutoFlush = true } ;
110+ _stdOutReader = new StreamReader ( _process . StandardOutput . BaseStream , utf8Encoding ) ;
111+
102112 _process . BeginErrorReadLine ( ) ;
103113
104114 // Start reading messages in the background
@@ -118,7 +128,7 @@ public async Task ConnectAsync(CancellationToken cancellationToken = default)
118128 /// <inheritdoc/>
119129 public override async Task SendMessageAsync ( IJsonRpcMessage message , CancellationToken cancellationToken = default )
120130 {
121- if ( ! IsConnected || _process ? . HasExited == true )
131+ if ( ! IsConnected || _process ? . HasExited == true || _stdInWriter == null )
122132 {
123133 _logger . TransportNotConnected ( EndpointName ) ;
124134 throw new McpTransportException ( "Transport is not connected" ) ;
@@ -134,10 +144,11 @@ public override async Task SendMessageAsync(IJsonRpcMessage message, Cancellatio
134144 {
135145 var json = JsonSerializer . Serialize ( message , _jsonOptions . GetTypeInfo < IJsonRpcMessage > ( ) ) ;
136146 _logger . TransportSendingMessage ( EndpointName , id , json ) ;
147+ _logger . TransportMessageBytesUtf8 ( EndpointName , json ) ;
137148
138- // Write the message followed by a newline
139- await _process ! . StandardInput . WriteLineAsync ( json . AsMemory ( ) , cancellationToken ) . ConfigureAwait ( false ) ;
140- await _process . StandardInput . FlushAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
149+ // Write the message followed by a newline using our UTF-8 writer
150+ await _stdInWriter . WriteLineAsync ( json ) . ConfigureAwait ( false ) ;
151+ await _stdInWriter . FlushAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
141152
142153 _logger . TransportSentMessage ( EndpointName , id ) ;
143154 }
@@ -161,12 +172,10 @@ private async Task ReadMessagesAsync(CancellationToken cancellationToken)
161172 {
162173 _logger . TransportEnteringReadMessagesLoop ( EndpointName ) ;
163174
164- using var reader = _process ! . StandardOutput ;
165-
166- while ( ! cancellationToken . IsCancellationRequested && ! _process . HasExited )
175+ while ( ! cancellationToken . IsCancellationRequested && ! _process ! . HasExited && _stdOutReader != null )
167176 {
168177 _logger . TransportWaitingForMessage ( EndpointName ) ;
169- var line = await reader . ReadLineAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
178+ var line = await _stdOutReader . ReadLineAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
170179 if ( line == null )
171180 {
172181 _logger . TransportEndOfStream ( EndpointName ) ;
@@ -179,6 +188,7 @@ private async Task ReadMessagesAsync(CancellationToken cancellationToken)
179188 }
180189
181190 _logger . TransportReceivedMessage ( EndpointName , line ) ;
191+ _logger . TransportMessageBytesUtf8 ( EndpointName , line ) ;
182192
183193 await ProcessMessageAsync ( line , cancellationToken ) . ConfigureAwait ( false ) ;
184194 }
@@ -230,14 +240,28 @@ private async Task ProcessMessageAsync(string line, CancellationToken cancellati
230240 private async Task CleanupAsync ( CancellationToken cancellationToken )
231241 {
232242 _logger . TransportCleaningUp ( EndpointName ) ;
233- if ( _process != null && _processStarted && ! _process . HasExited )
243+
244+ if ( _stdInWriter != null )
234245 {
235246 try
236247 {
237- // Try to close stdin to signal the process to exit
238248 _logger . TransportClosingStdin ( EndpointName ) ;
239- _process . StandardInput . Close ( ) ;
249+ _stdInWriter . Close ( ) ;
250+ }
251+ catch ( Exception ex )
252+ {
253+ _logger . TransportShutdownFailed ( EndpointName , ex ) ;
254+ }
240255
256+ _stdInWriter = null ;
257+ }
258+
259+ _stdOutReader = null ;
260+
261+ if ( _process != null && _processStarted && ! _process . HasExited )
262+ {
263+ try
264+ {
241265 // Wait for the process to exit
242266 _logger . TransportWaitingForShutdown ( EndpointName ) ;
243267
0 commit comments