From 40adc38ef57ef4c7d8dc819d2efd70a82f11448f Mon Sep 17 00:00:00 2001 From: omerc7 Date: Sun, 23 Mar 2025 13:25:01 +0200 Subject: [PATCH 1/2] func metadata: override input with json.loads only when value is valid --- .../server/fastmcp/utilities/func_metadata.py | 17 +++++++++++++++-- tests/server/fastmcp/test_func_metadata.py | 8 ++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/mcp/server/fastmcp/utilities/func_metadata.py b/src/mcp/server/fastmcp/utilities/func_metadata.py index 25832620d..7b3787b21 100644 --- a/src/mcp/server/fastmcp/utilities/func_metadata.py +++ b/src/mcp/server/fastmcp/utilities/func_metadata.py @@ -7,7 +7,15 @@ ForwardRef, ) -from pydantic import BaseModel, ConfigDict, Field, WithJsonSchema, create_model +from pydantic import ( + BaseModel, + ConfigDict, + Field, + TypeAdapter, + ValidationError, + WithJsonSchema, + create_model, +) from pydantic._internal._typing_extra import eval_type_backport from pydantic.fields import FieldInfo from pydantic_core import PydanticUndefined @@ -93,7 +101,12 @@ def pre_parse_json(self, data: dict[str, Any]) -> dict[str, Any]: # Should really be parsed as '"hello"' in Python - but if we parse # it as JSON it'll turn into just 'hello'. So we skip it. continue - new_data[field_name] = pre_parsed + try: + # Validate parsed value + TypeAdapter(_field_info.annotation).validate_python(pre_parsed) + new_data[field_name] = pre_parsed + except ValidationError: + continue # Parsed value is invalid - skip assert new_data.keys() == data.keys() return new_data diff --git a/tests/server/fastmcp/test_func_metadata.py b/tests/server/fastmcp/test_func_metadata.py index 6461648eb..5120636c6 100644 --- a/tests/server/fastmcp/test_func_metadata.py +++ b/tests/server/fastmcp/test_func_metadata.py @@ -59,6 +59,7 @@ def complex_arguments_fn( must_be_none_with_default: None = None, an_int_with_equals_field: int = Field(1, ge=0), int_annotated_with_default: Annotated[int, Field(description="hey")] = 5, + num_annotated_with_str: str = "", ) -> str: _ = ( an_int, @@ -81,6 +82,7 @@ def complex_arguments_fn( must_be_none_with_default, an_int_with_equals_field, int_annotated_with_default, + num_annotated_with_str, ) return "ok!" @@ -107,6 +109,7 @@ async def test_complex_function_runtime_arg_validation_non_json(): "my_model_a": {}, "my_model_a_forward_ref": {}, "my_model_b": {"how_many_shrimp": 5, "ok": {"x": 1}, "y": None}, + "num_annotated_with_str": "5", }, arguments_to_pass_directly=None, ) @@ -381,6 +384,11 @@ def test_complex_function_json_schema(): "title": "Int Annotated With Default", "type": "integer", }, + "num_annotated_with_str": { + "default": "", + "title": "Num Annotated With Str", + "type": "string", + }, }, "required": [ "an_int", From 528afddf5d5beed084819817b158f294afa5ed7a Mon Sep 17 00:00:00 2001 From: omerc7 Date: Sun, 23 Mar 2025 14:58:41 +0200 Subject: [PATCH 2/2] keep original code structure --- src/mcp/server/fastmcp/utilities/func_metadata.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mcp/server/fastmcp/utilities/func_metadata.py b/src/mcp/server/fastmcp/utilities/func_metadata.py index 7b3787b21..6435eaa7e 100644 --- a/src/mcp/server/fastmcp/utilities/func_metadata.py +++ b/src/mcp/server/fastmcp/utilities/func_metadata.py @@ -104,9 +104,10 @@ def pre_parse_json(self, data: dict[str, Any]) -> dict[str, Any]: try: # Validate parsed value TypeAdapter(_field_info.annotation).validate_python(pre_parsed) - new_data[field_name] = pre_parsed except ValidationError: continue # Parsed value is invalid - skip + + new_data[field_name] = pre_parsed assert new_data.keys() == data.keys() return new_data