use std::sync::Arc;
use coralstack_cmd_ipc::{CommandDef, ExecuteError, ExecuteErrorCode};
use rmcp::model::{CallToolResult, Content, ErrorCode, Tool};
use rmcp::ErrorData as McpError;
use serde_json::{Map, Value};
pub fn command_to_tool(def: &CommandDef) -> Tool {
let input_schema = def
.schema
.as_ref()
.and_then(|s| s.request.as_ref())
.and_then(value_as_object)
.unwrap_or_else(permissive_object_schema);
let description = def.description.clone().unwrap_or_default();
Tool::new(def.id.clone(), description, Arc::new(input_schema))
}
pub fn success_to_call_result(result: Option<Value>) -> CallToolResult {
match result {
None => CallToolResult::success(vec![]),
Some(Value::String(s)) => CallToolResult::success(vec![Content::text(s)]),
Some(other) => CallToolResult::structured(other),
}
}
pub fn execute_error_to_call_result(err: ExecuteError) -> CallToolResult {
let code_str = execute_error_code_str(err.code);
CallToolResult::structured_error(serde_json::json!({
"code": code_str,
"message": err.message,
}))
}
pub fn is_tool_not_found(err: &ExecuteError) -> bool {
matches!(err.code, ExecuteErrorCode::NotFound)
}
pub fn mcp_error_for_unknown_tool(name: &str) -> McpError {
McpError::new(
ErrorCode::INVALID_PARAMS,
format!("unknown tool: {name}"),
None,
)
}
fn execute_error_code_str(code: ExecuteErrorCode) -> &'static str {
match code {
ExecuteErrorCode::NotFound => "not_found",
ExecuteErrorCode::InvalidRequest => "invalid_request",
ExecuteErrorCode::InternalError => "internal_error",
ExecuteErrorCode::Timeout => "timeout",
ExecuteErrorCode::ChannelDisconnected => "channel_disconnected",
}
}
fn value_as_object(v: &Value) -> Option<Map<String, Value>> {
v.as_object().cloned()
}
fn permissive_object_schema() -> Map<String, Value> {
let mut m = Map::new();
m.insert("type".into(), Value::String("object".into()));
m.insert("additionalProperties".into(), Value::Bool(true));
m
}