Skip to content

Commit 90fdaee

Browse files
committed
fix: handle unserializable iterator return annotations
1 parent 77001c9 commit 90fdaee

2 files changed

Lines changed: 32 additions & 24 deletions

File tree

‎src/mcp/server/mcpserver/utilities/func_metadata.py‎

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -454,32 +454,31 @@ def func_metadata(
454454
# structured_output=True still forces one.
455455
return FuncMetadata(arg_model=arguments_model)
456456

457-
output_model, wrap_output = _create_output_model(original_annotation, return_type_expr, func.__name__)
458-
459-
if output_model is not None:
460-
try:
457+
try:
458+
output_model, wrap_output = _create_output_model(original_annotation, return_type_expr, func.__name__)
459+
if output_model is not None:
461460
# FuncMetadata builds the validator and schema on construction, so an unsupported return type
462461
# surfaces here, at registration, rather than on the first call.
463462
return FuncMetadata(arg_model=arguments_model, output_model=output_model, wrap_output=wrap_output)
464-
except (
465-
PydanticUserError,
466-
ForbiddenQualifier,
467-
NameError,
468-
TypeError,
469-
ValueError,
470-
pydantic_core.SchemaError,
471-
pydantic_core.ValidationError,
472-
) as e:
473-
# These are expected errors when a type can't be converted to a Pydantic schema
474-
# PydanticUserError: When Pydantic can't handle the type (e.g. PydanticInvalidForJsonSchema);
475-
# subclasses TypeError on pydantic <2.13 and RuntimeError on pydantic >=2.13
476-
# ForbiddenQualifier, NameError: an invalid qualifier or unresolvable annotation on a TypedDict key,
477-
# met while rebuilding a stdlib TypedDict below 3.12 (pydantic reports both as PydanticUserError)
478-
# ValueError: When there are issues with the type definition (including our custom warnings);
479-
# arrives wrapped in a ValidationError when raised during FuncMetadata construction
480-
# SchemaError: When Pydantic can't build a schema
481-
# ValidationError: When validation fails
482-
logger.info(f"Cannot create schema for type {return_type_expr} in {func.__name__}: {type(e).__name__}: {e}")
463+
except (
464+
PydanticUserError,
465+
ForbiddenQualifier,
466+
NameError,
467+
TypeError,
468+
ValueError,
469+
pydantic_core.SchemaError,
470+
pydantic_core.ValidationError,
471+
) as e:
472+
# These are expected errors when a type can't be converted to a Pydantic schema
473+
# PydanticUserError: When Pydantic can't handle the type (e.g. PydanticInvalidForJsonSchema);
474+
# subclasses TypeError on pydantic <2.13 and RuntimeError on pydantic >=2.13
475+
# ForbiddenQualifier, NameError: an invalid qualifier or unresolvable annotation on a TypedDict key,
476+
# met while rebuilding a stdlib TypedDict below 3.12 (pydantic reports both as PydanticUserError)
477+
# ValueError: When there are issues with the type definition (including our custom warnings);
478+
# arrives wrapped in a ValidationError when raised during FuncMetadata construction
479+
# SchemaError: When Pydantic can't build a schema
480+
# ValidationError: When validation fails
481+
logger.info(f"Cannot create schema for type {return_type_expr} in {func.__name__}: {type(e).__name__}: {e}")
483482

484483
if structured_output is True:
485484
# Model creation failed or produced warnings - no structured output

‎tests/server/mcpserver/test_func_metadata.py‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# pyright: reportMissingTypeArgument=false
77
# pyright: reportUnknownVariableType=false
88
import typing
9-
from collections.abc import Callable
9+
from collections.abc import Callable, Iterator
1010
from dataclasses import dataclass
1111
from typing import TYPE_CHECKING, Annotated, Any, Final, NamedTuple, NotRequired, Required, TypedDict
1212

@@ -1393,6 +1393,15 @@ def func_returning_namedtuple() -> Point: # pragma: no cover
13931393
assert "Point" in str(exc_info.value)
13941394

13951395

1396+
def test_structured_output_iterator_falls_back_to_unstructured() -> None:
1397+
def search() -> Iterator[str]:
1398+
yield 'result'
1399+
1400+
assert func_metadata(search).output_schema is None
1401+
with pytest.raises(InvalidSignature, match='is not serializable for structured output'):
1402+
func_metadata(search, structured_output=True)
1403+
1404+
13961405
def test_structured_output_aliases():
13971406
"""Test that field aliases are consistent between schema and output"""
13981407

0 commit comments

Comments
 (0)