huskies: merge 590_story_gateway_native_mcp_tools_return_json_rpc_responses_missing_request_id
This commit is contained in:
@@ -44,7 +44,10 @@ fn strip_front_matter(text: &str) -> (String, String) {
|
|||||||
parts.push("**QA:** human review required".to_string());
|
parts.push("**QA:** human review required".to_string());
|
||||||
}
|
}
|
||||||
} else if line.starts_with("merge_failure:") {
|
} 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() {
|
if !val.is_empty() {
|
||||||
parts.push(format!("**Merge failure:** {val}"));
|
parts.push(format!("**Merge failure:** {val}"));
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-19
@@ -320,7 +320,9 @@ pub async fn gateway_mcp_post_handler(
|
|||||||
.unwrap_or("");
|
.unwrap_or("");
|
||||||
|
|
||||||
if GATEWAY_TOOLS.contains(&tool_name) {
|
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 {
|
} else {
|
||||||
// Proxy to active project's container.
|
// Proxy to active project's container.
|
||||||
match proxy_mcp_call(&state, &bytes).await {
|
match proxy_mcp_call(&state, &bytes).await {
|
||||||
@@ -482,18 +484,22 @@ async fn handle_gateway_tool(
|
|||||||
tool_name: &str,
|
tool_name: &str,
|
||||||
params: &Value,
|
params: &Value,
|
||||||
state: &GatewayState,
|
state: &GatewayState,
|
||||||
|
id: Option<Value>,
|
||||||
) -> JsonRpcResponse {
|
) -> JsonRpcResponse {
|
||||||
let id = None; // The caller wraps this in a proper response.
|
|
||||||
match tool_name {
|
match tool_name {
|
||||||
"switch_project" => handle_switch_project(params, state).await,
|
"switch_project" => handle_switch_project(params, state, id).await,
|
||||||
"gateway_status" => handle_gateway_status(state).await,
|
"gateway_status" => handle_gateway_status(state, id).await,
|
||||||
"gateway_health" => handle_gateway_health(state).await,
|
"gateway_health" => handle_gateway_health(state, id).await,
|
||||||
_ => JsonRpcResponse::error(id, -32601, format!("Unknown gateway tool: {tool_name}")),
|
_ => JsonRpcResponse::error(id, -32601, format!("Unknown gateway tool: {tool_name}")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Switch the active project.
|
/// 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<Value>,
|
||||||
|
) -> JsonRpcResponse {
|
||||||
let project = params
|
let project = params
|
||||||
.get("arguments")
|
.get("arguments")
|
||||||
.and_then(|a| a.get("project"))
|
.and_then(|a| a.get("project"))
|
||||||
@@ -502,7 +508,7 @@ async fn handle_switch_project(params: &Value, state: &GatewayState) -> JsonRpcR
|
|||||||
.unwrap_or("");
|
.unwrap_or("");
|
||||||
|
|
||||||
if project.is_empty() {
|
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 = {
|
let url = {
|
||||||
@@ -510,7 +516,7 @@ async fn handle_switch_project(params: &Value, state: &GatewayState) -> JsonRpcR
|
|||||||
if !projects.contains_key(project) {
|
if !projects.contains_key(project) {
|
||||||
let available: Vec<&str> = projects.keys().map(|s| s.as_str()).collect();
|
let available: Vec<&str> = projects.keys().map(|s| s.as_str()).collect();
|
||||||
return JsonRpcResponse::error(
|
return JsonRpcResponse::error(
|
||||||
None,
|
id,
|
||||||
-32602,
|
-32602,
|
||||||
format!(
|
format!(
|
||||||
"unknown project '{project}'. Available: {}",
|
"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();
|
*state.active_project.write().await = project.to_string();
|
||||||
|
|
||||||
JsonRpcResponse::success(
|
JsonRpcResponse::success(
|
||||||
None,
|
id,
|
||||||
json!({
|
json!({
|
||||||
"content": [{
|
"content": [{
|
||||||
"type": "text",
|
"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`.
|
/// 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<Value>) -> JsonRpcResponse {
|
||||||
let active = state.active_project.read().await.clone();
|
let active = state.active_project.read().await.clone();
|
||||||
let url = match state.active_url().await {
|
let url = match state.active_url().await {
|
||||||
Ok(u) => u,
|
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('/'));
|
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.
|
// Extract the result from the upstream response and wrap it.
|
||||||
let pipeline = upstream.get("result").cloned().unwrap_or(json!(null));
|
let pipeline = upstream.get("result").cloned().unwrap_or(json!(null));
|
||||||
JsonRpcResponse::success(
|
JsonRpcResponse::success(
|
||||||
None,
|
id,
|
||||||
json!({
|
json!({
|
||||||
"content": [{
|
"content": [{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -573,16 +579,16 @@ async fn handle_gateway_status(state: &GatewayState) -> JsonRpcResponse {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
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.
|
/// Aggregate health checks across all registered projects.
|
||||||
async fn handle_gateway_health(state: &GatewayState) -> JsonRpcResponse {
|
async fn handle_gateway_health(state: &GatewayState, id: Option<Value>) -> JsonRpcResponse {
|
||||||
let mut results = BTreeMap::new();
|
let mut results = BTreeMap::new();
|
||||||
|
|
||||||
let project_entries: Vec<(String, String)> = state
|
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();
|
let active = state.active_project.read().await.clone();
|
||||||
JsonRpcResponse::success(
|
JsonRpcResponse::success(
|
||||||
None,
|
id,
|
||||||
json!({
|
json!({
|
||||||
"content": [{
|
"content": [{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -1104,7 +1110,7 @@ pub async fn gateway_switch_handler(
|
|||||||
body: Json<SwitchRequest>,
|
body: Json<SwitchRequest>,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
let params = json!({ "arguments": { "project": body.project } });
|
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() {
|
let (ok, error) = if resp.result.is_some() {
|
||||||
(true, None)
|
(true, None)
|
||||||
@@ -1907,7 +1913,7 @@ url = "http://localhost:3002"
|
|||||||
let state = GatewayState::new(config, PathBuf::from("."), 3000).unwrap();
|
let state = GatewayState::new(config, PathBuf::from("."), 3000).unwrap();
|
||||||
|
|
||||||
let params = json!({ "arguments": { "project": "beta" } });
|
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());
|
assert!(resp.result.is_some());
|
||||||
|
|
||||||
let active = state.active_project.read().await.clone();
|
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 state = GatewayState::new(config, PathBuf::from("."), 3000).unwrap();
|
||||||
|
|
||||||
let params = json!({ "arguments": { "project": "nonexistent" } });
|
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());
|
assert!(resp.error.is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user