use elenchus_solver::{CompileError, verify_source};
use serde_json::{Value, json};
use crate::{messages, rpc};
const CHECK: &str = "elenchus_check";
const VERSION: &str = "elenchus_version";
const ABOUT: &str = "elenchus_about";
pub fn definitions() -> Vec<Value> {
vec![check_def(), version_def(), about_def()]
}
fn simple_def(name: &str, description: &str) -> Value {
json!({
"name": name,
"description": description,
"inputSchema": { "type": "object", "properties": {} }
})
}
fn check_def() -> Value {
json!({
"name": CHECK,
"description": messages::CHECK_TOOL,
"inputSchema": {
"type": "object",
"properties": {
"program": { "type": "string", "description": messages::CHECK_ARG_PROGRAM },
"format": {
"type": "string",
"enum": ["human", "json"],
"description": messages::CHECK_ARG_FORMAT
},
"max_classes": {
"type": "integer",
"minimum": 0,
"description": messages::CHECK_ARG_MAX_CLASSES
},
"max_per_class": {
"type": "integer",
"minimum": 0,
"description": messages::CHECK_ARG_MAX_PER_CLASS
}
},
"required": ["program"]
}
})
}
fn version_def() -> Value {
simple_def(VERSION, messages::VERSION_TOOL)
}
fn about_def() -> Value {
simple_def(ABOUT, messages::ABOUT_TOOL)
}
pub fn call(id: Value, params: Option<&Value>) -> Value {
let Some(params) = params else {
return rpc::error(id, -32602, "missing params");
};
let name = params.get("name").and_then(Value::as_str).unwrap_or("");
match name {
VERSION => rpc::tool_result(id, format!("elenchus {}", env!("CARGO_PKG_VERSION")), false),
ABOUT => rpc::tool_result(id, messages::ABOUT_TOOL.to_string(), false),
CHECK => check(id, params.get("arguments")),
other => rpc::tool_result(id, format!("unknown tool: {other}"), true),
}
}
fn check(id: Value, args: Option<&Value>) -> Value {
let Some(program) = args.and_then(|a| a.get("program")).and_then(Value::as_str) else {
return rpc::tool_result(id, "missing required argument: program".into(), true);
};
let format = args
.and_then(|a| a.get("format"))
.and_then(Value::as_str)
.unwrap_or("json");
let arg_limit = |name: &str| {
let n = args
.and_then(|a| a.get(name))
.and_then(Value::as_u64)
.unwrap_or(0) as usize;
(n > 0).then_some(n)
};
match verify_source("<mcp>", program) {
Ok(report) => {
let text = if format == "human" {
format!("{report}")
} else {
report.to_json()
};
rpc::tool_result(id, text, false)
}
Err(CompileError::Parse(diag)) => {
let text = diag.render(arg_limit("max_classes"), arg_limit("max_per_class"));
rpc::tool_result(id, text, true)
}
Err(other) => rpc::tool_result(id, other.to_string(), true),
}
}