diff --git a/src/mcp/server/mcpserver/utilities/func_metadata.py b/src/mcp/server/mcpserver/utilities/func_metadata.py index e5721488f5..fd84067276 100644 --- a/src/mcp/server/mcpserver/utilities/func_metadata.py +++ b/src/mcp/server/mcpserver/utilities/func_metadata.py @@ -454,32 +454,31 @@ def func_metadata( # structured_output=True still forces one. return FuncMetadata(arg_model=arguments_model) - output_model, wrap_output = _create_output_model(original_annotation, return_type_expr, func.__name__) - - if output_model is not None: - try: + try: + output_model, wrap_output = _create_output_model(original_annotation, return_type_expr, func.__name__) + if output_model is not None: # FuncMetadata builds the validator and schema on construction, so an unsupported return type # surfaces here, at registration, rather than on the first call. return FuncMetadata(arg_model=arguments_model, output_model=output_model, wrap_output=wrap_output) - except ( - PydanticUserError, - ForbiddenQualifier, - NameError, - TypeError, - ValueError, - pydantic_core.SchemaError, - pydantic_core.ValidationError, - ) as e: - # These are expected errors when a type can't be converted to a Pydantic schema - # PydanticUserError: When Pydantic can't handle the type (e.g. PydanticInvalidForJsonSchema); - # subclasses TypeError on pydantic <2.13 and RuntimeError on pydantic >=2.13 - # ForbiddenQualifier, NameError: an invalid qualifier or unresolvable annotation on a TypedDict key, - # met while rebuilding a stdlib TypedDict below 3.12 (pydantic reports both as PydanticUserError) - # ValueError: When there are issues with the type definition (including our custom warnings); - # arrives wrapped in a ValidationError when raised during FuncMetadata construction - # SchemaError: When Pydantic can't build a schema - # ValidationError: When validation fails - logger.info(f"Cannot create schema for type {return_type_expr} in {func.__name__}: {type(e).__name__}: {e}") + except ( + PydanticUserError, + ForbiddenQualifier, + NameError, + TypeError, + ValueError, + pydantic_core.SchemaError, + pydantic_core.ValidationError, + ) as e: + # These are expected errors when a type can't be converted to a Pydantic schema + # PydanticUserError: When Pydantic can't handle the type (e.g. PydanticInvalidForJsonSchema); + # subclasses TypeError on pydantic <2.13 and RuntimeError on pydantic >=2.13 + # ForbiddenQualifier, NameError: an invalid qualifier or unresolvable annotation on a TypedDict key, + # met while rebuilding a stdlib TypedDict below 3.12 (pydantic reports both as PydanticUserError) + # ValueError: When there are issues with the type definition (including our custom warnings); + # arrives wrapped in a ValidationError when raised during FuncMetadata construction + # SchemaError: When Pydantic can't build a schema + # ValidationError: When validation fails + logger.info(f"Cannot create schema for type {return_type_expr} in {func.__name__}: {type(e).__name__}: {e}") if structured_output is True: # Model creation failed or produced warnings - no structured output diff --git a/tests/server/mcpserver/test_func_metadata.py b/tests/server/mcpserver/test_func_metadata.py index f929fb3954..daccc39836 100644 --- a/tests/server/mcpserver/test_func_metadata.py +++ b/tests/server/mcpserver/test_func_metadata.py @@ -6,7 +6,7 @@ # pyright: reportMissingTypeArgument=false # pyright: reportUnknownVariableType=false import typing -from collections.abc import Callable +from collections.abc import Callable, Iterator from dataclasses import dataclass from typing import TYPE_CHECKING, Annotated, Any, Final, NamedTuple, NotRequired, Required, TypedDict @@ -1393,6 +1393,15 @@ def func_returning_namedtuple() -> Point: # pragma: no cover assert "Point" in str(exc_info.value) +def test_structured_output_iterator_falls_back_to_unstructured() -> None: + def search() -> Iterator[str]: + yield 'result' + + assert func_metadata(search).output_schema is None + with pytest.raises(InvalidSignature, match='is not serializable for structured output'): + func_metadata(search, structured_output=True) + + def test_structured_output_aliases(): """Test that field aliases are consistent between schema and output"""