From 65695fb878feb0eac55ccecf57a35cb08e04e3cf Mon Sep 17 00:00:00 2001 From: Bruno Postle Date: Mon, 13 Jul 2026 08:55:43 +0100 Subject: [PATCH] ifcedit: fix Optional[entity_instance] coercion crash on native JSON values coerce_value assumed value_str was always a CLI string, but ifcmcp passes JSON-decoded native types (int, None) straight through. Guard the Union/Optional "none" check so it only calls .lower() on strings, and handle native None explicitly. --- src/ifcedit/ifcedit/coerce.py | 4 +++- src/ifcedit/tests/test_coerce.py | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/ifcedit/ifcedit/coerce.py b/src/ifcedit/ifcedit/coerce.py index 5a410ec186..319257399d 100644 --- a/src/ifcedit/ifcedit/coerce.py +++ b/src/ifcedit/ifcedit/coerce.py @@ -60,9 +60,11 @@ def coerce_value( # Union / Optional if origin is typing.Union: non_none_types = [a for a in args if a is not type(None)] - if value_str.lower() == "none": + if isinstance(value_str, str) and value_str.lower() == "none": if type(None) in args: return None + if value_str is None and type(None) in args: + return None # Try each non-None type in order for t in non_none_types: try: diff --git a/src/ifcedit/tests/test_coerce.py b/src/ifcedit/tests/test_coerce.py index 4ee6895717..907c81a5dd 100644 --- a/src/ifcedit/tests/test_coerce.py +++ b/src/ifcedit/tests/test_coerce.py @@ -57,6 +57,16 @@ class TestOptionalCoercion: def test_optional_int(self): assert coerce_value("42", Optional[int]) == 42 + def test_optional_entity_native_int(self, model): + # MCP callers pass JSON-decoded native types (int), not CLI strings. + wall = model.by_type("IfcWall")[0] + result = coerce_value(wall.id(), Optional[ifcopenshell.entity_instance], model) + assert result == wall + + def test_optional_entity_native_none(self, model): + # JSON null decodes to Python None, not the string "none". + assert coerce_value(None, Optional[ifcopenshell.entity_instance], model) is None + class TestUnionCoercion: def test_union_str_int(self):