Skip to content

func_metadata raises uncaught PydanticSchemaGenerationError for Iterator/AsyncIterator tool return annotations instead of the unstructured fallback #3573

Description

@BlueX888

Initial Checks

Release line

2.x (current stable)

Description

Registering a tool whose function is annotated -> Iterator[...] or -> AsyncIterator[...] — the PEP 484 spelling for generator functions — raises a raw pydantic.errors.PydanticSchemaGenerationError at registration time, instead of either falling back to an unstructured tool (structured_output=None, the default) or raising the SDK's InvalidSignature (structured_output=True). The same happens via @server.tool() / Tool.from_function(), so a properly typed generator tool cannot be registered at all.

Actual output of the script in "Example Code" (error text truncated at 80 chars by the script):

func_metadata(search): UNCAUGHT pydantic.errors.PydanticSchemaGenerationError: Unable to generate pydantic-core schema for typing.Iterator[str]. Set `arbitrary
func_metadata(search, structured_output=True): UNCAUGHT pydantic.errors.PydanticSchemaGenerationError: Unable to generate pydantic-core schema for typing.Iterator[str]. Set `arbitrary
Tool.from_function(search): UNCAUGHT pydantic.errors.PydanticSchemaGenerationError: Unable to generate pydantic-core schema for typing.Iterator[str]. Set `arbitrary

Full traceback (captured with the collections.abc spelling of the same annotation, so the error names it accordingly):

  File "src/mcp/server/mcpserver/utilities/func_metadata.py", line 444, in func_metadata
    output_model, wrap_output = _create_output_model(original_annotation, return_type_expr, func.__name__)
  File "src/mcp/server/mcpserver/utilities/func_metadata.py", line 550, in _create_output_model
    model = _create_wrapped_model(func_name, original_annotation)
  File "src/mcp/server/mcpserver/utilities/func_metadata.py", line 621, in _create_wrapped_model
    return create_model(model_name, result=annotation)
pydantic.errors.PydanticSchemaGenerationError: Unable to generate pydantic-core schema for collections.abc.Iterator[str]. Set `arbitrary_types_allowed=True` in the model_config to ignore this error or implement `__get_pydantic_core_schema__` on your type to fully support it.

What I expected is what already happens for other unserializable return types, pinned by test_structured_output_unserializable_type_error (tests/server/mcpserver/test_func_metadata.py:1233, passes on main) and documented in docs/servers/structured-output.md: with structured_output=None, registration succeeds and output_schema is None (fallback to text); with structured_output=True, InvalidSignature: Function search: return type ... is not serializable for structured output. For contrast, Iterable[str] and Generator[str, None, None] both register successfully through the same wrapped-model path — only the PEP 484-recommended spellings for generators crash.

Root cause: _create_output_model(...) is called at src/mcp/server/mcpserver/utilities/func_metadata.py:444, outside the try/except at lines 446–470 whose except tuple (PydanticUserError, pydantic_core.SchemaError, ...) exists exactly so that "an unsupported return type surfaces here, at registration" as a clean failure. _create_output_model_create_wrapped_modelcreate_model(model_name, result=annotation) (line 621) builds a schema itself, and its PydanticSchemaGenerationError (a PydanticUserError subclass) escapes uncaught. Moving the line 444 call inside the existing try/except looks like it would restore both documented behaviours; I'd be happy to be assigned and open a PR with that approach.

Related: the guard was added in #2434 (for #1131), but it wraps only the FuncMetadata construction, not this call. #1060 reports the same error class for a different type (Image, 1.x fastmcp) and looks unrelated to this code path.

AI disclosure: this issue and its reproduction were prepared with AI assistance.

Example Code

from typing import Iterator

from mcp.server.mcpserver.tools import Tool
from mcp.server.mcpserver.utilities.func_metadata import func_metadata


def search(n: int) -> Iterator[str]:
    yield from ["a"] * n


for label, call in [
    ("func_metadata(search)", lambda: func_metadata(search)),
    ("func_metadata(search, structured_output=True)", lambda: func_metadata(search, structured_output=True)),
    ("Tool.from_function(search)", lambda: Tool.from_function(search)),
]:
    try:
        call()
        print(f"{label}: OK")
    except Exception as e:
        print(f"{label}: UNCAUGHT {type(e).__module__}.{type(e).__name__}: {str(e)[:80]}")
# AsyncIterator[str] return annotations behave identically

Python & MCP Python SDK

mcp: main @ 6affe5c0d3588fd1705713b3703dc68015cfe3eb
     func_metadata.py is identical in v2.2.0 (latest 2.x release);
     the same crash reproduces on a fresh `pip install mcp==2.2.0`
Python 3.13.15
pydantic 2.12.5
macOS 26.6.2 (arm64)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    v1Affects the v1.x maintenance linev2Affects the v2 line (2.x on main)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions