11use std:: { marker:: PhantomData , sync:: Arc } ;
22
33// use crate::schema::*;
4- use futures:: { SinkExt , StreamExt } ;
4+ use futures:: SinkExt ;
55use serde:: { Serialize , de:: DeserializeOwned } ;
66use thiserror:: Error ;
77use tokio:: {
8- io:: { AsyncRead , AsyncWrite } ,
8+ io:: { AsyncBufReadExt , AsyncRead , AsyncWrite , AsyncWriteExt , BufReader } ,
99 sync:: Mutex ,
1010} ;
1111use tokio_util:: {
1212 bytes:: { Buf , BufMut , BytesMut } ,
13- codec:: { Decoder , Encoder , FramedRead , FramedWrite } ,
13+ codec:: { Decoder , Encoder , FramedWrite } ,
1414} ;
1515
1616use super :: { IntoTransport , Transport } ;
4747pub type TransportWriter < Role , W > = FramedWrite < W , JsonRpcMessageCodec < TxJsonRpcMessage < Role > > > ;
4848
4949pub struct AsyncRwTransport < Role : ServiceRole , R : AsyncRead , W : AsyncWrite > {
50- read : FramedRead < R , JsonRpcMessageCodec < RxJsonRpcMessage < Role > > > ,
50+ read : BufReader < R > ,
51+ line_buf : Vec < u8 > ,
5152 write : Arc < Mutex < Option < TransportWriter < Role , W > > > > ,
53+ _role : PhantomData < fn ( ) -> Role > ,
5254}
5355
5456impl < Role : ServiceRole , R , W > AsyncRwTransport < Role , R , W >
@@ -57,15 +59,17 @@ where
5759 W : Send + AsyncWrite + Unpin + ' static ,
5860{
5961 pub fn new ( read : R , write : W ) -> Self {
60- let read = FramedRead :: new (
61- read,
62- JsonRpcMessageCodec :: < RxJsonRpcMessage < Role > > :: default ( ) ,
63- ) ;
62+ let read = BufReader :: new ( read) ;
6463 let write = Arc :: new ( Mutex :: new ( Some ( FramedWrite :: new (
6564 write,
6665 JsonRpcMessageCodec :: < TxJsonRpcMessage < Role > > :: default ( ) ,
6766 ) ) ) ) ;
68- Self { read, write }
67+ Self {
68+ read,
69+ line_buf : Vec :: new ( ) ,
70+ write,
71+ _role : PhantomData ,
72+ }
6973 }
7074}
7175
@@ -116,15 +120,40 @@ where
116120 }
117121 }
118122
119- fn receive ( & mut self ) -> impl Future < Output = Option < RxJsonRpcMessage < Role > > > {
120- let next = self . read . next ( ) ;
121- async {
122- next. await . and_then ( |e| {
123- e. inspect_err ( |e| {
123+ async fn receive ( & mut self ) -> Option < RxJsonRpcMessage < Role > > {
124+ loop {
125+ self . line_buf . clear ( ) ;
126+ match self . read . read_until ( b'\n' , & mut self . line_buf ) . await {
127+ Ok ( 0 ) => return None ,
128+ Ok ( _) => { }
129+ Err ( e) => {
124130 tracing:: error!( "Error reading from stream: {}" , e) ;
125- } )
126- . ok ( )
127- } )
131+ return None ;
132+ }
133+ }
134+ let line = without_line_terminator ( & self . line_buf ) ;
135+ if line. is_empty ( ) {
136+ continue ;
137+ }
138+ match try_parse_with_compatibility :: < RxJsonRpcMessage < Role > > ( line, "receive" ) {
139+ Ok ( Some ( msg) ) => return Some ( msg) ,
140+ Ok ( None ) => continue ,
141+ Err ( JsonRpcMessageCodecError :: Serde ( e) ) => {
142+ tracing:: debug!( "Parse error on incoming message: {e}" ) ;
143+ let mut write = self . write . lock ( ) . await ;
144+ let framed = write. as_mut ( ) ?;
145+ let inner = framed. get_mut ( ) ;
146+ if inner. write_all ( PARSE_ERROR_RESPONSE ) . await . is_err ( )
147+ || inner. flush ( ) . await . is_err ( )
148+ {
149+ return None ;
150+ }
151+ }
152+ Err ( e) => {
153+ tracing:: error!( "Error reading from stream: {}" , e) ;
154+ return None ;
155+ }
156+ }
128157 }
129158 }
130159
@@ -172,13 +201,25 @@ impl<T> JsonRpcMessageCodec<T> {
172201}
173202
174203fn without_carriage_return ( s : & [ u8 ] ) -> & [ u8 ] {
175- if let Some ( & b'\r' ) = s. last ( ) {
176- & s[ ..s. len ( ) - 1 ]
177- } else {
178- s
179- }
204+ s. strip_suffix ( b"\r " ) . unwrap_or ( s)
180205}
181206
207+ fn without_line_terminator ( s : & [ u8 ] ) -> & [ u8 ] {
208+ without_carriage_return ( s. strip_suffix ( b"\n " ) . unwrap_or ( s) )
209+ }
210+
211+ /// UTF-8 byte order mark. RFC 8259 §8.1 allows JSON parsers to ignore a leading BOM.
212+ const UTF8_BOM : & [ u8 ; 3 ] = b"\xEF \xBB \xBF " ;
213+
214+ fn without_utf8_bom ( s : & [ u8 ] ) -> & [ u8 ] {
215+ s. strip_prefix ( UTF8_BOM . as_slice ( ) ) . unwrap_or ( s)
216+ }
217+
218+ // Sent verbatim because `RequestId` has no `Null` variant, so we cannot
219+ // construct an `id: null` JsonRpcError through the typed codec.
220+ const PARSE_ERROR_RESPONSE : & [ u8 ] =
221+ b"{\" jsonrpc\" :\" 2.0\" ,\" error\" :{\" code\" :-32700,\" message\" :\" Parse error\" },\" id\" :null}\n " ;
222+
182223/// Check if a method is a standard MCP method (request, response, or notification).
183224/// This includes both requests and notifications defined in the MCP specification.
184225///
@@ -247,6 +288,7 @@ fn try_parse_with_compatibility<T: serde::de::DeserializeOwned>(
247288 line : & [ u8 ] ,
248289 context : & str ,
249290) -> Result < Option < T > , JsonRpcMessageCodecError > {
291+ let line = without_utf8_bom ( line) ;
250292 if let Ok ( line_str) = std:: str:: from_utf8 ( line) {
251293 match serde_json:: from_slice ( line) {
252294 Ok ( item) => Ok ( Some ( item) ) ,
@@ -406,7 +448,8 @@ impl<T: Serialize> Encoder<T> for JsonRpcMessageCodec<T> {
406448
407449#[ cfg( test) ]
408450mod test {
409- use futures:: { Sink , Stream } ;
451+ use futures:: { Sink , Stream , StreamExt } ;
452+ use tokio_util:: codec:: FramedRead ;
410453
411454 use super :: * ;
412455 fn from_async_read < T : DeserializeOwned , R : AsyncRead > ( reader : R ) -> impl Stream < Item = T > {
@@ -555,4 +598,78 @@ mod test {
555598
556599 println ! ( "Standard notifications are preserved, non-standard are handled gracefully" ) ;
557600 }
601+
602+ #[ tokio:: test]
603+ async fn test_decode_strips_utf8_bom ( ) {
604+ use futures:: StreamExt ;
605+ use tokio:: io:: BufReader ;
606+
607+ // Valid JSON-RPC message preceded by a UTF-8 BOM (EF BB BF). Some Windows
608+ // tooling and editors prepend this; the codec should ignore it per RFC 8259 §8.1.
609+ let mut data = Vec :: new ( ) ;
610+ data. extend_from_slice ( UTF8_BOM ) ;
611+ data. extend_from_slice ( br#"{"jsonrpc":"2.0","method":"ping","id":1}"# ) ;
612+ data. push ( b'\n' ) ;
613+
614+ let mut cursor = BufReader :: new ( & data[ ..] ) ;
615+ let mut stream = from_async_read :: < serde_json:: Value , _ > ( & mut cursor) ;
616+
617+ let item = stream
618+ . next ( )
619+ . await
620+ . expect ( "should decode BOM-prefixed line" ) ;
621+ assert_eq ! (
622+ item,
623+ serde_json:: json!( { "jsonrpc" : "2.0" , "method" : "ping" , "id" : 1 } )
624+ ) ;
625+ }
626+
627+ #[ cfg( feature = "server" ) ]
628+ async fn drive_parse_error_recovery ( ) -> ( RxJsonRpcMessage < crate :: RoleServer > , Vec < u8 > ) {
629+ use tokio:: io:: AsyncReadExt ;
630+
631+ use crate :: { RoleServer , transport:: Transport } ;
632+
633+ // Two paired streams: `server_io` is wrapped by the transport; the test
634+ // drives `client_io` to act as the peer.
635+ let ( server_io, client_io) = tokio:: io:: duplex ( 4096 ) ;
636+ let ( server_r, server_w) = tokio:: io:: split ( server_io) ;
637+ let ( mut client_r, mut client_w) = tokio:: io:: split ( client_io) ;
638+
639+ let mut transport = AsyncRwTransport :: < RoleServer , _ , _ > :: new ( server_r, server_w) ;
640+
641+ client_w
642+ . write_all (
643+ b"not json\n {\" jsonrpc\" :\" 2.0\" ,\" method\" :\" notifications/initialized\" }\n " ,
644+ )
645+ . await
646+ . unwrap ( ) ;
647+
648+ let received = transport
649+ . receive ( )
650+ . await
651+ . expect ( "transport should recover and yield the next valid message" ) ;
652+
653+ let mut reply = vec ! [ 0u8 ; PARSE_ERROR_RESPONSE . len( ) ] ;
654+ client_r. read_exact ( & mut reply) . await . unwrap ( ) ;
655+
656+ ( received, reply)
657+ }
658+
659+ #[ cfg( feature = "server" ) ]
660+ #[ tokio:: test]
661+ async fn receive_yields_next_valid_message_after_parse_error ( ) {
662+ let ( received, _) = drive_parse_error_recovery ( ) . await ;
663+ assert_eq ! (
664+ serde_json:: to_value( & received) . unwrap( ) [ "method" ] ,
665+ "notifications/initialized"
666+ ) ;
667+ }
668+
669+ #[ cfg( feature = "server" ) ]
670+ #[ tokio:: test]
671+ async fn receive_emits_parse_error_response_to_peer ( ) {
672+ let ( _, reply) = drive_parse_error_recovery ( ) . await ;
673+ assert_eq ! ( reply, PARSE_ERROR_RESPONSE ) ;
674+ }
558675}
0 commit comments