use serde::Serialize;
use serde_json::Value;
#[derive(Debug, Clone, Serialize)]
pub struct CommandDef {
pub name: String,
pub description: String,
pub params: Vec<ParamDef>,
}
#[derive(Debug, Clone, Serialize)]
pub struct ParamDef {
pub name: String,
pub description: String,
pub param_type: ParamType,
pub required: bool,
pub default: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum ParamType {
String,
Integer,
Number,
Boolean,
Array,
Object,
}
impl ParamType {
pub fn from_json_schema(schema_type: &str) -> Self {
match schema_type {
"integer" => ParamType::Integer,
"number" => ParamType::Number,
"boolean" => ParamType::Boolean,
"array" => ParamType::Array,
"object" => ParamType::Object,
_ => ParamType::String,
}
}
}
pub fn commands_from_mcp_tools(tools: &[rmcp::model::Tool]) -> Vec<CommandDef> {
tools
.iter()
.map(|tool| {
let params = extract_params_from_schema(tool.input_schema.as_ref());
CommandDef {
name: tool.name.to_string(),
description: tool.description.as_deref().unwrap_or("").to_string(),
params,
}
})
.collect()
}
fn extract_params_from_schema(schema: &serde_json::Map<String, Value>) -> Vec<ParamDef> {
let mut params = Vec::new();
let required_set: Vec<String> = schema
.get("required")
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect()
})
.unwrap_or_default();
if let Some(props) = schema.get("properties").and_then(|v| v.as_object()) {
for (name, prop_schema) in props {
let description = prop_schema
.get("description")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let param_type = prop_schema
.get("type")
.and_then(|v| v.as_str())
.map(ParamType::from_json_schema)
.unwrap_or(ParamType::String);
let default = prop_schema.get("default").map(|v| v.to_string());
params.push(ParamDef {
name: name.clone(),
description,
param_type,
required: required_set.contains(name),
default,
});
}
}
params.sort_by(|a, b| b.required.cmp(&a.required).then(a.name.cmp(&b.name)));
params
}