use aion_mcp::tools::service::{ToolCall, ToolFailure, ToolOutcome};
use aion_proto::{
ProtoCancelRequest, ProtoQueryRequest, ProtoSignalRequest, ProtoWorkflowId, WireError,
proto_query_response,
};
use serde_json::json;
use crate::mcp::args::{
optional_json, optional_run_id, optional_str, required_str, required_workflow_id,
};
use crate::{CallerIdentity, ServerState, api::handlers};
use super::errors::tool_failure;
pub(crate) async fn signal(
state: &ServerState,
caller: &CallerIdentity,
call: &ToolCall,
) -> Result<ToolOutcome, ToolFailure> {
let namespace = required_str(call, "namespace")?;
let workflow_id = required_workflow_id(call, "workflow_id")?;
let run_id = optional_run_id(call, "run_id")?;
let signal_name = required_str(call, "signal_name")?;
let payload = optional_json(call, "payload")
.map(|value| {
aion_core::Payload::from_json(&value).map_err(|error| {
tool_failure(&WireError::invalid_input(format!(
"the signal payload could not be encoded: {error}"
)))
})
})
.transpose()?;
handlers::signal(
state.namespace_guard(),
caller,
ProtoSignalRequest {
namespace,
workflow_id: Some(ProtoWorkflowId {
uuid: workflow_id.to_string(),
}),
run_id: run_id.map(Into::into),
signal_name: signal_name.clone(),
payload: payload.map(Into::into),
},
)
.await
.map_err(|error| tool_failure(&error))?;
Ok(ToolOutcome {
summary: format!("signal `{signal_name}` delivered to workflow {workflow_id}"),
structured: json!({
"workflow_id": workflow_id.to_string(),
"signal_name": signal_name,
"delivered": true,
}),
})
}
pub(crate) async fn query(
state: &ServerState,
caller: &CallerIdentity,
call: &ToolCall,
) -> Result<ToolOutcome, ToolFailure> {
let namespace = required_str(call, "namespace")?;
let workflow_id = required_workflow_id(call, "workflow_id")?;
let run_id = optional_run_id(call, "run_id")?;
let query_name = required_str(call, "query_name")?;
let arguments = optional_json(call, "arguments")
.map(|value| {
aion_core::Payload::from_json(&value).map_err(|error| {
tool_failure(&WireError::invalid_input(format!(
"the query arguments could not be encoded: {error}"
)))
})
})
.transpose()?;
let response = handlers::query(
state.namespace_guard(),
caller,
ProtoQueryRequest {
namespace,
workflow_id: Some(ProtoWorkflowId {
uuid: workflow_id.to_string(),
}),
run_id: run_id.map(Into::into),
query_name: query_name.clone(),
arguments: arguments.map(Into::into),
},
)
.await
.map_err(|error| tool_failure(&error))?;
let result = match response.outcome {
Some(proto_query_response::Outcome::Result(payload)) => {
let payload =
aion_core::Payload::try_from(payload).map_err(|error| tool_failure(&error))?;
payload.to_json().map_err(|error| {
tool_failure(&WireError::backend(format!(
"the query result is not JSON: {error}"
)))
})?
}
Some(proto_query_response::Outcome::Error(error)) => {
let error = WireError::try_from(error).map_err(|error| tool_failure(&error))?;
return Err(tool_failure(&error));
}
None => {
return Err(tool_failure(&WireError::backend(
"the query response carried no outcome",
)));
}
};
Ok(ToolOutcome {
summary: format!("query `{query_name}` answered by workflow {workflow_id}"),
structured: json!({
"workflow_id": workflow_id.to_string(),
"query_name": query_name,
"result": result,
}),
})
}
pub(crate) async fn cancel(
state: &ServerState,
caller: &CallerIdentity,
call: &ToolCall,
) -> Result<ToolOutcome, ToolFailure> {
let namespace = required_str(call, "namespace")?;
let workflow_id = required_workflow_id(call, "workflow_id")?;
let run_id = optional_run_id(call, "run_id")?;
let reason = optional_str(call, "reason").unwrap_or_default();
handlers::cancel(
state,
state.namespace_guard(),
caller,
ProtoCancelRequest {
namespace,
workflow_id: Some(ProtoWorkflowId {
uuid: workflow_id.to_string(),
}),
run_id: run_id.map(Into::into),
reason: reason.clone(),
},
)
.await
.map_err(|error| tool_failure(&error))?;
Ok(ToolOutcome {
summary: format!("workflow {workflow_id} cancelled"),
structured: json!({
"workflow_id": workflow_id.to_string(),
"cancelled": true,
"reason": reason,
}),
})
}