mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-22 21:44:18 +02:00
Remove output_schema=False support (deprecated 2.11.4) (#2339)
* Remove deprecated client parameter from FastMCPProxy (#2333) * Delete deprecated test_output_schema_false.py Tests functionality that has been removed
This commit is contained in:
parent
1c885c48e7
commit
ae9fe6f4be
3 changed files with 5 additions and 167 deletions
|
|
@ -1,7 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import warnings
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import (
|
||||
|
|
@ -9,7 +8,6 @@ from typing import (
|
|||
Annotated,
|
||||
Any,
|
||||
Generic,
|
||||
Literal,
|
||||
TypeAlias,
|
||||
get_type_hints,
|
||||
)
|
||||
|
|
@ -21,7 +19,6 @@ from mcp.types import Tool as MCPTool
|
|||
from pydantic import Field, PydanticSchemaGenerationError
|
||||
from typing_extensions import TypeVar
|
||||
|
||||
import fastmcp
|
||||
from fastmcp.server.dependencies import get_context
|
||||
from fastmcp.utilities.components import FastMCPComponent
|
||||
from fastmcp.utilities.json_schema import compress_schema
|
||||
|
|
@ -173,7 +170,7 @@ class Tool(FastMCPComponent):
|
|||
tags: set[str] | None = None,
|
||||
annotations: ToolAnnotations | None = None,
|
||||
exclude_args: list[str] | None = None,
|
||||
output_schema: dict[str, Any] | Literal[False] | NotSetT | None = NotSet,
|
||||
output_schema: dict[str, Any] | NotSetT | None = NotSet,
|
||||
serializer: ToolResultSerializerType | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
enabled: bool | None = None,
|
||||
|
|
@ -216,7 +213,7 @@ class Tool(FastMCPComponent):
|
|||
description: str | NotSetT | None = NotSet,
|
||||
tags: set[str] | None = None,
|
||||
annotations: ToolAnnotations | NotSetT | None = NotSet,
|
||||
output_schema: dict[str, Any] | Literal[False] | NotSetT | None = NotSet,
|
||||
output_schema: dict[str, Any] | NotSetT | None = NotSet,
|
||||
serializer: ToolResultSerializerType | None = None,
|
||||
meta: dict[str, Any] | NotSetT | None = NotSet,
|
||||
transform_args: dict[str, ArgTransform] | None = None,
|
||||
|
|
@ -255,7 +252,7 @@ class FunctionTool(Tool):
|
|||
tags: set[str] | None = None,
|
||||
annotations: ToolAnnotations | None = None,
|
||||
exclude_args: list[str] | None = None,
|
||||
output_schema: dict[str, Any] | Literal[False] | NotSetT | None = NotSet,
|
||||
output_schema: dict[str, Any] | NotSetT | None = NotSet,
|
||||
serializer: ToolResultSerializerType | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
enabled: bool | None = None,
|
||||
|
|
@ -269,17 +266,8 @@ class FunctionTool(Tool):
|
|||
|
||||
if isinstance(output_schema, NotSetT):
|
||||
final_output_schema = parsed_fn.output_schema
|
||||
elif output_schema is False:
|
||||
# Handle False as deprecated synonym for None (deprecated in 2.11.4)
|
||||
if fastmcp.settings.deprecation_warnings:
|
||||
warnings.warn(
|
||||
"Passing output_schema=False is deprecated. Use output_schema=None instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
final_output_schema = None
|
||||
else:
|
||||
# At this point output_schema is not NotSetT and not False, so it must be dict | None
|
||||
# At this point output_schema is not NotSetT, so it must be dict | None
|
||||
final_output_schema = output_schema
|
||||
# Note: explicit schemas (dict) are used as-is without auto-wrapping
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import warnings
|
||||
from collections.abc import Callable
|
||||
from contextvars import ContextVar
|
||||
from dataclasses import dataclass
|
||||
|
|
@ -13,7 +12,6 @@ from pydantic import ConfigDict
|
|||
from pydantic.fields import Field
|
||||
from pydantic.functional_validators import BeforeValidator
|
||||
|
||||
import fastmcp
|
||||
from fastmcp.tools.tool import ParsedFunction, Tool, ToolResult, _convert_to_content
|
||||
from fastmcp.utilities.components import _convert_set_default_none
|
||||
from fastmcp.utilities.json_schema import compress_schema
|
||||
|
|
@ -371,7 +369,7 @@ class TransformedTool(Tool):
|
|||
transform_fn: Callable[..., Any] | None = None,
|
||||
transform_args: dict[str, ArgTransform] | None = None,
|
||||
annotations: ToolAnnotations | NotSetT | None = NotSet,
|
||||
output_schema: dict[str, Any] | Literal[False] | NotSetT | None = NotSet,
|
||||
output_schema: dict[str, Any] | NotSetT | None = NotSet,
|
||||
serializer: Callable[[Any], str] | NotSetT | None = NotSet,
|
||||
meta: dict[str, Any] | NotSetT | None = NotSet,
|
||||
enabled: bool | None = None,
|
||||
|
|
@ -486,15 +484,6 @@ class TransformedTool(Tool):
|
|||
final_output_schema = tool.output_schema
|
||||
else:
|
||||
final_output_schema = tool.output_schema
|
||||
elif output_schema is False:
|
||||
# Handle False as deprecated synonym for None (deprecated in 2.11.4)
|
||||
if fastmcp.settings.deprecation_warnings:
|
||||
warnings.warn(
|
||||
"Passing output_schema=False is deprecated. Use output_schema=None instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
final_output_schema = None
|
||||
else:
|
||||
final_output_schema = cast(dict | None, output_schema)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,139 +0,0 @@
|
|||
"""Test deprecated output_schema=False behavior (deprecated in 2.11.4)."""
|
||||
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.tools import Tool
|
||||
|
||||
|
||||
class TestDeprecatedOutputSchemaFalse:
|
||||
"""Test that output_schema=False is deprecated but still works."""
|
||||
|
||||
async def test_tool_decorator_output_schema_false_deprecated(self):
|
||||
"""Test that @mcp.tool(output_schema=False) shows deprecation warning."""
|
||||
mcp = FastMCP()
|
||||
|
||||
with pytest.warns(
|
||||
DeprecationWarning, match="output_schema=False is deprecated"
|
||||
):
|
||||
|
||||
@mcp.tool(output_schema=False) # type: ignore[arg-type]
|
||||
def simple_tool() -> int:
|
||||
"""A simple tool."""
|
||||
return 42
|
||||
|
||||
# Verify the tool was created with None as output_schema
|
||||
tool = mcp._tool_manager._tools["simple_tool"]
|
||||
assert tool.output_schema is None
|
||||
|
||||
async def test_tool_from_function_output_schema_false_deprecated(self):
|
||||
"""Test that Tool.from_function(output_schema=False) shows deprecation warning."""
|
||||
|
||||
def my_function() -> str:
|
||||
"""A simple function."""
|
||||
return "hello"
|
||||
|
||||
with pytest.warns(
|
||||
DeprecationWarning, match="output_schema=False is deprecated"
|
||||
):
|
||||
tool = Tool.from_function(my_function, output_schema=False) # type: ignore[arg-type]
|
||||
|
||||
# Verify the tool was created with None as output_schema
|
||||
assert tool.output_schema is None
|
||||
|
||||
async def test_tool_from_tool_output_schema_false_deprecated(self):
|
||||
"""Test that Tool.from_tool(output_schema=False) shows deprecation warning."""
|
||||
|
||||
# Create a parent tool
|
||||
def parent_function() -> dict[str, str]:
|
||||
"""A parent function."""
|
||||
return {"status": "ok"}
|
||||
|
||||
parent_tool = Tool.from_function(parent_function)
|
||||
|
||||
with pytest.warns(
|
||||
DeprecationWarning, match="output_schema=False is deprecated"
|
||||
):
|
||||
transformed_tool = Tool.from_tool(parent_tool, output_schema=False) # type: ignore[arg-type]
|
||||
|
||||
# Verify the tool was created with None as output_schema
|
||||
assert transformed_tool.output_schema is None
|
||||
|
||||
async def test_output_schema_false_functionality_preserved(self):
|
||||
"""Test that output_schema=False still works functionally like output_schema=None."""
|
||||
mcp = FastMCP()
|
||||
|
||||
# Create two tools - one with False, one with None
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", DeprecationWarning)
|
||||
|
||||
@mcp.tool(output_schema=False) # type: ignore[arg-type]
|
||||
def tool_with_false() -> dict[str, str]:
|
||||
"""Tool with output_schema=False."""
|
||||
return {"result": "false"}
|
||||
|
||||
@mcp.tool(output_schema=None)
|
||||
def tool_with_none() -> dict[str, str]:
|
||||
"""Tool with output_schema=None."""
|
||||
return {"result": "none"}
|
||||
|
||||
# Both should have None as output_schema
|
||||
assert mcp._tool_manager._tools["tool_with_false"].output_schema is None
|
||||
assert mcp._tool_manager._tools["tool_with_none"].output_schema is None
|
||||
|
||||
# Both should work the same way
|
||||
result_false = await mcp._tool_manager._tools["tool_with_false"].run({})
|
||||
result_none = await mcp._tool_manager._tools["tool_with_none"].run({})
|
||||
|
||||
# Both should return structured content for dict-like objects
|
||||
assert result_false.structured_content == {"result": "false"}
|
||||
assert result_none.structured_content == {"result": "none"}
|
||||
|
||||
async def test_output_schema_false_with_scalar_return(self):
|
||||
"""Test that output_schema=False works with scalar returns (no structured content)."""
|
||||
mcp = FastMCP()
|
||||
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", DeprecationWarning)
|
||||
|
||||
@mcp.tool(output_schema=False) # type: ignore[arg-type]
|
||||
def scalar_tool() -> int:
|
||||
"""Tool returning a scalar."""
|
||||
return 42
|
||||
|
||||
tool = mcp._tool_manager._tools["scalar_tool"]
|
||||
assert tool.output_schema is None
|
||||
|
||||
result = await tool.run({})
|
||||
# Scalar values don't produce structured content
|
||||
assert result.structured_content is None
|
||||
assert len(result.content) == 1
|
||||
assert result.content[0].text == "42" # type: ignore[attr-defined]
|
||||
|
||||
async def test_transform_with_output_schema_false(self):
|
||||
"""Test that transformation with output_schema=False still works."""
|
||||
|
||||
# Create a parent tool
|
||||
def parent_function(x: int) -> dict[str, int]:
|
||||
"""A parent function."""
|
||||
return {"value": x * 2}
|
||||
|
||||
parent_tool = Tool.from_function(parent_function)
|
||||
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", DeprecationWarning)
|
||||
|
||||
# Transform with output_schema=False
|
||||
transformed = Tool.from_tool(
|
||||
parent_tool,
|
||||
name="doubled",
|
||||
output_schema=False, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
assert transformed.output_schema is None
|
||||
|
||||
# Tool should still work
|
||||
result = await transformed.run({"x": 5})
|
||||
assert result.structured_content == {"value": 10}
|
||||
Loading…
Add table
Add a link
Reference in a new issue