diff --git a/src/fastmcp/__init__.py b/src/fastmcp/__init__.py index 3e706880d..8685ee767 100644 --- a/src/fastmcp/__init__.py +++ b/src/fastmcp/__init__.py @@ -25,7 +25,6 @@ __all__ = [ ] +# ensure deprecation warnings are displayedby default if settings.deprecation_warnings: warnings.simplefilter("default", DeprecationWarning) -else: - warnings.simplefilter("ignore", DeprecationWarning) diff --git a/src/fastmcp/client/transports.py b/src/fastmcp/client/transports.py index 162c82844..778c58447 100644 --- a/src/fastmcp/client/transports.py +++ b/src/fastmcp/client/transports.py @@ -20,6 +20,7 @@ from mcp.shared.memory import create_client_server_memory_streams from pydantic import AnyUrl from typing_extensions import Unpack +import fastmcp from fastmcp.client.auth.bearer import BearerAuth from fastmcp.client.auth.oauth import OAuth from fastmcp.server.dependencies import get_http_headers @@ -109,11 +110,12 @@ class WSTransport(ClientTransport): def __init__(self, url: str | AnyUrl): # we never really used this transport, so it can be removed at any time - warnings.warn( - "WSTransport is a deprecated MCP transport and will be removed in a future version. Use StreamableHttpTransport instead.", - DeprecationWarning, - stacklevel=2, - ) + if fastmcp.settings.deprecation_warnings: + warnings.warn( + "WSTransport is a deprecated MCP transport and will be removed in a future version. Use StreamableHttpTransport instead.", + DeprecationWarning, + stacklevel=2, + ) if isinstance(url, AnyUrl): url = str(url) if not isinstance(url, str) or not url.startswith("ws"): diff --git a/src/fastmcp/prompts/prompt_manager.py b/src/fastmcp/prompts/prompt_manager.py index f2f706c5b..10805f5e6 100644 --- a/src/fastmcp/prompts/prompt_manager.py +++ b/src/fastmcp/prompts/prompt_manager.py @@ -60,11 +60,12 @@ class PromptManager: ) -> FunctionPrompt: """Create a prompt from a function.""" # deprecated in 2.7.0 - warnings.warn( - "PromptManager.add_prompt_from_fn() is deprecated. Use Prompt.from_function() and call add_prompt() instead.", - DeprecationWarning, - stacklevel=2, - ) + if settings.deprecation_warnings: + warnings.warn( + "PromptManager.add_prompt_from_fn() is deprecated. Use Prompt.from_function() and call add_prompt() instead.", + DeprecationWarning, + stacklevel=2, + ) prompt = FunctionPrompt.from_function( fn, name=name, description=description, tags=tags ) diff --git a/src/fastmcp/resources/resource_manager.py b/src/fastmcp/resources/resource_manager.py index ed299d0c0..64a11aae1 100644 --- a/src/fastmcp/resources/resource_manager.py +++ b/src/fastmcp/resources/resource_manager.py @@ -123,11 +123,12 @@ class ResourceManager: returns the existing resource. """ # deprecated in 2.7.0 - warnings.warn( - "add_resource_from_fn is deprecated. Use Resource.from_function() and call add_resource() instead.", - DeprecationWarning, - stacklevel=2, - ) + if settings.deprecation_warnings: + warnings.warn( + "add_resource_from_fn is deprecated. Use Resource.from_function() and call add_resource() instead.", + DeprecationWarning, + stacklevel=2, + ) resource = Resource.from_function( fn=fn, uri=uri, @@ -180,11 +181,12 @@ class ResourceManager: ) -> ResourceTemplate: """Create a template from a function.""" # deprecated in 2.7.0 - warnings.warn( - "add_template_from_fn is deprecated. Use ResourceTemplate.from_function() and call add_template() instead.", - DeprecationWarning, - stacklevel=2, - ) + if settings.deprecation_warnings: + warnings.warn( + "add_template_from_fn is deprecated. Use ResourceTemplate.from_function() and call add_template() instead.", + DeprecationWarning, + stacklevel=2, + ) template = ResourceTemplate.from_function( fn, uri_template=uri_template, diff --git a/src/fastmcp/server/context.py b/src/fastmcp/server/context.py index 97a05c672..92f725db7 100644 --- a/src/fastmcp/server/context.py +++ b/src/fastmcp/server/context.py @@ -22,6 +22,7 @@ from pydantic.networks import AnyUrl from starlette.requests import Request import fastmcp.server.dependencies +from fastmcp import settings from fastmcp.server.server import FastMCP from fastmcp.utilities.logging import get_logger @@ -242,14 +243,15 @@ class Context: def get_http_request(self) -> Request: """Get the active starlette request.""" - # Deprecation warning, added in FastMCP 2.2.11 - warnings.warn( - "Context.get_http_request() is deprecated and will be removed in a future version. " - "Use get_http_request() from fastmcp.server.dependencies instead. " - "See https://gofastmcp.com/patterns/http-requests for more details.", - DeprecationWarning, - stacklevel=2, - ) + # Deprecated in 2.2.11 + if settings.deprecation_warnings: + warnings.warn( + "Context.get_http_request() is deprecated and will be removed in a future version. " + "Use get_http_request() from fastmcp.server.dependencies instead. " + "See https://gofastmcp.com/patterns/http-requests for more details.", + DeprecationWarning, + stacklevel=2, + ) return fastmcp.server.dependencies.get_http_request() diff --git a/src/fastmcp/server/openapi.py b/src/fastmcp/server/openapi.py index 7ee675d8c..315b7bf8d 100644 --- a/src/fastmcp/server/openapi.py +++ b/src/fastmcp/server/openapi.py @@ -16,6 +16,7 @@ import httpx from mcp.types import EmbeddedResource, ImageContent, TextContent, ToolAnnotations from pydantic.networks import AnyUrl +import fastmcp from fastmcp.exceptions import ToolError from fastmcp.resources import Resource, ResourceTemplate from fastmcp.server.dependencies import get_http_headers @@ -129,27 +130,30 @@ class RouteMap: """Validate and process the route map after initialization.""" # Handle backward compatibility for route_type, deprecated in 2.5.0 if self.mcp_type is None and self.route_type is not None: - warnings.warn( - "The 'route_type' parameter is deprecated and will be removed in a future version. " - "Use 'mcp_type' instead with the appropriate MCPType value.", - DeprecationWarning, - stacklevel=2, - ) - if isinstance(self.route_type, RouteType): + if fastmcp.settings.deprecation_warnings: warnings.warn( - "The RouteType class is deprecated and will be removed in a future version. " - "Use MCPType instead.", + "The 'route_type' parameter is deprecated and will be removed in a future version. " + "Use 'mcp_type' instead with the appropriate MCPType value.", DeprecationWarning, stacklevel=2, ) + if isinstance(self.route_type, RouteType): + if fastmcp.settings.deprecation_warnings: + warnings.warn( + "The RouteType class is deprecated and will be removed in a future version. " + "Use MCPType instead.", + DeprecationWarning, + stacklevel=2, + ) # Check for the deprecated IGNORE value if self.route_type == RouteType.IGNORE: - warnings.warn( - "RouteType.IGNORE is deprecated and will be removed in a future version. " - "Use MCPType.EXCLUDE instead.", - DeprecationWarning, - stacklevel=2, - ) + if fastmcp.settings.deprecation_warnings: + warnings.warn( + "RouteType.IGNORE is deprecated and will be removed in a future version. " + "Use MCPType.EXCLUDE instead.", + DeprecationWarning, + stacklevel=2, + ) # Convert from RouteType to MCPType if needed if isinstance(self.route_type, RouteType): diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py index 33a0088e7..b3051c91d 100644 --- a/src/fastmcp/server/server.py +++ b/src/fastmcp/server/server.py @@ -242,11 +242,12 @@ class FastMCP(Generic[LifespanResultT]): ]: if arg is not None: # Deprecated in 2.8.0 - warnings.warn( - f"Providing `{name}` when creating a server is deprecated. Provide it when calling `run` or as a global setting instead.", - DeprecationWarning, - stacklevel=2, - ) + if fastmcp.settings.deprecation_warnings: + warnings.warn( + f"Providing `{name}` when creating a server is deprecated. Provide it when calling `run` or as a global setting instead.", + DeprecationWarning, + stacklevel=2, + ) deprecated_settings[name] = arg combined_settings = fastmcp.settings.model_dump() | deprecated_settings @@ -254,11 +255,13 @@ class FastMCP(Generic[LifespanResultT]): @property def settings(self) -> Settings: - warnings.warn( - "Accessing `.settings` on a FastMCP instance is deprecated. Use the global `fastmcp.settings` instead.", - DeprecationWarning, - stacklevel=2, - ) + # Deprecated in 2.8.0 + if fastmcp.settings.deprecation_warnings: + warnings.warn( + "Accessing `.settings` on a FastMCP instance is deprecated. Use the global `fastmcp.settings` instead.", + DeprecationWarning, + stacklevel=2, + ) return self._deprecated_settings @property @@ -865,11 +868,12 @@ class FastMCP(Generic[LifespanResultT]): tags: Optional set of tags for categorizing the resource """ # deprecated since 2.7.0 - warnings.warn( - "The add_resource_fn method is deprecated. Use the resource decorator instead.", - DeprecationWarning, - stacklevel=2, - ) + if fastmcp.settings.deprecation_warnings: + warnings.warn( + "The add_resource_fn method is deprecated. Use the resource decorator instead.", + DeprecationWarning, + stacklevel=2, + ) self._resource_manager.add_resource_or_template_from_fn( fn=fn, uri=uri, @@ -1233,13 +1237,14 @@ class FastMCP(Generic[LifespanResultT]): """Run the server using SSE transport.""" # Deprecated since 2.3.2 - warnings.warn( - "The run_sse_async method is deprecated (as of 2.3.2). Use run_http_async for a " - "modern (non-SSE) alternative, or create an SSE app with " - "`fastmcp.server.http.create_sse_app` and run it directly.", - DeprecationWarning, - stacklevel=2, - ) + if fastmcp.settings.deprecation_warnings: + warnings.warn( + "The run_sse_async method is deprecated (as of 2.3.2). Use run_http_async for a " + "modern (non-SSE) alternative, or create an SSE app with " + "`fastmcp.server.http.create_sse_app` and run it directly.", + DeprecationWarning, + stacklevel=2, + ) await self.run_http_async( transport="sse", host=host, @@ -1264,12 +1269,13 @@ class FastMCP(Generic[LifespanResultT]): middleware: A list of middleware to apply to the app """ # Deprecated since 2.3.2 - warnings.warn( - "The sse_app method is deprecated (as of 2.3.2). Use http_app as a modern (non-SSE) " - "alternative, or call `fastmcp.server.http.create_sse_app` directly.", - DeprecationWarning, - stacklevel=2, - ) + if fastmcp.settings.deprecation_warnings: + warnings.warn( + "The sse_app method is deprecated (as of 2.3.2). Use http_app as a modern (non-SSE) " + "alternative, or call `fastmcp.server.http.create_sse_app` directly.", + DeprecationWarning, + stacklevel=2, + ) return create_sse_app( server=self, message_path=message_path or self._deprecated_settings.message_path, @@ -1292,11 +1298,12 @@ class FastMCP(Generic[LifespanResultT]): middleware: A list of middleware to apply to the app """ # Deprecated since 2.3.2 - warnings.warn( - "The streamable_http_app method is deprecated (as of 2.3.2). Use http_app() instead.", - DeprecationWarning, - stacklevel=2, - ) + if fastmcp.settings.deprecation_warnings: + warnings.warn( + "The streamable_http_app method is deprecated (as of 2.3.2). Use http_app() instead.", + DeprecationWarning, + stacklevel=2, + ) return self.http_app(path=path, middleware=middleware) def http_app( @@ -1349,12 +1356,13 @@ class FastMCP(Generic[LifespanResultT]): uvicorn_config: dict[str, Any] | None = None, ) -> None: # Deprecated since 2.3.2 - warnings.warn( - "The run_streamable_http_async method is deprecated (as of 2.3.2). " - "Use run_http_async instead.", - DeprecationWarning, - stacklevel=2, - ) + if fastmcp.settings.deprecation_warnings: + warnings.warn( + "The run_streamable_http_async method is deprecated (as of 2.3.2). " + "Use run_http_async instead.", + DeprecationWarning, + stacklevel=2, + ) await self.run_http_async( transport="streamable-http", host=host, @@ -1422,30 +1430,33 @@ class FastMCP(Generic[LifespanResultT]): if tool_separator is not None: # Deprecated since 2.4.0 - warnings.warn( - "The tool_separator parameter is deprecated and will be removed in a future version. " - "Tools are now prefixed using 'prefix_toolname' format.", - DeprecationWarning, - stacklevel=2, - ) + if fastmcp.settings.deprecation_warnings: + warnings.warn( + "The tool_separator parameter is deprecated and will be removed in a future version. " + "Tools are now prefixed using 'prefix_toolname' format.", + DeprecationWarning, + stacklevel=2, + ) if resource_separator is not None: # Deprecated since 2.4.0 - warnings.warn( - "The resource_separator parameter is deprecated and ignored. " - "Resource prefixes are now added using the protocol://prefix/path format.", - DeprecationWarning, - stacklevel=2, - ) + if fastmcp.settings.deprecation_warnings: + warnings.warn( + "The resource_separator parameter is deprecated and ignored. " + "Resource prefixes are now added using the protocol://prefix/path format.", + DeprecationWarning, + stacklevel=2, + ) if prompt_separator is not None: # Deprecated since 2.4.0 - warnings.warn( - "The prompt_separator parameter is deprecated and will be removed in a future version. " - "Prompts are now prefixed using 'prefix_promptname' format.", - DeprecationWarning, - stacklevel=2, - ) + if fastmcp.settings.deprecation_warnings: + warnings.warn( + "The prompt_separator parameter is deprecated and will be removed in a future version. " + "Prompts are now prefixed using 'prefix_promptname' format.", + DeprecationWarning, + stacklevel=2, + ) # if as_proxy is not specified and the server has a custom lifespan, # we should treat it as a proxy @@ -1507,30 +1518,33 @@ class FastMCP(Generic[LifespanResultT]): """ if tool_separator is not None: # Deprecated since 2.4.0 - warnings.warn( - "The tool_separator parameter is deprecated and will be removed in a future version. " - "Tools are now prefixed using 'prefix_toolname' format.", - DeprecationWarning, - stacklevel=2, - ) + if fastmcp.settings.deprecation_warnings: + warnings.warn( + "The tool_separator parameter is deprecated and will be removed in a future version. " + "Tools are now prefixed using 'prefix_toolname' format.", + DeprecationWarning, + stacklevel=2, + ) if resource_separator is not None: # Deprecated since 2.4.0 - warnings.warn( - "The resource_separator parameter is deprecated and ignored. " - "Resource prefixes are now added using the protocol://prefix/path format.", - DeprecationWarning, - stacklevel=2, - ) + if fastmcp.settings.deprecation_warnings: + warnings.warn( + "The resource_separator parameter is deprecated and ignored. " + "Resource prefixes are now added using the protocol://prefix/path format.", + DeprecationWarning, + stacklevel=2, + ) if prompt_separator is not None: # Deprecated since 2.4.0 - warnings.warn( - "The prompt_separator parameter is deprecated and will be removed in a future version. " - "Prompts are now prefixed using 'prefix_promptname' format.", - DeprecationWarning, - stacklevel=2, - ) + if fastmcp.settings.deprecation_warnings: + warnings.warn( + "The prompt_separator parameter is deprecated and will be removed in a future version. " + "Prompts are now prefixed using 'prefix_promptname' format.", + DeprecationWarning, + stacklevel=2, + ) # Import tools from the mounted server tool_prefix = f"{prefix}_" @@ -1666,11 +1680,12 @@ class FastMCP(Generic[LifespanResultT]): Create a FastMCP proxy server from a FastMCP client. """ # Deprecated since 2.3.5 - warnings.warn( - "FastMCP.from_client() is deprecated; use FastMCP.as_proxy() instead.", - DeprecationWarning, - stacklevel=2, - ) + if fastmcp.settings.deprecation_warnings: + warnings.warn( + "FastMCP.from_client() is deprecated; use FastMCP.as_proxy() instead.", + DeprecationWarning, + stacklevel=2, + ) return cls.as_proxy(client, **settings) diff --git a/src/fastmcp/settings.py b/src/fastmcp/settings.py index b6cdc18a4..96fd657b3 100644 --- a/src/fastmcp/settings.py +++ b/src/fastmcp/settings.py @@ -1,7 +1,6 @@ from __future__ import annotations as _annotations import inspect -import warnings from pathlib import Path from typing import Annotated, Any, Literal @@ -15,6 +14,10 @@ from pydantic_settings import ( ) from typing_extensions import Self +from fastmcp.utilities.logging import get_logger + +logger = get_logger(__name__) + LOG_LEVEL = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] DuplicateBehavior = Literal["warn", "error", "replace", "ignore"] @@ -39,10 +42,8 @@ class ExtendedEnvSettingsSource(EnvSettingsSource): if env_val is not None: if prefix == "FASTMCP_SERVER_": # Deprecated in 2.8.0 - warnings.warn( + logger.warning( "Using `FASTMCP_SERVER_` environment variables is deprecated. Use `FASTMCP_` instead.", - DeprecationWarning, - stacklevel=2, ) return env_val, field_key, value_is_complex @@ -89,10 +90,8 @@ class Settings(BaseSettings): which accessed fastmcp.settings.settings """ # Deprecated in 2.8.0 - warnings.warn( + logger.warning( "Using fastmcp.settings.settings is deprecated. Use fastmcp.settings instead.", - DeprecationWarning, - stacklevel=2, ) return self diff --git a/src/fastmcp/tools/tool_manager.py b/src/fastmcp/tools/tool_manager.py index fe9eeb14c..3a1d04e73 100644 --- a/src/fastmcp/tools/tool_manager.py +++ b/src/fastmcp/tools/tool_manager.py @@ -71,11 +71,12 @@ class ToolManager: ) -> Tool: """Add a tool to the server.""" # deprecated in 2.7.0 - warnings.warn( - "ToolManager.add_tool_from_fn() is deprecated. Use Tool.from_function() and call add_tool() instead.", - DeprecationWarning, - stacklevel=2, - ) + if settings.deprecation_warnings: + warnings.warn( + "ToolManager.add_tool_from_fn() is deprecated. Use Tool.from_function() and call add_tool() instead.", + DeprecationWarning, + stacklevel=2, + ) tool = Tool.from_function( fn, name=name, diff --git a/tests/deprecated/test_deprecated.py b/tests/deprecated/test_deprecated.py index a48e7c216..a20b970bb 100644 --- a/tests/deprecated/test_deprecated.py +++ b/tests/deprecated/test_deprecated.py @@ -5,11 +5,34 @@ import pytest from starlette.applications import Starlette from fastmcp import Client, FastMCP +from fastmcp.utilities.tests import temporary_settings # reset deprecation warnings for this module pytestmark = pytest.mark.filterwarnings("default::DeprecationWarning") +class TestDeprecationWarningsSetting: + def test_deprecation_warnings_setting_true(self): + with temporary_settings(deprecation_warnings=True): + with pytest.warns(DeprecationWarning) as recorded_warnings: + # will warn once for providing deprecated arg + mcp = FastMCP(host="1.2.3.4") + # will warn once for accessing deprecated property + mcp.settings + + assert len(recorded_warnings) == 2 + + def test_deprecation_warnings_setting_false(self): + with temporary_settings(deprecation_warnings=False): + # will error if a warning is raised + with warnings.catch_warnings(): + warnings.simplefilter("error") + # will warn once for providing deprecated arg + mcp = FastMCP(host="1.2.3.4") + # will warn once for accessing deprecated property + mcp.settings + + def test_sse_app_deprecation_warning(): """Test that sse_app raises a deprecation warning.""" server = FastMCP("TestServer") diff --git a/tests/deprecated/test_settings.py b/tests/deprecated/test_settings.py index 490b88c1a..6c8fc9862 100644 --- a/tests/deprecated/test_settings.py +++ b/tests/deprecated/test_settings.py @@ -305,7 +305,7 @@ class TestDeprecatedServerInitKwargs: class TestDeprecatedEnvironmentVariables: """Test deprecated environment variable prefixes.""" - def test_fastmcp_server_env_var_deprecation_warning(self): + 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) @@ -313,11 +313,15 @@ class TestDeprecatedEnvironmentVariables: try: os.environ[env_var_name] = "192.168.1.1" - with pytest.warns( - DeprecationWarning, - match=r"Using `FASTMCP_SERVER_` environment variables is deprecated\. Use `FASTMCP_` instead\.", - ): - settings = Settings() + 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" @@ -333,16 +337,20 @@ class TestDeprecatedEnvironmentVariables: class TestDeprecatedSettingsProperty: """Test deprecated settings property access.""" - def test_settings_property_deprecation_warning(self): - """Test that accessing fastmcp.settings.settings raises a deprecation warning.""" + def test_settings_property_deprecation_warning(self, caplog): + """Test that accessing fastmcp.settings.settings logs a deprecation warning.""" from fastmcp import settings - with pytest.warns( - DeprecationWarning, - match=r"Using fastmcp\.settings\.settings is deprecated\. Use fastmcp\.settings instead\.", - ): - # Access the deprecated property - deprecated_settings = settings.settings + # 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