Skip to content

Commit eda39bf

Browse files
authored
fix(mcp): distinguish invalid JSON-RPC requests (#33)
Co-authored-by: Mark Stuart <mstuart@users.noreply.github.com>
1 parent 1d44423 commit eda39bf

1 file changed

Lines changed: 51 additions & 17 deletions

File tree

‎src/mcp/server.rs‎

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -40,24 +40,11 @@ impl McpServer {
4040

4141
debug!("Received: {}", line);
4242

43-
let request_value: Value = match serde_json::from_str(&line) {
44-
Ok(value) => value,
45-
Err(e) => {
46-
error!("Failed to parse request: {}", e);
47-
let resp = JsonRpcResponse::error(None, -32700, format!("Parse error: {}", e));
48-
Self::write_response(&mut stdout, &resp).await?;
49-
continue;
50-
}
51-
};
52-
let is_notification = request_value
53-
.as_object()
54-
.is_some_and(|request| !request.contains_key("id"));
55-
let request: JsonRpcRequest = match serde_json::from_value(request_value) {
43+
let (request, is_notification) = match Self::parse_request(&line) {
5644
Ok(request) => request,
57-
Err(e) => {
58-
error!("Failed to parse request: {}", e);
59-
let resp = JsonRpcResponse::error(None, -32700, format!("Parse error: {}", e));
60-
Self::write_response(&mut stdout, &resp).await?;
45+
Err(response) => {
46+
error!("Rejected invalid JSON-RPC input");
47+
Self::write_response(&mut stdout, &response).await?;
6148
continue;
6249
}
6350
};
@@ -71,6 +58,36 @@ impl McpServer {
7158
Ok(())
7259
}
7360

61+
fn parse_request(line: &str) -> Result<(JsonRpcRequest, bool), Box<JsonRpcResponse>> {
62+
let request_value: Value = serde_json::from_str(line).map_err(|error| {
63+
Box::new(JsonRpcResponse::error(
64+
None,
65+
-32700,
66+
format!("Parse error: {error}"),
67+
))
68+
})?;
69+
let is_notification = request_value
70+
.as_object()
71+
.is_some_and(|request| !request.contains_key("id"));
72+
let request: JsonRpcRequest = serde_json::from_value(request_value).map_err(|error| {
73+
Box::new(JsonRpcResponse::error(
74+
None,
75+
-32600,
76+
format!("Invalid Request: {error}"),
77+
))
78+
})?;
79+
80+
if request.jsonrpc != "2.0" {
81+
return Err(Box::new(JsonRpcResponse::error(
82+
None,
83+
-32600,
84+
"Invalid Request: jsonrpc must be \"2.0\"".to_string(),
85+
)));
86+
}
87+
88+
Ok((request, is_notification))
89+
}
90+
7491
async fn write_response(stdout: &mut io::Stdout, response: &JsonRpcResponse) -> Result<()> {
7592
let response_str = serde_json::to_string(response)?;
7693
debug!("Sending: {}", response_str);
@@ -193,4 +210,21 @@ mod tests {
193210
assert!(response.get("id").is_some());
194211
assert!(response["id"].is_null());
195212
}
213+
214+
#[test]
215+
fn distinguishes_malformed_json_from_invalid_requests() {
216+
let malformed = McpServer::parse_request("{").unwrap_err();
217+
let invalid = McpServer::parse_request(r#"{"jsonrpc":"2.0","id":1}"#).unwrap_err();
218+
219+
assert_eq!(malformed.error.unwrap().code, -32700);
220+
assert_eq!(invalid.error.unwrap().code, -32600);
221+
}
222+
223+
#[test]
224+
fn rejects_unsupported_json_rpc_versions() {
225+
let response =
226+
McpServer::parse_request(r#"{"jsonrpc":"1.0","id":1,"method":"ping"}"#).unwrap_err();
227+
228+
assert_eq!(response.error.unwrap().code, -32600);
229+
}
196230
}

0 commit comments

Comments
 (0)