Skip to content

Commit e4052b2

Browse files
committed
.
1 parent 5804afb commit e4052b2

1 file changed

Lines changed: 28 additions & 5 deletions

File tree

crates/mcp-proxy/src/lib.rs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
711733
fn 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

Comments
 (0)