11use std:: { marker:: PhantomData , sync:: Arc } ;
22
3- // use crate::schema::*;
4- use futures:: { SinkExt , StreamExt } ;
3+ use futures:: SinkExt ;
54use serde:: { Serialize , de:: DeserializeOwned } ;
65use thiserror:: Error ;
76use tokio:: {
8- io:: { AsyncRead , AsyncWrite } ,
7+ io:: { AsyncBufReadExt , AsyncRead , AsyncWrite , AsyncWriteExt , BufReader } ,
98 sync:: Mutex ,
109} ;
1110use tokio_util:: {
1211 bytes:: { Buf , BufMut , BytesMut } ,
13- codec:: { Decoder , Encoder , FramedRead , FramedWrite } ,
12+ codec:: { Decoder , Encoder , FramedWrite } ,
1413} ;
1514
1615use super :: { IntoTransport , Transport } ;
4746pub type TransportWriter < Role , W > = FramedWrite < W , JsonRpcMessageCodec < TxJsonRpcMessage < Role > > > ;
4847
4948pub struct AsyncRwTransport < Role : ServiceRole , R : AsyncRead , W : AsyncWrite > {
50- read : FramedRead < R , JsonRpcMessageCodec < RxJsonRpcMessage < Role > > > ,
49+ read : BufReader < R > ,
50+ line_buf : Vec < u8 > ,
5151 write : Arc < Mutex < Option < TransportWriter < Role , W > > > > ,
52+ _role : PhantomData < fn ( ) -> Role > ,
5253}
5354
5455impl < Role : ServiceRole , R , W > AsyncRwTransport < Role , R , W >
@@ -57,15 +58,17 @@ where
5758 W : Send + AsyncWrite + Unpin + ' static ,
5859{
5960 pub fn new ( read : R , write : W ) -> Self {
60- let read = FramedRead :: new (
61- read,
62- JsonRpcMessageCodec :: < RxJsonRpcMessage < Role > > :: default ( ) ,
63- ) ;
61+ let read = BufReader :: new ( read) ;
6462 let write = Arc :: new ( Mutex :: new ( Some ( FramedWrite :: new (
6563 write,
6664 JsonRpcMessageCodec :: < TxJsonRpcMessage < Role > > :: default ( ) ,
6765 ) ) ) ) ;
68- Self { read, write }
66+ Self {
67+ read,
68+ line_buf : Vec :: new ( ) ,
69+ write,
70+ _role : PhantomData ,
71+ }
6972 }
7073}
7174
@@ -116,15 +119,42 @@ where
116119 }
117120 }
118121
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| {
122+ async fn receive ( & mut self ) -> Option < RxJsonRpcMessage < Role > > {
123+ loop {
124+ self . line_buf . clear ( ) ;
125+ match self . read . read_until ( b'\n' , & mut self . line_buf ) . await {
126+ Ok ( 0 ) => return None ,
127+ Ok ( _) => { }
128+ Err ( e) => {
124129 tracing:: error!( "Error reading from stream: {}" , e) ;
125- } )
126- . ok ( )
127- } )
130+ return None ;
131+ }
132+ }
133+ let line = without_carriage_return (
134+ self . line_buf . strip_suffix ( b"\n " ) . unwrap_or ( & self . line_buf ) ,
135+ ) ;
136+ if line. is_empty ( ) {
137+ continue ;
138+ }
139+ match try_parse_with_compatibility :: < RxJsonRpcMessage < Role > > ( line, "receive" ) {
140+ Ok ( Some ( msg) ) => return Some ( msg) ,
141+ Ok ( None ) => continue ,
142+ Err ( JsonRpcMessageCodecError :: Serde ( e) ) => {
143+ tracing:: debug!( "Parse error on incoming message: {e}" ) ;
144+ let mut write = self . write . lock ( ) . await ;
145+ let framed = write. as_mut ( ) ?;
146+ let inner = framed. get_mut ( ) ;
147+ if inner. write_all ( PARSE_ERROR_RESPONSE ) . await . is_err ( )
148+ || inner. flush ( ) . await . is_err ( )
149+ {
150+ return None ;
151+ }
152+ }
153+ Err ( e) => {
154+ tracing:: error!( "Error reading from stream: {}" , e) ;
155+ return None ;
156+ }
157+ }
128158 }
129159 }
130160
@@ -172,13 +202,17 @@ impl<T> JsonRpcMessageCodec<T> {
172202}
173203
174204fn without_carriage_return ( s : & [ u8 ] ) -> & [ u8 ] {
175- if let Some ( & b'\r' ) = s. last ( ) {
176- & s[ ..s. len ( ) - 1 ]
177- } else {
178- s
179- }
205+ s. strip_suffix ( b"\r " ) . unwrap_or ( s)
180206}
181207
208+ /// UTF-8 byte order mark. RFC 8259 §8.1 allows JSON parsers to ignore a leading BOM.
209+ const UTF8_BOM : & [ u8 ; 3 ] = b"\xEF \xBB \xBF " ;
210+
211+ // Sent verbatim because `RequestId` has no `Null` variant, so we cannot
212+ // construct an `id: null` JsonRpcError through the typed codec.
213+ const PARSE_ERROR_RESPONSE : & [ u8 ] =
214+ b"{\" jsonrpc\" :\" 2.0\" ,\" error\" :{\" code\" :-32700,\" message\" :\" Parse error\" },\" id\" :null}\n " ;
215+
182216/// Check if a method is a standard MCP method (request, response, or notification).
183217/// This includes both requests and notifications defined in the MCP specification.
184218///
@@ -247,6 +281,7 @@ fn try_parse_with_compatibility<T: serde::de::DeserializeOwned>(
247281 line : & [ u8 ] ,
248282 context : & str ,
249283) -> Result < Option < T > , JsonRpcMessageCodecError > {
284+ let line = line. strip_prefix ( UTF8_BOM . as_slice ( ) ) . unwrap_or ( line) ;
250285 if let Ok ( line_str) = std:: str:: from_utf8 ( line) {
251286 match serde_json:: from_slice ( line) {
252287 Ok ( item) => Ok ( Some ( item) ) ,
@@ -406,7 +441,8 @@ impl<T: Serialize> Encoder<T> for JsonRpcMessageCodec<T> {
406441
407442#[ cfg( test) ]
408443mod test {
409- use futures:: { Sink , Stream } ;
444+ use futures:: { Sink , Stream , StreamExt } ;
445+ use tokio_util:: codec:: FramedRead ;
410446
411447 use super :: * ;
412448 fn from_async_read < T : DeserializeOwned , R : AsyncRead > ( reader : R ) -> impl Stream < Item = T > {
@@ -555,4 +591,66 @@ mod test {
555591
556592 println ! ( "Standard notifications are preserved, non-standard are handled gracefully" ) ;
557593 }
594+
595+ #[ tokio:: test]
596+ async fn test_decode_strips_utf8_bom ( ) {
597+ use futures:: StreamExt ;
598+ use tokio:: io:: BufReader ;
599+
600+ // Valid JSON-RPC message preceded by a UTF-8 BOM (EF BB BF). Some Windows
601+ // tooling and editors prepend this; the codec should ignore it per RFC 8259 §8.1.
602+ let mut data = Vec :: new ( ) ;
603+ data. extend_from_slice ( UTF8_BOM ) ;
604+ data. extend_from_slice ( br#"{"jsonrpc":"2.0","method":"ping","id":1}"# ) ;
605+ data. push ( b'\n' ) ;
606+
607+ let mut cursor = BufReader :: new ( & data[ ..] ) ;
608+ let mut stream = from_async_read :: < serde_json:: Value , _ > ( & mut cursor) ;
609+
610+ let item = stream
611+ . next ( )
612+ . await
613+ . expect ( "should decode BOM-prefixed line" ) ;
614+ assert_eq ! (
615+ item,
616+ serde_json:: json!( { "jsonrpc" : "2.0" , "method" : "ping" , "id" : 1 } )
617+ ) ;
618+ }
619+
620+ #[ cfg( feature = "server" ) ]
621+ #[ tokio:: test]
622+ async fn receive_recovers_from_parse_error ( ) {
623+ use tokio:: io:: AsyncReadExt ;
624+
625+ use crate :: { RoleServer , transport:: Transport } ;
626+
627+ // Two paired streams: `server_io` is wrapped by the transport; the test
628+ // drives `client_io` to act as the peer.
629+ let ( server_io, client_io) = tokio:: io:: duplex ( 4096 ) ;
630+ let ( server_r, server_w) = tokio:: io:: split ( server_io) ;
631+ let ( mut client_r, mut client_w) = tokio:: io:: split ( client_io) ;
632+
633+ let mut transport = AsyncRwTransport :: < RoleServer , _ , _ > :: new ( server_r, server_w) ;
634+
635+ client_w
636+ . write_all (
637+ b"not json\n {\" jsonrpc\" :\" 2.0\" ,\" method\" :\" notifications/initialized\" }\n " ,
638+ )
639+ . await
640+ . unwrap ( ) ;
641+
642+ let received = transport
643+ . receive ( )
644+ . await
645+ . expect ( "transport should recover and yield the next valid message" ) ;
646+
647+ let mut reply = vec ! [ 0u8 ; PARSE_ERROR_RESPONSE . len( ) ] ;
648+ client_r. read_exact ( & mut reply) . await . unwrap ( ) ;
649+
650+ assert_eq ! ( reply, PARSE_ERROR_RESPONSE ) ;
651+ assert_eq ! (
652+ serde_json:: to_value( & received) . unwrap( ) [ "method" ] ,
653+ "notifications/initialized" ,
654+ ) ;
655+ }
558656}
0 commit comments