Deprecate passing settings to the FastMCP instance

This commit is contained in:
Jeremiah Lowin 2025-05-12 15:37:12 -04:00
commit db3c59c098
4 changed files with 81 additions and 46 deletions

View file

@ -3,7 +3,6 @@
from __future__ import annotations
import datetime
import inspect
import warnings
from collections.abc import AsyncIterator, Awaitable, Callable
from contextlib import (
@ -54,7 +53,7 @@ from fastmcp.tools import ToolManager
from fastmcp.tools.tool import Tool
from fastmcp.utilities.cache import TimedCache
from fastmcp.utilities.decorators import DecoratedFunction
from fastmcp.utilities.logging import configure_logging, get_logger
from fastmcp.utilities.logging import get_logger
if TYPE_CHECKING:
from fastmcp.client import Client
@ -63,6 +62,8 @@ if TYPE_CHECKING:
logger = get_logger(__name__)
DuplicateBehavior = Literal["warn", "error", "replace", "ignore"]
@asynccontextmanager
async def default_lifespan(server: FastMCP) -> AsyncIterator[Any]:
@ -107,40 +108,52 @@ class FastMCP(Generic[LifespanResultT]):
| None
) = None,
tags: set[str] | None = None,
dependencies: list[str] | None = None,
tool_serializer: Callable[[Any], str] | None = None,
cache_expiration_seconds: float | None = None,
on_duplicate_tools: DuplicateBehavior | None = None,
on_duplicate_resources: DuplicateBehavior | None = None,
on_duplicate_prompts: DuplicateBehavior | None = None,
**settings: Any,
):
self.tags: set[str] = tags or set()
self.settings = fastmcp.settings.ServerSettings(**settings)
self._cache = TimedCache(
expiration=datetime.timedelta(
seconds=self.settings.cache_expiration_seconds
if settings:
# TODO: remove settings. Deprecated since 2.3.4
warnings.warn(
"Passing transport-specific and other runtime settings as kwargs "
"to the FastMCP constructor is deprecated (as of 2.3.4), "
"including most transport settings. Provide settings when calling "
"run() instead.",
DeprecationWarning,
stacklevel=2,
)
)
self.settings = fastmcp.settings.ServerSettings(**settings)
self.tags: set[str] = tags or set()
self.dependencies = dependencies
self._cache = TimedCache(
expiration=datetime.timedelta(seconds=cache_expiration_seconds or 0)
)
self._mounted_servers: dict[str, MountedServer] = {}
self._additional_http_routes: list[BaseRoute] = []
self._tool_manager = ToolManager(
duplicate_behavior=on_duplicate_tools,
serializer=tool_serializer,
)
self._resource_manager = ResourceManager(
duplicate_behavior=on_duplicate_resources
)
self._prompt_manager = PromptManager(duplicate_behavior=on_duplicate_prompts)
if lifespan is None:
self._has_lifespan = False
lifespan = default_lifespan
else:
self._has_lifespan = True
self._mcp_server = MCPServer[LifespanResultT](
name=name or "FastMCP",
instructions=instructions,
lifespan=_lifespan_wrapper(self, lifespan),
)
self._tool_manager = ToolManager(
duplicate_behavior=self.settings.on_duplicate_tools,
serializer=tool_serializer,
)
self._resource_manager = ResourceManager(
duplicate_behavior=self.settings.on_duplicate_resources
)
self._prompt_manager = PromptManager(
duplicate_behavior=self.settings.on_duplicate_prompts
)
if (self.settings.auth is not None) != (auth_server_provider is not None):
# TODO: after we support separate authorization servers (see
@ -150,15 +163,9 @@ class FastMCP(Generic[LifespanResultT]):
)
self._auth_server_provider = auth_server_provider
self._additional_http_routes: list[BaseRoute] = []
self.dependencies = self.settings.dependencies
# Set up MCP protocol handlers
self._setup_handlers()
# Configure logging
configure_logging(self.settings.log_level)
def __repr__(self) -> str:
return f"{type(self).__name__}({self.name!r})"
@ -764,15 +771,14 @@ class FastMCP(Generic[LifespanResultT]):
uvicorn_config: dict | None = None,
) -> None:
"""Run the server using SSE transport."""
# Deprecated since 2.3.2
warnings.warn(
inspect.cleandoc(
"""
The run_sse_async method is deprecated. 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.
"""
),
"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",
@ -797,14 +803,12 @@ class FastMCP(Generic[LifespanResultT]):
message_path: The path to the message endpoint
middleware: A list of middleware to apply to the app
"""
# Deprecated since 2.3.2
warnings.warn(
inspect.cleandoc(
"""
The sse_app method is deprecated. Use http_app as a modern (non-SSE)
alternative, or call `fastmcp.server.http.create_sse_app` directly.
"""
),
"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,
@ -829,9 +833,11 @@ class FastMCP(Generic[LifespanResultT]):
path: The path to the StreamableHTTP endpoint
middleware: A list of middleware to apply to the app
"""
# Deprecated since 2.3.2
warnings.warn(
"The streamable_http_app method is deprecated. Use http_app() instead.",
"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)
@ -886,9 +892,12 @@ class FastMCP(Generic[LifespanResultT]):
path: str | None = None,
uvicorn_config: dict | None = None,
) -> None:
# Deprecated since 2.3.2
warnings.warn(
"The run_streamable_http_async method is deprecated. Use run_http_async instead.",
"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",

View file

@ -3,8 +3,9 @@ from __future__ import annotations as _annotations
from typing import TYPE_CHECKING, Literal
from mcp.server.auth.settings import AuthSettings
from pydantic import Field
from pydantic import Field, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
from typing_extensions import Self
if TYPE_CHECKING:
pass
@ -38,6 +39,15 @@ class Settings(BaseSettings):
Defaults to False.""",
)
@model_validator(mode="after")
def setup_logging(self) -> Self:
"""Finalize the settings."""
from fastmcp.utilities.logging import configure_logging
configure_logging(self.log_level)
return self
class ServerSettings(BaseSettings):
"""FastMCP server settings.

View file

@ -21,22 +21,27 @@ def get_logger(name: str) -> logging.Logger:
def configure_logging(
level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | int = "INFO",
logger: logging.Logger | None = None,
) -> None:
"""Configure logging for FastMCP.
"""
Configure logging for FastMCP.
Args:
logger: the logger to configure
level: the log level to use
"""
if logger is None:
logger = logging.getLogger("FastMCP")
# Only configure the FastMCP logger namespace
handler = RichHandler(console=Console(stderr=True), rich_tracebacks=True)
formatter = logging.Formatter("%(message)s")
handler.setFormatter(formatter)
fastmcp_logger = logging.getLogger("FastMCP")
fastmcp_logger.setLevel(level)
logger.setLevel(level)
# Remove any existing handlers to avoid duplicates on reconfiguration
for hdlr in fastmcp_logger.handlers[:]:
fastmcp_logger.removeHandler(hdlr)
for hdlr in logger.handlers[:]:
logger.removeHandler(hdlr)
fastmcp_logger.addHandler(handler)
logger.addHandler(handler)

View file

@ -9,6 +9,17 @@ from starlette.applications import Starlette
from fastmcp import FastMCP
def test_fastmcp_kwargs_settings_deprecation_warning():
"""Test that passing settings as kwargs to FastMCP raises a deprecation warning."""
with pytest.warns(
DeprecationWarning,
match="Passing settings as kwargs to the FastMCP constructor is deprecated",
):
server = FastMCP("TestServer", host="127.0.0.2", port=8001)
assert server.settings.host == "127.0.0.2"
assert server.settings.port == 8001
def test_sse_app_deprecation_warning():
"""Test that sse_app raises a deprecation warning."""
server = FastMCP("TestServer")