Skip to content

Commit ec89e98

Browse files
committed
fix: skip structured output for list[Any]
1 parent 02ccedf commit ec89e98

File tree

2 files changed

+22
-0
lines changed

2 files changed

+22
-0
lines changed

src/mcp/server/mcpserver/utilities/func_metadata.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,9 @@ def _try_create_model_and_schema(
349349
elif isinstance(type_expr, GenericAlias):
350350
origin = get_origin(type_expr)
351351

352+
if origin in (list, tuple, set, frozenset, Sequence) and _annotation_contains_any(type_expr):
353+
return None, None, False
354+
352355
# Special case: dict with string keys can use RootModel
353356
if origin is dict:
354357
args = get_args(type_expr)
@@ -474,6 +477,18 @@ def _create_wrapped_model(func_name: str, annotation: Any) -> type[BaseModel]:
474477
return create_model(model_name, result=annotation)
475478

476479

480+
def _annotation_contains_any(annotation: Any) -> bool:
481+
"""Return True if a type annotation contains `Any` anywhere within it."""
482+
if annotation is Any:
483+
return True
484+
485+
origin = get_origin(annotation)
486+
if origin is None:
487+
return False
488+
489+
return any(_annotation_contains_any(arg) for arg in get_args(annotation) if arg is not Ellipsis)
490+
491+
477492
def _create_dict_model(func_name: str, dict_annotation: Any) -> type[BaseModel]:
478493
"""Create a RootModel for dict[str, T] types."""
479494
# TODO(Marcelo): We should not rely on RootModel for this.

tests/server/mcpserver/test_func_metadata.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,9 @@ def func_list_str() -> list[str]: # pragma: no cover
674674
def func_dict_str_int() -> dict[str, int]: # pragma: no cover
675675
return {"a": 1, "b": 2}
676676

677+
def func_list_any() -> list[Any]: # pragma: no cover
678+
return ["a", "b", "c"]
679+
677680
def func_union() -> str | int: # pragma: no cover
678681
return "hello"
679682

@@ -689,6 +692,10 @@ def func_optional() -> str | None: # pragma: no cover
689692
"title": "func_list_strOutput",
690693
}
691694

695+
# Test list[Any] - should stay unstructured because it can contain arbitrary non-serializable values
696+
meta = func_metadata(func_list_any)
697+
assert meta.output_schema is None
698+
692699
# Test dict[str, int] - should NOT be wrapped
693700
meta = func_metadata(func_dict_str_int)
694701
assert meta.output_schema == {

0 commit comments

Comments
 (0)