diff --git a/server/src/chat/commands/show.rs b/server/src/chat/commands/show.rs index 881f9998..bd31b1d8 100644 --- a/server/src/chat/commands/show.rs +++ b/server/src/chat/commands/show.rs @@ -44,7 +44,10 @@ fn strip_front_matter(text: &str) -> (String, String) { parts.push("**QA:** human review required".to_string()); } } else if line.starts_with("merge_failure:") { - let val = line.trim_start_matches("merge_failure:").trim().trim_matches('"'); + let val = line + .trim_start_matches("merge_failure:") + .trim() + .trim_matches('"'); if !val.is_empty() { parts.push(format!("**Merge failure:** {val}")); } diff --git a/server/src/gateway.rs b/server/src/gateway.rs index f0051033..3f66872c 100644 --- a/server/src/gateway.rs +++ b/server/src/gateway.rs @@ -320,7 +320,9 @@ pub async fn gateway_mcp_post_handler( .unwrap_or(""); if GATEWAY_TOOLS.contains(&tool_name) { - to_json_response(handle_gateway_tool(tool_name, &rpc.params, &state).await) + to_json_response( + handle_gateway_tool(tool_name, &rpc.params, &state, rpc.id.clone()).await, + ) } else { // Proxy to active project's container. match proxy_mcp_call(&state, &bytes).await { @@ -482,18 +484,22 @@ async fn handle_gateway_tool( tool_name: &str, params: &Value, state: &GatewayState, + id: Option, ) -> JsonRpcResponse { - let id = None; // The caller wraps this in a proper response. match tool_name { - "switch_project" => handle_switch_project(params, state).await, - "gateway_status" => handle_gateway_status(state).await, - "gateway_health" => handle_gateway_health(state).await, + "switch_project" => handle_switch_project(params, state, id).await, + "gateway_status" => handle_gateway_status(state, id).await, + "gateway_health" => handle_gateway_health(state, id).await, _ => JsonRpcResponse::error(id, -32601, format!("Unknown gateway tool: {tool_name}")), } } /// Switch the active project. -async fn handle_switch_project(params: &Value, state: &GatewayState) -> JsonRpcResponse { +async fn handle_switch_project( + params: &Value, + state: &GatewayState, + id: Option, +) -> JsonRpcResponse { let project = params .get("arguments") .and_then(|a| a.get("project")) @@ -502,7 +508,7 @@ async fn handle_switch_project(params: &Value, state: &GatewayState) -> JsonRpcR .unwrap_or(""); if project.is_empty() { - return JsonRpcResponse::error(None, -32602, "missing required parameter: project".into()); + return JsonRpcResponse::error(id, -32602, "missing required parameter: project".into()); } let url = { @@ -510,7 +516,7 @@ async fn handle_switch_project(params: &Value, state: &GatewayState) -> JsonRpcR if !projects.contains_key(project) { let available: Vec<&str> = projects.keys().map(|s| s.as_str()).collect(); return JsonRpcResponse::error( - None, + id, -32602, format!( "unknown project '{project}'. Available: {}", @@ -524,7 +530,7 @@ async fn handle_switch_project(params: &Value, state: &GatewayState) -> JsonRpcR *state.active_project.write().await = project.to_string(); JsonRpcResponse::success( - None, + id, json!({ "content": [{ "type": "text", @@ -535,11 +541,11 @@ async fn handle_switch_project(params: &Value, state: &GatewayState) -> JsonRpcR } /// Show pipeline status for the active project by proxying `get_pipeline_status`. -async fn handle_gateway_status(state: &GatewayState) -> JsonRpcResponse { +async fn handle_gateway_status(state: &GatewayState, id: Option) -> JsonRpcResponse { let active = state.active_project.read().await.clone(); let url = match state.active_url().await { Ok(u) => u, - Err(e) => return JsonRpcResponse::error(None, -32603, e), + Err(e) => return JsonRpcResponse::error(id.clone(), -32603, e), }; let mcp_url = format!("{}/mcp", url.trim_end_matches('/')); @@ -560,7 +566,7 @@ async fn handle_gateway_status(state: &GatewayState) -> JsonRpcResponse { // Extract the result from the upstream response and wrap it. let pipeline = upstream.get("result").cloned().unwrap_or(json!(null)); JsonRpcResponse::success( - None, + id, json!({ "content": [{ "type": "text", @@ -573,16 +579,16 @@ async fn handle_gateway_status(state: &GatewayState) -> JsonRpcResponse { ) } Err(e) => { - JsonRpcResponse::error(None, -32603, format!("invalid upstream response: {e}")) + JsonRpcResponse::error(id, -32603, format!("invalid upstream response: {e}")) } } } - Err(e) => JsonRpcResponse::error(None, -32603, format!("failed to reach {mcp_url}: {e}")), + Err(e) => JsonRpcResponse::error(id, -32603, format!("failed to reach {mcp_url}: {e}")), } } /// Aggregate health checks across all registered projects. -async fn handle_gateway_health(state: &GatewayState) -> JsonRpcResponse { +async fn handle_gateway_health(state: &GatewayState, id: Option) -> JsonRpcResponse { let mut results = BTreeMap::new(); let project_entries: Vec<(String, String)> = state @@ -609,7 +615,7 @@ async fn handle_gateway_health(state: &GatewayState) -> JsonRpcResponse { let active = state.active_project.read().await.clone(); JsonRpcResponse::success( - None, + id, json!({ "content": [{ "type": "text", @@ -1104,7 +1110,7 @@ pub async fn gateway_switch_handler( body: Json, ) -> Response { let params = json!({ "arguments": { "project": body.project } }); - let resp = handle_switch_project(¶ms, &state).await; + let resp = handle_switch_project(¶ms, &state, None).await; let (ok, error) = if resp.result.is_some() { (true, None) @@ -1907,7 +1913,7 @@ url = "http://localhost:3002" let state = GatewayState::new(config, PathBuf::from("."), 3000).unwrap(); let params = json!({ "arguments": { "project": "beta" } }); - let resp = handle_switch_project(¶ms, &state).await; + let resp = handle_switch_project(¶ms, &state, None).await; assert!(resp.result.is_some()); let active = state.active_project.read().await.clone(); @@ -1927,7 +1933,7 @@ url = "http://localhost:3002" let state = GatewayState::new(config, PathBuf::from("."), 3000).unwrap(); let params = json!({ "arguments": { "project": "nonexistent" } }); - let resp = handle_switch_project(¶ms, &state).await; + let resp = handle_switch_project(¶ms, &state, None).await; assert!(resp.error.is_some()); }