diff --git a/src/fastmcp/tools/tool.py b/src/fastmcp/tools/tool.py index b58645579..2a6b1a04b 100644 --- a/src/fastmcp/tools/tool.py +++ b/src/fastmcp/tools/tool.py @@ -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 diff --git a/src/fastmcp/tools/tool_transform.py b/src/fastmcp/tools/tool_transform.py index efbd1fb8c..afa3d30d9 100644 --- a/src/fastmcp/tools/tool_transform.py +++ b/src/fastmcp/tools/tool_transform.py @@ -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) diff --git a/tests/deprecated/test_output_schema_false.py b/tests/deprecated/test_output_schema_false.py deleted file mode 100644 index 0dcc20d06..000000000 --- a/tests/deprecated/test_output_schema_false.py +++ /dev/null @@ -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}