Skip to content

Commit ee570a7

Browse files
committed
Feedback
1 parent cf3ae5c commit ee570a7

File tree

2 files changed

+31
-7
lines changed

2 files changed

+31
-7
lines changed

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

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from pydantic.fields import FieldInfo
1818
from pydantic.json_schema import GenerateJsonSchema, JsonSchemaWarningKind
1919
from typing_extensions import is_typeddict
20-
from typing_inspection.introspection import UNKNOWN, AnnotationSource, inspect_annotation, is_union_origin
20+
from typing_inspection.introspection import UNKNOWN, ForbiddenQualifier, AnnotationSource, inspect_annotation, is_union_origin
2121

2222
from mcp.server.fastmcp.exceptions import InvalidSignature
2323
from mcp.server.fastmcp.utilities.logging import get_logger
@@ -262,12 +262,16 @@ def func_metadata(
262262
if sig.return_annotation is inspect.Parameter.empty and structured_output is True:
263263
raise InvalidSignature(f"Function {func.__name__}: return annotation required for structured output")
264264

265-
inspected_return_ann = inspect_annotation(sig.return_annotation, annotation_source=AnnotationSource.FUNCTION)
265+
try:
266+
inspected_return_ann = inspect_annotation(sig.return_annotation, annotation_source=AnnotationSource.FUNCTION)
267+
except ForbiddenQualifier as e:
268+
raise InvalidSignature(f"Function {func.__name__}: return annotation contains an invalid type qualifier") from e
269+
266270
return_type_expr = inspected_return_ann.type
267-
if return_type_expr is UNKNOWN and structured_output is True:
268-
# `return_type_expr` is `UNKNOWN` when a bare type qualifier is used (unlikely to happen as a return annotation
269-
# because it doesn't make any sense, but technically possible).
270-
raise InvalidSignature(f"Function {func.__name__}: return annotation required for structured output")
271+
272+
# `AnnotationSource.FUNCTION` allows no type qualifier to be used, so `return_type_expr` is guaranteed to *not* be
273+
# unknown (i.e. a bare `Final`).
274+
assert return_type_expr is not UNKNOWN
271275

272276
if is_union_origin(get_origin(return_type_expr)):
273277
args = get_args(return_type_expr)

tests/server/fastmcp/test_func_metadata.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# pyright: reportUnknownLambdaType=false
66
from collections.abc import Callable
77
from dataclasses import dataclass
8-
from typing import Annotated, Any, TypedDict
8+
from typing import Annotated, Any, TypedDict, Final
99

1010
import annotated_types
1111
import pytest
@@ -1182,3 +1182,23 @@ def func_with_reserved_json(
11821182
assert result["json"] == {"nested": "data"}
11831183
assert result["model_dump"] == [1, 2, 3]
11841184
assert result["normal"] == "plain string"
1185+
1186+
1187+
def test_disallowed_type_qualifier():
1188+
from mcp.server.fastmcp.exceptions import InvalidSignature
1189+
1190+
def func_disallowed_qualifier() -> Final[int]: # type: ignore
1191+
pass
1192+
1193+
with pytest.raises(InvalidSignature) as exc_info:
1194+
func_metadata(func_disallowed_qualifier)
1195+
assert "return annotation contains an invalid type qualifier" in str(exc_info.value)
1196+
1197+
1198+
def test_preserves_pydantic_metadata():
1199+
def func_with_metadata() -> Annotated[int, Field(gt=1)]: ...
1200+
1201+
meta = func_metadata(func_with_metadata)
1202+
1203+
assert meta.output_schema is not None
1204+
assert meta.output_schema["properties"]["result"] == {"exclusiveMinimum": 1, "title": "Result", "type": "integer"}

0 commit comments

Comments
 (0)