headless_lms_mcp/tools/
schema.rs1use schemars::{Schema, SchemaGenerator, json_schema};
5
6pub fn untyped_value(_: &mut SchemaGenerator) -> Schema {
9 json_schema!({
10 "description": "Value to write: a block or list of blocks, an entity, a field value or null."
11 })
12}
13
14pub fn input_schema<T: schemars::JsonSchema>() -> serde_json::Map<String, serde_json::Value> {
17 let settings =
18 schemars::generate::SchemaSettings::draft2020_12().with(|s| s.inline_subschemas = true);
19 let schema = settings.into_generator().into_root_schema_for::<T>();
20 let mut value = schema.to_value();
21 flatten_unions(&mut value);
22 let mut object = match value {
23 serde_json::Value::Object(object) => object,
24 _ => serde_json::Map::new(),
25 };
26 object.remove("title");
27 object.remove("description");
28 object
29}
30
31fn flatten_unions(value: &mut serde_json::Value) {
34 use serde_json::Value;
35 match value {
36 Value::Object(object) => {
37 if let Some(Value::Array(types)) = object.get("type") {
38 let non_null: Vec<Value> = types
39 .iter()
40 .filter(|t| t.as_str() != Some("null"))
41 .cloned()
42 .collect();
43 if non_null.len() == 1 {
44 object.insert("type".into(), non_null[0].clone());
45 }
46 }
47 for key in ["anyOf", "oneOf"] {
48 if let Some(Value::Array(alternatives)) = object.get(key) {
49 let non_null: Vec<Value> = alternatives
50 .iter()
51 .filter(|a| a.get("type").and_then(Value::as_str) != Some("null"))
52 .cloned()
53 .collect();
54 if non_null.len() == 1 {
55 object.remove(key);
56 if let Value::Object(inner) = &non_null[0] {
57 for (k, v) in inner {
58 object.entry(k.clone()).or_insert_with(|| v.clone());
59 }
60 }
61 }
62 }
63 }
64 if let Some(Value::Array(alternatives)) = object.get("oneOf") {
65 let constants: Option<Vec<Value>> = alternatives
66 .iter()
67 .map(|a| a.get("const").cloned())
68 .collect();
69 if let Some(constants) = constants.filter(|c| c.iter().all(Value::is_string)) {
70 let described: Vec<String> = alternatives
71 .iter()
72 .filter_map(|a| {
73 Some(format!(
74 "{}: {}",
75 a.get("const")?.as_str()?,
76 a.get("description")?.as_str()?.trim_end_matches('.')
77 ))
78 })
79 .collect();
80 object.remove("oneOf");
81 object.insert("type".into(), Value::String("string".into()));
82 object.insert("enum".into(), Value::Array(constants));
83 if !described.is_empty() && !object.contains_key("description") {
84 object.insert("description".into(), Value::String(described.join("; ")));
85 }
86 }
87 }
88 if object.get("default").is_some_and(Value::is_null) {
89 object.remove("default");
90 }
91 if let Some(Value::Array(options)) = object.get_mut("enum") {
92 options.retain(|o| !o.is_null());
93 }
94 object.values_mut().for_each(flatten_unions);
95 }
96 Value::Array(items) => items.iter_mut().for_each(flatten_unions),
97 _ => {}
98 }
99}