From cd7f9850d313b307866d3d9f1d7da9fe9f89eef9 Mon Sep 17 00:00:00 2001
From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Date: Wed, 11 Feb 2026 11:22:28 -0500
Subject: [PATCH 1/3] Remove deprecated FastMCP() constructor kwargs
Replace 16 deprecated kwargs with **kwargs catch that raises TypeError
with specific migration instructions. Transport methods now read from
fastmcp.settings directly. Delete ExperimentalSettings (dead code).
---
docs/development/v3-notes/v3-features.mdx | 56 +--
src/fastmcp/mcp_config.py | 7 +-
src/fastmcp/server/mixins/transport.py | 25 +-
src/fastmcp/server/server.py | 204 ++--------
src/fastmcp/settings.py | 27 --
.../server/test_include_exclude_tags.py | 69 +---
.../test_add_tool_transformation.py | 35 +-
tests/deprecated/test_deprecated.py | 49 +--
tests/deprecated/test_openapi_deprecations.py | 25 --
tests/deprecated/test_settings.py | 369 +++---------------
tests/deprecated/test_tool_serializer.py | 9 +-
tests/server/mount/test_filtering.py | 12 +-
.../local_provider_tools/test_tags.py | 7 +-
.../providers/test_local_provider_prompts.py | 7 +-
.../test_local_provider_resources.py | 14 +-
tests/server/test_server.py | 4 +-
tests/utilities/test_inspect.py | 12 +-
17 files changed, 212 insertions(+), 719 deletions(-)
diff --git a/docs/development/v3-notes/v3-features.mdx b/docs/development/v3-notes/v3-features.mdx
index 690390e39..04e6120d8 100644
--- a/docs/development/v3-notes/v3-features.mdx
+++ b/docs/development/v3-notes/v3-features.mdx
@@ -66,6 +66,32 @@ async def get_emails(
Background tasks now use a distributed Redis notification queue for reliable delivery ([#2906](https://github.com/jlowin/fastmcp/pull/2906)). Elicitation switches from polling to BLPOP (single blocking call instead of ~7,200 round-trips/hour), and notification delivery retries up to 3x with TTL-based expiration.
+### Breaking: Deprecated `FastMCP()` Constructor Kwargs Removed
+
+Sixteen deprecated keyword arguments have been removed from `FastMCP.__init__`. Passing any of them now raises `TypeError` with a migration hint. Environment variables (e.g., `FASTMCP_HOST`) continue to work — only the constructor kwargs moved.
+
+**Transport/server settings** (`host`, `port`, `log_level`, `debug`, `sse_path`, `message_path`, `streamable_http_path`, `json_response`, `stateless_http`): Pass to `run_http_async()` or `http_app()`, or set via environment variables.
+
+```python
+# Before
+mcp = FastMCP("server", host="0.0.0.0", port=8080)
+mcp.run()
+
+# After
+mcp = FastMCP("server")
+mcp.run_http(host="0.0.0.0", port=8080)
+```
+
+**Duplicate handling** (`on_duplicate_tools`, `on_duplicate_resources`, `on_duplicate_prompts`): Use the unified `on_duplicate=` parameter.
+
+**Tag filtering** (`include_tags`, `exclude_tags`): Use `server.enable(tags=..., only=True)` and `server.disable(tags=...)` after construction.
+
+**Tool serializer** (`tool_serializer`): Return `ToolResult` from tools instead.
+
+**Tool transformations** (`tool_transformations`): Use `server.add_transform(ToolTransform(...))` after construction.
+
+The `_deprecated_settings` attribute and `.settings` property are also removed. `ExperimentalSettings` has been deleted (dead code).
+
### Breaking: `ui=` Renamed to `app=`
The MCP Apps decorator parameter has been renamed from `ui=ToolUI(...)` / `ui=ResourceUI(...)` to `app=AppConfig(...)` ([#3117](https://github.com/jlowin/fastmcp/pull/3117)). `ToolUI` and `ResourceUI` are consolidated into a single `AppConfig` class. Wire format is unchanged. See the MCP Apps section under beta2 for full details.
@@ -1266,35 +1292,9 @@ main.mount(subserver, prefix="api")
main.mount(subserver, namespace="api")
```
-#### Tag Filtering Init Parameters
-
-`FastMCP(include_tags=..., exclude_tags=...)` deprecated. Use `enable()`/`disable()` methods:
+#### Tag Filtering, Tool Serializer, Tool Transformations Init Parameters
-```python
-# Deprecated
-mcp = FastMCP("server", exclude_tags={"internal"})
-
-# New
-mcp = FastMCP("server")
-mcp.disable(tags={"internal"})
-```
-
-#### Tool Serializer Parameter
-
-The `tool_serializer` parameter on `FastMCP` is deprecated. Return `ToolResult` for explicit serialization control.
-
-#### Tool Transformation Methods
-
-`add_tool_transformation()`, `remove_tool_transformation()`, and `tool_transformations` constructor parameter are deprecated. Use `add_transform(ToolTransform({...}))` instead:
-
-```python
-# Deprecated
-mcp.add_tool_transformation("name", config)
-
-# New
-from fastmcp.server.transforms import ToolTransform
-mcp.add_transform(ToolTransform({"name": config}))
-```
+These constructor parameters have been **removed** (not just deprecated) as of rc1. See "Breaking: Deprecated `FastMCP()` Constructor Kwargs Removed" in the rc1 section above. The `add_tool_transformation()` and `remove_tool_transformation()` methods remain as deprecated shims.
---
diff --git a/src/fastmcp/mcp_config.py b/src/fastmcp/mcp_config.py
index f65132edb..98146652d 100644
--- a/src/fastmcp/mcp_config.py
+++ b/src/fastmcp/mcp_config.py
@@ -109,10 +109,13 @@ def _to_server_and_underlying_transport(
wrapped_mcp_server = create_proxy(
client,
name=server_name,
- include_tags=self.include_tags,
- exclude_tags=self.exclude_tags,
)
+ if self.include_tags:
+ wrapped_mcp_server.enable(tags=self.include_tags, only=True)
+ if self.exclude_tags:
+ wrapped_mcp_server.disable(tags=self.exclude_tags)
+
# Apply tool transforms if configured
if self.tools:
from fastmcp.server.transforms import ToolTransform
diff --git a/src/fastmcp/server/mixins/transport.py b/src/fastmcp/server/mixins/transport.py
index 9e797dc1f..1f069a02d 100644
--- a/src/fastmcp/server/mixins/transport.py
+++ b/src/fastmcp/server/mixins/transport.py
@@ -231,17 +231,15 @@ async def run_http_async(
# Resolve from settings/env var if not explicitly set
if stateless_http is None:
- stateless_http = self._deprecated_settings.stateless_http
+ stateless_http = fastmcp.settings.stateless_http
# SSE doesn't support stateless mode
if stateless_http and transport == "sse":
raise ValueError("SSE transport does not support stateless mode")
- host = host or self._deprecated_settings.host
- port = port or self._deprecated_settings.port
- default_log_level_to_use = (
- log_level or self._deprecated_settings.log_level
- ).lower()
+ host = host or fastmcp.settings.host
+ port = port or fastmcp.settings.port
+ default_log_level_to_use = (log_level or fastmcp.settings.log_level).lower()
app = self.http_app(
path=path,
@@ -311,31 +309,30 @@ def http_app(
if transport in ("streamable-http", "http"):
return create_streamable_http_app(
server=self,
- streamable_http_path=path
- or self._deprecated_settings.streamable_http_path,
+ streamable_http_path=path or fastmcp.settings.streamable_http_path,
event_store=event_store,
retry_interval=retry_interval,
auth=self.auth,
json_response=(
json_response
if json_response is not None
- else self._deprecated_settings.json_response
+ else fastmcp.settings.json_response
),
stateless_http=(
stateless_http
if stateless_http is not None
- else self._deprecated_settings.stateless_http
+ else fastmcp.settings.stateless_http
),
- debug=self._deprecated_settings.debug,
+ debug=fastmcp.settings.debug,
middleware=middleware,
)
elif transport == "sse":
return create_sse_app(
server=self,
- message_path=self._deprecated_settings.message_path,
- sse_path=path or self._deprecated_settings.sse_path,
+ message_path=fastmcp.settings.message_path,
+ sse_path=path or fastmcp.settings.sse_path,
auth=self.auth,
- debug=self._deprecated_settings.debug,
+ debug=fastmcp.settings.debug,
middleware=middleware,
)
else:
diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py
index 35d717e8c..66c60d978 100644
--- a/src/fastmcp/server/server.py
+++ b/src/fastmcp/server/server.py
@@ -10,8 +10,6 @@
AsyncIterator,
Awaitable,
Callable,
- Collection,
- Mapping,
Sequence,
)
from contextlib import (
@@ -79,7 +77,6 @@
)
from fastmcp.server.transforms.visibility import apply_session_transforms, is_enabled
from fastmcp.settings import DuplicateBehavior as DuplicateBehaviorSetting
-from fastmcp.settings import Settings
from fastmcp.tools.function_tool import FunctionTool
from fastmcp.tools.tool import AuthCheckCallable, Tool, ToolResult
from fastmcp.tools.tool_transform import ToolTransformConfig
@@ -99,7 +96,6 @@
from fastmcp.server.providers.openapi import RouteMap
from fastmcp.server.providers.openapi import RouteMapFn as OpenAPIRouteMapFn
from fastmcp.server.providers.proxy import FastMCPProxy
- from fastmcp.tools.tool import ToolResultSerializerType
logger = get_logger(__name__)
@@ -107,39 +103,37 @@
DuplicateBehavior = Literal["warn", "error", "replace", "ignore"]
-def _resolve_on_duplicate(
- on_duplicate: DuplicateBehavior | None,
- on_duplicate_tools: DuplicateBehavior | None,
- on_duplicate_resources: DuplicateBehavior | None,
- on_duplicate_prompts: DuplicateBehavior | None,
-) -> DuplicateBehavior:
- """Resolve on_duplicate from deprecated per-type params.
-
- Takes the most strict value if multiple are provided.
- Delete this function when removing deprecated params.
- """
- strictness_order: list[DuplicateBehavior] = ["error", "warn", "replace", "ignore"]
- deprecated_values: list[DuplicateBehavior] = []
-
- deprecated_params: list[tuple[str, DuplicateBehavior | None]] = [
- ("on_duplicate_tools", on_duplicate_tools),
- ("on_duplicate_resources", on_duplicate_resources),
- ("on_duplicate_prompts", on_duplicate_prompts),
- ]
- for name, value in deprecated_params:
- if value is not None:
- if fastmcp.settings.deprecation_warnings:
- warnings.warn(
- f"{name} is deprecated, use on_duplicate instead",
- DeprecationWarning,
- stacklevel=4,
- )
- deprecated_values.append(value)
-
- if on_duplicate is None and deprecated_values:
- return min(deprecated_values, key=lambda x: strictness_order.index(x))
-
- return on_duplicate or "warn"
+_REMOVED_KWARGS: dict[str, str] = {
+ "host": "Pass `host` to `run_http_async()`, or set FASTMCP_HOST.",
+ "port": "Pass `port` to `run_http_async()`, or set FASTMCP_PORT.",
+ "sse_path": "Pass `path` to `run_http_async()` or `http_app()`, or set FASTMCP_SSE_PATH.",
+ "message_path": "Set FASTMCP_MESSAGE_PATH.",
+ "streamable_http_path": "Pass `path` to `run_http_async()` or `http_app()`, or set FASTMCP_STREAMABLE_HTTP_PATH.",
+ "json_response": "Pass `json_response` to `run_http_async()` or `http_app()`, or set FASTMCP_JSON_RESPONSE.",
+ "stateless_http": "Pass `stateless_http` to `run_http_async()` or `http_app()`, or set FASTMCP_STATELESS_HTTP.",
+ "debug": "Set FASTMCP_DEBUG.",
+ "log_level": "Pass `log_level` to `run_http_async()`, or set FASTMCP_LOG_LEVEL.",
+ "on_duplicate_tools": "Use `on_duplicate=` instead.",
+ "on_duplicate_resources": "Use `on_duplicate=` instead.",
+ "on_duplicate_prompts": "Use `on_duplicate=` instead.",
+ "tool_serializer": "Return ToolResult from your tools instead. See https://gofastmcp.com/servers/tools#custom-serialization",
+ "include_tags": "Use `server.enable(tags=..., only=True)` after creating the server.",
+ "exclude_tags": "Use `server.disable(tags=...)` after creating the server.",
+ "tool_transformations": "Use `server.add_transform(ToolTransform(...))` after creating the server.",
+}
+
+
+def _check_removed_kwargs(kwargs: dict[str, Any]) -> None:
+ """Raise helpful TypeErrors for kwargs removed in v3."""
+ for key in kwargs:
+ if key in _REMOVED_KWARGS:
+ raise TypeError(
+ f"FastMCP() no longer accepts `{key}`. {_REMOVED_KWARGS[key]}"
+ )
+ if kwargs:
+ raise TypeError(
+ f"FastMCP() got unexpected keyword argument(s): {', '.join(repr(k) for k in kwargs)}"
+ )
Transport = Literal["stdio", "http", "sse", "streamable-http"]
@@ -232,45 +226,23 @@ def __init__(
middleware: Sequence[Middleware] | None = None,
providers: Sequence[Provider] | None = None,
lifespan: LifespanCallable | Lifespan | None = None,
- mask_error_details: bool | None = None,
tools: Sequence[Tool | Callable[..., Any]] | None = None,
- tool_serializer: ToolResultSerializerType | None = None,
- include_tags: Collection[str] | None = None,
- exclude_tags: Collection[str] | None = None,
on_duplicate: DuplicateBehavior | None = None,
+ mask_error_details: bool | None = None,
strict_input_validation: bool | None = None,
list_page_size: int | None = None,
tasks: bool | None = None,
session_state_store: AsyncKeyValue | None = None,
- # ---
- # --- DEPRECATED parameters ---
- # ---
- on_duplicate_tools: DuplicateBehavior | None = None,
- on_duplicate_resources: DuplicateBehavior | None = None,
- on_duplicate_prompts: DuplicateBehavior | None = None,
- log_level: str | None = None,
- debug: bool | None = None,
- host: str | None = None,
- port: int | None = None,
- sse_path: str | None = None,
- message_path: str | None = None,
- streamable_http_path: str | None = None,
- json_response: bool | None = None,
- stateless_http: bool | None = None,
sampling_handler: SamplingHandler | None = None,
sampling_handler_behavior: Literal["always", "fallback"] | None = None,
- tool_transformations: Mapping[str, ToolTransformConfig] | None = None,
+ **kwargs: Any,
):
+ _check_removed_kwargs(kwargs)
+
# Initialize Provider (sets up _transforms)
super().__init__()
- # Resolve on_duplicate from deprecated params (delete when removing deprecation)
- self._on_duplicate: DuplicateBehaviorSetting = _resolve_on_duplicate(
- on_duplicate,
- on_duplicate_tools,
- on_duplicate_resources,
- on_duplicate_prompts,
- )
+ self._on_duplicate: DuplicateBehaviorSetting = on_duplicate or "warn"
# Resolve server default for background task support
self._support_tasks_by_default: bool = tasks if tasks is not None else False
@@ -312,16 +284,6 @@ def __init__(
raise ValueError("list_page_size must be a positive integer")
self._list_page_size: int | None = list_page_size
- if tool_serializer is not None and fastmcp.settings.deprecation_warnings:
- warnings.warn(
- "The `tool_serializer` parameter is deprecated. "
- "Return ToolResult from your tools for full control over serialization. "
- "See https://gofastmcp.com/servers/tools#custom-serialization for migration examples.",
- DeprecationWarning,
- stacklevel=2,
- )
- self._tool_serializer: Callable[[Any], str] | None = tool_serializer
-
# Handle Lifespan instances (they're callable) or regular lifespan functions
if lifespan is not None:
self._lifespan: LifespanCallable[LifespanResultT] = lifespan
@@ -349,38 +311,9 @@ def __init__(
if tools:
for tool in tools:
if not isinstance(tool, Tool):
- tool = Tool.from_function(tool, serializer=self._tool_serializer)
+ tool = Tool.from_function(tool)
self.add_tool(tool)
- # Handle deprecated include_tags and exclude_tags parameters
- if include_tags is not None:
- warnings.warn(
- "include_tags is deprecated. Use server.enable(tags=..., only=True) instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- # For backwards compatibility, initialize allowlist from include_tags
- self.enable(tags=set(include_tags), only=True)
- if exclude_tags is not None:
- warnings.warn(
- "exclude_tags is deprecated. Use server.disable(tags=...) instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- # For backwards compatibility, initialize blocklist from exclude_tags
- self.disable(tags=set(exclude_tags))
-
- # Handle deprecated tool_transformations parameter
- if tool_transformations:
- if fastmcp.settings.deprecation_warnings:
- warnings.warn(
- "The tool_transformations parameter is deprecated. Use "
- "server.add_transform(ToolTransform({...})) instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- self._transforms.append(ToolTransform(dict(tool_transformations)))
-
self.strict_input_validation: bool = (
strict_input_validation
if strict_input_validation is not None
@@ -397,71 +330,9 @@ def __init__(
sampling_handler_behavior or "fallback"
)
- self._handle_deprecated_settings(
- log_level=log_level,
- debug=debug,
- host=host,
- port=port,
- sse_path=sse_path,
- message_path=message_path,
- streamable_http_path=streamable_http_path,
- json_response=json_response,
- stateless_http=stateless_http,
- )
-
def __repr__(self) -> str:
return f"{type(self).__name__}({self.name!r})"
- def _handle_deprecated_settings(
- self,
- log_level: str | None,
- debug: bool | None,
- host: str | None,
- port: int | None,
- sse_path: str | None,
- message_path: str | None,
- streamable_http_path: str | None,
- json_response: bool | None,
- stateless_http: bool | None,
- ) -> None:
- """Handle deprecated settings. Deprecated in 2.8.0."""
- deprecated_settings: dict[str, Any] = {}
-
- for name, arg in [
- ("log_level", log_level),
- ("debug", debug),
- ("host", host),
- ("port", port),
- ("sse_path", sse_path),
- ("message_path", message_path),
- ("streamable_http_path", streamable_http_path),
- ("json_response", json_response),
- ("stateless_http", stateless_http),
- ]:
- if arg is not None:
- # Deprecated in 2.8.0
- 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
- self._deprecated_settings = Settings(**combined_settings)
-
- @property
- def settings(self) -> Settings:
- # 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
def name(self) -> str:
return self._mcp_server.name
@@ -1530,7 +1401,6 @@ def my_tool(x: int) -> str:
meta=meta,
task=task if task is not None else self._support_tasks_by_default,
timeout=timeout,
- serializer=self._tool_serializer,
auth=auth,
)
diff --git a/src/fastmcp/settings.py b/src/fastmcp/settings.py
index 0257e8d3b..561a80437 100644
--- a/src/fastmcp/settings.py
+++ b/src/fastmcp/settings.py
@@ -2,7 +2,6 @@
import inspect
import os
-import warnings
from datetime import timedelta
from pathlib import Path
from typing import Annotated, Any, Literal
@@ -115,30 +114,6 @@ class DocketSettings(BaseSettings):
] = timedelta(seconds=5)
-class ExperimentalSettings(BaseSettings):
- model_config = SettingsConfigDict(
- env_prefix="FASTMCP_EXPERIMENTAL_",
- extra="ignore",
- validate_assignment=True,
- )
-
- # Deprecated in 2.14 - the new OpenAPI parser is now the default and only parser
- enable_new_openapi_parser: bool = False
-
- @field_validator("enable_new_openapi_parser", mode="after")
- @classmethod
- def _warn_openapi_parser_deprecated(cls, v: bool) -> bool:
- if v:
- warnings.warn(
- "enable_new_openapi_parser is deprecated. "
- "The new OpenAPI parser is now the default (and only) parser. "
- "You can remove this setting.",
- DeprecationWarning,
- stacklevel=2,
- )
- return v
-
-
class Settings(BaseSettings):
"""FastMCP settings."""
@@ -191,8 +166,6 @@ def normalize_log_level(cls, v):
return v.upper()
return v
- experimental: ExperimentalSettings = ExperimentalSettings()
-
docket: DocketSettings = DocketSettings()
enable_rich_logging: Annotated[
diff --git a/tests/deprecated/server/test_include_exclude_tags.py b/tests/deprecated/server/test_include_exclude_tags.py
index 1bdf68432..70312b412 100644
--- a/tests/deprecated/server/test_include_exclude_tags.py
+++ b/tests/deprecated/server/test_include_exclude_tags.py
@@ -1,68 +1,25 @@
-"""Tests for deprecated include_tags/exclude_tags parameters."""
+"""Tests for removed include_tags/exclude_tags parameters."""
import pytest
from fastmcp import FastMCP
-from fastmcp.server.transforms.visibility import Visibility
-class TestIncludeExcludeTagsDeprecation:
- """Test that include_tags/exclude_tags emit deprecation warnings but still work."""
+class TestIncludeExcludeTagsRemoved:
+ """Test that include_tags/exclude_tags raise TypeError with migration hints."""
- def test_exclude_tags_emits_warning(self):
- """exclude_tags parameter emits deprecation warning."""
- with pytest.warns(DeprecationWarning, match="exclude_tags.*deprecated"):
+ def test_exclude_tags_raises_type_error(self):
+ with pytest.raises(TypeError, match="no longer accepts `exclude_tags`"):
FastMCP(exclude_tags={"internal"})
- def test_include_tags_emits_warning(self):
- """include_tags parameter emits deprecation warning."""
- with pytest.warns(DeprecationWarning, match="include_tags.*deprecated"):
+ def test_include_tags_raises_type_error(self):
+ with pytest.raises(TypeError, match="no longer accepts `include_tags`"):
FastMCP(include_tags={"public"})
- def test_exclude_tags_still_works(self):
- """exclude_tags adds a Visibility transform that disables matching tags."""
- with pytest.warns(DeprecationWarning):
- mcp = FastMCP(exclude_tags={"internal"})
-
- # Should have added a Visibility transform that disables the tag
- enabled_transforms = [t for t in mcp._transforms if isinstance(t, Visibility)]
- assert len(enabled_transforms) == 1
- e = enabled_transforms[0]
- assert e._enabled is False
- assert e.tags == {"internal"}
-
- def test_include_tags_still_works(self):
- """include_tags adds Visibility transforms for allowlist mode."""
- with pytest.warns(DeprecationWarning):
- mcp = FastMCP(include_tags={"public"})
-
- # Should have added Visibility transforms for allowlist mode
- # (one to disable all, one to enable matching)
- enabled_transforms = [t for t in mcp._transforms if isinstance(t, Visibility)]
- assert len(enabled_transforms) == 2
-
- # First should disable all (Visibility.all(False))
- disable_all_transform = enabled_transforms[0]
- assert disable_all_transform._enabled is False
- assert disable_all_transform.match_all is True
-
- # Second should enable matching tags
- enable_transform = enabled_transforms[1]
- assert enable_transform._enabled is True
- assert enable_transform.tags == {"public"}
-
- def test_exclude_and_include_both_create_transforms(self):
- """exclude_tags and include_tags both create transforms."""
- with pytest.warns(DeprecationWarning):
- mcp = FastMCP(include_tags={"public"}, exclude_tags={"deprecated"})
-
- # Should have added transforms for both
- # include_tags creates 2 (disable all + enable matching)
- # exclude_tags creates 1 (disable matching)
- enabled_transforms = [t for t in mcp._transforms if isinstance(t, Visibility)]
- assert len(enabled_transforms) == 3
+ def test_exclude_tags_error_mentions_disable(self):
+ with pytest.raises(TypeError, match="server.disable"):
+ FastMCP(exclude_tags={"internal"})
- # Check we have both tag rules
- tags_in_transforms = [t.tags for t in enabled_transforms if t.tags]
- assert {"public"} in tags_in_transforms
- assert {"deprecated"} in tags_in_transforms
+ def test_include_tags_error_mentions_enable(self):
+ with pytest.raises(TypeError, match="server.enable"):
+ FastMCP(include_tags={"public"})
diff --git a/tests/deprecated/test_add_tool_transformation.py b/tests/deprecated/test_add_tool_transformation.py
index 348247b9f..0228b8b60 100644
--- a/tests/deprecated/test_add_tool_transformation.py
+++ b/tests/deprecated/test_add_tool_transformation.py
@@ -68,37 +68,12 @@ async def test_remove_tool_transformation_emits_warning(self):
assert "remove_tool_transformation is deprecated" in str(w[0].message)
assert "no effect" in str(w[0].message)
- async def test_tool_transformations_constructor_emits_warning(self):
- """tool_transformations constructor param should emit deprecation warning."""
- with warnings.catch_warnings(record=True) as w:
- warnings.simplefilter("always")
+ async def test_tool_transformations_constructor_raises_type_error(self):
+ """tool_transformations constructor param should raise TypeError."""
+ import pytest
+
+ with pytest.raises(TypeError, match="no longer accepts `tool_transformations`"):
FastMCP(
"test",
tool_transformations={"my_tool": ToolTransformConfig(name="renamed")},
)
-
- assert len(w) == 1
- assert issubclass(w[0].category, DeprecationWarning)
- assert "tool_transformations parameter is deprecated" in str(w[0].message)
-
- async def test_tool_transformations_constructor_still_works(self):
- """tool_transformations constructor param should still apply transforms."""
- with warnings.catch_warnings():
- warnings.simplefilter("ignore", DeprecationWarning)
- mcp = FastMCP(
- "test",
- tool_transformations={
- "my_tool": ToolTransformConfig(name="renamed_tool")
- },
- )
-
- @mcp.tool
- def my_tool() -> str:
- return "result"
-
- async with Client(mcp) as client:
- tools = await client.list_tools()
- tool_names = [t.name for t in tools]
-
- assert "my_tool" not in tool_names
- assert "renamed_tool" in tool_names
diff --git a/tests/deprecated/test_deprecated.py b/tests/deprecated/test_deprecated.py
index 25a32aff6..37f33cf27 100644
--- a/tests/deprecated/test_deprecated.py
+++ b/tests/deprecated/test_deprecated.py
@@ -1,48 +1,23 @@
-import warnings
-
import pytest
from starlette.applications import Starlette
from fastmcp import 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
+class TestRemovedKwargs:
+ def test_host_kwarg_raises_type_error(self):
+ with pytest.raises(TypeError, match="no longer accepts `host`"):
+ FastMCP(host="1.2.3.4")
- 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_settings_property_removed(self):
+ mcp = FastMCP()
+ assert not hasattr(mcp, "_deprecated_settings")
+ with pytest.raises(AttributeError):
+ mcp.settings # noqa: B018 # ty: ignore[unresolved-attribute]
def test_http_app_with_sse_transport():
- """Test that http_app with SSE transport works (no warning)."""
+ """Test that http_app with SSE transport works."""
server = FastMCP("TestServer")
-
- # This should not raise a warning since we're using the new API
- with warnings.catch_warnings(record=True) as recorded_warnings:
- app = server.http_app(transport="sse")
- assert isinstance(app, Starlette)
-
- # Verify no deprecation warnings were raised for using transport parameter
- deprecation_warnings = [
- w for w in recorded_warnings if issubclass(w.category, DeprecationWarning)
- ]
- assert len(deprecation_warnings) == 0
+ app = server.http_app(transport="sse")
+ assert isinstance(app, Starlette)
diff --git a/tests/deprecated/test_openapi_deprecations.py b/tests/deprecated/test_openapi_deprecations.py
index d55f84736..b57611c6f 100644
--- a/tests/deprecated/test_openapi_deprecations.py
+++ b/tests/deprecated/test_openapi_deprecations.py
@@ -5,34 +5,9 @@
import pytest
-import fastmcp
-
pytestmark = pytest.mark.filterwarnings("default::DeprecationWarning")
-class TestEnableNewOpenAPIParserDeprecation:
- """Test enable_new_openapi_parser setting deprecation."""
-
- def test_setting_true_emits_warning(self):
- """Setting enable_new_openapi_parser=True should emit deprecation warning."""
- with pytest.warns(
- DeprecationWarning,
- match=r"enable_new_openapi_parser is deprecated.*now the default",
- ):
- fastmcp.settings.experimental.enable_new_openapi_parser = True
-
- def test_setting_false_no_warning(self):
- """Setting enable_new_openapi_parser=False should not emit warning."""
- with warnings.catch_warnings(record=True) as recorded:
- warnings.simplefilter("always")
- fastmcp.settings.experimental.enable_new_openapi_parser = False
-
- deprecation_warnings = [
- w for w in recorded if issubclass(w.category, DeprecationWarning)
- ]
- assert len(deprecation_warnings) == 0
-
-
class TestExperimentalOpenAPIImportDeprecation:
"""Test experimental OpenAPI import path deprecations."""
diff --git a/tests/deprecated/test_settings.py b/tests/deprecated/test_settings.py
index 301abf410..47ea6613c 100644
--- a/tests/deprecated/test_settings.py
+++ b/tests/deprecated/test_settings.py
@@ -1,319 +1,64 @@
-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",
- on_duplicate="warn", # New unified parameter
- 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
+class TestRemovedServerInitKwargs:
+ """Test that removed server initialization keyword arguments raise TypeError."""
+
+ @pytest.mark.parametrize(
+ "kwarg, value, expected_message",
+ [
+ ("host", "0.0.0.0", "run_http_async"),
+ ("port", 8080, "run_http_async"),
+ ("sse_path", "/custom-sse", "FASTMCP_SSE_PATH"),
+ ("message_path", "/custom-message", "FASTMCP_MESSAGE_PATH"),
+ ("streamable_http_path", "/custom-http", "run_http_async"),
+ ("json_response", True, "run_http_async"),
+ ("stateless_http", True, "run_http_async"),
+ ("debug", True, "FASTMCP_DEBUG"),
+ ("log_level", "DEBUG", "run_http_async"),
+ ("on_duplicate_tools", "warn", "on_duplicate="),
+ ("on_duplicate_resources", "error", "on_duplicate="),
+ ("on_duplicate_prompts", "replace", "on_duplicate="),
+ ("tool_serializer", lambda x: str(x), "ToolResult"),
+ ("include_tags", {"public"}, "server.enable"),
+ ("exclude_tags", {"internal"}, "server.disable"),
+ (
+ "tool_transformations",
+ {"my_tool": {"name": "renamed"}},
+ "server.add_transform",
+ ),
+ ],
+ )
+ def test_removed_kwarg_raises_type_error(self, kwarg, value, expected_message):
+ with pytest.raises(TypeError, match=f"no longer accepts `{kwarg}`"):
+ FastMCP("TestServer", **{kwarg: value})
+
+ @pytest.mark.parametrize(
+ "kwarg, value, expected_message",
+ [
+ ("host", "0.0.0.0", "run_http_async"),
+ ("on_duplicate_tools", "warn", "on_duplicate="),
+ ("include_tags", {"public"}, "server.enable"),
+ ],
+ )
+ def test_removed_kwarg_error_includes_migration_hint(
+ self, kwarg, value, expected_message
+ ):
+ with pytest.raises(TypeError, match=expected_message):
+ FastMCP("TestServer", **{kwarg: value})
+
+ def test_unknown_kwarg_raises_standard_type_error(self):
+ with pytest.raises(TypeError, match="unexpected keyword argument"):
+ FastMCP("TestServer", **{"totally_fake_param": True}) # ty: ignore[invalid-argument-type]
+
+ def test_valid_kwargs_still_work(self):
+ server = FastMCP(
+ name="TestServer",
+ instructions="Test instructions",
+ on_duplicate="warn",
+ mask_error_details=True,
+ )
assert server.name == "TestServer"
assert server.instructions == "Test instructions"
-
- def test_deprecated_duplicate_kwargs_raise_warnings(self):
- """Test that deprecated on_duplicate_* kwargs raise warnings."""
- with warnings.catch_warnings(record=True) as recorded_warnings:
- warnings.simplefilter("always")
- FastMCP(
- name="TestServer",
- on_duplicate_tools="warn",
- on_duplicate_resources="error",
- on_duplicate_prompts="replace",
- )
-
- # Should have 3 deprecation warnings (one for each deprecated param)
- deprecation_warnings = [
- w for w in recorded_warnings if issubclass(w.category, DeprecationWarning)
- ]
- assert len(deprecation_warnings) == 3
-
- # Check warning messages
- warning_messages = [str(w.message) for w in deprecation_warnings]
- assert any("on_duplicate_tools" in msg for msg in warning_messages)
- assert any("on_duplicate_resources" in msg for msg in warning_messages)
- assert any("on_duplicate_prompts" in msg for msg in warning_messages)
-
- 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,
- }
-
- 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")
-
- 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
diff --git a/tests/deprecated/test_tool_serializer.py b/tests/deprecated/test_tool_serializer.py
index f90bf06cc..2b706ae74 100644
--- a/tests/deprecated/test_tool_serializer.py
+++ b/tests/deprecated/test_tool_serializer.py
@@ -143,15 +143,14 @@ def my_tool(x: int) -> int:
with pytest.warns(DeprecationWarning, match="serializer.*deprecated"):
provider.tool(my_tool, serializer=custom_serializer)
- def test_fastmcp_tool_serializer_parameter_warning(self):
- """Test that FastMCP tool_serializer parameter warns."""
+ def test_fastmcp_tool_serializer_parameter_raises_type_error(self):
+ """Test that FastMCP tool_serializer parameter raises TypeError."""
def custom_serializer(data) -> str:
return f"Custom: {data}"
- with temporary_settings(deprecation_warnings=True):
- with pytest.warns(DeprecationWarning, match="tool_serializer.*deprecated"):
- FastMCP("TestServer", tool_serializer=custom_serializer)
+ with pytest.raises(TypeError, match="no longer accepts `tool_serializer`"):
+ FastMCP("TestServer", tool_serializer=custom_serializer)
def test_transformed_tool_from_tool_serializer_warning(self):
"""Test that TransformedTool.from_tool warns when serializer is provided."""
diff --git a/tests/server/mount/test_filtering.py b/tests/server/mount/test_filtering.py
index 413cc0e60..376db4c75 100644
--- a/tests/server/mount/test_filtering.py
+++ b/tests/server/mount/test_filtering.py
@@ -11,7 +11,8 @@ class TestParentTagFiltering:
async def test_parent_include_tags_filters_mounted_tools(self):
"""Test that parent include_tags filters out non-matching mounted tools."""
- parent = FastMCP("Parent", include_tags={"allowed"})
+ parent = FastMCP("Parent")
+ parent.enable(tags={"allowed"}, only=True)
mounted = FastMCP("Mounted")
@mounted.tool(tags={"allowed"})
@@ -38,7 +39,8 @@ def blocked_tool() -> str:
async def test_parent_exclude_tags_filters_mounted_tools(self):
"""Test that parent exclude_tags filters out matching mounted tools."""
- parent = FastMCP("Parent", exclude_tags={"blocked"})
+ parent = FastMCP("Parent")
+ parent.disable(tags={"blocked"})
mounted = FastMCP("Mounted")
@mounted.tool(tags={"production"})
@@ -58,7 +60,8 @@ def blocked_tool() -> str:
async def test_parent_filters_apply_to_mounted_resources(self):
"""Test that parent tag filters apply to mounted resources."""
- parent = FastMCP("Parent", include_tags={"allowed"})
+ parent = FastMCP("Parent")
+ parent.enable(tags={"allowed"}, only=True)
mounted = FastMCP("Mounted")
@mounted.resource("resource://allowed", tags={"allowed"})
@@ -78,7 +81,8 @@ def blocked_resource() -> str:
async def test_parent_filters_apply_to_mounted_prompts(self):
"""Test that parent tag filters apply to mounted prompts."""
- parent = FastMCP("Parent", exclude_tags={"blocked"})
+ parent = FastMCP("Parent")
+ parent.disable(tags={"blocked"})
mounted = FastMCP("Mounted")
@mounted.prompt(tags={"allowed"})
diff --git a/tests/server/providers/local_provider_tools/test_tags.py b/tests/server/providers/local_provider_tools/test_tags.py
index 019ed4c07..fb32e3515 100644
--- a/tests/server/providers/local_provider_tools/test_tags.py
+++ b/tests/server/providers/local_provider_tools/test_tags.py
@@ -40,7 +40,7 @@ class PersonDataclass:
class TestToolTags:
def create_server(self, include_tags=None, exclude_tags=None):
- mcp = FastMCP(include_tags=include_tags, exclude_tags=exclude_tags)
+ mcp = FastMCP()
@mcp.tool(tags={"a", "b"})
def tool_1() -> int:
@@ -50,6 +50,11 @@ def tool_1() -> int:
def tool_2() -> int:
return 2
+ if include_tags:
+ mcp.enable(tags=include_tags, only=True)
+ if exclude_tags:
+ mcp.disable(tags=exclude_tags)
+
return mcp
async def test_include_tags_all_tools(self):
diff --git a/tests/server/providers/test_local_provider_prompts.py b/tests/server/providers/test_local_provider_prompts.py
index 6b0400e64..ac0df86a3 100644
--- a/tests/server/providers/test_local_provider_prompts.py
+++ b/tests/server/providers/test_local_provider_prompts.py
@@ -415,7 +415,7 @@ def sample_prompt() -> str:
class TestPromptTags:
def create_server(self, include_tags=None, exclude_tags=None):
- mcp = FastMCP(include_tags=include_tags, exclude_tags=exclude_tags)
+ mcp = FastMCP()
@mcp.prompt(tags={"a", "b"})
def prompt_1() -> str:
@@ -425,6 +425,11 @@ def prompt_1() -> str:
def prompt_2() -> str:
return "2"
+ if include_tags:
+ mcp.enable(tags=include_tags, only=True)
+ if exclude_tags:
+ mcp.disable(tags=exclude_tags)
+
return mcp
async def test_include_tags_all_prompts(self):
diff --git a/tests/server/providers/test_local_provider_resources.py b/tests/server/providers/test_local_provider_resources.py
index 972b9358b..4c8da4559 100644
--- a/tests/server/providers/test_local_provider_resources.py
+++ b/tests/server/providers/test_local_provider_resources.py
@@ -676,7 +676,7 @@ def get_template_data(param: str) -> str:
class TestResourceTags:
def create_server(self, include_tags=None, exclude_tags=None):
- mcp = FastMCP(include_tags=include_tags, exclude_tags=exclude_tags)
+ mcp = FastMCP()
@mcp.resource("resource://1", tags={"a", "b"})
def resource_1() -> str:
@@ -686,6 +686,11 @@ def resource_1() -> str:
def resource_2() -> str:
return "2"
+ if include_tags:
+ mcp.enable(tags=include_tags, only=True)
+ if exclude_tags:
+ mcp.disable(tags=exclude_tags)
+
return mcp
async def test_include_tags_all_resources(self):
@@ -823,7 +828,7 @@ def sample_resource() -> str:
class TestResourceTemplatesTags:
def create_server(self, include_tags=None, exclude_tags=None):
- mcp = FastMCP(include_tags=include_tags, exclude_tags=exclude_tags)
+ mcp = FastMCP()
@mcp.resource("resource://1/{param}", tags={"a", "b"})
def template_resource_1(param: str) -> str:
@@ -833,6 +838,11 @@ def template_resource_1(param: str) -> str:
def template_resource_2(param: str) -> str:
return f"Template resource 2: {param}"
+ if include_tags:
+ mcp.enable(tags=include_tags, only=True)
+ if exclude_tags:
+ mcp.disable(tags=exclude_tags)
+
return mcp
async def test_include_tags_all_resources(self):
diff --git a/tests/server/test_server.py b/tests/server/test_server.py
index 12166715c..ba7af98d5 100644
--- a/tests/server/test_server.py
+++ b/tests/server/test_server.py
@@ -211,9 +211,9 @@ def dummy_tool() -> str:
"test",
middleware=(), # Empty tuple
tools=(Tool.from_function(dummy_tool),), # Tuple of tools
- include_tags={"tag1", "tag2"}, # Set
- exclude_tags={"tag3"}, # Set
)
+ mcp.enable(tags={"tag1", "tag2"}, only=True)
+ mcp.disable(tags={"tag3"})
assert mcp is not None
assert mcp.name == "test"
assert isinstance(mcp.middleware, list) # Should be converted to list
diff --git a/tests/utilities/test_inspect.py b/tests/utilities/test_inspect.py
index 8df506466..448e1eb03 100644
--- a/tests/utilities/test_inspect.py
+++ b/tests/utilities/test_inspect.py
@@ -281,10 +281,8 @@ async def test_inspect_respects_tag_filtering(self):
components weren't actually available to clients.
"""
# Create server with include_tags that will filter out untagged components
- mcp = FastMCP(
- "FilteredServer",
- include_tags={"fetch", "analyze", "create"},
- )
+ mcp = FastMCP("FilteredServer")
+ mcp.enable(tags={"fetch", "analyze", "create"}, only=True)
# Add tools with and without matching tags
@mcp.tool(tags={"fetch"})
@@ -396,7 +394,8 @@ def blocked_prompt() -> list:
return [{"role": "user", "content": "blocked"}]
# Create parent server with tag filtering
- parent = FastMCP("ParentServer", include_tags={"allowed"})
+ parent = FastMCP("ParentServer")
+ parent.enable(tags={"allowed"}, only=True)
parent.mount(mounted)
# Get inspect info
@@ -448,7 +447,8 @@ def untagged_tool() -> str:
return "untagged"
# Create parent with exclude_tags - should filter mounted components
- parent = FastMCP("ParentServer", exclude_tags={"development"})
+ parent = FastMCP("ParentServer")
+ parent.disable(tags={"development"})
parent.mount(mounted)
# Get inspect info
From 29a37a84321eee65985bd6a0b3331ffbeee81952 Mon Sep 17 00:00:00 2001
From: "marvin-context-protocol[bot]"
<225465937+marvin-context-protocol[bot]@users.noreply.github.com>
Date: Wed, 11 Feb 2026 16:23:48 +0000
Subject: [PATCH 2/3] chore: Update SDK documentation
---
docs/python-sdk/fastmcp-mcp_config.mdx | 32 +++---
docs/python-sdk/fastmcp-server-context.mdx | 76 ++++++------
.../fastmcp-server-dependencies.mdx | 77 +++++++------
.../fastmcp-server-mixins-transport.mdx | 2 +-
docs/python-sdk/fastmcp-server-server.mdx | 108 +++++++++---------
docs/python-sdk/fastmcp-settings.mdx | 12 +-
6 files changed, 152 insertions(+), 155 deletions(-)
diff --git a/docs/python-sdk/fastmcp-mcp_config.mdx b/docs/python-sdk/fastmcp-mcp_config.mdx
index 87ed568a0..71046dd4a 100644
--- a/docs/python-sdk/fastmcp-mcp_config.mdx
+++ b/docs/python-sdk/fastmcp-mcp_config.mdx
@@ -42,7 +42,7 @@ infer_transport_type_from_url(url: str | AnyUrl) -> Literal['http', 'sse']
Infer the appropriate transport type from the given URL.
-### `update_config_file`
+### `update_config_file`
```python
update_config_file(file_path: Path, server_name: str, server_config: CanonicalMCPServerTypes) -> None
@@ -57,7 +57,7 @@ worry about transforming server objects here.
## Classes
-### `StdioMCPServer`
+### `StdioMCPServer`
MCP server configuration for stdio transport.
@@ -67,19 +67,19 @@ This is the canonical configuration format for MCP servers using stdio transport
**Methods:**
-#### `to_transport`
+#### `to_transport`
```python
to_transport(self) -> StdioTransport
```
-### `TransformingStdioMCPServer`
+### `TransformingStdioMCPServer`
A Stdio server with tool transforms.
-### `RemoteMCPServer`
+### `RemoteMCPServer`
MCP server configuration for HTTP/SSE transport.
@@ -89,19 +89,19 @@ This is the canonical configuration format for MCP servers using remote transpor
**Methods:**
-#### `to_transport`
+#### `to_transport`
```python
to_transport(self) -> StreamableHttpTransport | SSETransport
```
-### `TransformingRemoteMCPServer`
+### `TransformingRemoteMCPServer`
A Remote server with tool transforms.
-### `MCPConfig`
+### `MCPConfig`
A configuration object for MCP Servers that conforms to the canonical MCP configuration format
@@ -113,7 +113,7 @@ For an MCPConfig that is strictly canonical, see the `CanonicalMCPConfig` class.
**Methods:**
-#### `wrap_servers_at_root`
+#### `wrap_servers_at_root`
```python
wrap_servers_at_root(cls, values: dict[str, Any]) -> dict[str, Any]
@@ -122,7 +122,7 @@ wrap_servers_at_root(cls, values: dict[str, Any]) -> dict[str, Any]
If there's no mcpServers key but there are server configs at root, wrap them.
-#### `add_server`
+#### `add_server`
```python
add_server(self, name: str, server: MCPServerTypes) -> None
@@ -131,7 +131,7 @@ add_server(self, name: str, server: MCPServerTypes) -> None
Add or update a server in the configuration.
-#### `from_dict`
+#### `from_dict`
```python
from_dict(cls, config: dict[str, Any]) -> Self
@@ -140,7 +140,7 @@ from_dict(cls, config: dict[str, Any]) -> Self
Parse MCP configuration from dictionary format.
-#### `to_dict`
+#### `to_dict`
```python
to_dict(self) -> dict[str, Any]
@@ -149,7 +149,7 @@ to_dict(self) -> dict[str, Any]
Convert MCPConfig to dictionary format, preserving all fields.
-#### `write_to_file`
+#### `write_to_file`
```python
write_to_file(self, file_path: Path) -> None
@@ -158,7 +158,7 @@ write_to_file(self, file_path: Path) -> None
Write configuration to JSON file.
-#### `from_file`
+#### `from_file`
```python
from_file(cls, file_path: Path) -> Self
@@ -167,7 +167,7 @@ from_file(cls, file_path: Path) -> Self
Load configuration from JSON file.
-### `CanonicalMCPConfig`
+### `CanonicalMCPConfig`
Canonical MCP configuration format.
@@ -178,7 +178,7 @@ The format is designed to be client-agnostic and extensible for future use cases
**Methods:**
-#### `add_server`
+#### `add_server`
```python
add_server(self, name: str, server: CanonicalMCPServerTypes) -> None
diff --git a/docs/python-sdk/fastmcp-server-context.mdx b/docs/python-sdk/fastmcp-server-context.mdx
index 2873100b7..a87096234 100644
--- a/docs/python-sdk/fastmcp-server-context.mdx
+++ b/docs/python-sdk/fastmcp-server-context.mdx
@@ -170,6 +170,10 @@ Returns the context dict yielded by the server's lifespan function.
Returns an empty dict if no lifespan was configured or if the MCP
session is not yet established.
+In background tasks (Docket workers), where request_context is not
+available, falls back to reading from the FastMCP server's lifespan
+result directly.
+
Example:
```python
@server.tool
@@ -181,7 +185,7 @@ def my_tool(ctx: Context) -> str:
```
-#### `report_progress`
+#### `report_progress`
```python
report_progress(self, progress: float, total: float | None = None, message: str | None = None) -> None
@@ -198,7 +202,7 @@ Works in both foreground (MCP progress notifications) and background
- `message`: Optional status message describing current progress
-#### `list_resources`
+#### `list_resources`
```python
list_resources(self) -> list[SDKResource]
@@ -210,7 +214,7 @@ List all available resources from the server.
- List of Resource objects available on the server
-#### `list_prompts`
+#### `list_prompts`
```python
list_prompts(self) -> list[SDKPrompt]
@@ -222,7 +226,7 @@ List all available prompts from the server.
- List of Prompt objects available on the server
-#### `get_prompt`
+#### `get_prompt`
```python
get_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> GetPromptResult
@@ -238,7 +242,7 @@ Get a prompt by name with optional arguments.
- The prompt result
-#### `read_resource`
+#### `read_resource`
```python
read_resource(self, uri: str | AnyUrl) -> ResourceResult
@@ -253,7 +257,7 @@ Read a resource by URI.
- ResourceResult with contents
-#### `log`
+#### `log`
```python
log(self, message: str, level: LoggingLevel | None = None, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
@@ -271,7 +275,7 @@ Messages sent to Clients are also logged to the `fastmcp.server.context.to_clien
- `extra`: Optional mapping for additional arguments
-#### `transport`
+#### `transport`
```python
transport(self) -> TransportType | None
@@ -283,7 +287,7 @@ Returns the transport type used to run this server: "stdio", "sse",
or "streamable-http". Returns None if called outside of a server context.
-#### `client_supports_extension`
+#### `client_supports_extension`
```python
client_supports_extension(self, extension_id: str) -> bool
@@ -308,7 +312,7 @@ Example::
return "text-only client"
-#### `client_id`
+#### `client_id`
```python
client_id(self) -> str | None
@@ -317,7 +321,7 @@ client_id(self) -> str | None
Get the client ID if available.
-#### `request_id`
+#### `request_id`
```python
request_id(self) -> str
@@ -328,7 +332,7 @@ Get the unique ID for this request.
Raises RuntimeError if MCP request context is not available.
-#### `session_id`
+#### `session_id`
```python
session_id(self) -> str
@@ -345,7 +349,7 @@ the same client session.
- for other transports.
-#### `session`
+#### `session`
```python
session(self) -> ServerSession
@@ -359,7 +363,7 @@ In background task mode: Returns the session stored at Context creation.
Raises RuntimeError if no session is available.
-#### `debug`
+#### `debug`
```python
debug(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
@@ -370,7 +374,7 @@ Send a `DEBUG`-level message to the connected MCP Client.
Messages sent to Clients are also logged to the `fastmcp.server.context.to_client` logger with a level of `DEBUG`.
-#### `info`
+#### `info`
```python
info(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
@@ -381,7 +385,7 @@ Send a `INFO`-level message to the connected MCP Client.
Messages sent to Clients are also logged to the `fastmcp.server.context.to_client` logger with a level of `DEBUG`.
-#### `warning`
+#### `warning`
```python
warning(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
@@ -392,7 +396,7 @@ Send a `WARNING`-level message to the connected MCP Client.
Messages sent to Clients are also logged to the `fastmcp.server.context.to_client` logger with a level of `DEBUG`.
-#### `error`
+#### `error`
```python
error(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
@@ -403,7 +407,7 @@ Send a `ERROR`-level message to the connected MCP Client.
Messages sent to Clients are also logged to the `fastmcp.server.context.to_client` logger with a level of `DEBUG`.
-#### `list_roots`
+#### `list_roots`
```python
list_roots(self) -> list[Root]
@@ -412,7 +416,7 @@ list_roots(self) -> list[Root]
List the roots available to the server, as indicated by the client.
-#### `send_notification`
+#### `send_notification`
```python
send_notification(self, notification: mcp.types.ServerNotificationType) -> None
@@ -424,7 +428,7 @@ Send a notification to the client immediately.
- `notification`: An MCP notification instance (e.g., ToolListChangedNotification())
-#### `close_sse_stream`
+#### `close_sse_stream`
```python
close_sse_stream(self) -> None
@@ -442,7 +446,7 @@ Instead of holding a connection open for minutes, you can periodically close
and let the client reconnect.
-#### `sample_step`
+#### `sample_step`
```python
sample_step(self, messages: str | Sequence[str | SamplingMessage]) -> SampleStep
@@ -485,7 +489,7 @@ regardless of this setting.
- - .text: The text content (if any)
-#### `sample`
+#### `sample`
```python
sample(self, messages: str | Sequence[str | SamplingMessage]) -> SamplingResult[ResultT]
@@ -494,7 +498,7 @@ sample(self, messages: str | Sequence[str | SamplingMessage]) -> SamplingResult[
Overload: With result_type, returns SamplingResult[ResultT].
-#### `sample`
+#### `sample`
```python
sample(self, messages: str | Sequence[str | SamplingMessage]) -> SamplingResult[str]
@@ -503,7 +507,7 @@ sample(self, messages: str | Sequence[str | SamplingMessage]) -> SamplingResult[
Overload: Without result_type, returns SamplingResult[str].
-#### `sample`
+#### `sample`
```python
sample(self, messages: str | Sequence[str | SamplingMessage]) -> SamplingResult[ResultT] | SamplingResult[str]
@@ -551,43 +555,43 @@ regardless of this setting.
- - .history: All messages exchanged during sampling
-#### `elicit`
+#### `elicit`
```python
elicit(self, message: str, response_type: None) -> AcceptedElicitation[dict[str, Any]] | DeclinedElicitation | CancelledElicitation
```
-#### `elicit`
+#### `elicit`
```python
elicit(self, message: str, response_type: type[T]) -> AcceptedElicitation[T] | DeclinedElicitation | CancelledElicitation
```
-#### `elicit`
+#### `elicit`
```python
elicit(self, message: str, response_type: list[str]) -> AcceptedElicitation[str] | DeclinedElicitation | CancelledElicitation
```
-#### `elicit`
+#### `elicit`
```python
elicit(self, message: str, response_type: dict[str, dict[str, str]]) -> AcceptedElicitation[str] | DeclinedElicitation | CancelledElicitation
```
-#### `elicit`
+#### `elicit`
```python
elicit(self, message: str, response_type: list[list[str]]) -> AcceptedElicitation[list[str]] | DeclinedElicitation | CancelledElicitation
```
-#### `elicit`
+#### `elicit`
```python
elicit(self, message: str, response_type: list[dict[str, dict[str, str]]]) -> AcceptedElicitation[list[str]] | DeclinedElicitation | CancelledElicitation
```
-#### `elicit`
+#### `elicit`
```python
elicit(self, message: str, response_type: type[T] | list[str] | dict[str, dict[str, str]] | list[list[str]] | list[dict[str, dict[str, str]]] | None = None) -> AcceptedElicitation[T] | AcceptedElicitation[dict[str, Any]] | AcceptedElicitation[str] | AcceptedElicitation[list[str]] | DeclinedElicitation | CancelledElicitation
@@ -616,7 +620,7 @@ type or dataclass or BaseModel. If it is a primitive type, an
object schema with a single "value" field will be generated.
-#### `set_state`
+#### `set_state`
```python
set_state(self, key: str, value: Any) -> None
@@ -629,7 +633,7 @@ The key is automatically prefixed with the session identifier.
State expires after 1 day to prevent unbounded memory growth.
-#### `get_state`
+#### `get_state`
```python
get_state(self, key: str) -> Any
@@ -640,7 +644,7 @@ Get a value from the session-scoped state store.
Returns None if the key is not found.
-#### `delete_state`
+#### `delete_state`
```python
delete_state(self, key: str) -> None
@@ -649,7 +653,7 @@ delete_state(self, key: str) -> None
Delete a value from the session-scoped state store.
-#### `enable_components`
+#### `enable_components`
```python
enable_components(self) -> None
@@ -673,7 +677,7 @@ ResourceListChangedNotification, and PromptListChangedNotification.
- `match_all`: If True, matches all components regardless of other criteria.
-#### `disable_components`
+#### `disable_components`
```python
disable_components(self) -> None
@@ -697,7 +701,7 @@ ResourceListChangedNotification, and PromptListChangedNotification.
- `match_all`: If True, matches all components regardless of other criteria.
-#### `reset_visibility`
+#### `reset_visibility`
```python
reset_visibility(self) -> None
diff --git a/docs/python-sdk/fastmcp-server-dependencies.mdx b/docs/python-sdk/fastmcp-server-dependencies.mdx
index 496a19e8c..5660ac13a 100644
--- a/docs/python-sdk/fastmcp-server-dependencies.mdx
+++ b/docs/python-sdk/fastmcp-server-dependencies.mdx
@@ -15,7 +15,7 @@ CurrentWorker) and background task execution require fastmcp[tasks].
## Functions
-### `get_task_context`
+### `get_task_context`
```python
get_task_context() -> TaskContextInfo | None
@@ -31,7 +31,7 @@ Returns None if not running in a task context (e.g., foreground execution).
- TaskContextInfo with task_id and session_id, or None if not in a task.
-### `register_task_session`
+### `register_task_session`
```python
register_task_session(session_id: str, session: ServerSession) -> None
@@ -49,7 +49,7 @@ client disconnects.
- `session`: The ServerSession instance
-### `get_task_session`
+### `get_task_session`
```python
get_task_session(session_id: str) -> ServerSession | None
@@ -65,7 +65,7 @@ Get a registered session by ID if still alive.
- The ServerSession if found and alive, None otherwise
-### `is_docket_available`
+### `is_docket_available`
```python
is_docket_available() -> bool
@@ -75,7 +75,7 @@ is_docket_available() -> bool
Check if pydocket is installed.
-### `require_docket`
+### `require_docket`
```python
require_docket(feature: str) -> None
@@ -89,7 +89,7 @@ Raise ImportError with install instructions if docket not available.
"CurrentDocket()"). Will be included in the error message.
-### `transform_context_annotations`
+### `transform_context_annotations`
```python
transform_context_annotations(fn: Callable[..., Any]) -> Callable[..., Any]
@@ -115,7 +115,7 @@ allows them to have defaults in any order.
- Function with modified signature (same function object, updated __signature__)
-### `get_context`
+### `get_context`
```python
get_context() -> Context
@@ -125,7 +125,7 @@ get_context() -> Context
Get the current FastMCP Context instance directly.
-### `get_server`
+### `get_server`
```python
get_server() -> FastMCP
@@ -141,7 +141,7 @@ Get the current FastMCP server instance directly.
- `RuntimeError`: If no server in context
-### `get_http_request`
+### `get_http_request`
```python
get_http_request() -> Request
@@ -153,7 +153,7 @@ Get the current HTTP request.
Tries MCP SDK's request_ctx first, then falls back to FastMCP's HTTP context.
-### `get_http_headers`
+### `get_http_headers`
```python
get_http_headers(include_all: bool = False) -> dict[str, str]
@@ -169,7 +169,7 @@ By default, strips problematic headers like `content-length` that cause issues
if forwarded to downstream clients. If `include_all` is True, all headers are returned.
-### `get_access_token`
+### `get_access_token`
```python
get_access_token() -> AccessToken | None
@@ -181,13 +181,14 @@ Get the FastMCP access token from the current context.
This function first tries to get the token from the current HTTP request's scope,
which is more reliable for long-lived connections where the SDK's auth_context_var
may become stale after token refresh. Falls back to the SDK's context var if no
-request is available.
+request is available. In background tasks (Docket workers), falls back to the
+token snapshot stored in Redis at task submission time.
**Returns:**
- The access token if an authenticated user is available, None otherwise.
-### `without_injected_parameters`
+### `without_injected_parameters`
```python
without_injected_parameters(fn: Callable[..., Any]) -> Callable[..., Any]
@@ -212,7 +213,7 @@ Handles:
- Async wrapper function without injected parameters
-### `resolve_dependencies`
+### `resolve_dependencies`
```python
resolve_dependencies(fn: Callable[..., Any], arguments: dict[str, Any]) -> AsyncGenerator[dict[str, Any], None]
@@ -238,7 +239,7 @@ time, so all injection goes through the unified DI system.
which will be filtered out)
-### `CurrentContext`
+### `CurrentContext`
```python
CurrentContext() -> Context
@@ -257,7 +258,7 @@ current MCP operation (tool/resource/prompt call).
- `RuntimeError`: If no active context found (during resolution)
-### `CurrentDocket`
+### `CurrentDocket`
```python
CurrentDocket() -> Docket
@@ -277,7 +278,7 @@ automatically creates for background task scheduling.
- `ImportError`: If fastmcp[tasks] not installed
-### `CurrentWorker`
+### `CurrentWorker`
```python
CurrentWorker() -> Worker
@@ -297,7 +298,7 @@ automatically creates for background task processing.
- `ImportError`: If fastmcp[tasks] not installed
-### `CurrentFastMCP`
+### `CurrentFastMCP`
```python
CurrentFastMCP() -> FastMCP
@@ -315,7 +316,7 @@ This dependency provides access to the active FastMCP server.
- `RuntimeError`: If no server in context (during resolution)
-### `CurrentRequest`
+### `CurrentRequest`
```python
CurrentRequest() -> Request
@@ -335,7 +336,7 @@ current HTTP request. Only available when running over HTTP transports
- `RuntimeError`: If no HTTP request in context (e.g., STDIO transport)
-### `CurrentHeaders`
+### `CurrentHeaders`
```python
CurrentHeaders() -> dict[str, str]
@@ -352,7 +353,7 @@ safe to use in code that might run over any transport.
- A dependency that resolves to a dictionary of header name -> value
-### `CurrentAccessToken`
+### `CurrentAccessToken`
```python
CurrentAccessToken() -> AccessToken
@@ -371,7 +372,7 @@ authenticated request. Raises an error if no authentication is present.
- `RuntimeError`: If no authenticated user (use get_access_token() for optional)
-### `TokenClaim`
+### `TokenClaim`
```python
TokenClaim(name: str) -> str
@@ -396,7 +397,7 @@ without needing the full token object.
## Classes
-### `TaskContextInfo`
+### `TaskContextInfo`
Information about the current background task context.
@@ -405,7 +406,7 @@ Returned by ``get_task_context()`` when running inside a Docket worker.
Contains identifiers needed to communicate with the MCP session.
-### `ProgressLike`
+### `ProgressLike`
Protocol for progress tracking interface.
@@ -416,7 +417,7 @@ and Docket's Progress (worker context).
**Methods:**
-#### `current`
+#### `current`
```python
current(self) -> int | None
@@ -425,7 +426,7 @@ current(self) -> int | None
Current progress value.
-#### `total`
+#### `total`
```python
total(self) -> int
@@ -434,7 +435,7 @@ total(self) -> int
Total/target progress value.
-#### `message`
+#### `message`
```python
message(self) -> str | None
@@ -443,7 +444,7 @@ message(self) -> str | None
Current progress message.
-#### `set_total`
+#### `set_total`
```python
set_total(self, total: int) -> None
@@ -452,7 +453,7 @@ set_total(self, total: int) -> None
Set the total/target value for progress tracking.
-#### `increment`
+#### `increment`
```python
increment(self, amount: int = 1) -> None
@@ -461,7 +462,7 @@ increment(self, amount: int = 1) -> None
Atomically increment the current progress value.
-#### `set_message`
+#### `set_message`
```python
set_message(self, message: str | None) -> None
@@ -470,7 +471,7 @@ set_message(self, message: str | None) -> None
Update the progress status message.
-### `InMemoryProgress`
+### `InMemoryProgress`
In-memory progress tracker for immediate tool execution.
@@ -482,25 +483,25 @@ progress doesn't need to be observable across processes.
**Methods:**
-#### `current`
+#### `current`
```python
current(self) -> int | None
```
-#### `total`
+#### `total`
```python
total(self) -> int
```
-#### `message`
+#### `message`
```python
message(self) -> str | None
```
-#### `set_total`
+#### `set_total`
```python
set_total(self, total: int) -> None
@@ -509,7 +510,7 @@ set_total(self, total: int) -> None
Set the total/target value for progress tracking.
-#### `increment`
+#### `increment`
```python
increment(self, amount: int = 1) -> None
@@ -518,7 +519,7 @@ increment(self, amount: int = 1) -> None
Atomically increment the current progress value.
-#### `set_message`
+#### `set_message`
```python
set_message(self, message: str | None) -> None
@@ -527,7 +528,7 @@ set_message(self, message: str | None) -> None
Update the progress status message.
-### `Progress`
+### `Progress`
FastMCP Progress dependency that works in both server and worker contexts.
diff --git a/docs/python-sdk/fastmcp-server-mixins-transport.mdx b/docs/python-sdk/fastmcp-server-mixins-transport.mdx
index ed61e5a5d..0ce07f2fb 100644
--- a/docs/python-sdk/fastmcp-server-mixins-transport.mdx
+++ b/docs/python-sdk/fastmcp-server-mixins-transport.mdx
@@ -104,7 +104,7 @@ Run the server using HTTP transport.
- `stateless`: Alias for stateless_http for CLI consistency
-#### `http_app`
+#### `http_app`
```python
http_app(self: FastMCP, path: str | None = None, middleware: list[ASGIMiddleware] | None = None, json_response: bool | None = None, stateless_http: bool | None = None, transport: Literal['http', 'streamable-http', 'sse'] = 'http', event_store: EventStore | None = None, retry_interval: int | None = None) -> StarletteWithLifespan
diff --git a/docs/python-sdk/fastmcp-server-server.mdx b/docs/python-sdk/fastmcp-server-server.mdx
index 80b45257b..efaa20d12 100644
--- a/docs/python-sdk/fastmcp-server-server.mdx
+++ b/docs/python-sdk/fastmcp-server-server.mdx
@@ -10,7 +10,7 @@ FastMCP - A more ergonomic interface for MCP servers.
## Functions
-### `default_lifespan`
+### `default_lifespan`
```python
default_lifespan(server: FastMCP[LifespanResultT]) -> AsyncIterator[Any]
@@ -26,7 +26,7 @@ Default lifespan context manager that does nothing.
- An empty dictionary as the lifespan result.
-### `create_proxy`
+### `create_proxy`
```python
create_proxy(target: Client[ClientTransportT] | ClientTransport | FastMCP[Any] | FastMCP1Server | AnyUrl | Path | MCPConfig | dict[str, Any] | str, **settings: Any) -> FastMCPProxy
@@ -54,65 +54,59 @@ use `FastMCPProxy` or `ProxyProvider` directly from `fastmcp.server.providers.pr
## Classes
-### `StateValue`
+### `StateValue`
Wrapper for stored context state values.
-### `FastMCP`
+### `FastMCP`
**Methods:**
-#### `settings`
-
-```python
-settings(self) -> Settings
-```
-
-#### `name`
+#### `name`
```python
name(self) -> str
```
-#### `instructions`
+#### `instructions`
```python
instructions(self) -> str | None
```
-#### `instructions`
+#### `instructions`
```python
instructions(self, value: str | None) -> None
```
-#### `version`
+#### `version`
```python
version(self) -> str | None
```
-#### `website_url`
+#### `website_url`
```python
website_url(self) -> str | None
```
-#### `icons`
+#### `icons`
```python
icons(self) -> list[mcp.types.Icon]
```
-#### `add_middleware`
+#### `add_middleware`
```python
add_middleware(self, middleware: Middleware) -> None
```
-#### `add_provider`
+#### `add_provider`
```python
add_provider(self, provider: Provider) -> None
@@ -132,7 +126,7 @@ always take precedence over providers.
- Prompts become "namespace_promptname"
-#### `get_tasks`
+#### `get_tasks`
```python
get_tasks(self) -> Sequence[FastMCPComponent]
@@ -144,7 +138,7 @@ Overrides AggregateProvider.get_tasks() to apply server-level transforms
after aggregation. AggregateProvider handles provider-level namespacing.
-#### `add_transform`
+#### `add_transform`
```python
add_transform(self, transform: Transform) -> None
@@ -159,7 +153,7 @@ They transform tools, resources, and prompts from ALL providers.
- `transform`: The transform to add.
-#### `add_tool_transformation`
+#### `add_tool_transformation`
```python
add_tool_transformation(self, tool_name: str, transformation: ToolTransformConfig) -> None
@@ -171,7 +165,7 @@ Add a tool transformation.
Use ``add_transform(ToolTransform({...}))`` instead.
-#### `remove_tool_transformation`
+#### `remove_tool_transformation`
```python
remove_tool_transformation(self, _tool_name: str) -> None
@@ -183,7 +177,7 @@ Remove a tool transformation.
Tool transformations are now immutable. Use enable/disable controls instead.
-#### `list_tools`
+#### `list_tools`
```python
list_tools(self) -> Sequence[Tool]
@@ -196,7 +190,7 @@ and middleware execution. Returns all versions (no deduplication).
Protocol handlers deduplicate for MCP wire format.
-#### `get_tool`
+#### `get_tool`
```python
get_tool(self, name: str, version: VersionSpec | None = None) -> Tool | None
@@ -216,7 +210,7 @@ session transforms can override provider-level disables.
- The tool if found and enabled, None otherwise.
-#### `list_resources`
+#### `list_resources`
```python
list_resources(self) -> Sequence[Resource]
@@ -229,7 +223,7 @@ and middleware execution. Returns all versions (no deduplication).
Protocol handlers deduplicate for MCP wire format.
-#### `get_resource`
+#### `get_resource`
```python
get_resource(self, uri: str, version: VersionSpec | None = None) -> Resource | None
@@ -248,7 +242,7 @@ transforms (including session-level) have been applied.
- The resource if found and enabled, None otherwise.
-#### `list_resource_templates`
+#### `list_resource_templates`
```python
list_resource_templates(self) -> Sequence[ResourceTemplate]
@@ -261,7 +255,7 @@ auth filtering, and middleware execution. Returns all versions (no deduplication
Protocol handlers deduplicate for MCP wire format.
-#### `get_resource_template`
+#### `get_resource_template`
```python
get_resource_template(self, uri: str, version: VersionSpec | None = None) -> ResourceTemplate | None
@@ -280,7 +274,7 @@ all transforms (including session-level) have been applied.
- The template if found and enabled, None otherwise.
-#### `list_prompts`
+#### `list_prompts`
```python
list_prompts(self) -> Sequence[Prompt]
@@ -293,7 +287,7 @@ and middleware execution. Returns all versions (no deduplication).
Protocol handlers deduplicate for MCP wire format.
-#### `get_prompt`
+#### `get_prompt`
```python
get_prompt(self, name: str, version: VersionSpec | None = None) -> Prompt | None
@@ -312,19 +306,19 @@ transforms (including session-level) have been applied.
- The prompt if found and enabled, None otherwise.
-#### `call_tool`
+#### `call_tool`
```python
call_tool(self, name: str, arguments: dict[str, Any] | None = None) -> ToolResult
```
-#### `call_tool`
+#### `call_tool`
```python
call_tool(self, name: str, arguments: dict[str, Any] | None = None) -> mcp.types.CreateTaskResult
```
-#### `call_tool`
+#### `call_tool`
```python
call_tool(self, name: str, arguments: dict[str, Any] | None = None) -> ToolResult | mcp.types.CreateTaskResult
@@ -354,19 +348,19 @@ return ToolResult.
- `ValidationError`: If arguments fail validation
-#### `read_resource`
+#### `read_resource`
```python
read_resource(self, uri: str) -> ResourceResult
```
-#### `read_resource`
+#### `read_resource`
```python
read_resource(self, uri: str) -> mcp.types.CreateTaskResult
```
-#### `read_resource`
+#### `read_resource`
```python
read_resource(self, uri: str) -> ResourceResult | mcp.types.CreateTaskResult
@@ -395,19 +389,19 @@ return ResourceResult.
- `ResourceError`: If resource read fails
-#### `render_prompt`
+#### `render_prompt`
```python
render_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> PromptResult
```
-#### `render_prompt`
+#### `render_prompt`
```python
render_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> mcp.types.CreateTaskResult
```
-#### `render_prompt`
+#### `render_prompt`
```python
render_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> PromptResult | mcp.types.CreateTaskResult
@@ -437,7 +431,7 @@ return PromptResult.
- `PromptError`: If prompt rendering fails
-#### `add_tool`
+#### `add_tool`
```python
add_tool(self, tool: Tool | Callable[..., Any]) -> Tool
@@ -455,7 +449,7 @@ with the Context type annotation. See the @tool decorator for examples.
- The tool instance that was added to the server.
-#### `remove_tool`
+#### `remove_tool`
```python
remove_tool(self, name: str, version: str | None = None) -> None
@@ -471,19 +465,19 @@ Remove tool(s) from the server.
- `NotFoundError`: If no matching tool is found.
-#### `tool`
+#### `tool`
```python
tool(self, name_or_fn: AnyFunction) -> FunctionTool
```
-#### `tool`
+#### `tool`
```python
tool(self, name_or_fn: str | None = None) -> Callable[[AnyFunction], FunctionTool]
```
-#### `tool`
+#### `tool`
```python
tool(self, name_or_fn: str | AnyFunction | None = None) -> Callable[[AnyFunction], FunctionTool] | FunctionTool | partial[Callable[[AnyFunction], FunctionTool] | FunctionTool]
@@ -539,7 +533,7 @@ server.tool(my_function, name="custom_name")
```
-#### `add_resource`
+#### `add_resource`
```python
add_resource(self, resource: Resource | Callable[..., Any]) -> Resource | ResourceTemplate
@@ -554,7 +548,7 @@ Add a resource to the server.
- The resource instance that was added to the server.
-#### `add_template`
+#### `add_template`
```python
add_template(self, template: ResourceTemplate) -> ResourceTemplate
@@ -569,7 +563,7 @@ Add a resource template to the server.
- The template instance that was added to the server.
-#### `resource`
+#### `resource`
```python
resource(self, uri: str) -> Callable[[AnyFunction], Resource | ResourceTemplate | AnyFunction]
@@ -628,7 +622,7 @@ async def get_weather(city: str) -> str:
```
-#### `add_prompt`
+#### `add_prompt`
```python
add_prompt(self, prompt: Prompt | Callable[..., Any]) -> Prompt
@@ -643,19 +637,19 @@ Add a prompt to the server.
- The prompt instance that was added to the server.
-#### `prompt`
+#### `prompt`
```python
prompt(self, name_or_fn: AnyFunction) -> FunctionPrompt
```
-#### `prompt`
+#### `prompt`
```python
prompt(self, name_or_fn: str | None = None) -> Callable[[AnyFunction], FunctionPrompt]
```
-#### `prompt`
+#### `prompt`
```python
prompt(self, name_or_fn: str | AnyFunction | None = None) -> Callable[[AnyFunction], FunctionPrompt] | FunctionPrompt | partial[Callable[[AnyFunction], FunctionPrompt] | FunctionPrompt]
@@ -732,7 +726,7 @@ Decorator to register a prompt.
```
-#### `mount`
+#### `mount`
```python
mount(self, server: FastMCP[LifespanResultT], namespace: str | None = None, as_proxy: bool | None = None, tool_names: dict[str, str] | None = None, prefix: str | None = None) -> None
@@ -779,7 +773,7 @@ mounted server.
- `prefix`: Deprecated. Use namespace instead.
-#### `import_server`
+#### `import_server`
```python
import_server(self, server: FastMCP[LifespanResultT], prefix: str | None = None) -> None
@@ -820,7 +814,7 @@ templates, and prompts are imported with their original names.
objects are imported with their original names.
-#### `from_openapi`
+#### `from_openapi`
```python
from_openapi(cls, openapi_spec: dict[str, Any], client: httpx.AsyncClient | None = None, name: str = 'OpenAPI Server', route_maps: list[RouteMap] | None = None, route_map_fn: OpenAPIRouteMapFn | None = None, mcp_component_fn: OpenAPIComponentFn | None = None, mcp_names: dict[str, str] | None = None, tags: set[str] | None = None, validate_output: bool = True, **settings: Any) -> Self
@@ -849,7 +843,7 @@ response structure while still returning structured JSON.
- A FastMCP server with an OpenAPIProvider attached.
-#### `from_fastapi`
+#### `from_fastapi`
```python
from_fastapi(cls, app: Any, name: str | None = None, route_maps: list[RouteMap] | None = None, route_map_fn: OpenAPIRouteMapFn | None = None, mcp_component_fn: OpenAPIComponentFn | None = None, mcp_names: dict[str, str] | None = None, httpx_client_kwargs: dict[str, Any] | None = None, tags: set[str] | None = None, **settings: Any) -> Self
@@ -873,7 +867,7 @@ Use this to configure timeout and other client settings.
- A FastMCP server with an OpenAPIProvider attached.
-#### `as_proxy`
+#### `as_proxy`
```python
as_proxy(cls, backend: Client[ClientTransportT] | ClientTransport | FastMCP[Any] | FastMCP1Server | AnyUrl | Path | MCPConfig | dict[str, Any] | str, **settings: Any) -> FastMCPProxy
@@ -891,7 +885,7 @@ instance or any value accepted as the `transport` argument of
`fastmcp.client.Client` constructor.
-#### `generate_name`
+#### `generate_name`
```python
generate_name(cls, name: str | None = None) -> str
diff --git a/docs/python-sdk/fastmcp-settings.mdx b/docs/python-sdk/fastmcp-settings.mdx
index 8c6470a39..6b630615a 100644
--- a/docs/python-sdk/fastmcp-settings.mdx
+++ b/docs/python-sdk/fastmcp-settings.mdx
@@ -7,15 +7,13 @@ sidebarTitle: settings
## Classes
-### `DocketSettings`
+### `DocketSettings`
Docket worker configuration.
-### `ExperimentalSettings`
-
-### `Settings`
+### `Settings`
FastMCP settings.
@@ -23,7 +21,7 @@ FastMCP settings.
**Methods:**
-#### `get_setting`
+#### `get_setting`
```python
get_setting(self, attr: str) -> Any
@@ -33,7 +31,7 @@ Get a setting. If the setting contains one or more `__`, it will be
treated as a nested setting.
-#### `set_setting`
+#### `set_setting`
```python
set_setting(self, attr: str, value: Any) -> None
@@ -43,7 +41,7 @@ Set a setting. If the setting contains one or more `__`, it will be
treated as a nested setting.
-#### `normalize_log_level`
+#### `normalize_log_level`
```python
normalize_log_level(cls, v)
From ac00017028f9219271907d007e6d0c0d3d68fc0d Mon Sep 17 00:00:00 2001
From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Date: Wed, 11 Feb 2026 11:32:00 -0500
Subject: [PATCH 3/3] Fix review feedback: is not None checks, docs method name
---
docs/development/v3-notes/v3-features.mdx | 4 ++--
src/fastmcp/mcp_config.py | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/docs/development/v3-notes/v3-features.mdx b/docs/development/v3-notes/v3-features.mdx
index 04e6120d8..93420c6b8 100644
--- a/docs/development/v3-notes/v3-features.mdx
+++ b/docs/development/v3-notes/v3-features.mdx
@@ -70,7 +70,7 @@ Background tasks now use a distributed Redis notification queue for reliable del
Sixteen deprecated keyword arguments have been removed from `FastMCP.__init__`. Passing any of them now raises `TypeError` with a migration hint. Environment variables (e.g., `FASTMCP_HOST`) continue to work — only the constructor kwargs moved.
-**Transport/server settings** (`host`, `port`, `log_level`, `debug`, `sse_path`, `message_path`, `streamable_http_path`, `json_response`, `stateless_http`): Pass to `run_http_async()` or `http_app()`, or set via environment variables.
+**Transport/server settings** (`host`, `port`, `log_level`, `debug`, `sse_path`, `message_path`, `streamable_http_path`, `json_response`, `stateless_http`): Pass to `run()`, `run_http_async()`, or `http_app()` as appropriate, or set via environment variables.
```python
# Before
@@ -79,7 +79,7 @@ mcp.run()
# After
mcp = FastMCP("server")
-mcp.run_http(host="0.0.0.0", port=8080)
+mcp.run(transport="http", host="0.0.0.0", port=8080)
```
**Duplicate handling** (`on_duplicate_tools`, `on_duplicate_resources`, `on_duplicate_prompts`): Use the unified `on_duplicate=` parameter.
diff --git a/src/fastmcp/mcp_config.py b/src/fastmcp/mcp_config.py
index 98146652d..c7dac539f 100644
--- a/src/fastmcp/mcp_config.py
+++ b/src/fastmcp/mcp_config.py
@@ -111,9 +111,9 @@ def _to_server_and_underlying_transport(
name=server_name,
)
- if self.include_tags:
+ if self.include_tags is not None:
wrapped_mcp_server.enable(tags=self.include_tags, only=True)
- if self.exclude_tags:
+ if self.exclude_tags is not None:
wrapped_mcp_server.disable(tags=self.exclude_tags)
# Apply tool transforms if configured