use serde_json::Value;
pub(crate) fn legacy_props(tool_name: &str) -> serde_json::Map<String, Value> {
let defs = crate::mcp::registry::tool_definitions();
let tools = defs
.get("tools")
.and_then(Value::as_array)
.expect("tool_definitions emits `tools` array");
let entry = tools
.iter()
.find(|t| t.get("name").and_then(Value::as_str) == Some(tool_name))
.unwrap_or_else(|| panic!("{tool_name} must be in legacy catalog"));
entry
.pointer("/inputSchema/properties")
.and_then(Value::as_object)
.unwrap_or_else(|| panic!("{tool_name}.inputSchema.properties must be object"))
.clone()
}
pub(crate) fn derived_props_for<T: schemars::JsonSchema>() -> serde_json::Map<String, Value> {
let schema = schemars::schema_for!(T);
let v = serde_json::to_value(schema).expect("schema -> value");
if let Some(props) = v.get("properties").and_then(Value::as_object) {
return props.clone();
}
if let Some(props) = v
.pointer(&format!(
"/definitions/{}/properties",
std::any::type_name::<T>().rsplit("::").next().unwrap_or("")
))
.and_then(Value::as_object)
{
return props.clone();
}
serde_json::Map::new()
}
pub(crate) fn assert_property_set_parity(
tool_name: &str,
derived: &serde_json::Map<String, Value>,
) {
let legacy = legacy_props(tool_name);
let legacy_keys: std::collections::BTreeSet<&str> = legacy.keys().map(String::as_str).collect();
let derived_keys: std::collections::BTreeSet<&str> =
derived.keys().map(String::as_str).collect();
assert_eq!(
legacy_keys,
derived_keys,
"{tool_name}: property set drift; diff = {:?}",
legacy_keys
.symmetric_difference(&derived_keys)
.collect::<Vec<_>>()
);
}
pub(crate) fn assert_descriptions_match(tool_name: &str, derived: &serde_json::Map<String, Value>) {
let legacy = legacy_props(tool_name);
for (name, legacy_prop) in &legacy {
if let Some(want) = legacy_prop.get("description").and_then(Value::as_str) {
let got = derived
.get(name)
.and_then(|p| p.get("description"))
.and_then(Value::as_str);
assert_eq!(
got,
Some(want),
"{tool_name}.{name}: description must match legacy byte-for-byte"
);
}
}
}
#[cfg(test)]
mod self_tests {
use super::{
assert_descriptions_match, assert_property_set_parity, derived_props_for, legacy_props,
};
#[test]
fn helpers_round_trip_on_memory_delete() {
let legacy = legacy_props("memory_delete");
assert!(
!legacy.is_empty(),
"memory_delete must carry legacy properties"
);
let derived = derived_props_for::<crate::mcp::delete::DeleteRequest>();
assert_property_set_parity("memory_delete", &derived);
assert_descriptions_match("memory_delete", &derived);
}
#[test]
fn derived_props_for_empty_request_is_empty_map() {
#[derive(schemars::JsonSchema)]
#[allow(dead_code)]
struct EmptyReq {}
let derived = derived_props_for::<EmptyReq>();
assert!(derived.is_empty());
}
}