use serde_json::{Value, json};
use super::call::{GuardedToolCall, InteropError, ToolCallRequest};
use super::wire;
pub const DIALECT: &str = "mcp";
pub fn parse_tool_calls(payload: &Value) -> Result<Vec<ToolCallRequest>, InteropError> {
let requests: &[Value] = match payload {
Value::Array(items) => items,
Value::Object(_) => std::slice::from_ref(payload),
_ => {
return Err(InteropError::malformed(
DIALECT,
"expected a JSON-RPC request object or an array of them",
));
}
};
requests
.iter()
.filter(|request| is_tools_call(request))
.map(parse_request)
.collect()
}
pub fn is_tools_call(message: &Value) -> bool {
message.get("method").and_then(Value::as_str) == Some("tools/call")
}
fn parse_request(request: &Value) -> Result<ToolCallRequest, InteropError> {
let params = request
.get("params")
.ok_or_else(|| InteropError::malformed(DIALECT, "tools/call request has no 'params'"))?;
let name = wire::required_str(params, "name", DIALECT)?;
let arguments = wire::parse_arguments(params.get("arguments"), DIALECT, name)?;
let mut call = ToolCallRequest::new(name, arguments);
if let Some(id) = request.get("id").filter(|id| !id.is_null()) {
call = call.with_call_id(match id {
Value::String(s) => s.clone(),
other => other.to_string(),
});
}
Ok(call)
}
pub fn filter_tools(payload: &Value, keep: &dyn Fn(&str) -> bool) -> Value {
let name_of = |item: &Value| wire::optional_str(item, "name");
match payload.get("tools") {
Some(tools) => {
let mut result = payload.clone();
result["tools"] = wire::filter_tool_array(tools, name_of, keep);
result
}
None => wire::filter_tool_array(payload, name_of, keep),
}
}
pub fn denial(call: &GuardedToolCall) -> Option<Value> {
let id = match &call.request.call_id {
Some(raw) => raw
.parse::<i64>()
.map_or_else(|_| json!(raw), |number| json!(number)),
None => Value::Null,
};
denial_with_id(call, id)
}
pub fn denial_with_id(call: &GuardedToolCall, id: Value) -> Option<Value> {
call.denial_message().map(|text| {
json!({
"jsonrpc": "2.0",
"id": id,
"result": {
"content": [{"type": "text", "text": text}],
"isError": true,
},
})
})
}