Skip to content

Commit 7c6105e

Browse files
committed
fix: keep 400 for malformed request _meta
1 parent 1b1177f commit 7c6105e

3 files changed

Lines changed: 32 additions & 19 deletions

File tree

‎crates/rmcp/src/transport/streamable_http_server/tower.rs‎

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -643,7 +643,12 @@ fn validate_required_protocol_meta(
643643
.into())
644644
}
645645

646-
fn jsonrpc_http_status(message: &ServerJsonRpcMessage) -> http::StatusCode {
646+
/// `request_meta_malformed` marks a request missing required `_meta` fields;
647+
/// only then is `-32602` a malformed-request 400 rather than an in-band error.
648+
fn jsonrpc_http_status(
649+
message: &ServerJsonRpcMessage,
650+
request_meta_malformed: bool,
651+
) -> http::StatusCode {
647652
let ServerJsonRpcMessage::Error(error) = message else {
648653
return http::StatusCode::OK;
649654
};
@@ -652,6 +657,7 @@ fn jsonrpc_http_status(message: &ServerJsonRpcMessage) -> http::StatusCode {
652657
ErrorCode::UNSUPPORTED_PROTOCOL_VERSION
653658
| ErrorCode::MISSING_REQUIRED_CLIENT_CAPABILITY
654659
| ErrorCode::HEADER_MISMATCH => http::StatusCode::BAD_REQUEST,
660+
ErrorCode::INVALID_PARAMS if request_meta_malformed => http::StatusCode::BAD_REQUEST,
655661
ErrorCode::METHOD_NOT_FOUND => http::StatusCode::NOT_FOUND,
656662
_ => http::StatusCode::OK,
657663
}
@@ -674,31 +680,39 @@ mod jsonrpc_http_status_tests {
674680
#[test]
675681
fn header_mismatch_maps_to_bad_request() {
676682
assert_eq!(
677-
jsonrpc_http_status(&error_message(ErrorCode::HEADER_MISMATCH)),
683+
jsonrpc_http_status(&error_message(ErrorCode::HEADER_MISMATCH), false),
678684
http::StatusCode::BAD_REQUEST
679685
);
680686
}
681687

682688
#[test]
683689
fn method_not_found_maps_to_not_found() {
684690
assert_eq!(
685-
jsonrpc_http_status(&error_message(ErrorCode::METHOD_NOT_FOUND)),
691+
jsonrpc_http_status(&error_message(ErrorCode::METHOD_NOT_FOUND), false),
686692
http::StatusCode::NOT_FOUND
687693
);
688694
}
689695

690696
#[test]
691-
fn invalid_params_maps_to_ok() {
697+
fn invalid_params_for_malformed_request_meta_maps_to_bad_request() {
692698
assert_eq!(
693-
jsonrpc_http_status(&error_message(ErrorCode::INVALID_PARAMS)),
699+
jsonrpc_http_status(&error_message(ErrorCode::INVALID_PARAMS), true),
700+
http::StatusCode::BAD_REQUEST
701+
);
702+
}
703+
704+
#[test]
705+
fn invalid_params_for_well_formed_request_meta_maps_to_ok() {
706+
assert_eq!(
707+
jsonrpc_http_status(&error_message(ErrorCode::INVALID_PARAMS), false),
694708
http::StatusCode::OK
695709
);
696710
}
697711

698712
#[test]
699713
fn unmapped_error_defaults_to_ok() {
700714
assert_eq!(
701-
jsonrpc_http_status(&error_message(ErrorCode::INTERNAL_ERROR)),
715+
jsonrpc_http_status(&error_message(ErrorCode::INTERNAL_ERROR), false),
702716
http::StatusCode::OK
703717
);
704718
}
@@ -799,13 +813,8 @@ mod standard_header_init_tests {
799813

800814
fn jsonrpc_message_response(
801815
message: ServerJsonRpcMessage,
802-
map_protocol_status: bool,
816+
status: http::StatusCode,
803817
) -> HttpResult<BoxResponse> {
804-
let status = if map_protocol_status {
805-
jsonrpc_http_status(&message)
806-
} else {
807-
http::StatusCode::OK
808-
};
809818
let body =
810819
serde_json::to_vec(&message).map_err(internal_error_response("serialize json response"))?;
811820
Ok(Response::builder()
@@ -1526,6 +1535,11 @@ where
15261535
mut request: crate::model::JsonRpcRequest<ClientRequest>,
15271536
parts: http::request::Parts,
15281537
) -> HttpResult<BoxResponse> {
1538+
let request_meta_malformed = !request
1539+
.request
1540+
.get_meta()
1541+
.missing_required_keys(&ProtocolVersion::V_2026_07_28)
1542+
.is_empty();
15291543
let peer_info = Self::peer_info_for_stateless_request(&request, &parts.headers);
15301544
request.request.extensions_mut().insert(parts);
15311545
let (transport, mut receiver) =
@@ -1570,17 +1584,16 @@ where
15701584
&first,
15711585
ServerJsonRpcMessage::Response(_) | ServerJsonRpcMessage::Error(_)
15721586
);
1573-
if terminal
1574-
&& (self.config.json_response || jsonrpc_http_status(&first) != http::StatusCode::OK)
1575-
{
1587+
let status = jsonrpc_http_status(&first, request_meta_malformed);
1588+
if terminal && (self.config.json_response || status != http::StatusCode::OK) {
15761589
// This message is the whole reply, so `receiver` is dropped here and
15771590
// anything the handler emits afterwards is undeliverable. Cancel it so
15781591
// a still-running handler stops instead of running on unobserved: its
15791592
// terminal `send` would otherwise fail before adding the termination
15801593
// permit, leaving the serve loop parked forever. A no-op when the
15811594
// handler already completed.
15821595
request_ct.cancel();
1583-
return jsonrpc_message_response(first, true);
1596+
return jsonrpc_message_response(first, status);
15841597
}
15851598

15861599
Ok(self.stateless_sse_response(Some(first), receiver, request_ct))

‎crates/rmcp/tests/test_server_discover_http.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ async fn discover_rejects_missing_client_capabilities() {
322322
.await
323323
.expect("request should send");
324324

325-
assert_eq!(response.status(), 200);
325+
assert_eq!(response.status(), 400);
326326
let body: serde_json::Value = response.json().await.expect("response should be JSON");
327327
assert_eq!(body["error"]["code"], -32602);
328328

‎crates/rmcp/tests/test_streamable_http_protocol_version.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ async fn stateless_request_rejects_missing_meta_client_capabilities() {
424424
)
425425
.await;
426426

427-
assert_eq!(response.status(), 200);
427+
assert_eq!(response.status(), 400);
428428
let body: Value = response.json().await.expect("response should be JSON");
429429
assert_eq!(body["error"]["code"], -32602);
430430
assert!(
@@ -791,7 +791,7 @@ async fn seam_opt_in_rejects_missing_client_capabilities_before_dispatch() -> an
791791
&[("Mcp-Method", "tools/list")],
792792
)
793793
.await;
794-
assert_eq!(response.status(), 200);
794+
assert_eq!(response.status(), 400);
795795
let payload: serde_json::Value = response.json().await?;
796796
assert_eq!(payload["error"]["code"], -32602);
797797
assert_eq!(

0 commit comments

Comments
 (0)