Skip to content

Conversation

@SSOBHY2
Copy link

@SSOBHY2 SSOBHY2 commented Nov 27, 2025

brief summary of changes :

Improve FastMCP structured output handling for "dict[str, T]" and mixed-list tool outputs:

  • Preserve "Annotated" / "Field(...)" metadata for "dict[str, T]" return types in "func_metadata".
  • Make mixed-list tool outputs fail gracefully for structured output (disable schema) when Pydantic cannot generate a valid schema.

Motivation and Context :

FastMCP’s structured-output support had two rough edges:

  • For "dict[str, T]" return types, the "RootModel" was built from a stripped "GenericAlias", which lost any top-level "Annotated" metadata. Root-level "Field(description=...)" and similar metadata were not reflected in the generated JSON Schema.
  • For tools that return mixed lists (e.g. "list[str | Image | Audio | dict[str, str] | TextContent]"), Pydantic could not generate a schema and raised "PydanticSchemaGenerationError". To avoid this, the test used an untyped "list" and "# type: ignore", so the structured-output path was effectively bypassed.

This PR:

  • Uses the original return annotation (including "Annotated[...]") when creating the "RootModel" for "dict[str, T]", so root-level metadata is preserved.
  • Extends error handling in "func_metadata" so that if Pydantic cannot generate a schema (including "PydanticSchemaGenerationError"), structured output is cleanly disabled for that tool instead of raising. The mixed-list tool can now be properly typed without breaking schema generation.

How Has This Been Tested? :

From "python-sdk" root, using "uv" as per "CONTRIBUTING.md":

  • "uv run pytest"
    • Result: 785 passed, 5 skipped, 1 xfailed.
    • Includes updated tests in:
      • "tests/server/fastmcp/test_func_metadata.py" (new "Annotated[dict[str, int], Field(...)]" case).
      • "tests/server/fastmcp/test_server.py::TestServerTools::test_tool_mixed_list_with_audio_and_image" (now using a precise mixed-list return type).
  • "uv run ruff check ."
    • Result: all checks passed.

"uv run pyright" currently reports existing errors in OS-specific utilities ("src/mcp/os/posix/utilities.py", "src/mcp/os/win32/utilities.py", and "tests/issues/test_1027_win_unreachable_cleanup.py"). These appear to be pre-existing and unrelated to the FastMCP structured-output changes in this PR.

I have not yet tested this in a separate real-world application; testing so far has been via the repository’s existing test suite.

Breaking Changes :

This change is intended to be non-breaking:

  • No public APIs were removed or renamed.
  • Structured-output behavior is strictly more robust:
    • "dict[str, T]" tools now preserve "Annotated" metadata in their JSON Schema.
    • Mixed-list tools that could not previously generate a schema now disable structured output instead of raising, while still returning the same content.

Existing code using FastMCP tools should continue to work without changes.

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 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

Additional context :

  • For "dict[str, T]" outputs, "_try_create_model_and_schema" now passes the original return annotation (which may be "Annotated[...]") into "_create_dict_model", instead of the stripped "GenericAlias". This allows Pydantic to keep root-level "Field(...)" metadata on the "RootModel".
  • Schema generation now also catches "PydanticSchemaGenerationError" when calling "model.model_json_schema(schema_generator=StrictJsonSchema)". On failure, we log and fall back to no structured output ("output_schema=None"), rather than raising.
  • The mixed-list tool in "test_tool_mixed_list_with_audio_and_image" is now fully typed as "list[str | Image | Audio | dict[str, str] | TextContent]", while still asserting that "structuredContent is None". This exercises the structured-output path and confirms that unsupported unions disable structured output instead of failing tests.

@SSOBHY2 SSOBHY2 closed this Nov 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant