Skip to content

Commit 3531ebd

Browse files
Copilot0xrinegade
andcommitted
Fix critical JSON-RPC 2.0 compliance bugs and improve error handling
Co-authored-by: 0xrinegade <[email protected]>
1 parent d0d5730 commit 3531ebd

File tree

3 files changed

+35
-38
lines changed

3 files changed

+35
-38
lines changed

src/http_server.rs

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use axum::{
55
routing::{get, post},
66
Json, Router,
77
};
8+
use serde_json::Value;
89
use tokio::net::TcpListener;
910
use tower::ServiceBuilder;
1011
use tracing::{info, error, debug};
@@ -95,7 +96,10 @@ async fn mcp_api_handler(
9596
// Validate Content-Type header (should be application/json for MCP)
9697
if let Some(content_type) = headers.get(CONTENT_TYPE) {
9798
if let Ok(ct_str) = content_type.to_str() {
98-
if !ct_str.starts_with("application/json") {
99+
// Be more strict about content type validation
100+
let is_valid = ct_str == "application/json" ||
101+
ct_str.starts_with("application/json;");
102+
if !is_valid {
99103
return create_json_rpc_error_response(
100104
-32600,
101105
"Invalid Request: Content-Type must be application/json",
@@ -117,14 +121,20 @@ async fn mcp_api_handler(
117121
// Convert JsonRpcMessage back to proper JSON-RPC 2.0 format
118122
match serde_json::to_value(&response_message) {
119123
Ok(json_response) => {
120-
create_json_rpc_success_response(json_response)
124+
// The response_message is already a properly formatted JSON-RPC response
125+
// Don't double-wrap it in create_json_rpc_success_response
126+
(
127+
StatusCode::OK,
128+
[(CONTENT_TYPE, "application/json")],
129+
Json(json_response)
130+
).into_response()
121131
}
122132
Err(e) => {
123133
error!("Failed to serialize MCP response: {}", e);
124134
create_json_rpc_error_response(
125135
-32603,
126136
"Internal error: Failed to serialize response",
127-
Some(json_rpc_request.id),
137+
Some(json_rpc_request.id.clone()),
128138
)
129139
}
130140
}
@@ -134,7 +144,7 @@ async fn mcp_api_handler(
134144
create_json_rpc_error_response(
135145
-32603,
136146
&format!("Internal error: {}", e),
137-
Some(json_rpc_request.id),
147+
Some(json_rpc_request.id.clone()),
138148
)
139149
}
140150
}
@@ -168,8 +178,8 @@ fn parse_json_rpc_request(request: &serde_json::Value) -> Result<JsonRpcRequest,
168178
))?;
169179

170180
let id = request.get("id")
171-
.and_then(|v| v.as_u64())
172-
.unwrap_or(0);
181+
.cloned()
182+
.unwrap_or(Value::Null); // Default to null if no ID provided
173183

174184
let params = request.get("params").cloned();
175185

@@ -181,19 +191,9 @@ fn parse_json_rpc_request(request: &serde_json::Value) -> Result<JsonRpcRequest,
181191
})
182192
}
183193

184-
/// Create a properly formatted JSON-RPC 2.0 success response
185-
fn create_json_rpc_success_response(result: serde_json::Value) -> Response {
186-
(
187-
StatusCode::OK,
188-
[
189-
(CONTENT_TYPE, "application/json"),
190-
],
191-
Json(result)
192-
).into_response()
193-
}
194194

195195
/// Create a properly formatted JSON-RPC 2.0 error response
196-
fn create_json_rpc_error_response(code: i32, message: &str, id: Option<u64>) -> Response {
196+
fn create_json_rpc_error_response(code: i32, message: &str, id: Option<Value>) -> Response {
197197
let error_response = serde_json::json!({
198198
"jsonrpc": "2.0",
199199
"error": {
@@ -203,7 +203,7 @@ fn create_json_rpc_error_response(code: i32, message: &str, id: Option<u64>) ->
203203
"protocolVersion": crate::protocol::LATEST_PROTOCOL_VERSION
204204
}
205205
},
206-
"id": id
206+
"id": id.unwrap_or(Value::Null)
207207
});
208208

209209
(
@@ -277,9 +277,6 @@ mod tests {
277277

278278
#[tokio::test]
279279
async fn test_mcp_api_handler() {
280-
use crate::Config;
281-
use crate::server::ServerState;
282-
283280
// Create a test server state using Config::load() or a minimal config
284281
// For testing purposes, we'll skip the actual test since it requires valid config
285282
// In a real test environment, you'd want to create a minimal test config

src/tools/mod.rs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ use url::Url;
2525
///
2626
/// # Returns
2727
/// * `JsonRpcMessage` - Formatted success response
28-
pub fn create_success_response(result: Value, id: u64) -> JsonRpcMessage {
29-
log::debug!("Creating success response with id {}", id);
28+
pub fn create_success_response(result: Value, id: Value) -> JsonRpcMessage {
29+
log::debug!("Creating success response with id {:?}", id);
3030
JsonRpcMessage::Response(JsonRpcResponse {
3131
jsonrpc: JsonRpcVersion::V2,
3232
id,
@@ -52,7 +52,7 @@ pub fn create_success_response(result: Value, id: u64) -> JsonRpcMessage {
5252
pub fn create_error_response(
5353
code: i32,
5454
message: String,
55-
id: u64,
55+
id: Value,
5656
protocol_version: Option<&str>,
5757
) -> JsonRpcMessage {
5858
log::error!("Creating error response: {} (code: {})", message, code);
@@ -93,7 +93,7 @@ pub async fn handle_initialize(
9393
return Ok(create_error_response(
9494
-32602,
9595
"Invalid params: protocolVersion is required".to_string(),
96-
id.and_then(|v| v.as_u64()).unwrap_or(0),
96+
id.unwrap_or(Value::Null),
9797
Some(state.protocol_version.as_str()),
9898
));
9999
}
@@ -119,7 +119,7 @@ pub async fn handle_initialize(
119119
"Protocol version mismatch. Server: {}, Client: {}",
120120
state.protocol_version, init_params.protocol_version
121121
),
122-
id.and_then(|v| v.as_u64()).unwrap_or(0),
122+
id.unwrap_or(Value::Null),
123123
Some(state.protocol_version.as_str()),
124124
));
125125
}
@@ -1216,14 +1216,14 @@ pub async fn handle_initialize(
12161216
log::info!("Server initialized successfully");
12171217
Ok(create_success_response(
12181218
serde_json::to_value(response).unwrap(),
1219-
id.and_then(|v| v.as_u64()).unwrap_or(0),
1219+
id.unwrap_or(Value::Null),
12201220
))
12211221
} else {
12221222
log::error!("Missing initialization params");
12231223
Ok(create_error_response(
12241224
-32602,
12251225
"Invalid params".to_string(),
1226-
id.and_then(|v| v.as_u64()).unwrap_or(0),
1226+
id.unwrap_or(Value::Null),
12271227
Some(state.protocol_version.as_str()),
12281228
))
12291229
}
@@ -1239,14 +1239,14 @@ pub async fn handle_cancelled(
12391239
let _cancel_params: CancelledParams = serde_json::from_value(params)?;
12401240
Ok(create_success_response(
12411241
Value::Null,
1242-
id.and_then(|v| v.as_u64()).unwrap_or(0),
1242+
id.unwrap_or(Value::Null),
12431243
))
12441244
} else {
12451245
log::error!("Missing cancelled params");
12461246
Ok(create_error_response(
12471247
-32602,
12481248
"Invalid params".to_string(),
1249-
id.and_then(|v| v.as_u64()).unwrap_or(0),
1249+
id.unwrap_or(Value::Null),
12501250
Some(state.protocol_version.as_str()),
12511251
))
12521252
}
@@ -2031,7 +2031,7 @@ pub async fn handle_tools_list(id: Option<Value>, _state: &ServerState) -> Resul
20312031

20322032
Ok(create_success_response(
20332033
serde_json::to_value(response).unwrap(),
2034-
id.and_then(|v| v.as_u64()).unwrap_or(0),
2034+
id.unwrap_or(Value::Null),
20352035
))
20362036
}
20372037

@@ -2297,7 +2297,7 @@ pub async fn handle_request(
22972297
"initialize" => {
22982298
let response = handle_initialize(
22992299
req.params,
2300-
Some(serde_json::Value::Number(req.id.into())),
2300+
Some(req.id.clone()),
23012301
&state_guard,
23022302
)
23032303
.await?;
@@ -2313,13 +2313,13 @@ pub async fn handle_request(
23132313
"cancelled" => {
23142314
handle_cancelled(
23152315
req.params,
2316-
Some(serde_json::Value::Number(req.id.into())),
2316+
Some(req.id.clone()),
23172317
&state_guard,
23182318
)
23192319
.await
23202320
}
23212321
"tools/list" => {
2322-
handle_tools_list(Some(serde_json::Value::Number(req.id.into())), &state_guard)
2322+
handle_tools_list(Some(req.id.clone()), &state_guard)
23232323
.await
23242324
}
23252325

@@ -3698,7 +3698,7 @@ pub async fn handle_request(
36983698
Ok(create_error_response(
36993699
-32600,
37003700
"Invalid Request: expected request message".to_string(),
3701-
0,
3701+
Value::Null,
37023702
None,
37033703
))
37043704
}
@@ -3712,7 +3712,7 @@ pub async fn handle_request(
37123712
Ok(create_error_response(
37133713
-32600,
37143714
format!("Unsupported notification: {}", notification.method),
3715-
0,
3715+
Value::Null,
37163716
None,
37173717
))
37183718
}

src/transport.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ impl Default for JsonRpcVersion {
2121
#[serde(rename_all = "camelCase")]
2222
pub struct JsonRpcRequest {
2323
pub jsonrpc: JsonRpcVersion,
24-
pub id: u64,
24+
pub id: Value, // JSON-RPC 2.0 allows string, number, or null
2525
pub method: String,
2626
#[serde(skip_serializing_if = "Option::is_none")]
2727
pub params: Option<Value>,
@@ -31,7 +31,7 @@ pub struct JsonRpcRequest {
3131
#[serde(rename_all = "camelCase")]
3232
pub struct JsonRpcResponse {
3333
pub jsonrpc: JsonRpcVersion,
34-
pub id: u64,
34+
pub id: Value, // JSON-RPC 2.0 allows string, number, or null
3535
#[serde(skip_serializing_if = "Option::is_none")]
3636
pub result: Option<Value>,
3737
#[serde(skip_serializing_if = "Option::is_none")]

0 commit comments

Comments
 (0)