File size: 5,297 Bytes
3685b24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
"""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}