From 9ccaef2b6a1692f5b6327e6d54689b7a96330005 Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Sat, 7 Mar 2026 11:40:29 -0500 Subject: [PATCH] Raise ValueError for invalid boolean query params in resource templates (#3424) (#3434) --- src/fastmcp/resources/template.py | 12 +++- .../test_resource_template_query_params.py | 67 +++++++++++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/src/fastmcp/resources/template.py b/src/fastmcp/resources/template.py index c2fb1b622..f7750aea4 100644 --- a/src/fastmcp/resources/template.py +++ b/src/fastmcp/resources/template.py @@ -409,9 +409,17 @@ class FunctionResourceTemplate(ResourceTemplate): elif annotation is float: kwargs[param_name] = float(param_value) elif annotation is bool: - kwargs[param_name] = param_value.lower() in ("true", "1", "yes") + lower = param_value.lower() + if lower in ("true", "1", "yes"): + kwargs[param_name] = True + elif lower in ("false", "0", "no"): + kwargs[param_name] = False + else: + raise ValueError( + f"Invalid boolean value for {param_name}: {param_value!r}" + ) except (ValueError, AttributeError): - pass + raise # self.fn is wrapped by without_injected_parameters which handles # dependency resolution internally, so we call it directly diff --git a/tests/resources/test_resource_template_query_params.py b/tests/resources/test_resource_template_query_params.py index d68025f66..9d7f86b57 100644 --- a/tests/resources/test_resource_template_query_params.py +++ b/tests/resources/test_resource_template_query_params.py @@ -263,6 +263,73 @@ class TestQueryParameterWithWildcards: assert result["lines"] == 50 # provided +class TestBooleanQueryParameterValidation: + """Test that invalid boolean query parameter values raise errors.""" + + async def _make_template(self): + def get_config(name: str, enabled: bool = False) -> dict: + return {"name": name, "enabled": enabled} + + return ResourceTemplate.from_function( + fn=get_config, + uri_template="config://{name}{?enabled}", + name="test", + ) + + async def test_invalid_boolean_value_raises_error(self): + """Test that nonsense boolean values like 'banana' raise ValueError.""" + template = await self._make_template() + + with pytest.raises(ValueError, match="Invalid boolean value for enabled"): + resource = await template.create_resource( + "config://feature?enabled=banana", + {"name": "feature", "enabled": "banana"}, + ) + await resource.read() + + @pytest.mark.parametrize( + "value", ["true", "True", "TRUE", "1", "yes", "Yes", "YES"] + ) + async def test_valid_true_values(self, value: str): + """Test that all accepted truthy string values coerce to True.""" + template = await self._make_template() + + resource = await template.create_resource( + f"config://feature?enabled={value}", + {"name": "feature", "enabled": value}, + ) + result = await resource.read() + assert isinstance(result, dict) + assert result["enabled"] is True + + @pytest.mark.parametrize( + "value", ["false", "False", "FALSE", "0", "no", "No", "NO"] + ) + async def test_valid_false_values(self, value: str): + """Test that all accepted falsy string values coerce to False.""" + template = await self._make_template() + + resource = await template.create_resource( + f"config://feature?enabled={value}", + {"name": "feature", "enabled": value}, + ) + result = await resource.read() + assert isinstance(result, dict) + assert result["enabled"] is False + + @pytest.mark.parametrize("value", ["banana", "nope", "2", "truee", ""]) + async def test_various_invalid_boolean_values(self, value: str): + """Test that various invalid boolean strings raise ValueError.""" + template = await self._make_template() + + with pytest.raises(ValueError, match="Invalid boolean value for enabled"): + resource = await template.create_resource( + f"config://feature?enabled={value}", + {"name": "feature", "enabled": value}, + ) + await resource.read() + + class TestResourceTemplateFieldDefaults: """Test resource templates with Field() defaults."""