Jeremiah Lowin commited on
Commit
d7ca855
·
unverified ·
2 Parent(s): 112271892415e9

Merge pull request #424 from jlowin/dep-settings

Browse files

Deprecate passing settings to the FastMCP instance

src/fastmcp/server/server.py CHANGED
@@ -3,7 +3,6 @@
3
  from __future__ import annotations
4
 
5
  import datetime
6
- import inspect
7
  import warnings
8
  from collections.abc import AsyncIterator, Awaitable, Callable
9
  from contextlib import (
@@ -54,7 +53,7 @@ from fastmcp.tools import ToolManager
54
  from fastmcp.tools.tool import Tool
55
  from fastmcp.utilities.cache import TimedCache
56
  from fastmcp.utilities.decorators import DecoratedFunction
57
- from fastmcp.utilities.logging import configure_logging, get_logger
58
 
59
  if TYPE_CHECKING:
60
  from fastmcp.client import Client
@@ -63,6 +62,8 @@ if TYPE_CHECKING:
63
 
64
  logger = get_logger(__name__)
65
 
 
 
66
 
67
  @asynccontextmanager
68
  async def default_lifespan(server: FastMCP) -> AsyncIterator[Any]:
@@ -107,40 +108,52 @@ class FastMCP(Generic[LifespanResultT]):
107
  | None
108
  ) = None,
109
  tags: set[str] | None = None,
 
110
  tool_serializer: Callable[[Any], str] | None = None,
 
 
 
 
111
  **settings: Any,
112
  ):
113
- self.tags: set[str] = tags or set()
 
 
 
 
 
 
 
 
 
114
  self.settings = fastmcp.settings.ServerSettings(**settings)
 
 
 
115
  self._cache = TimedCache(
116
- expiration=datetime.timedelta(
117
- seconds=self.settings.cache_expiration_seconds
118
- )
119
  )
120
-
121
  self._mounted_servers: dict[str, MountedServer] = {}
 
 
 
 
 
 
 
 
 
122
 
123
  if lifespan is None:
124
  self._has_lifespan = False
125
  lifespan = default_lifespan
126
  else:
127
  self._has_lifespan = True
128
-
129
  self._mcp_server = MCPServer[LifespanResultT](
130
  name=name or "FastMCP",
131
  instructions=instructions,
132
  lifespan=_lifespan_wrapper(self, lifespan),
133
  )
134
- self._tool_manager = ToolManager(
135
- duplicate_behavior=self.settings.on_duplicate_tools,
136
- serializer=tool_serializer,
137
- )
138
- self._resource_manager = ResourceManager(
139
- duplicate_behavior=self.settings.on_duplicate_resources
140
- )
141
- self._prompt_manager = PromptManager(
142
- duplicate_behavior=self.settings.on_duplicate_prompts
143
- )
144
 
145
  if (self.settings.auth is not None) != (auth_server_provider is not None):
146
  # TODO: after we support separate authorization servers (see
@@ -150,15 +163,9 @@ class FastMCP(Generic[LifespanResultT]):
150
  )
151
  self._auth_server_provider = auth_server_provider
152
 
153
- self._additional_http_routes: list[BaseRoute] = []
154
- self.dependencies = self.settings.dependencies
155
-
156
  # Set up MCP protocol handlers
157
  self._setup_handlers()
158
 
159
- # Configure logging
160
- configure_logging(self.settings.log_level)
161
-
162
  def __repr__(self) -> str:
163
  return f"{type(self).__name__}({self.name!r})"
164
 
@@ -764,15 +771,14 @@ class FastMCP(Generic[LifespanResultT]):
764
  uvicorn_config: dict | None = None,
765
  ) -> None:
766
  """Run the server using SSE transport."""
 
 
767
  warnings.warn(
768
- inspect.cleandoc(
769
- """
770
- The run_sse_async method is deprecated. Use run_http_async for a
771
- modern (non-SSE) alternative, or create an SSE app with
772
- `fastmcp.server.http.create_sse_app` and run it directly.
773
- """
774
- ),
775
  DeprecationWarning,
 
776
  )
777
  await self.run_http_async(
778
  transport="sse",
@@ -797,14 +803,12 @@ class FastMCP(Generic[LifespanResultT]):
797
  message_path: The path to the message endpoint
798
  middleware: A list of middleware to apply to the app
799
  """
 
800
  warnings.warn(
801
- inspect.cleandoc(
802
- """
803
- The sse_app method is deprecated. Use http_app as a modern (non-SSE)
804
- alternative, or call `fastmcp.server.http.create_sse_app` directly.
805
- """
806
- ),
807
  DeprecationWarning,
 
808
  )
809
  return create_sse_app(
810
  server=self,
@@ -829,9 +833,11 @@ class FastMCP(Generic[LifespanResultT]):
829
  path: The path to the StreamableHTTP endpoint
830
  middleware: A list of middleware to apply to the app
831
  """
 
832
  warnings.warn(
833
- "The streamable_http_app method is deprecated. Use http_app() instead.",
834
  DeprecationWarning,
 
835
  )
836
  return self.http_app(path=path, middleware=middleware)
837
 
@@ -886,9 +892,12 @@ class FastMCP(Generic[LifespanResultT]):
886
  path: str | None = None,
887
  uvicorn_config: dict | None = None,
888
  ) -> None:
 
889
  warnings.warn(
890
- "The run_streamable_http_async method is deprecated. Use run_http_async instead.",
 
891
  DeprecationWarning,
 
892
  )
893
  await self.run_http_async(
894
  transport="streamable-http",
 
3
  from __future__ import annotations
4
 
5
  import datetime
 
6
  import warnings
7
  from collections.abc import AsyncIterator, Awaitable, Callable
8
  from contextlib import (
 
53
  from fastmcp.tools.tool import Tool
54
  from fastmcp.utilities.cache import TimedCache
55
  from fastmcp.utilities.decorators import DecoratedFunction
56
+ from fastmcp.utilities.logging import get_logger
57
 
58
  if TYPE_CHECKING:
59
  from fastmcp.client import Client
 
62
 
63
  logger = get_logger(__name__)
64
 
65
+ DuplicateBehavior = Literal["warn", "error", "replace", "ignore"]
66
+
67
 
68
  @asynccontextmanager
69
  async def default_lifespan(server: FastMCP) -> AsyncIterator[Any]:
 
108
  | None
109
  ) = None,
110
  tags: set[str] | None = None,
111
+ dependencies: list[str] | None = None,
112
  tool_serializer: Callable[[Any], str] | None = None,
113
+ cache_expiration_seconds: float | None = None,
114
+ on_duplicate_tools: DuplicateBehavior | None = None,
115
+ on_duplicate_resources: DuplicateBehavior | None = None,
116
+ on_duplicate_prompts: DuplicateBehavior | None = None,
117
  **settings: Any,
118
  ):
119
+ if settings:
120
+ # TODO: remove settings. Deprecated since 2.3.4
121
+ warnings.warn(
122
+ "Passing runtime and transport-specific settings as kwargs "
123
+ "to the FastMCP constructor is deprecated (as of 2.3.4), "
124
+ "including most transport settings. If possible, provide settings when calling "
125
+ "run() instead.",
126
+ DeprecationWarning,
127
+ stacklevel=2,
128
+ )
129
  self.settings = fastmcp.settings.ServerSettings(**settings)
130
+
131
+ self.tags: set[str] = tags or set()
132
+ self.dependencies = dependencies
133
  self._cache = TimedCache(
134
+ expiration=datetime.timedelta(seconds=cache_expiration_seconds or 0)
 
 
135
  )
 
136
  self._mounted_servers: dict[str, MountedServer] = {}
137
+ self._additional_http_routes: list[BaseRoute] = []
138
+ self._tool_manager = ToolManager(
139
+ duplicate_behavior=on_duplicate_tools,
140
+ serializer=tool_serializer,
141
+ )
142
+ self._resource_manager = ResourceManager(
143
+ duplicate_behavior=on_duplicate_resources
144
+ )
145
+ self._prompt_manager = PromptManager(duplicate_behavior=on_duplicate_prompts)
146
 
147
  if lifespan is None:
148
  self._has_lifespan = False
149
  lifespan = default_lifespan
150
  else:
151
  self._has_lifespan = True
 
152
  self._mcp_server = MCPServer[LifespanResultT](
153
  name=name or "FastMCP",
154
  instructions=instructions,
155
  lifespan=_lifespan_wrapper(self, lifespan),
156
  )
 
 
 
 
 
 
 
 
 
 
157
 
158
  if (self.settings.auth is not None) != (auth_server_provider is not None):
159
  # TODO: after we support separate authorization servers (see
 
163
  )
164
  self._auth_server_provider = auth_server_provider
165
 
 
 
 
166
  # Set up MCP protocol handlers
167
  self._setup_handlers()
168
 
 
 
 
169
  def __repr__(self) -> str:
170
  return f"{type(self).__name__}({self.name!r})"
171
 
 
771
  uvicorn_config: dict | None = None,
772
  ) -> None:
773
  """Run the server using SSE transport."""
774
+
775
+ # Deprecated since 2.3.2
776
  warnings.warn(
777
+ "The run_sse_async method is deprecated (as of 2.3.2). Use run_http_async for a "
778
+ "modern (non-SSE) alternative, or create an SSE app with "
779
+ "`fastmcp.server.http.create_sse_app` and run it directly.",
 
 
 
 
780
  DeprecationWarning,
781
+ stacklevel=2,
782
  )
783
  await self.run_http_async(
784
  transport="sse",
 
803
  message_path: The path to the message endpoint
804
  middleware: A list of middleware to apply to the app
805
  """
806
+ # Deprecated since 2.3.2
807
  warnings.warn(
808
+ "The sse_app method is deprecated (as of 2.3.2). Use http_app as a modern (non-SSE) "
809
+ "alternative, or call `fastmcp.server.http.create_sse_app` directly.",
 
 
 
 
810
  DeprecationWarning,
811
+ stacklevel=2,
812
  )
813
  return create_sse_app(
814
  server=self,
 
833
  path: The path to the StreamableHTTP endpoint
834
  middleware: A list of middleware to apply to the app
835
  """
836
+ # Deprecated since 2.3.2
837
  warnings.warn(
838
+ "The streamable_http_app method is deprecated (as of 2.3.2). Use http_app() instead.",
839
  DeprecationWarning,
840
+ stacklevel=2,
841
  )
842
  return self.http_app(path=path, middleware=middleware)
843
 
 
892
  path: str | None = None,
893
  uvicorn_config: dict | None = None,
894
  ) -> None:
895
+ # Deprecated since 2.3.2
896
  warnings.warn(
897
+ "The run_streamable_http_async method is deprecated (as of 2.3.2). "
898
+ "Use run_http_async instead.",
899
  DeprecationWarning,
900
+ stacklevel=2,
901
  )
902
  await self.run_http_async(
903
  transport="streamable-http",
src/fastmcp/settings.py CHANGED
@@ -3,8 +3,9 @@ from __future__ import annotations as _annotations
3
  from typing import TYPE_CHECKING, Literal
4
 
5
  from mcp.server.auth.settings import AuthSettings
6
- from pydantic import Field
7
  from pydantic_settings import BaseSettings, SettingsConfigDict
 
8
 
9
  if TYPE_CHECKING:
10
  pass
@@ -38,6 +39,15 @@ class Settings(BaseSettings):
38
  Defaults to False.""",
39
  )
40
 
 
 
 
 
 
 
 
 
 
41
 
42
  class ServerSettings(BaseSettings):
43
  """FastMCP server settings.
 
3
  from typing import TYPE_CHECKING, Literal
4
 
5
  from mcp.server.auth.settings import AuthSettings
6
+ from pydantic import Field, model_validator
7
  from pydantic_settings import BaseSettings, SettingsConfigDict
8
+ from typing_extensions import Self
9
 
10
  if TYPE_CHECKING:
11
  pass
 
39
  Defaults to False.""",
40
  )
41
 
42
+ @model_validator(mode="after")
43
+ def setup_logging(self) -> Self:
44
+ """Finalize the settings."""
45
+ from fastmcp.utilities.logging import configure_logging
46
+
47
+ configure_logging(self.log_level)
48
+
49
+ return self
50
+
51
 
52
  class ServerSettings(BaseSettings):
53
  """FastMCP server settings.
src/fastmcp/utilities/logging.py CHANGED
@@ -21,22 +21,27 @@ def get_logger(name: str) -> logging.Logger:
21
 
22
  def configure_logging(
23
  level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | int = "INFO",
 
24
  ) -> None:
25
- """Configure logging for FastMCP.
 
26
 
27
  Args:
 
28
  level: the log level to use
29
  """
 
 
 
30
  # Only configure the FastMCP logger namespace
31
  handler = RichHandler(console=Console(stderr=True), rich_tracebacks=True)
32
  formatter = logging.Formatter("%(message)s")
33
  handler.setFormatter(formatter)
34
 
35
- fastmcp_logger = logging.getLogger("FastMCP")
36
- fastmcp_logger.setLevel(level)
37
 
38
  # Remove any existing handlers to avoid duplicates on reconfiguration
39
- for hdlr in fastmcp_logger.handlers[:]:
40
- fastmcp_logger.removeHandler(hdlr)
41
 
42
- fastmcp_logger.addHandler(handler)
 
21
 
22
  def configure_logging(
23
  level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | int = "INFO",
24
+ logger: logging.Logger | None = None,
25
  ) -> None:
26
+ """
27
+ Configure logging for FastMCP.
28
 
29
  Args:
30
+ logger: the logger to configure
31
  level: the log level to use
32
  """
33
+ if logger is None:
34
+ logger = logging.getLogger("FastMCP")
35
+
36
  # Only configure the FastMCP logger namespace
37
  handler = RichHandler(console=Console(stderr=True), rich_tracebacks=True)
38
  formatter = logging.Formatter("%(message)s")
39
  handler.setFormatter(formatter)
40
 
41
+ logger.setLevel(level)
 
42
 
43
  # Remove any existing handlers to avoid duplicates on reconfiguration
44
+ for hdlr in logger.handlers[:]:
45
+ logger.removeHandler(hdlr)
46
 
47
+ logger.addHandler(handler)
tests/test_deprecated.py CHANGED
@@ -9,6 +9,17 @@ from starlette.applications import Starlette
9
  from fastmcp import FastMCP
10
 
11
 
 
 
 
 
 
 
 
 
 
 
 
12
  def test_sse_app_deprecation_warning():
13
  """Test that sse_app raises a deprecation warning."""
14
  server = FastMCP("TestServer")
 
9
  from fastmcp import FastMCP
10
 
11
 
12
+ def test_fastmcp_kwargs_settings_deprecation_warning():
13
+ """Test that passing settings as kwargs to FastMCP raises a deprecation warning."""
14
+ with pytest.warns(
15
+ DeprecationWarning,
16
+ match="Passing runtime and transport-specific settings as kwargs to the FastMCP constructor is deprecated",
17
+ ):
18
+ server = FastMCP("TestServer", host="127.0.0.2", port=8001)
19
+ assert server.settings.host == "127.0.0.2"
20
+ assert server.settings.port == 8001
21
+
22
+
23
  def test_sse_app_deprecation_warning():
24
  """Test that sse_app raises a deprecation warning."""
25
  server = FastMCP("TestServer")