|
2 | 2 | import json
|
3 | 3 | from collections.abc import Awaitable, Callable, Iterable, Sequence
|
4 | 4 | from dataclasses import asdict, is_dataclass
|
| 5 | +from itertools import chain |
5 | 6 | from types import GenericAlias
|
6 | 7 | from typing import Annotated, Any, ForwardRef, Literal, get_args, get_origin, get_type_hints
|
7 | 8 |
|
| 9 | +import pydantic_core |
8 | 10 | from pydantic import (
|
9 | 11 | BaseModel,
|
10 | 12 | ConfigDict,
|
|
19 | 21 |
|
20 | 22 | from mcp.server.fastmcp.exceptions import InvalidSignature
|
21 | 23 | from mcp.server.fastmcp.utilities.logging import get_logger
|
22 |
| -from mcp.types import ContentBlock |
| 24 | +from mcp.server.fastmcp.utilities.types import Image |
| 25 | +from mcp.types import ContentBlock, TextContent |
23 | 26 |
|
24 | 27 | logger = get_logger(__name__)
|
25 | 28 |
|
@@ -85,13 +88,11 @@ def convert_result(self, result: Any) -> Any:
|
85 | 88 | tool call handler provides generic backwards compatibility serialization of
|
86 | 89 | structured content**. This is for FastMCP backwards compatibility: we need to
|
87 | 90 | retain FastMCP's ad hoc conversion logic for constructing unstructured output
|
88 |
| - from function return values (see _convert_to_content in mcp.server.fastmcp.server), |
89 |
| - whereas the lowlevel server simply serializes the structured output. |
| 91 | + from function return values, whereas the lowlevel server simply serializes |
| 92 | + the structured output. |
90 | 93 |
|
91 | 94 | This backwards compatibility provision will be removed in a future version of FastMCP.
|
92 | 95 | """
|
93 |
| - from mcp.server.fastmcp.server import _convert_to_content # type: ignore |
94 |
| - |
95 | 96 | unstructured_content = _convert_to_content(result)
|
96 | 97 |
|
97 | 98 | if self.output_model is None:
|
@@ -271,9 +272,6 @@ def func_metadata(
|
271 | 272 | f"Function {func.__name__}: return type {annotation} is not supported for structured output. "
|
272 | 273 | )
|
273 | 274 |
|
274 |
| - import sys |
275 |
| - |
276 |
| - print(f"HEY Function {func.__name__} metadata: {output_model=} {output_conversion=}", file=sys.stderr) |
277 | 275 |
|
278 | 276 | return FuncMetadata(arg_model=arguments_model, output_model=output_model, output_conversion=output_conversion)
|
279 | 277 |
|
@@ -476,3 +474,36 @@ def _get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
|
476 | 474 | typed_return = _get_typed_annotation(signature.return_annotation, globalns)
|
477 | 475 | typed_signature = inspect.Signature(typed_params, return_annotation=typed_return)
|
478 | 476 | return typed_signature
|
| 477 | + |
| 478 | + |
| 479 | +def _convert_to_content( |
| 480 | + result: Any, |
| 481 | +) -> Sequence[ContentBlock]: |
| 482 | + """ |
| 483 | + Convert a result to a sequence of content objects. |
| 484 | +
|
| 485 | + Note: This conversion logic comes from previous versions of FastMCP and is being |
| 486 | + retained for purposes of backwards compatibility. It produces different unstructured |
| 487 | + output than the lowlevel server tool call handler, which serializes structured content. |
| 488 | + """ |
| 489 | + if result is None: |
| 490 | + return [] |
| 491 | + |
| 492 | + if isinstance(result, ContentBlock): |
| 493 | + return [result] |
| 494 | + |
| 495 | + if isinstance(result, Image): |
| 496 | + return [result.to_image_content()] |
| 497 | + |
| 498 | + if isinstance(result, list | tuple): |
| 499 | + return list( |
| 500 | + chain.from_iterable( |
| 501 | + _convert_to_content(item) |
| 502 | + for item in result # type: ignore |
| 503 | + ) |
| 504 | + ) |
| 505 | + |
| 506 | + if not isinstance(result, str): |
| 507 | + result = pydantic_core.to_json(result, fallback=str, indent=2).decode() |
| 508 | + |
| 509 | + return [TextContent(type="text", text=result)] |
0 commit comments