feat(mcp): expose entity creation schemas on demand

Expose creation fields, constraints and examples for the 11 supported types without expanding startup tool schemas. Share the creation contract and constructor with execution to keep accepted inputs consistent.

Validation: MCP, contract/factory and entity creation regression tests.
This commit is contained in:
Karim Jerbi committed 2026-10-13 09:33:49 +01:00
1 parent a5435a6df6
commit 97afb57333
10 files changed
+1118 -488

No files matched your search

+16 -4
View File
@@ -39,6 +39,8 @@ pub enum Ty {
EnumCi(&'static [&'static str]),
ActionName,
ArrayOf(&'static Ty),
/// Entity definitions with on-demand creation schemas.
Entities,
/// Escape hatch: a JSON Schema fragment as text.
Raw(&'static str),
}
@@ -409,10 +411,8 @@ pub static ENTITIES_CREATE: OpDef = OpDef {
batchable: true,
params: &[req(
"entities",
Ty::ArrayOf(&Ty::Raw(
r#"{"type":"object","properties":{"type":{"type":"string"}},"required":["type"]}"#,
)),
"List of entity definitions to create.",
Ty::Entities,
"Entity definitions. Read ocs_read op entity_schema with parameters.type for fields and examples before creating an unfamiliar type.",
)],
rules: &[],
example: r#"{"op":"entities_create","entities":[{"type":"Line","start":[0,0],"end":[10,10]}]}"#,
@@ -930,6 +930,9 @@ fn ty_schema(ty: &Ty) -> Value {
json!({"type": "string", "enum": names})
}
Ty::ArrayOf(inner) => json!({"type": "array", "items": ty_schema(inner)}),
Ty::Entities => {
json!({"type":"array","minItems":1,"items":{"type":"object","required":["type"],"properties":{"type":{"type":"string"}}}})
}
Ty::Raw(s) => serde_json::from_str(s).expect("Raw fragment must be valid JSON"),
}
}
@@ -1147,6 +1150,15 @@ fn validate_type(ty: &Ty, val: &Value) -> Result<(), String> {
validate_type(inner, item).map_err(|e| format!("item {idx}: {e}"))?;
}
}
Ty::Entities => {
let entities = val
.as_array()
.filter(|a| !a.is_empty())
.ok_or("expected nonempty entities array")?;
for (index, entity) in entities.iter().enumerate() {
crate::entity_creation::validate_entity(entity, &format!("/entities/{index}"))?;
}
}
Ty::Raw(_) => {}
}
Ok(())