@@ -301,8 +301,9 @@ impl McpProxy {
301301 error ! ( error = format!( "{error:#}" ) , "Couldn't forward request" ) ;
302302
303303 let message = if let Some ( id) = request_id {
304+ let detail = json_escape_str ( & format ! ( "{error:#}" ) ) ;
304305 let json_rpc_error_response = format ! (
305- r#"{{"jsonrpc":"2.0","id":{id},"error":{{"code":{FORWARD_FAILURE_CODE},"message":"Forward failure: {error:# }"}}}}"#
306+ r#"{{"jsonrpc":"2.0","id":{id},"error":{{"code":{FORWARD_FAILURE_CODE},"message":"Forward failure: {detail }"}}}}"#
306307 ) ;
307308 Some ( Message :: normalize ( json_rpc_error_response) )
308309 } else {
@@ -345,8 +346,9 @@ impl McpProxy {
345346 } )
346347 } else {
347348 let message = if let Some ( id) = request_id {
349+ let detail = json_escape_str ( & io_error. to_string ( ) ) ;
348350 let json_rpc_error_response = format ! (
349- r#"{{"jsonrpc":"2.0","id":{id},"error":{{"code":{FORWARD_FAILURE_CODE},"message":"Forward failure: {io_error }"}}}}"#
351+ r#"{{"jsonrpc":"2.0","id":{id},"error":{{"code":{FORWARD_FAILURE_CODE},"message":"Forward failure: {detail }"}}}}"#
350352 ) ;
351353 Some ( Message :: normalize ( json_rpc_error_response) )
352354 } else {
@@ -702,20 +704,41 @@ fn extract_sse_json_line(body: &str) -> Option<&str> {
702704 body. lines ( ) . find_map ( |l| l. strip_prefix ( "data: " ) . map ( |s| s. trim ( ) ) )
703705}
704706
707+ /// Escape a string for safe embedding inside a JSON string value.
708+ fn json_escape_str ( s : & str ) -> String {
709+ let mut out = String :: with_capacity ( s. len ( ) ) ;
710+ for ch in s. chars ( ) {
711+ match ch {
712+ '"' => out. push_str ( "\\ \" " ) ,
713+ '\\' => out. push_str ( "\\ \\ " ) ,
714+ '\n' => out. push_str ( "\\ n" ) ,
715+ '\r' => out. push_str ( "\\ r" ) ,
716+ '\t' => out. push_str ( "\\ t" ) ,
717+ c if ( c as u32 ) < 0x20 => {
718+ use std:: fmt:: Write as _;
719+ write ! ( out, "\\ u{:04x}" , c as u32 ) . expect ( "write to String is infallible" ) ;
720+ }
721+ c => out. push ( c) ,
722+ }
723+ }
724+ out
725+ }
726+
705727/// Build the message to send to the MCP client when the backend connection breaks fatally.
706728///
707729/// - If there is a `pending_request_id`, returns a JSON-RPC error response correlating the
708730/// failure to that outstanding request.
709731/// - Otherwise, returns a `$/proxy/serverDisconnected` notification so the client knows the
710732/// server is no longer reachable without having an in-flight request to correlate to.
711733fn make_server_disconnected_message ( pending_request_id : Option < i32 > , error_detail : & str ) -> Message {
734+ let detail = json_escape_str ( error_detail) ;
712735 let raw = if let Some ( id) = pending_request_id {
713736 format ! (
714- r#"{{"jsonrpc":"2.0","id":{id},"error":{{"code":{FORWARD_FAILURE_CODE},"message":"server disconnected: {error_detail }"}}}}"#
737+ r#"{{"jsonrpc":"2.0","id":{id},"error":{{"code":{FORWARD_FAILURE_CODE},"message":"server disconnected: {detail }"}}}}"#
715738 )
716739 } else {
717740 format ! (
718- r#"{{"jsonrpc":"2.0","method":"$/proxy/serverDisconnected","params":{{"message":"server disconnected: {error_detail }"}}}}"#
741+ r#"{{"jsonrpc":"2.0","method":"$/proxy/serverDisconnected","params":{{"message":"server disconnected: {detail }"}}}}"#
719742 )
720743 } ;
721744 Message :: normalize ( raw)
@@ -752,7 +775,7 @@ fn extract_id_best_effort(message: &str) -> Option<i32> {
752775
753776 loop {
754777 match rest. next ( ) {
755- Some ( ',' ) => break ,
778+ Some ( ',' ) | Some ( '}' ) => break ,
756779 Some ( ch) => acc. push ( ch) ,
757780 None => break ,
758781 }
0 commit comments