use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use rmcp::handler::server::router::tool::ToolRouter;
use rmcp::model::{JsonObject, ListToolsResult, Tool};
use serde_json::{Map, Value};
const ANNOTATIONS: &[&str] = &[
"title",
"description",
"default",
"examples",
"deprecated",
"readOnly",
"writeOnly",
"$comment",
];
const ONE_SUBSCHEMA: &[&str] = &[
"items",
"additionalProperties",
"unevaluatedProperties",
"unevaluatedItems",
"contains",
"propertyNames",
"not",
"if",
"then",
"else",
];
const SUBSCHEMA_ARRAYS: &[&str] = &["anyOf", "oneOf", "allOf", "prefixItems"];
const SUBSCHEMA_MAPS: &[&str] = &["patternProperties", "dependentSchemas", "$defs"];
pub fn compact(schema: &mut JsonObject) {
compact_with(schema, true);
}
pub fn compact_output(schema: &mut JsonObject) {
compact_with(schema, false);
}
pub fn compact_tool(tool: &mut Tool) {
compact(Arc::make_mut(&mut tool.input_schema));
if let Some(output) = tool.output_schema.as_mut() {
compact_output(Arc::make_mut(output));
}
}
pub fn compact_tools(tools: &mut [Tool]) {
tools.iter_mut().for_each(compact_tool);
}
pub fn compact_list(result: &mut ListToolsResult) {
compact_tools(&mut result.tools);
}
pub fn compact_router<S>(router: &mut ToolRouter<S>) {
for route in router.map.values_mut() {
compact_tool(&mut route.attr);
}
}
pub trait CompactSchemas {
#[must_use]
fn compact_schemas(self) -> Self;
}
impl<S> CompactSchemas for ToolRouter<S> {
fn compact_schemas(mut self) -> Self {
compact_router(&mut self);
self
}
}
impl CompactSchemas for Vec<Tool> {
fn compact_schemas(mut self) -> Self {
compact_tools(&mut self);
self
}
}
impl CompactSchemas for ListToolsResult {
fn compact_schemas(mut self) -> Self {
compact_list(&mut self);
self
}
}
fn compact_with(schema: &mut JsonObject, strip_null: bool) {
schema.remove("$schema");
inline_defs(schema);
walk(schema, strip_null);
}
fn walk(node: &mut Map<String, Value>, strip_null: bool) {
if node.get("default").is_some_and(Value::is_null) {
node.remove("default");
}
if node
.get("title")
.and_then(Value::as_str)
.is_some_and(|t| t.trim().is_empty())
{
node.remove("title");
}
drop_numeric_format(node);
let required: HashSet<String> = node
.get("required")
.and_then(Value::as_array)
.map(|r| {
r.iter()
.filter_map(Value::as_str)
.map(String::from)
.collect()
})
.unwrap_or_default();
if let Some(Value::Object(props)) = node.get_mut("properties") {
for (name, prop) in props.iter_mut() {
let Value::Object(prop) = prop else { continue };
if prop
.get("title")
.and_then(Value::as_str)
.is_some_and(|t| same_name(t, name))
{
prop.remove("title");
}
if strip_null && !required.contains(name) {
drop_null(prop);
}
walk(prop, strip_null);
}
}
for key in ONE_SUBSCHEMA {
if let Some(Value::Object(sub)) = node.get_mut(*key) {
walk(sub, strip_null);
}
}
for key in SUBSCHEMA_ARRAYS {
if let Some(Value::Array(subs)) = node.get_mut(*key) {
for sub in subs.iter_mut().filter_map(Value::as_object_mut) {
walk(sub, strip_null);
}
}
}
for key in SUBSCHEMA_MAPS {
if let Some(Value::Object(subs)) = node.get_mut(*key) {
for sub in subs.values_mut().filter_map(Value::as_object_mut) {
walk(sub, strip_null);
}
}
}
}
fn same_name(title: &str, name: &str) -> bool {
let norm = |s: &str| -> String {
s.chars()
.filter(char::is_ascii_alphanumeric)
.map(|c| c.to_ascii_lowercase())
.collect()
};
norm(title) == norm(name)
}
fn drop_numeric_format(node: &mut Map<String, Value>) {
let Some(format) = node.get("format").and_then(Value::as_str) else {
return;
};
let unsigned = match format {
"uint" | "uint8" | "uint16" | "uint32" | "uint64" | "uint128" => true,
"int" | "int8" | "int16" | "int32" | "int64" | "int128" | "float" | "double" => false,
_ => return,
};
node.remove("format");
if unsigned && node.get("minimum").and_then(Value::as_f64) == Some(0.0) {
node.remove("minimum");
}
}
fn is_null_only(schema: &Value) -> bool {
let Some(obj) = schema.as_object() else {
return false;
};
let mut validation = obj
.iter()
.filter(|(k, _)| !ANNOTATIONS.contains(&k.as_str()));
let Some((key, value)) = validation.next() else {
return false;
};
validation.next().is_none()
&& match key.as_str() {
"type" => value == "null",
"const" => value.is_null(),
"enum" => value
.as_array()
.is_some_and(|v| v.len() == 1 && v[0].is_null()),
_ => false,
}
}
fn drop_null(prop: &mut Map<String, Value>) {
if let Some(Value::Array(types)) = prop.get_mut("type")
&& types.len() > 1
&& types.iter().any(|t| t == "null")
{
types.retain(|t| t != "null");
if types.len() == 1 {
let only = types.remove(0);
prop.insert("type".into(), only);
}
}
if let Some(Value::Array(values)) = prop.get_mut("enum")
&& values.len() > 1
&& values.iter().any(Value::is_null)
{
values.retain(|v| !v.is_null());
}
for key in ["anyOf", "oneOf"] {
let Some(Value::Array(branches)) = prop.get_mut(key) else {
continue;
};
if branches.len() < 2 || !branches.iter().any(is_null_only) {
continue;
}
branches.retain(|b| !is_null_only(b));
if branches.len() == 1 {
let Some(Value::Object(only)) = branches.pop() else {
continue;
};
prop.remove(key);
if !merge_into(prop, &only) {
prop.insert(key.into(), Value::Array(vec![Value::Object(only)]));
}
}
}
}
fn merge_into(outer: &mut Map<String, Value>, inner: &Map<String, Value>) -> bool {
let clash = inner
.iter()
.any(|(k, v)| !ANNOTATIONS.contains(&k.as_str()) && outer.get(k).is_some_and(|o| o != v));
if clash {
return false;
}
for (k, v) in inner {
outer.entry(k.clone()).or_insert_with(|| v.clone());
}
true
}
fn inline_defs(root: &mut JsonObject) {
let mut keep: HashSet<String> = HashSet::new();
loop {
let Some(Value::Object(defs)) = root.get("$defs") else {
return;
};
let counts = ref_counts(root);
let candidate = defs
.iter()
.find(|(name, body)| {
!keep.contains(*name) && counts.get(*name) == Some(&1) && !contains_any_ref(body)
})
.map(|(name, _)| name.clone());
let Some(name) = candidate else { break };
let Some(Value::Object(defs)) = root.get_mut("$defs") else {
return;
};
let Some(Value::Object(mut body)) = defs.remove(&name) else {
keep.insert(name);
continue;
};
if body
.get("title")
.and_then(Value::as_str)
.is_some_and(|t| same_name(t, &name))
{
body.remove("title");
}
let target = def_pointer(&name);
if !replace_ref(root, &target, &body) {
if let Some(Value::Object(defs)) = root.get_mut("$defs") {
defs.insert(name.clone(), Value::Object(body));
}
keep.insert(name);
}
}
loop {
let counts = ref_counts(root);
let Some(Value::Object(defs)) = root.get_mut("$defs") else {
return;
};
let before = defs.len();
defs.retain(|name, _| counts.contains_key(name));
if defs.is_empty() {
root.remove("$defs");
return;
}
if defs.len() == before {
return;
}
}
}
const DEFS_PREFIX: &str = "#/$defs/";
fn def_pointer(name: &str) -> String {
format!(
"{DEFS_PREFIX}{}",
name.replace('~', "~0").replace('/', "~1")
)
}
fn def_name(reference: &str) -> Option<String> {
let rest = reference.strip_prefix(DEFS_PREFIX)?;
(!rest.contains('/')).then(|| rest.replace("~1", "/").replace("~0", "~"))
}
fn ref_counts(root: &JsonObject) -> HashMap<String, usize> {
let mut counts = HashMap::new();
for value in root.values() {
count_refs(value, &mut counts);
}
if let Some(name) = root.get("$ref").and_then(Value::as_str).and_then(def_name) {
*counts.entry(name).or_default() += 1;
}
counts
}
fn count_refs(value: &Value, counts: &mut HashMap<String, usize>) {
match value {
Value::Object(obj) => {
if let Some(name) = obj.get("$ref").and_then(Value::as_str).and_then(def_name) {
*counts.entry(name).or_default() += 1;
}
obj.values().for_each(|v| count_refs(v, counts));
}
Value::Array(items) => items.iter().for_each(|v| count_refs(v, counts)),
_ => {}
}
}
fn contains_any_ref(value: &Value) -> bool {
match value {
Value::Object(obj) => obj.contains_key("$ref") || obj.values().any(contains_any_ref),
Value::Array(items) => items.iter().any(contains_any_ref),
_ => false,
}
}
fn replace_ref(root: &mut JsonObject, target: &str, body: &Map<String, Value>) -> bool {
fn visit(value: &mut Value, target: &str, body: &Map<String, Value>) -> Option<bool> {
match value {
Value::Object(obj) => {
if obj.get("$ref").and_then(Value::as_str) == Some(target) {
let mut siblings = obj.clone();
siblings.remove("$ref");
let mut inlined = body.clone();
for (k, v) in &siblings {
if ANNOTATIONS.contains(&k.as_str()) {
inlined.insert(k.clone(), v.clone());
}
}
if !merge_into(&mut inlined, &siblings) {
return Some(false);
}
*obj = inlined;
return Some(true);
}
obj.iter_mut()
.filter(|(k, _)| k.as_str() != "$defs")
.find_map(|(_, v)| visit(v, target, body))
}
Value::Array(items) => items.iter_mut().find_map(|v| visit(v, target, body)),
_ => None,
}
}
root.iter_mut()
.filter(|(k, _)| k.as_str() != "$defs")
.find_map(|(_, v)| visit(v, target, body))
.unwrap_or(false)
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn compacted(schema: Value) -> Value {
let Value::Object(mut obj) = schema else {
panic!("not an object")
};
compact(&mut obj);
Value::Object(obj)
}
#[test]
fn optional_integer_loses_boilerplate() {
let before = json!({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"path": { "type": "string", "description": "File to read." },
"end_line": { "default": null, "description": "Last line.",
"format": "uint", "minimum": 0, "type": ["integer", "null"] }
},
"required": ["path"]
});
assert_eq!(
compacted(before),
json!({
"type": "object",
"properties": {
"path": { "type": "string", "description": "File to read." },
"end_line": { "description": "Last line.", "type": "integer" }
},
"required": ["path"]
})
);
}
#[test]
fn required_nullable_and_real_bounds_stay() {
let before = json!({
"type": "object",
"properties": {
"a": { "type": ["string", "null"] },
"n": { "type": "integer", "format": "uint32", "minimum": 1, "maximum": 300 },
"b": { "type": "integer", "format": "uint8", "minimum": 0, "maximum": 255 },
"s": { "type": "integer", "format": "int16", "minimum": -32768, "maximum": 32767 },
"f": { "type": "boolean", "default": false },
"e": { "type": "string", "format": "email" }
},
"required": ["a"],
"additionalProperties": false
});
assert_eq!(
compacted(before),
json!({
"type": "object",
"properties": {
"a": { "type": ["string", "null"] },
"n": { "type": "integer", "minimum": 1, "maximum": 300 },
"b": { "type": "integer", "maximum": 255 },
"s": { "type": "integer", "minimum": -32768, "maximum": 32767 },
"f": { "type": "boolean", "default": false },
"e": { "type": "string", "format": "email" }
},
"required": ["a"],
"additionalProperties": false
})
);
}
#[test]
fn titles_that_repeat_the_name_go() {
let before = json!({ "type": "object", "properties": {
"max_hits": { "title": "Max Hits", "type": "integer" },
"q": { "title": "", "type": "string" },
"r": { "title": "Search pattern", "type": "string" }
}});
assert_eq!(
compacted(before),
json!({ "type": "object", "properties": {
"max_hits": { "type": "integer" },
"q": { "type": "string" },
"r": { "title": "Search pattern", "type": "string" }
}})
);
}
#[test]
fn single_use_definition_is_inlined_and_reference_annotations_win() {
let before = json!({
"$defs": { "Mode": { "description": "How.", "type": "string",
"enum": ["a", "b"] } },
"type": "object",
"properties": { "mode": { "$ref": "#/$defs/Mode", "default": "a",
"description": "Pick one." } }
});
assert_eq!(
compacted(before),
json!({ "type": "object", "properties": { "mode": {
"description": "Pick one.", "type": "string", "enum": ["a", "b"],
"default": "a" } } })
);
}
#[test]
fn optional_reference_drops_its_null_branch() {
let before = json!({
"$defs": { "Ref": { "description": "A project.",
"anyOf": [ { "type": "integer", "format": "int64" }, { "type": "string" } ] } },
"type": "object",
"properties": { "project": { "description": "Only this project.",
"anyOf": [ { "$ref": "#/$defs/Ref" }, { "type": "null" } ] } }
});
assert_eq!(
compacted(before),
json!({ "type": "object", "properties": { "project": {
"description": "Only this project.",
"anyOf": [ { "type": "integer" }, { "type": "string" } ] } } })
);
}
#[test]
fn shared_and_recursive_definitions_stay() {
let before = json!({
"$defs": {
"P": { "type": "string" },
"Tree": { "type": "object", "properties": {
"kids": { "type": "array", "items": { "$ref": "#/$defs/Tree" } } } },
"Unused": { "type": "string" }
},
"type": "object",
"properties": {
"a": { "$ref": "#/$defs/P" },
"b": { "$ref": "#/$defs/P" },
"t": { "$ref": "#/$defs/Tree" }
}
});
let after = compacted(before);
let defs = after["$defs"].as_object().unwrap();
assert!(
defs.contains_key("P") && defs.contains_key("Tree"),
"{after}"
);
assert!(!defs.contains_key("Unused"), "{after}");
assert_eq!(after["properties"]["a"], json!({ "$ref": "#/$defs/P" }));
}
#[test]
fn clashing_reference_is_left_alone() {
let before = json!({
"$defs": { "S": { "type": "string" } },
"type": "object",
"properties": { "x": { "$ref": "#/$defs/S", "type": "integer" } }
});
let after = compacted(before.clone());
assert_eq!(after["properties"]["x"]["$ref"], "#/$defs/S");
assert!(after["$defs"]["S"].is_object());
}
#[test]
fn nested_objects_items_and_enums() {
let before = json!({ "type": "object", "properties": {
"inputs": { "type": "array", "items": { "type": "object",
"properties": {
"pages": { "default": null, "type": ["string", "null"] },
"path": { "type": "string" } },
"required": ["path"] } },
"color": { "type": ["string", "null"], "enum": ["red", "blue", null] }
}});
assert_eq!(
compacted(before),
json!({ "type": "object", "properties": {
"inputs": { "type": "array", "items": { "type": "object",
"properties": { "pages": { "type": "string" },
"path": { "type": "string" } },
"required": ["path"] } },
"color": { "type": "string", "enum": ["red", "blue"] }
}})
);
}
#[test]
fn output_schema_keeps_null() {
let mut out = json!({ "$schema": "x", "type": "object", "properties": {
"n": { "type": ["integer", "null"], "format": "uint", "minimum": 0,
"default": null } } })
.as_object()
.unwrap()
.clone();
compact_output(&mut out);
assert_eq!(
Value::Object(out),
json!({ "type": "object", "properties": { "n": { "type": ["integer", "null"] } } })
);
}
#[test]
fn compaction_is_idempotent() {
let before = json!({ "$schema": "x", "type": "object", "properties": {
"a": { "type": ["integer", "null"], "format": "uint", "minimum": 0 } } });
let once = compacted(before);
assert_eq!(compacted(once.clone()), once);
}
}