mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-09 15:19:10 +02:00
Remove deprecated tool-level serializer parameter
This commit is contained in:
parent
6621024ce4
commit
7f032bb82e
8 changed files with 8 additions and 306 deletions
|
|
@ -1,12 +1,9 @@
|
|||
"""Provides a base mixin class and decorators for easy registration of class methods with FastMCP."""
|
||||
|
||||
import inspect
|
||||
import warnings
|
||||
from collections.abc import Callable
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import fastmcp
|
||||
from fastmcp.exceptions import FastMCPDeprecationWarning
|
||||
from fastmcp.prompts.base import Prompt
|
||||
from fastmcp.resources.base import Resource
|
||||
from fastmcp.tools.base import Tool
|
||||
|
|
@ -73,15 +70,6 @@ def mcp_tool(
|
|||
f"Valid keyword arguments are: {sorted(_TOOL_VALID_KWARGS)}"
|
||||
)
|
||||
|
||||
if "serializer" in kwargs and fastmcp.settings.deprecation_warnings:
|
||||
warnings.warn(
|
||||
"The `serializer` parameter is deprecated. "
|
||||
"Return ToolResult from your tools for full control over serialization. "
|
||||
"See https://gofastmcp.com/servers/tools#custom-serialization for migration examples.",
|
||||
FastMCPDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
||||
call_args: dict[str, Any] = {"name": name or get_fn_name(func), **kwargs}
|
||||
if enabled is not None:
|
||||
|
|
|
|||
|
|
@ -344,7 +344,6 @@ def extract_components(module: ModuleType) -> list[FastMCPComponent]:
|
|||
meta=meta.meta,
|
||||
task=resolved_task,
|
||||
exclude_args=meta.exclude_args,
|
||||
serializer=meta.serializer,
|
||||
timeout=meta.timeout,
|
||||
auth=meta.auth,
|
||||
run_in_thread=meta.run_in_thread,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ from __future__ import annotations
|
|||
|
||||
import inspect
|
||||
import types
|
||||
import warnings
|
||||
from collections.abc import Callable
|
||||
from functools import partial
|
||||
from typing import (
|
||||
|
|
@ -27,7 +26,6 @@ import mcp_types
|
|||
from mcp_types import ToolAnnotations
|
||||
|
||||
import fastmcp
|
||||
from fastmcp.exceptions import FastMCPDeprecationWarning
|
||||
from fastmcp.server.auth.authorization import AuthCheck
|
||||
from fastmcp.server.tasks.config import TaskConfig
|
||||
from fastmcp.tools.base import Tool
|
||||
|
|
@ -44,7 +42,6 @@ except ImportError:
|
|||
|
||||
if TYPE_CHECKING:
|
||||
from fastmcp.server.providers.local_provider import LocalProvider
|
||||
from fastmcp.tools.base import ToolResultSerializerType
|
||||
|
||||
F = TypeVar("F", bound=Callable[..., Any])
|
||||
|
||||
|
|
@ -162,7 +159,6 @@ class ToolDecoratorMixin:
|
|||
meta=tool_meta,
|
||||
task=resolved_task,
|
||||
exclude_args=fmeta.exclude_args,
|
||||
serializer=fmeta.serializer,
|
||||
timeout=fmeta.timeout,
|
||||
auth=fmeta.auth,
|
||||
run_in_thread=fmeta.run_in_thread,
|
||||
|
|
@ -192,7 +188,6 @@ class ToolDecoratorMixin:
|
|||
meta: dict[str, Any] | None = None,
|
||||
enabled: bool = True,
|
||||
task: bool | TaskConfig | None = None,
|
||||
serializer: ToolResultSerializerType | None = None, # Deprecated
|
||||
timeout: float | None = None,
|
||||
auth: AuthCheck | list[AuthCheck] | None = None,
|
||||
run_in_thread: bool = True,
|
||||
|
|
@ -215,14 +210,13 @@ class ToolDecoratorMixin:
|
|||
meta: dict[str, Any] | None = None,
|
||||
enabled: bool = True,
|
||||
task: bool | TaskConfig | None = None,
|
||||
serializer: ToolResultSerializerType | None = None, # Deprecated
|
||||
timeout: float | None = None,
|
||||
auth: AuthCheck | list[AuthCheck] | None = None,
|
||||
run_in_thread: bool = True,
|
||||
) -> Callable[[F], F]: ...
|
||||
|
||||
# NOTE: This method mirrors fastmcp.tools.tool() but adds registration,
|
||||
# the `enabled` param, and supports deprecated params (serializer, exclude_args).
|
||||
# the `enabled` param, and supports the deprecated `exclude_args` param.
|
||||
# When deprecated params are removed, this should delegate to the standalone
|
||||
# decorator to reduce duplication.
|
||||
def tool(
|
||||
|
|
@ -241,7 +235,6 @@ class ToolDecoratorMixin:
|
|||
meta: dict[str, Any] | None = None,
|
||||
enabled: bool = True,
|
||||
task: bool | TaskConfig | None = None,
|
||||
serializer: ToolResultSerializerType | None = None, # Deprecated
|
||||
timeout: float | None = None,
|
||||
auth: AuthCheck | list[AuthCheck] | None = None,
|
||||
run_in_thread: bool = True,
|
||||
|
|
@ -272,7 +265,6 @@ class ToolDecoratorMixin:
|
|||
meta: Optional meta information about the tool
|
||||
enabled: Whether the tool is enabled (default True). If False, adds to blocklist.
|
||||
task: Optional task configuration for background execution
|
||||
serializer: Deprecated. Return ToolResult from your tools for full control over serialization.
|
||||
|
||||
Returns:
|
||||
The registered FunctionTool or a decorator function.
|
||||
|
|
@ -290,14 +282,6 @@ class ToolDecoratorMixin:
|
|||
return str(x)
|
||||
```
|
||||
"""
|
||||
if serializer is not None and fastmcp.settings.deprecation_warnings:
|
||||
warnings.warn(
|
||||
"The `serializer` parameter is deprecated. "
|
||||
"Return ToolResult from your tools for full control over serialization. "
|
||||
"See https://gofastmcp.com/servers/tools#custom-serialization for migration examples.",
|
||||
FastMCPDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
if isinstance(annotations, dict):
|
||||
annotations = ToolAnnotations(**annotations)
|
||||
|
||||
|
|
@ -345,7 +329,6 @@ class ToolDecoratorMixin:
|
|||
annotations=annotations,
|
||||
exclude_args=exclude_args,
|
||||
meta=meta,
|
||||
serializer=serializer,
|
||||
task=resolved_task,
|
||||
timeout=timeout,
|
||||
auth=auth,
|
||||
|
|
@ -371,7 +354,6 @@ class ToolDecoratorMixin:
|
|||
meta=meta,
|
||||
task=task,
|
||||
exclude_args=exclude_args,
|
||||
serializer=serializer,
|
||||
timeout=timeout,
|
||||
auth=auth,
|
||||
enabled=enabled,
|
||||
|
|
@ -416,7 +398,6 @@ class ToolDecoratorMixin:
|
|||
meta=meta,
|
||||
enabled=enabled,
|
||||
task=task,
|
||||
serializer=serializer,
|
||||
timeout=timeout,
|
||||
auth=auth,
|
||||
run_in_thread=run_in_thread,
|
||||
|
|
|
|||
|
|
@ -4,16 +4,12 @@ from __future__ import annotations
|
|||
|
||||
import json
|
||||
import re
|
||||
import warnings
|
||||
from collections.abc import Callable
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import httpx
|
||||
from mcp_types import ToolAnnotations
|
||||
from pydantic.networks import AnyUrl
|
||||
|
||||
import fastmcp
|
||||
from fastmcp.exceptions import FastMCPDeprecationWarning
|
||||
from fastmcp.resources import (
|
||||
Resource,
|
||||
ResourceContent,
|
||||
|
|
@ -151,16 +147,7 @@ class OpenAPITool(Tool):
|
|||
output_schema: dict[str, Any] | None = None,
|
||||
tags: set[str] | None = None,
|
||||
annotations: ToolAnnotations | None = None,
|
||||
serializer: Callable[[Any], str] | None = None, # Deprecated
|
||||
):
|
||||
if serializer is not None and fastmcp.settings.deprecation_warnings:
|
||||
warnings.warn(
|
||||
"The `serializer` parameter is deprecated. "
|
||||
"Return ToolResult from your tools for full control over serialization. "
|
||||
"See https://gofastmcp.com/servers/tools#custom-serialization for migration examples.",
|
||||
FastMCPDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
super().__init__(
|
||||
name=name,
|
||||
description=description,
|
||||
|
|
@ -168,7 +155,6 @@ class OpenAPITool(Tool):
|
|||
output_schema=output_schema,
|
||||
tags=tags or set(),
|
||||
annotations=annotations,
|
||||
serializer=serializer,
|
||||
)
|
||||
self._client = client
|
||||
self._route = route
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ from typing import (
|
|||
Annotated,
|
||||
Any,
|
||||
ClassVar,
|
||||
TypeAlias,
|
||||
overload,
|
||||
)
|
||||
|
||||
|
|
@ -59,9 +58,6 @@ if TYPE_CHECKING:
|
|||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
ToolResultSerializerType: TypeAlias = Callable[[Any], str]
|
||||
|
||||
|
||||
def resolve_serialize_by_alias(value: Any) -> bool:
|
||||
"""Resolve the effective ``by_alias`` setting for serializing *value*.
|
||||
|
||||
|
|
@ -196,12 +192,6 @@ class Tool(FastMCPComponent):
|
|||
ToolExecution | None,
|
||||
Field(description="Task execution configuration (SEP-1686)"),
|
||||
] = None
|
||||
serializer: Annotated[
|
||||
SkipJsonSchema[ToolResultSerializerType | None],
|
||||
Field(
|
||||
description="Deprecated. Return ToolResult from your tools for full control over serialization."
|
||||
),
|
||||
] = None
|
||||
auth: Annotated[
|
||||
SkipJsonSchema[AuthCheck | list[AuthCheck] | None],
|
||||
Field(description="Authorization checks for this tool", exclude=True),
|
||||
|
|
@ -268,7 +258,6 @@ class Tool(FastMCPComponent):
|
|||
annotations: ToolAnnotations | None = None,
|
||||
exclude_args: list[str] | None = None,
|
||||
output_schema: dict[str, Any] | NotSetT | None = NotSet,
|
||||
serializer: ToolResultSerializerType | None = None, # Deprecated
|
||||
meta: dict[str, Any] | None = None,
|
||||
task: bool | TaskConfig | None = None,
|
||||
timeout: float | None = None,
|
||||
|
|
@ -289,7 +278,6 @@ class Tool(FastMCPComponent):
|
|||
annotations=annotations,
|
||||
exclude_args=exclude_args,
|
||||
output_schema=output_schema,
|
||||
serializer=serializer,
|
||||
meta=meta,
|
||||
task=task,
|
||||
timeout=timeout,
|
||||
|
|
@ -313,7 +301,7 @@ class Tool(FastMCPComponent):
|
|||
"""Convert a raw result to ToolResult.
|
||||
|
||||
Handles ToolResult passthrough and converts raw values using the tool's
|
||||
attributes (serializer, output_schema) for proper conversion.
|
||||
attributes (output_schema) for proper conversion.
|
||||
"""
|
||||
if isinstance(raw_value, ToolResult):
|
||||
return raw_value
|
||||
|
|
@ -330,7 +318,7 @@ class Tool(FastMCPComponent):
|
|||
fastmcp_app_name=_get_fastmcp_app_name(self),
|
||||
)
|
||||
|
||||
content = _convert_to_content(raw_value, serializer=self.serializer)
|
||||
content = _convert_to_content(raw_value)
|
||||
|
||||
# Bytes can't be represented as structured JSON content
|
||||
if isinstance(raw_value, bytes):
|
||||
|
|
@ -460,7 +448,6 @@ class Tool(FastMCPComponent):
|
|||
tags: set[str] | None = None,
|
||||
annotations: ToolAnnotations | NotSetT | None = NotSet,
|
||||
output_schema: dict[str, Any] | NotSetT | None = NotSet,
|
||||
serializer: ToolResultSerializerType | None = None, # Deprecated
|
||||
meta: dict[str, Any] | NotSetT | None = NotSet,
|
||||
transform_args: dict[str, ArgTransform] | None = None,
|
||||
transform_fn: Callable[..., Any] | None = None,
|
||||
|
|
@ -479,7 +466,6 @@ class Tool(FastMCPComponent):
|
|||
tags=tags,
|
||||
annotations=annotations,
|
||||
output_schema=output_schema,
|
||||
serializer=serializer,
|
||||
meta=meta,
|
||||
)
|
||||
|
||||
|
|
@ -505,25 +491,8 @@ class Tool(FastMCPComponent):
|
|||
}
|
||||
|
||||
|
||||
def _serialize_with_fallback(
|
||||
result: Any, serializer: ToolResultSerializerType | None = None
|
||||
) -> str:
|
||||
if serializer is not None:
|
||||
try:
|
||||
return serializer(result)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"Error serializing tool result: %s",
|
||||
e,
|
||||
exc_info=True,
|
||||
)
|
||||
|
||||
return default_serializer(result)
|
||||
|
||||
|
||||
def _convert_to_single_content_block(
|
||||
item: Any,
|
||||
serializer: ToolResultSerializerType | None = None,
|
||||
) -> ContentBlock:
|
||||
if isinstance(item, ContentBlock):
|
||||
return item
|
||||
|
|
@ -548,7 +517,7 @@ def _convert_to_single_content_block(
|
|||
|
||||
return TextContent(type="text", text=base64.b64encode(item).decode("ascii"))
|
||||
|
||||
return TextContent(type="text", text=_serialize_with_fallback(item, serializer))
|
||||
return TextContent(type="text", text=default_serializer(item))
|
||||
|
||||
|
||||
_PREFAB_TEXT_FALLBACK = "[Rendered Prefab UI]"
|
||||
|
|
@ -599,7 +568,6 @@ def _prefab_to_tool_result(app: Any, fastmcp_app_name: str | None = None) -> Too
|
|||
|
||||
def _convert_to_content(
|
||||
result: Any,
|
||||
serializer: ToolResultSerializerType | None = None,
|
||||
) -> list[ContentBlock]:
|
||||
"""Convert a result to a sequence of content objects."""
|
||||
|
||||
|
|
@ -607,7 +575,7 @@ def _convert_to_content(
|
|||
return []
|
||||
|
||||
if not isinstance(result, (list | tuple)):
|
||||
return [_convert_to_single_content_block(result, serializer)]
|
||||
return [_convert_to_single_content_block(result)]
|
||||
|
||||
# If all items are ContentBlocks, return them as is
|
||||
if all(isinstance(item, ContentBlock) for item in result):
|
||||
|
|
@ -617,13 +585,13 @@ def _convert_to_content(
|
|||
# without aggregating them
|
||||
if any(isinstance(item, ContentBlock | Image | Audio | File) for item in result):
|
||||
return [
|
||||
_convert_to_single_content_block(item, serializer)
|
||||
_convert_to_single_content_block(item)
|
||||
if not isinstance(item, ContentBlock)
|
||||
else item
|
||||
for item in result
|
||||
]
|
||||
# If none of the items are ContentBlocks, aggregate all items into a single TextContent
|
||||
return [TextContent(type="text", text=_serialize_with_fallback(result, serializer))]
|
||||
return [TextContent(type="text", text=default_serializer(result))]
|
||||
|
||||
|
||||
__all__ = ["Tool", "ToolResult"]
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ from fastmcp.exceptions import FastMCPDeprecationWarning, ValidationError
|
|||
from fastmcp.tools.base import (
|
||||
Tool,
|
||||
ToolResult,
|
||||
ToolResultSerializerType,
|
||||
)
|
||||
from fastmcp.tools.function_parsing import ParsedFunction, _is_object_schema
|
||||
from fastmcp.utilities.async_utils import (
|
||||
|
|
@ -171,7 +170,6 @@ class ToolMeta:
|
|||
app: Any = None
|
||||
task: bool | TaskConfig | None = None
|
||||
exclude_args: list[str] | None = None
|
||||
serializer: Any | None = None
|
||||
timeout: float | None = None
|
||||
auth: AuthCheck | list[AuthCheck] | None = None
|
||||
enabled: bool = True
|
||||
|
|
@ -238,7 +236,6 @@ class FunctionTool(Tool):
|
|||
annotations: ToolAnnotations | None = None,
|
||||
exclude_args: list[str] | None = None,
|
||||
output_schema: dict[str, Any] | NotSetT | None = NotSet,
|
||||
serializer: ToolResultSerializerType | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
task: bool | TaskConfig | None = None,
|
||||
timeout: float | None = None,
|
||||
|
|
@ -268,7 +265,6 @@ class FunctionTool(Tool):
|
|||
annotations,
|
||||
meta,
|
||||
task,
|
||||
serializer,
|
||||
timeout,
|
||||
auth,
|
||||
run_in_thread,
|
||||
|
|
@ -303,20 +299,11 @@ class FunctionTool(Tool):
|
|||
meta=meta,
|
||||
task=task,
|
||||
exclude_args=exclude_args,
|
||||
serializer=serializer,
|
||||
timeout=timeout,
|
||||
auth=auth,
|
||||
run_in_thread=True if run_in_thread is None else run_in_thread,
|
||||
)
|
||||
|
||||
if metadata.serializer is not None and fastmcp.settings.deprecation_warnings:
|
||||
warnings.warn(
|
||||
"The `serializer` parameter is deprecated. "
|
||||
"Return ToolResult from your tools for full control over serialization. "
|
||||
"See https://gofastmcp.com/servers/tools#custom-serialization for migration examples.",
|
||||
FastMCPDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
if metadata.exclude_args and fastmcp.settings.deprecation_warnings:
|
||||
warnings.warn(
|
||||
"The `exclude_args` parameter is deprecated as of FastMCP 2.14. "
|
||||
|
|
@ -389,7 +376,6 @@ class FunctionTool(Tool):
|
|||
output_schema=final_output_schema,
|
||||
annotations=metadata.annotations,
|
||||
tags=metadata.tags or set(),
|
||||
serializer=metadata.serializer,
|
||||
meta=metadata.meta,
|
||||
task_config=task_config,
|
||||
timeout=metadata.timeout,
|
||||
|
|
@ -606,7 +592,6 @@ def tool(
|
|||
meta: dict[str, Any] | None = None,
|
||||
task: bool | TaskConfig | None = None,
|
||||
exclude_args: list[str] | None = None,
|
||||
serializer: Any | None = None,
|
||||
timeout: float | None = None,
|
||||
auth: AuthCheck | list[AuthCheck] | None = None,
|
||||
run_in_thread: bool = True,
|
||||
|
|
@ -626,7 +611,6 @@ def tool(
|
|||
meta: dict[str, Any] | None = None,
|
||||
task: bool | TaskConfig | None = None,
|
||||
exclude_args: list[str] | None = None,
|
||||
serializer: Any | None = None,
|
||||
timeout: float | None = None,
|
||||
auth: AuthCheck | list[AuthCheck] | None = None,
|
||||
run_in_thread: bool = True,
|
||||
|
|
@ -647,7 +631,6 @@ def tool(
|
|||
meta: dict[str, Any] | None = None,
|
||||
task: bool | TaskConfig | None = None,
|
||||
exclude_args: list[str] | None = None,
|
||||
serializer: Any | None = None,
|
||||
timeout: float | None = None,
|
||||
auth: AuthCheck | list[AuthCheck] | None = None,
|
||||
run_in_thread: bool = True,
|
||||
|
|
@ -690,7 +673,6 @@ def tool(
|
|||
meta=meta,
|
||||
task=resolve_task_config(task),
|
||||
exclude_args=exclude_args,
|
||||
serializer=serializer,
|
||||
timeout=timeout,
|
||||
auth=auth,
|
||||
run_in_thread=run_in_thread,
|
||||
|
|
@ -710,7 +692,6 @@ def tool(
|
|||
meta=meta,
|
||||
task=task,
|
||||
exclude_args=exclude_args,
|
||||
serializer=serializer,
|
||||
timeout=timeout,
|
||||
auth=auth,
|
||||
run_in_thread=run_in_thread,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import warnings
|
||||
from collections.abc import Callable
|
||||
from contextvars import ContextVar
|
||||
from copy import deepcopy
|
||||
|
|
@ -15,8 +14,6 @@ from pydantic.fields import Field
|
|||
from pydantic.functional_validators import BeforeValidator
|
||||
from pydantic.json_schema import SkipJsonSchema
|
||||
|
||||
import fastmcp
|
||||
from fastmcp.exceptions import FastMCPDeprecationWarning
|
||||
from fastmcp.tools.base import (
|
||||
Tool,
|
||||
ToolResult,
|
||||
|
|
@ -343,9 +340,7 @@ class TransformedTool(Tool):
|
|||
|
||||
# Otherwise convert to content and create ToolResult with proper structured content
|
||||
|
||||
unstructured_result = _convert_to_content(
|
||||
result, serializer=self.serializer
|
||||
)
|
||||
unstructured_result = _convert_to_content(result)
|
||||
|
||||
structured_output = None
|
||||
# First handle structured content based on output schema, if any
|
||||
|
|
@ -393,7 +388,6 @@ class TransformedTool(Tool):
|
|||
transform_args: dict[str, ArgTransform] | None = None,
|
||||
annotations: ToolAnnotations | NotSetT | None = NotSet,
|
||||
output_schema: dict[str, Any] | NotSetT | None = NotSet,
|
||||
serializer: Callable[[Any], str] | NotSetT | None = NotSet, # Deprecated
|
||||
meta: dict[str, Any] | NotSetT | None = NotSet,
|
||||
) -> TransformedTool:
|
||||
"""Create a transformed tool from a parent tool.
|
||||
|
|
@ -415,7 +409,6 @@ class TransformedTool(Tool):
|
|||
output_schema: Control output schema for structured outputs:
|
||||
- None (default): Inherit from transform_fn if available, then parent tool
|
||||
- dict: Use custom output schema
|
||||
serializer: Deprecated. Return ToolResult from your tools for full control over serialization.
|
||||
meta: Control meta information:
|
||||
- NotSet (default): Inherit from parent tool
|
||||
- dict: Use custom meta information
|
||||
|
|
@ -470,18 +463,6 @@ class TransformedTool(Tool):
|
|||
"""
|
||||
tool = Tool._ensure_tool(tool)
|
||||
|
||||
if (
|
||||
serializer is not NotSet
|
||||
and serializer is not None
|
||||
and fastmcp.settings.deprecation_warnings
|
||||
):
|
||||
warnings.warn(
|
||||
"The `serializer` parameter is deprecated. "
|
||||
"Return ToolResult from your tools for full control over serialization. "
|
||||
"See https://gofastmcp.com/servers/tools#custom-serialization for migration examples.",
|
||||
FastMCPDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
transform_args = transform_args or {}
|
||||
|
||||
if transform_fn is not None:
|
||||
|
|
@ -601,9 +582,6 @@ class TransformedTool(Tool):
|
|||
final_annotations = (
|
||||
annotations if not isinstance(annotations, NotSetT) else tool.annotations
|
||||
)
|
||||
final_serializer = (
|
||||
serializer if not isinstance(serializer, NotSetT) else tool.serializer
|
||||
)
|
||||
|
||||
transformed_tool = cls(
|
||||
fn=final_fn,
|
||||
|
|
@ -617,7 +595,6 @@ class TransformedTool(Tool):
|
|||
output_schema=final_output_schema,
|
||||
tags=tags or tool.tags,
|
||||
annotations=final_annotations,
|
||||
serializer=final_serializer,
|
||||
meta=final_meta,
|
||||
transform_args=transform_args,
|
||||
auth=tool.auth,
|
||||
|
|
|
|||
|
|
@ -1,178 +0,0 @@
|
|||
"""Tests for deprecated tool serializer functionality.
|
||||
|
||||
These tests verify that serializer parameters still work but are deprecated.
|
||||
All serializer-related tests should be moved here.
|
||||
"""
|
||||
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
from inline_snapshot import snapshot
|
||||
from mcp_types import TextContent
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.contrib.mcp_mixin import mcp_tool
|
||||
from fastmcp.server.providers import LocalProvider
|
||||
from fastmcp.tools.base import Tool, _convert_to_content
|
||||
from fastmcp.tools.tool_transform import TransformedTool
|
||||
from fastmcp.utilities.tests import temporary_settings
|
||||
|
||||
|
||||
class TestToolSerializerDeprecated:
|
||||
"""Tests for deprecated serializer functionality."""
|
||||
|
||||
async def test_tool_serializer(self):
|
||||
"""Test that a tool's serializer is used to serialize the result."""
|
||||
|
||||
def custom_serializer(data) -> str:
|
||||
return f"Custom serializer: {data}"
|
||||
|
||||
def process_list(items: list[int]) -> int:
|
||||
return sum(items)
|
||||
|
||||
tool = Tool.from_function(process_list, serializer=custom_serializer)
|
||||
|
||||
result = await tool.run(arguments={"items": [1, 2, 3, 4, 5]})
|
||||
# Custom serializer affects unstructured content
|
||||
assert isinstance(result.content[0], TextContent)
|
||||
assert result.content[0].text == "Custom serializer: 15"
|
||||
# Structured output should have the raw value
|
||||
assert result.structured_content == {"result": 15}
|
||||
|
||||
def test_custom_serializer(self):
|
||||
"""Test that a custom serializer is used for non-MCP types."""
|
||||
|
||||
def custom_serializer(data):
|
||||
return f"Serialized: {data}"
|
||||
|
||||
result = _convert_to_content({"a": 1}, serializer=custom_serializer)
|
||||
|
||||
assert result == snapshot(
|
||||
[TextContent(type="text", text="Serialized: {'a': 1}")]
|
||||
)
|
||||
|
||||
def test_custom_serializer_error_fallback(self, caplog):
|
||||
"""Test that if a custom serializer fails, it falls back to the default."""
|
||||
|
||||
def custom_serializer_that_fails(data):
|
||||
raise ValueError("Serialization failed")
|
||||
|
||||
result = _convert_to_content({"a": 1}, serializer=custom_serializer_that_fails)
|
||||
|
||||
assert isinstance(result, list)
|
||||
assert result == snapshot([TextContent(type="text", text='{"a":1}')])
|
||||
|
||||
assert "Error serializing tool result" in caplog.text
|
||||
|
||||
|
||||
class TestSerializerDeprecationWarnings:
|
||||
"""Tests that deprecation warnings are raised when serializer is used."""
|
||||
|
||||
def test_tool_from_function_serializer_warning(self):
|
||||
"""Test that Tool.from_function warns when serializer is provided."""
|
||||
|
||||
def custom_serializer(data) -> str:
|
||||
return f"Custom: {data}"
|
||||
|
||||
def my_tool(x: int) -> int:
|
||||
return x * 2
|
||||
|
||||
with temporary_settings(deprecation_warnings=True):
|
||||
with pytest.warns(DeprecationWarning, match="serializer.*deprecated"):
|
||||
Tool.from_function(my_tool, serializer=custom_serializer)
|
||||
|
||||
def test_tool_from_function_serializer_no_warning_when_disabled(self):
|
||||
"""Test that no warning is raised when deprecation_warnings is False."""
|
||||
|
||||
def custom_serializer(data) -> str:
|
||||
return f"Custom: {data}"
|
||||
|
||||
def my_tool(x: int) -> int:
|
||||
return x * 2
|
||||
|
||||
with temporary_settings(deprecation_warnings=False):
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("error")
|
||||
# Should not raise
|
||||
Tool.from_function(my_tool, serializer=custom_serializer)
|
||||
|
||||
def test_local_provider_tool_serializer_warning(self):
|
||||
"""Test that LocalProvider.tool warns when serializer is provided."""
|
||||
provider = LocalProvider()
|
||||
|
||||
def custom_serializer(data) -> str:
|
||||
return f"Custom: {data}"
|
||||
|
||||
def my_tool(x: int) -> int:
|
||||
return x * 2
|
||||
|
||||
with temporary_settings(deprecation_warnings=True):
|
||||
with pytest.warns(DeprecationWarning, match="serializer.*deprecated"):
|
||||
provider.tool(my_tool, serializer=custom_serializer)
|
||||
|
||||
def test_local_provider_tool_decorator_serializer_warning(self):
|
||||
"""Test that LocalProvider.tool decorator warns when serializer is provided."""
|
||||
provider = LocalProvider()
|
||||
|
||||
def custom_serializer(data) -> str:
|
||||
return f"Custom: {data}"
|
||||
|
||||
with temporary_settings(deprecation_warnings=True):
|
||||
with pytest.warns(DeprecationWarning, match="serializer.*deprecated"):
|
||||
|
||||
@provider.tool(serializer=custom_serializer)
|
||||
def my_tool(x: int) -> int:
|
||||
return x * 2
|
||||
|
||||
def test_fastmcp_tool_serializer_warning(self):
|
||||
"""Test that FastMCP.tool warns when serializer is provided via LocalProvider."""
|
||||
|
||||
def custom_serializer(data) -> str:
|
||||
return f"Custom: {data}"
|
||||
|
||||
def my_tool(x: int) -> int:
|
||||
return x * 2
|
||||
|
||||
# FastMCP.tool doesn't accept serializer directly, it goes through LocalProvider
|
||||
# So we test LocalProvider.tool which is what FastMCP uses internally
|
||||
provider = LocalProvider()
|
||||
with temporary_settings(deprecation_warnings=True):
|
||||
with pytest.warns(DeprecationWarning, match="serializer.*deprecated"):
|
||||
provider.tool(my_tool, serializer=custom_serializer)
|
||||
|
||||
def test_fastmcp_tool_serializer_parameter_raises_type_error(self):
|
||||
"""Test that FastMCP tool_serializer parameter raises TypeError."""
|
||||
|
||||
def custom_serializer(data) -> str:
|
||||
return f"Custom: {data}"
|
||||
|
||||
with pytest.raises(TypeError, match="no longer accepts `tool_serializer`"):
|
||||
FastMCP("TestServer", tool_serializer=custom_serializer)
|
||||
|
||||
def test_transformed_tool_from_tool_serializer_warning(self):
|
||||
"""Test that TransformedTool.from_tool warns when serializer is provided."""
|
||||
|
||||
def custom_serializer(data) -> str:
|
||||
return f"Custom: {data}"
|
||||
|
||||
def my_tool(x: int) -> int:
|
||||
return x * 2
|
||||
|
||||
parent_tool = Tool.from_function(my_tool)
|
||||
|
||||
with temporary_settings(deprecation_warnings=True):
|
||||
with pytest.warns(DeprecationWarning, match="serializer.*deprecated"):
|
||||
TransformedTool.from_tool(parent_tool, serializer=custom_serializer)
|
||||
|
||||
def test_mcp_mixin_tool_serializer_warning(self):
|
||||
"""Test that mcp_tool decorator warns when serializer is provided."""
|
||||
|
||||
def custom_serializer(data) -> str:
|
||||
return f"Custom: {data}"
|
||||
|
||||
with temporary_settings(deprecation_warnings=True):
|
||||
with pytest.warns(DeprecationWarning, match="serializer.*deprecated"):
|
||||
|
||||
@mcp_tool(serializer=custom_serializer)
|
||||
def my_tool(x: int) -> int:
|
||||
return x * 2
|
||||
Loading…
Add table
Add a link
Reference in a new issue