Fall back to unstructured output when output model selection fails (#3573)

Closed
RA
19d ago
+54-2

Fixes #3573

Disclosure: this fix was developed with AI assistance; I have reviewed the diff, run the tests locally, and can explain the change.

Summary

func_metadata raises an uncaught pydantic.errors.PydanticSchemaGenerationError when a tool's return annotation is Iterator[...] / AsyncIterator[...] (either the typing or the collections.abc spelling), so a correctly annotated generator tool cannot register at all. The fallback machinery for unserializable return types already exists — the call to _create_output_model() simply sits outside the try that routes those failures to it.

This wraps that call in the same failure path: on PydanticSchemaGenerationError the tool degrades to unstructured output (output_schema=None), and with structured_output=True the existing flow raises InvalidSignature. Generator[...] is untouched — pydantic treats it as a sequence and its structured array schema keeps working.

Motivation and Context

-> Iterator[str] is the PEP 484 spelling for generator functions, but registering one currently crashes at decoration time instead of taking the documented unstructured fallback (#3573). Fixing it at the model-selection step reuses the existing fallback semantics in one place rather than special-casing iterator types.

How Has This Been Tested?

  • Reproduced on main (f1b6589) for all four spellings (typing/collections.abc × Iterator/AsyncIterator) with the default and structured_output=True.
  • tests/server/mcpserver/test_func_metadata.py: 60/60 pass, including two new regression tests (the four spellings fall back to unstructured / raise InvalidSignature; Generator[str, None, None] keeps its structured array schema).
  • Adjacent suites (tests/server/mcpserver/tools, tests/interaction/mcpserver/test_tools.py): 85/85 pass. ruff check and ruff format --check clean.

Breaking Changes

None. Registrations that previously crashed now succeed as unstructured tools (or raise InvalidSignature under structured_output=True, matching the documented contract for unserializable return types).

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I am assigned to the linked issue (or it is labeled help wanted, or I'm a maintainer)
  • I have disclosed any AI assistance and can explain the change in my own words
  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed