File size: 15,198 Bytes
f0a0f92
927e044
 
 
 
 
 
f0a0f92
6acea03
927e044
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7b03b73
927e044
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7b03b73
927e044
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b031366
927e044
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3959209
927e044
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3959209
927e044
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b031366
927e044
 
 
 
 
 
 
 
 
 
 
f0a0f92
 
 
 
 
905120e
f0a0f92
 
 
 
 
 
 
6acea03
 
905120e
 
 
 
 
 
 
 
f0a0f92
 
 
 
 
 
 
 
 
 
2101c78
 
 
 
 
905120e
 
2101c78
 
6acea03
 
 
905120e
 
 
 
 
 
 
 
2101c78
 
 
 
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
import os
import warnings
from unittest.mock import patch

import pytest

from fastmcp import FastMCP
from fastmcp.settings import Settings
from fastmcp.utilities.tests import caplog_for_fastmcp

# reset deprecation warnings for this module
pytestmark = pytest.mark.filterwarnings("default::DeprecationWarning")


class TestDeprecatedServerInitKwargs:
    """Test deprecated server initialization keyword arguments."""

    def test_log_level_deprecation_warning(self):
        """Test that log_level raises a deprecation warning."""
        with pytest.warns(
            DeprecationWarning,
            match=r"Providing `log_level` when creating a server is deprecated\. Provide it when calling `run` or as a global setting instead\.",
        ):
            server = FastMCP("TestServer", log_level="DEBUG")

        # Verify the setting is still applied
        assert server._deprecated_settings.log_level == "DEBUG"

    def test_debug_deprecation_warning(self):
        """Test that debug raises a deprecation warning."""
        with pytest.warns(
            DeprecationWarning,
            match=r"Providing `debug` when creating a server is deprecated\. Provide it when calling `run` or as a global setting instead\.",
        ):
            server = FastMCP("TestServer", debug=True)

        # Verify the setting is still applied
        assert server._deprecated_settings.debug is True

    def test_host_deprecation_warning(self):
        """Test that host raises a deprecation warning."""
        with pytest.warns(
            DeprecationWarning,
            match=r"Providing `host` when creating a server is deprecated\. Provide it when calling `run` or as a global setting instead\.",
        ):
            server = FastMCP("TestServer", host="0.0.0.0")

        # Verify the setting is still applied
        assert server._deprecated_settings.host == "0.0.0.0"

    def test_port_deprecation_warning(self):
        """Test that port raises a deprecation warning."""
        with pytest.warns(
            DeprecationWarning,
            match=r"Providing `port` when creating a server is deprecated\. Provide it when calling `run` or as a global setting instead\.",
        ):
            server = FastMCP("TestServer", port=8080)

        # Verify the setting is still applied
        assert server._deprecated_settings.port == 8080

    def test_sse_path_deprecation_warning(self):
        """Test that sse_path raises a deprecation warning."""
        with pytest.warns(
            DeprecationWarning,
            match=r"Providing `sse_path` when creating a server is deprecated\. Provide it when calling `run` or as a global setting instead\.",
        ):
            server = FastMCP("TestServer", sse_path="/custom-sse")

        # Verify the setting is still applied
        assert server._deprecated_settings.sse_path == "/custom-sse"

    def test_message_path_deprecation_warning(self):
        """Test that message_path raises a deprecation warning."""
        with pytest.warns(
            DeprecationWarning,
            match=r"Providing `message_path` when creating a server is deprecated\. Provide it when calling `run` or as a global setting instead\.",
        ):
            server = FastMCP("TestServer", message_path="/custom-message")

        # Verify the setting is still applied
        assert server._deprecated_settings.message_path == "/custom-message"

    def test_streamable_http_path_deprecation_warning(self):
        """Test that streamable_http_path raises a deprecation warning."""
        with pytest.warns(
            DeprecationWarning,
            match=r"Providing `streamable_http_path` when creating a server is deprecated\. Provide it when calling `run` or as a global setting instead\.",
        ):
            server = FastMCP("TestServer", streamable_http_path="/custom-http")

        # Verify the setting is still applied
        assert server._deprecated_settings.streamable_http_path == "/custom-http"

    def test_json_response_deprecation_warning(self):
        """Test that json_response raises a deprecation warning."""
        with pytest.warns(
            DeprecationWarning,
            match=r"Providing `json_response` when creating a server is deprecated\. Provide it when calling `run` or as a global setting instead\.",
        ):
            server = FastMCP("TestServer", json_response=True)

        # Verify the setting is still applied
        assert server._deprecated_settings.json_response is True

    def test_stateless_http_deprecation_warning(self):
        """Test that stateless_http raises a deprecation warning."""
        with pytest.warns(
            DeprecationWarning,
            match=r"Providing `stateless_http` when creating a server is deprecated\. Provide it when calling `run` or as a global setting instead\.",
        ):
            server = FastMCP("TestServer", stateless_http=True)

        # Verify the setting is still applied
        assert server._deprecated_settings.stateless_http is True

    def test_multiple_deprecated_kwargs_warnings(self):
        """Test that multiple deprecated kwargs each raise their own warning."""
        with warnings.catch_warnings(record=True) as recorded_warnings:
            warnings.simplefilter("always")
            server = FastMCP(
                "TestServer",
                log_level="INFO",
                debug=False,
                host="127.0.0.1",
                port=9999,
                sse_path="/sse/",
                message_path="/msg",
                streamable_http_path="/http",
                json_response=False,
                stateless_http=False,
            )

        # Should have 9 deprecation warnings (one for each deprecated parameter)
        deprecation_warnings = [
            w for w in recorded_warnings if issubclass(w.category, DeprecationWarning)
        ]
        assert len(deprecation_warnings) == 9

        # Verify all expected parameters are mentioned in warnings
        expected_params = {
            "log_level",
            "debug",
            "host",
            "port",
            "sse_path",
            "message_path",
            "streamable_http_path",
            "json_response",
            "stateless_http",
        }
        mentioned_params = set()
        for warning in deprecation_warnings:
            message = str(warning.message)
            for param in expected_params:
                if f"Providing `{param}`" in message:
                    mentioned_params.add(param)

        assert mentioned_params == expected_params

        # Verify all settings are still applied
        assert server._deprecated_settings.log_level == "INFO"
        assert server._deprecated_settings.debug is False
        assert server._deprecated_settings.host == "127.0.0.1"
        assert server._deprecated_settings.port == 9999
        assert server._deprecated_settings.sse_path == "/sse/"
        assert server._deprecated_settings.message_path == "/msg"
        assert server._deprecated_settings.streamable_http_path == "/http"
        assert server._deprecated_settings.json_response is False
        assert server._deprecated_settings.stateless_http is False

    def test_non_deprecated_kwargs_no_warnings(self):
        """Test that non-deprecated kwargs don't raise warnings."""
        with warnings.catch_warnings(record=True) as recorded_warnings:
            warnings.simplefilter("always")
            server = FastMCP(
                name="TestServer",
                instructions="Test instructions",
                on_duplicate_tools="warn",
                on_duplicate_resources="error",
                on_duplicate_prompts="replace",
                resource_prefix_format="path",
                mask_error_details=True,
            )

        # Should have no deprecation warnings
        deprecation_warnings = [
            w for w in recorded_warnings if issubclass(w.category, DeprecationWarning)
        ]
        assert len(deprecation_warnings) == 0

        # Verify server was created successfully
        assert server.name == "TestServer"
        assert server.instructions == "Test instructions"

    def test_none_values_no_warnings(self):
        """Test that None values for deprecated kwargs don't raise warnings."""
        with warnings.catch_warnings(record=True) as recorded_warnings:
            warnings.simplefilter("always")
            FastMCP(
                "TestServer",
                log_level=None,
                debug=None,
                host=None,
                port=None,
                sse_path=None,
                message_path=None,
                streamable_http_path=None,
                json_response=None,
                stateless_http=None,
            )

        # Should have no deprecation warnings for None values
        deprecation_warnings = [
            w for w in recorded_warnings if issubclass(w.category, DeprecationWarning)
        ]
        assert len(deprecation_warnings) == 0

    def test_deprecated_settings_inheritance_from_global(self):
        """Test that deprecated settings inherit from global settings when not provided."""
        # Mock fastmcp.settings to test inheritance
        with patch("fastmcp.settings") as mock_settings:
            mock_settings.model_dump.return_value = {
                "log_level": "WARNING",
                "debug": True,
                "host": "0.0.0.0",
                "port": 3000,
                "sse_path": "/events",
                "message_path": "/messages",
                "streamable_http_path": "/stream",
                "json_response": True,
                "stateless_http": True,
            }
            mock_settings.server_auth = None  # Add server_auth attribute

            server = FastMCP("TestServer")

            # Verify settings are inherited from global settings
            assert server._deprecated_settings.log_level == "WARNING"
            assert server._deprecated_settings.debug is True
            assert server._deprecated_settings.host == "0.0.0.0"
            assert server._deprecated_settings.port == 3000
            assert server._deprecated_settings.sse_path == "/events"
            assert server._deprecated_settings.message_path == "/messages"
            assert server._deprecated_settings.streamable_http_path == "/stream"
            assert server._deprecated_settings.json_response is True
            assert server._deprecated_settings.stateless_http is True

    def test_deprecated_settings_override_global(self):
        """Test that deprecated settings override global settings when provided."""
        # Mock fastmcp.settings to test override behavior
        with patch("fastmcp.settings") as mock_settings:
            mock_settings.model_dump.return_value = {
                "log_level": "WARNING",
                "debug": True,
                "host": "0.0.0.0",
                "port": 3000,
                "sse_path": "/events",
                "message_path": "/messages",
                "streamable_http_path": "/stream",
                "json_response": True,
                "stateless_http": True,
            }
            mock_settings.server_auth = None  # Add server_auth attribute

            with warnings.catch_warnings():
                warnings.simplefilter("ignore")  # Ignore warnings for this test
                server = FastMCP(
                    "TestServer",
                    log_level="ERROR",
                    debug=False,
                    host="127.0.0.1",
                    port=8080,
                )

            # Verify provided settings override global settings
            assert server._deprecated_settings.log_level == "ERROR"
            assert server._deprecated_settings.debug is False
            assert server._deprecated_settings.host == "127.0.0.1"
            assert server._deprecated_settings.port == 8080
            # Non-overridden settings should still come from global
            assert server._deprecated_settings.sse_path == "/events"
            assert server._deprecated_settings.message_path == "/messages"
            assert server._deprecated_settings.streamable_http_path == "/stream"
            assert server._deprecated_settings.json_response is True
            assert server._deprecated_settings.stateless_http is True

    def test_stacklevel_points_to_constructor_call(self):
        """Test that deprecation warnings point to the FastMCP constructor call."""
        with warnings.catch_warnings(record=True) as recorded_warnings:
            warnings.simplefilter("always")

            FastMCP("TestServer", log_level="DEBUG")

        # Should have exactly one deprecation warning
        deprecation_warnings = [
            w for w in recorded_warnings if issubclass(w.category, DeprecationWarning)
        ]
        assert len(deprecation_warnings) == 1

        # The warning should point to the server.py file where FastMCP.__init__ is called
        # This verifies the stacklevel is working as intended (pointing to constructor)
        warning = deprecation_warnings[0]
        assert "server.py" in warning.filename


class TestDeprecatedEnvironmentVariables:
    """Test deprecated environment variable prefixes."""

    def test_fastmcp_server_env_var_deprecation_warning(self, caplog):
        """Test that FASTMCP_SERVER_ environment variables emit deprecation warnings."""
        env_var_name = "FASTMCP_SERVER_HOST"
        original_value = os.environ.get(env_var_name)

        try:
            os.environ[env_var_name] = "192.168.1.1"

            with caplog_for_fastmcp(caplog):
                settings = Settings()

            # Check that a warning was logged
            assert any(
                "Using `FASTMCP_SERVER_` environment variables is deprecated. Use `FASTMCP_` instead."
                in record.message
                for record in caplog.records
                if record.levelname == "WARNING"
            )

            # Verify the setting is still applied
            assert settings.host == "192.168.1.1"

        finally:
            # Clean up environment variable
            if original_value is not None:
                os.environ[env_var_name] = original_value
            else:
                os.environ.pop(env_var_name, None)


class TestDeprecatedSettingsProperty:
    """Test deprecated settings property access."""

    def test_settings_property_deprecation_warning(self, caplog):
        """Test that accessing fastmcp.settings.settings logs a deprecation warning."""
        from fastmcp import settings

        with caplog_for_fastmcp(caplog):
            # Access the deprecated property
            deprecated_settings = settings.settings

        # Check that a warning was logged
        assert any(
            "Using fastmcp.settings.settings is deprecated. Use fastmcp.settings instead."
            in record.message
            for record in caplog.records
            if record.levelname == "WARNING"
        )

        # Verify it still returns the same settings object
        assert deprecated_settings is settings
        assert isinstance(deprecated_settings, Settings)