diff --git a/fastmcp_slim/fastmcp/utilities/json_schema_type.py b/fastmcp_slim/fastmcp/utilities/json_schema_type.py index de1db2900..b3e64ffb5 100644 --- a/fastmcp_slim/fastmcp/utilities/json_schema_type.py +++ b/fastmcp_slim/fastmcp/utilities/json_schema_type.py @@ -269,13 +269,6 @@ def _create_string_type(schema: Mapping[str, Any]) -> type | Annotated[Any, ...] if "const" in schema: return Literal[schema["const"]] # type: ignore - if fmt := schema.get("format"): - if fmt == "uri": - return AnyUrl - elif fmt == "uri-reference": - return str - return FORMAT_TYPES.get(fmt, str) - constraints = { k: v for k, v in { @@ -286,7 +279,22 @@ def _create_string_type(schema: Mapping[str, Any]) -> type | Annotated[Any, ...] if v is not None } - if not constraints: + # Resolve format first. Non-str formats (date-time, email, uri, json) do not + # take length/pattern constraints. Str-backed formats (unknown custom names + # and uri-reference) must still honor minLength/maxLength/pattern. + if fmt := schema.get("format"): + if fmt == "uri": + return AnyUrl + if fmt == "uri-reference": + base: type | Annotated[Any, ...] = str + else: + base = FORMAT_TYPES.get(fmt, str) + if base is not str: + return base + if not constraints: + return str + # Fall through to apply StringConstraints on the str-backed format. + elif not constraints: return str annotated: Any = Annotated[str, StringConstraints(**constraints)] diff --git a/tests/utilities/json_schema_type/test_formats.py b/tests/utilities/json_schema_type/test_formats.py index 781b81a82..b36bafd36 100644 --- a/tests/utilities/json_schema_type/test_formats.py +++ b/tests/utilities/json_schema_type/test_formats.py @@ -112,3 +112,22 @@ class TestFormatTypes: ) assert isinstance(result.full_uri, AnyUrl) assert isinstance(result.ref_uri, str) + + def test_str_backed_format_keeps_max_length(self): + """Unknown/custom formats fall back to str and must still enforce length.""" + t = json_schema_to_type( + {"type": "string", "format": "phone", "maxLength": 3} + ) + validator = TypeAdapter(t) + assert validator.validate_python("123") == "123" + with pytest.raises(ValidationError): + validator.validate_python("abcd") + + def test_uri_reference_format_keeps_min_length(self): + t = json_schema_to_type( + {"type": "string", "format": "uri-reference", "minLength": 2} + ) + validator = TypeAdapter(t) + assert validator.validate_python("/a") == "/a" + with pytest.raises(ValidationError): + validator.validate_python("")