mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-27 03:36:51 -06:00
Reply with a parse error on non-UTF-8 lines in MCP serve
Found while testing crates/mcp/src/server.rs:62-64 (Server::serve) Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Haiku 5.5
parent
5971d46391
commit
22f896536d
@@ -59,10 +59,17 @@ impl Server {
|
||||
|
||||
/// Serve newline-delimited JSON-RPC until `input` closes. Logs go to stderr only (stdout is
|
||||
/// the protocol stream).
|
||||
pub fn serve(&mut self, input: impl BufRead, mut output: impl Write) -> std::io::Result<()> {
|
||||
for line in input.lines() {
|
||||
let line = line?;
|
||||
if let Some(reply) = self.handle_line(&line) {
|
||||
pub fn serve(&mut self, mut input: impl BufRead, mut output: impl Write) -> std::io::Result<()> {
|
||||
let mut buf = Vec::new();
|
||||
loop {
|
||||
buf.clear();
|
||||
if input.read_until(b'\n', &mut buf)? == 0 {
|
||||
break;
|
||||
}
|
||||
// Invalid UTF-8 becomes U+FFFD, so the line gets a parse error instead of ending the stream.
|
||||
let line = String::from_utf8_lossy(&buf);
|
||||
let line = line.trim_end_matches(['\r', '\n']);
|
||||
if let Some(reply) = self.handle_line(line) {
|
||||
output.write_all(reply.as_bytes())?;
|
||||
output.write_all(b"\n")?;
|
||||
output.flush()?;
|
||||
|
||||
@@ -76,6 +76,28 @@ fn framing_basics() {
|
||||
assert_eq!(rpc(&mut s, 5, "tools/call", json!({}))["error"]["code"], -32602);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serve_survives_invalid_utf8_line() {
|
||||
let mut s = server();
|
||||
let mut input = Vec::new();
|
||||
input.extend_from_slice(json!({"jsonrpc":"2.0","id":1,"method":"ping"}).to_string().as_bytes());
|
||||
input.extend_from_slice(b"\n\xff\xfe not utf-8\n");
|
||||
input.extend_from_slice(json!({"jsonrpc":"2.0","id":2,"method":"ping"}).to_string().as_bytes());
|
||||
input.extend_from_slice(b"\n\r\n");
|
||||
input.extend_from_slice(json!({"jsonrpc":"2.0","id":3,"method":"ping"}).to_string().as_bytes());
|
||||
input.extend_from_slice(b"\r\n");
|
||||
let mut out = Vec::new();
|
||||
let res = s.serve(input.as_slice(), &mut out);
|
||||
assert!(res.is_ok(), "serve stopped: {res:?}");
|
||||
let lines: Vec<Value> = String::from_utf8(out).unwrap().lines().map(|l| serde_json::from_str(l).unwrap()).collect();
|
||||
assert_eq!(lines.len(), 4, "{lines:?}");
|
||||
assert_eq!(lines[0]["id"], 1);
|
||||
assert_eq!(lines[1]["error"]["code"], -32700);
|
||||
assert!(lines[1]["id"].is_null());
|
||||
assert_eq!(lines[2]["id"], 2);
|
||||
assert_eq!(lines[3]["id"], 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn protocol_round_trip_over_serve() {
|
||||
let mut s = server();
|
||||
|
||||
Reference in New Issue
Block a user