diff --git a/fastmcp_slim/fastmcp/utilities/json_schema.py b/fastmcp_slim/fastmcp/utilities/json_schema.py index 4e71fc102..d84a3307f 100644 --- a/fastmcp_slim/fastmcp/utilities/json_schema.py +++ b/fastmcp_slim/fastmcp/utilities/json_schema.py @@ -281,6 +281,16 @@ def resolve_root_ref(schema: dict[str, Any]) -> dict[str, Any]: if def_name in defs: # Create a new schema by copying the referenced definition resolved = dict(defs[def_name]) + + # Preserve root-level sibling metadata from the original schema. + # Pydantic may put user-facing fields such as title, description, + # default, or examples next to the root $ref. Those fields still + # describe the root schema even when we can only resolve that + # root reference for circular schemas. + for key, value in schema.items(): + if key not in {"$ref", "$defs"}: + resolved[key] = value + # Preserve $defs for nested references (other fields may still use them) resolved["$defs"] = defs return resolved diff --git a/tests/utilities/test_json_schema.py b/tests/utilities/test_json_schema.py index 28a898af6..014a7dea3 100644 --- a/tests/utilities/test_json_schema.py +++ b/tests/utilities/test_json_schema.py @@ -120,6 +120,9 @@ class TestDereferenceRefs: } }, "$ref": "#/$defs/Node", + "title": "Tree node", + "description": "A recursive tree node.", + "examples": [{"children": []}], } result = dereference_refs(schema) @@ -127,6 +130,9 @@ class TestDereferenceRefs: # Root should be resolved but nested refs preserved assert result.get("type") == "object" assert "$defs" in result # $defs preserved for circular refs + assert result["title"] == "Tree node" + assert result["description"] == "A recursive tree node." + assert result["examples"] == [{"children": []}] def test_falls_back_for_circular_json_pointer_refs(self): """Test that circular JSON Pointer $ref (non-$defs) does not crash.