Remove open-ended and server-specific settings

This commit is contained in:
Jeremiah Lowin 2025-06-07 20:20:04 -04:00
commit aaa09ca7a3
16 changed files with 507 additions and 119 deletions

View file

@ -4,6 +4,8 @@ from pydantic import AnyHttpUrl, ValidationError
from fastmcp import FastMCP
from fastmcp.server.auth.providers.bearer import BearerAuthProvider
from fastmcp.server.auth.providers.bearer_env import EnvBearerAuthProvider
from fastmcp.settings import Settings
from fastmcp.utilities.tests import temporary_settings
def test_load_bearer_env_from_env_var(monkeypatch):
@ -13,7 +15,8 @@ def test_load_bearer_env_from_env_var(monkeypatch):
monkeypatch.setenv("FASTMCP_SERVER_DEFAULT_AUTH_PROVIDER", "bearer_env")
monkeypatch.setenv("FASTMCP_AUTH_BEARER_PUBLIC_KEY", "test-public-key")
mcp_with_auth = FastMCP()
with temporary_settings(**Settings().model_dump()):
mcp_with_auth = FastMCP()
assert isinstance(mcp_with_auth.auth, EnvBearerAuthProvider)
@ -23,10 +26,11 @@ def test_load_bearer_env_from_env_var_requires_public_key_or_jwks_uri(monkeypatc
monkeypatch.setenv("FASTMCP_SERVER_DEFAULT_AUTH_PROVIDER", "bearer_env")
with pytest.raises(
ValueError, match="Either public_key or jwks_uri must be provided"
):
FastMCP()
with temporary_settings(**Settings().model_dump()):
with pytest.raises(
ValueError, match="Either public_key or jwks_uri must be provided"
):
FastMCP()
def test_configure_bearer_env_from_env_var(monkeypatch):
@ -38,7 +42,8 @@ def test_configure_bearer_env_from_env_var(monkeypatch):
"FASTMCP_AUTH_BEARER_REQUIRED_SCOPES", '["test-scope1", "test-scope2"]'
)
mcp = FastMCP()
with temporary_settings(**Settings().model_dump()):
mcp = FastMCP()
assert isinstance(mcp.auth, EnvBearerAuthProvider)
assert mcp.auth.public_key == "test-public-key"
assert mcp.auth.issuer_url == AnyHttpUrl("http://test-issuer")
@ -50,17 +55,19 @@ def test_list_of_scopes_must_be_a_list(monkeypatch):
monkeypatch.setenv("FASTMCP_SERVER_DEFAULT_AUTH_PROVIDER", "bearer_env")
monkeypatch.setenv("FASTMCP_AUTH_BEARER_REQUIRED_SCOPES", "test-scope1")
with pytest.raises(ValidationError, match="Input should be a valid list"):
FastMCP()
with temporary_settings(**Settings().model_dump()):
with pytest.raises(ValidationError, match="Input should be a valid list"):
FastMCP()
def test_configure_bearer_env_jwks_uri_from_env_var(monkeypatch):
monkeypatch.setenv("FASTMCP_SERVER_DEFAULT_AUTH_PROVIDER", "bearer_env")
monkeypatch.setenv("FASTMCP_AUTH_BEARER_JWKS_URI", "test-jwks-uri")
mcp = FastMCP()
assert isinstance(mcp.auth, EnvBearerAuthProvider)
assert mcp.auth.jwks_uri == "test-jwks-uri"
with temporary_settings(**Settings().model_dump()):
mcp = FastMCP()
assert isinstance(mcp.auth, EnvBearerAuthProvider)
assert mcp.auth.jwks_uri == "test-jwks-uri"
def test_configure_bearer_env_public_key_and_jwks_uri_error(monkeypatch):
@ -68,15 +75,17 @@ def test_configure_bearer_env_public_key_and_jwks_uri_error(monkeypatch):
monkeypatch.setenv("FASTMCP_AUTH_BEARER_PUBLIC_KEY", "test-public-key")
monkeypatch.setenv("FASTMCP_AUTH_BEARER_JWKS_URI", "test-jwks-uri")
with pytest.raises(ValueError, match="Provide either public_key or jwks_uri"):
FastMCP()
with temporary_settings(**Settings().model_dump()):
with pytest.raises(ValueError, match="Provide either public_key or jwks_uri"):
FastMCP()
def test_provided_auth_takes_precedence_over_env_vars(monkeypatch):
monkeypatch.setenv("FASTMCP_SERVER_DEFAULT_AUTH_PROVIDER", "bearer_env")
monkeypatch.setenv("FASTMCP_AUTH_BEARER_PUBLIC_KEY", "test-public-key")
mcp = FastMCP(auth=BearerAuthProvider(public_key="test-public-key-2"))
assert isinstance(mcp.auth, BearerAuthProvider)
assert not isinstance(mcp.auth, EnvBearerAuthProvider)
assert mcp.auth.public_key == "test-public-key-2"
with temporary_settings(**Settings().model_dump()):
mcp = FastMCP(auth=BearerAuthProvider(public_key="test-public-key-2"))
assert isinstance(mcp.auth, BearerAuthProvider)
assert not isinstance(mcp.auth, EnvBearerAuthProvider)
assert mcp.auth.public_key == "test-public-key-2"

View file

@ -0,0 +1,305 @@
import warnings
from unittest.mock import patch
import pytest
from fastmcp import 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",
tags={"test", "server"},
cache_expiration_seconds=60.0,
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"
assert server.tags == {"test", "server"}
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")
server = 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,
}
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,
}
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")
def create_server_with_deprecated_kwargs():
return FastMCP("TestServer", log_level="DEBUG")
server = create_server_with_deprecated_kwargs()
# 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

View file

@ -4,7 +4,7 @@ from fastmcp.utilities.tests import temporary_settings
class TestTemporarySettings:
def test_temporary_settings(self):
assert fastmcp.settings.settings.log_level == "DEBUG"
assert fastmcp.settings.log_level == "DEBUG"
with temporary_settings(log_level="ERROR"):
assert fastmcp.settings.settings.log_level == "ERROR"
assert fastmcp.settings.settings.log_level == "DEBUG"
assert fastmcp.settings.log_level == "ERROR"
assert fastmcp.settings.log_level == "DEBUG"