From 9cc3a185c96fea8de68070a3c0dbd70d6aa28488 Mon Sep 17 00:00:00 2001 From: Bartok9 Date: Thu, 18 Jun 2026 02:08:14 -0400 Subject: [PATCH] test(mcpserver): cover PEP 604 union return annotations (#2591) Closes #2591. A tool whose return type uses a multi-member Python 3.10+ union mixing container and scalar types (dict | list | str) previously crashed at registration with PydanticUserError, because the bare types.UnionType was passed to create_model() as a field value. This is already fixed on main: such a union is neither a types.GenericAlias nor a type, so _try_create_model_and_schema falls through to the catch-all branch and wraps the result under {"result": ...}; #2434 additionally guards the schema-generation path. But there was no regression test for the exact reported signature -- the existing func_union test only covers a 2-member all-scalar union (str | int), not a container+scalar mix with bare generics. Adds test_structured_output_pep604_union_return asserting the wrapped anyOf output_schema for the issue's exact signature, so the fix can't silently regress. Verification: uv run pytest tests/server/mcpserver/test_func_metadata.py -q -> 34 passed; ruff check/format clean; pyright 0 errors. --- tests/server/mcpserver/test_func_metadata.py | 36 ++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/server/mcpserver/test_func_metadata.py b/tests/server/mcpserver/test_func_metadata.py index c57d1ee9f0..bfefd85d49 100644 --- a/tests/server/mcpserver/test_func_metadata.py +++ b/tests/server/mcpserver/test_func_metadata.py @@ -716,6 +716,42 @@ def func_optional() -> str | None: # pragma: no cover } +def test_structured_output_pep604_union_return(): + """Regression test for #2591. + + A tool whose return annotation is a multi-member PEP 604 union mixing + container and scalar types (``dict | list | str``) must not crash at + registration time. ``dict | list | str`` is a ``types.UnionType`` -- neither + a ``types.GenericAlias`` nor a ``type`` -- so it falls through to the + catch-all branch in ``_try_create_model_and_schema`` and is wrapped under + ``{"result": ...}`` rather than being passed to ``create_model()`` as a + bare field value (which previously raised ``PydanticUserError``). + """ + + # Intentionally bare (unparametrized) generics in the union: this is the exact + # signature from the issue report. The previously-closed fixes switched to typed + # generics to satisfy the type checker, which masked the reported case. + def func_pep604_union() -> dict | list | str: # type: ignore[type-arg] # pragma: no cover + return {"key": "value"} # type: ignore[return-value] + + meta = func_metadata(func_pep604_union, structured_output=True) + assert meta.output_schema == { + "type": "object", + "properties": { + "result": { + "title": "Result", + "anyOf": [ + {"additionalProperties": True, "type": "object"}, + {"items": {}, "type": "array"}, + {"type": "string"}, + ], + } + }, + "required": ["result"], + "title": "func_pep604_unionOutput", + } + + def test_structured_output_dataclass(): """Test structured output with dataclass return types"""