@@ -4,7 +4,7 @@ use futures::SinkExt;
44use serde:: { Serialize , de:: DeserializeOwned } ;
55use thiserror:: Error ;
66use tokio:: {
7- io:: { AsyncBufReadExt , AsyncRead , AsyncWrite , AsyncWriteExt , BufReader } ,
7+ io:: { AsyncBufReadExt , AsyncRead , AsyncWrite , BufReader } ,
88 sync:: Mutex ,
99} ;
1010use tokio_util:: {
@@ -13,7 +13,10 @@ use tokio_util::{
1313} ;
1414
1515use super :: { IntoTransport , Transport } ;
16- use crate :: service:: { RxJsonRpcMessage , ServiceRole , TxJsonRpcMessage } ;
16+ use crate :: {
17+ model:: ErrorData ,
18+ service:: { RxJsonRpcMessage , ServiceRole , TxJsonRpcMessage } ,
19+ } ;
1720
1821#[ non_exhaustive]
1922pub enum TransportAdapterAsyncRW { }
@@ -143,10 +146,11 @@ where
143146 tracing:: debug!( "Parse error on incoming message: {e}" ) ;
144147 let mut write = self . write . lock ( ) . await ;
145148 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- {
149+ let response = TxJsonRpcMessage :: < Role > :: error (
150+ ErrorData :: parse_error ( "Parse error" , None ) ,
151+ None ,
152+ ) ;
153+ if framed. send ( response) . await . is_err ( ) {
150154 return None ;
151155 }
152156 }
@@ -208,12 +212,6 @@ fn without_carriage_return(s: &[u8]) -> &[u8] {
208212/// UTF-8 byte order mark. RFC 8259 §8.1 allows JSON parsers to ignore a leading BOM.
209213const UTF8_BOM : & [ u8 ; 3 ] = b"\xEF \xBB \xBF " ;
210214
211- // JSON-RPC 2.0 §5.1: https://www.jsonrpc.org/specification#error_object
212- // Hardcoded bytes because `RequestId` has no `Null` variant — we can't
213- // build an `id: null` JsonRpcError through the typed codec.
214- const PARSE_ERROR_RESPONSE : & [ u8 ] =
215- b"{\" jsonrpc\" :\" 2.0\" ,\" error\" :{\" code\" :-32700,\" message\" :\" Parse error\" },\" id\" :null}\n " ;
216-
217215/// Check if a method is a standard MCP method (request, response, or notification).
218216/// This includes both requests and notifications defined in the MCP specification.
219217///
@@ -621,7 +619,7 @@ mod test {
621619 #[ cfg( feature = "server" ) ]
622620 #[ tokio:: test]
623621 async fn receive_recovers_from_parse_error ( ) {
624- use tokio:: io:: AsyncReadExt ;
622+ use tokio:: io:: { AsyncReadExt , AsyncWriteExt } ;
625623
626624 use crate :: { RoleServer , transport:: Transport } ;
627625
@@ -645,10 +643,20 @@ mod test {
645643 . await
646644 . expect ( "transport should recover and yield the next valid message" ) ;
647645
648- let mut reply = vec ! [ 0u8 ; PARSE_ERROR_RESPONSE . len( ) ] ;
649- client_r. read_exact ( & mut reply) . await . unwrap ( ) ;
646+ // Read one line back from the peer side and parse as JSON.
647+ let mut reply_buf = Vec :: new ( ) ;
648+ let mut peer = tokio:: io:: BufReader :: new ( & mut client_r) ;
649+ peer. read_until ( b'\n' , & mut reply_buf) . await . unwrap ( ) ;
650+ let reply: serde_json:: Value = serde_json:: from_slice ( & reply_buf) . unwrap ( ) ;
650651
651- assert_eq ! ( reply, PARSE_ERROR_RESPONSE ) ;
652+ // Per MCP 2025-11-25: id is omitted when the server can't read the request id.
653+ assert_eq ! (
654+ reply,
655+ serde_json:: json!( {
656+ "jsonrpc" : "2.0" ,
657+ "error" : { "code" : -32700 , "message" : "Parse error" } ,
658+ } )
659+ ) ;
652660 assert_eq ! (
653661 serde_json:: to_value( & received) . unwrap( ) [ "method" ] ,
654662 "notifications/initialized" ,
0 commit comments