| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Confirmed on mcp 2.2.0 — all three registration shapes raise the raw pydantic.errors.PydanticSchemaGenerationError for collections.abc.Iterator[str] / AsyncIterator[str] returns (func_metadata(search), func_metadata(search, structured_output=True), and the Tool.from_function path).
The traceback pins exactly where the intended fallback is defeated. In mcp/server/mcpserver/utilities/func_metadata.py:
line 444, in func_metadata -> output_model, wrap_output = _create_output_model(...) line 518, in _create_output_model -> model = _create_wrapped_model(func_name, original_annotation) line 621, in _create_wrapped_model -> create_model(... result: Iterator[str] ...) # raises
Two things make this a straightforward fix:
The raise happens at L444, outside the guarded region. The try/except at L447-469 exists precisely to turn unsupported return types into a graceful outcome, and PydanticSchemaGenerationError (a PydanticUserError subclass on pydantic 2.13) is already covered conceptually by its caught types — but _create_output_model runs before the try, so _create_wrapped_model's create_model call escapes unprotected. Both intended behaviors are defeated by the same escape: the unstructured fallback (return FuncMetadata(arg_model=arguments_model), L477) and, for structured_output=True, the proper InvalidSignature (L471-475).
Iterator returns aren't recognized as content-iterators. The early-out at L437 (if structured_output is None and _returns_content(return_type_expr)) returns an unstructured FuncMetadata for annotations the model reads as content — but it doesn't match the PEP 484 generator spellings Iterator[...]/AsyncIterator[...], so they fall through into schema construction instead.
Wrapping the _create_output_model call in the existing except handling would restore both documented behaviors with one change; optionally treating Iterator/AsyncIterator origins in _returns_content would give generator tools the same unstructured default as content-block returns.
Reproduced this. Root cause: src/mcp/server/mcpserver/utilities/func_metadata.py:444 calls _create_output_model outside the try/except at lines 446-470 that reports unserializable return types at registration, so the PydanticSchemaGenerationError it raises for Iterator/AsyncIterator escapes func_metadata uncaught.
Minimal reproduction:
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
Observed output:
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
Fix is ready on BlueX888:fix/prep-func-metadata-iterator-return-uncaught-pydantic-error (commit 2f4ce1ae). Moves the _create_output_model call inside that existing try/except, so Iterator/AsyncIterator returns follow the documented contract: no output schema by default, InvalidSignature with structured_output=True.
Verified with: uv run --frozen pytest tests/server/mcpserver/test_func_metadata.py
Happy to open the PR once you confirm the approach or assign this to me.
Confirmed on mcp 2.2.0 with the v2 API (MCPServer): both the default path and structured_output=True raise the raw PydanticSchemaGenerationError at registration for a -> Iterator[str] tool — the generator tool simply cannot be registered at all.
Minimal repro:
from mcp.server.mcpserver import MCPServer
from typing import Iterator
server = MCPServer("repro")
@server.tool()
def search(query: str) -> Iterator[str]:
"""Search things."""
yield "a"→ PydanticSchemaGenerationError: Unable to generate pydantic-core schema for typing.Iterator[str]. Same for structured_output=True.
The contract says the default should fall back to an unstructured tool (structured_output=None), and structured_output=True should raise the SDK's own InvalidSignature — a raw pydantic error escapes both. Root-cause candidate: func_metadata's return-type schema generation treats Iterator/AsyncIterator as a schema-able output type instead of short-circuiting to the generator/unstructured path first.
Happy to open a PR with a failing test first (both spellings must register without raising; structured_output=True must surface InvalidSignature) plus the narrow fix, if a maintainer assigns the issue — the repo's bot auto-closes non-assignee PRs, so I'm holding the branch until then.
Affiliation: we ship a 140-tool MCP server and hit this exact class of annotation edge case in our own tool registry.
Confirmed on Windows 11 / Python 3.13 / pydantic 2.12.5 against main (f1b6589) — all four spellings crash at registration, with both the default and structured_output=True:
from typing import Iterator
from mcp.server.mcpserver.utilities.func_metadata import func_metadata
def search(n: int) -> Iterator[str]:
yield from ["a"] * n
func_metadata(search) # pydantic.errors.PydanticSchemaGenerationErrorThe escape point matches @BlueX888's analysis: the _create_output_model() call (func_metadata.py:444 on main) sits outside the try at :446 that routes expected schema failures to the unstructured fallback, so the PydanticSchemaGenerationError raised by create_model(..., result=Iterator[str]) inside _create_wrapped_model() escapes func_metadata entirely.
The fix that keeps the existing semantics is to route that call through the same failure path: on PydanticSchemaGenerationError, degrade to the unstructured fallback (output_model=None), which also makes structured_output=True raise InvalidSignature via the existing check. Two notes from testing this locally:
I have this working locally with regression tests for all four spellings plus the Generator contrast — a PR is up at #3587 linking this issue.
Disclosure: this was developed with AI assistance; I have reviewed the diff and run the tests locally.
| Back | FazBrowse Home | New Git URL |
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):
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_model → create_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
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)