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:
Jeremiah Lowin 2025-11-01 12:58:04 -07:00 committed by GitHub
commit ae9fe6f4be
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 5 additions and 167 deletions

View file

@ -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

View file

@ -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)

View file

@ -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}