Skip to content

Commit 631cff2

Browse files
committed
FastMCP: Improve structured output for dict and mixed lists
1 parent 5983a65 commit 631cff2

File tree

3 files changed

+21
-3
lines changed

3 files changed

+21
-3
lines changed

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
WithJsonSchema,
1515
create_model,
1616
)
17+
from pydantic.errors import PydanticSchemaGenerationError
1718
from pydantic.fields import FieldInfo
1819
from pydantic.json_schema import GenerateJsonSchema, JsonSchemaWarningKind
1920
from typing_extensions import is_typeddict
@@ -364,7 +365,7 @@ def _try_create_model_and_schema(
364365
if len(args) == 2 and args[0] is str:
365366
# TODO: should we use the original annotation? We are loosing any potential `Annotated`
366367
# metadata for Pydantic here:
367-
model = _create_dict_model(func_name, type_expr)
368+
model = _create_dict_model(func_name, original_annotation)
368369
else:
369370
# dict with non-str keys needs wrapping
370371
model = _create_wrapped_model(func_name, original_annotation)
@@ -411,12 +412,19 @@ def _try_create_model_and_schema(
411412
# Use StrictJsonSchema to raise exceptions instead of warnings
412413
try:
413414
schema = model.model_json_schema(schema_generator=StrictJsonSchema)
414-
except (TypeError, ValueError, pydantic_core.SchemaError, pydantic_core.ValidationError) as e:
415+
except (
416+
TypeError,
417+
ValueError,
418+
pydantic_core.SchemaError,
419+
pydantic_core.ValidationError,
420+
PydanticSchemaGenerationError,
421+
) as e:
415422
# These are expected errors when a type can't be converted to a Pydantic schema
416423
# TypeError: When Pydantic can't handle the type
417424
# ValueError: When there are issues with the type definition (including our custom warnings)
418425
# SchemaError: When Pydantic can't build a schema
419426
# ValidationError: When validation fails
427+
# PydanticSchemaGenerationError: When pydantic-core cannot generate a schema for a type
420428
logger.info(f"Cannot create schema for type {type_expr} in {func_name}: {type(e).__name__}: {e}")
421429
return None, None, False
422430

tests/server/fastmcp/test_func_metadata.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,16 @@ def func_dict_int_key() -> dict[int, str]: # pragma: no cover
243243
assert meta.output_schema is not None
244244
assert "result" in meta.output_schema["properties"]
245245

246+
# Test Annotated dict[str, int] with Field metadata on the root type
247+
def func_dict_annotated() -> Annotated[dict[str, int], Field(description="User scores")]: # pragma: no cover
248+
return {"alice": 10, "bob": 20}
249+
250+
meta = func_metadata(func_dict_annotated)
251+
assert meta.output_schema is not None
252+
assert meta.output_schema["type"] == "object"
253+
assert meta.output_schema["title"] == "func_dict_annotatedDictOutput"
254+
assert meta.output_schema.get("description") == "User scores"
255+
246256

247257
@pytest.mark.anyio
248258
async def test_lambda_function():

tests/server/fastmcp/test_server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ async def test_tool_mixed_list_with_audio_and_image(self, tmp_path: Path):
403403

404404
# TODO(Marcelo): It seems if we add the proper type hint, it generates an invalid JSON schema.
405405
# We need to fix this.
406-
def mixed_list_fn() -> list: # type: ignore
406+
def mixed_list_fn() -> list[str | Image | Audio | dict[str, str] | TextContent]: # type: ignore
407407
return [ # type: ignore
408408
"text message",
409409
Image(image_path),

0 commit comments

Comments
 (0)