From 6a27cda48bdff88b6f62f53936b8d1dc40b93759 Mon Sep 17 00:00:00 2001
From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Date: Sat, 1 Nov 2025 11:57:01 -0400
Subject: [PATCH 01/17] Initialize 2.14 deprecation removal branch
From 63ca42ac90a09d2c053c4bf1a853f98f489fb59b Mon Sep 17 00:00:00 2001
From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Date: Sat, 1 Nov 2025 11:47:22 -0700
Subject: [PATCH 02/17] Remove deprecated FASTMCP_SERVER_ environment variable
prefix (#2330)
---
src/fastmcp/settings.py | 56 ++-----------------------------
tests/deprecated/test_settings.py | 34 -------------------
2 files changed, 2 insertions(+), 88 deletions(-)
diff --git a/src/fastmcp/settings.py b/src/fastmcp/settings.py
index ca84cb5a2..b8b579ab1 100644
--- a/src/fastmcp/settings.py
+++ b/src/fastmcp/settings.py
@@ -8,11 +8,8 @@ from typing import TYPE_CHECKING, Annotated, Any, Literal
from platformdirs import user_data_dir
from pydantic import Field, ImportString, field_validator
-from pydantic.fields import FieldInfo
from pydantic_settings import (
BaseSettings,
- EnvSettingsSource,
- PydanticBaseSettingsSource,
SettingsConfigDict,
)
from typing_extensions import Self
@@ -33,37 +30,6 @@ if TYPE_CHECKING:
from fastmcp.server.auth.auth import AuthProvider
-class ExtendedEnvSettingsSource(EnvSettingsSource):
- """
- A special EnvSettingsSource that allows for multiple env var prefixes to be used.
-
- Raises a deprecation warning if the old `FASTMCP_SERVER_` prefix is used.
- """
-
- def get_field_value(
- self, field: FieldInfo, field_name: str
- ) -> tuple[Any, str, bool]:
- if prefixes := self.config.get("env_prefixes"):
- for prefix in prefixes:
- self.env_prefix = prefix
- env_val, field_key, value_is_complex = super().get_field_value(
- field, field_name
- )
- if env_val is not None:
- if prefix == "FASTMCP_SERVER_":
- # Deprecated in 2.8.0
- logger.warning(
- "Using `FASTMCP_SERVER_` environment variables is deprecated. Use `FASTMCP_` instead.",
- )
- return env_val, field_key, value_is_complex
-
- return super().get_field_value(field, field_name)
-
-
-class ExtendedSettingsConfigDict(SettingsConfigDict, total=False):
- env_prefixes: list[str] | None
-
-
class ExperimentalSettings(BaseSettings):
model_config = SettingsConfigDict(
env_prefix="FASTMCP_EXPERIMENTAL_",
@@ -86,8 +52,8 @@ class ExperimentalSettings(BaseSettings):
class Settings(BaseSettings):
"""FastMCP settings."""
- model_config = ExtendedSettingsConfigDict(
- env_prefixes=["FASTMCP_", "FASTMCP_SERVER_"],
+ model_config = SettingsConfigDict(
+ env_prefix="FASTMCP_",
env_file=ENV_FILE,
extra="ignore",
env_nested_delimiter="__",
@@ -121,24 +87,6 @@ class Settings(BaseSettings):
settings = getattr(settings, parent_attr)
setattr(settings, attr, value)
- @classmethod
- def settings_customise_sources(
- cls,
- settings_cls: type[BaseSettings],
- init_settings: PydanticBaseSettingsSource,
- env_settings: PydanticBaseSettingsSource,
- dotenv_settings: PydanticBaseSettingsSource,
- file_secret_settings: PydanticBaseSettingsSource,
- ) -> tuple[PydanticBaseSettingsSource, ...]:
- # can remove this classmethod after deprecated FASTMCP_SERVER_ prefix is
- # removed
- return (
- init_settings,
- ExtendedEnvSettingsSource(settings_cls),
- dotenv_settings,
- file_secret_settings,
- )
-
@property
def settings(self) -> Self:
"""
diff --git a/tests/deprecated/test_settings.py b/tests/deprecated/test_settings.py
index ad6a29a67..84e2c250e 100644
--- a/tests/deprecated/test_settings.py
+++ b/tests/deprecated/test_settings.py
@@ -1,4 +1,3 @@
-import os
import warnings
from unittest.mock import patch
@@ -304,39 +303,6 @@ class TestDeprecatedServerInitKwargs:
assert "server.py" in warning.filename
-class TestDeprecatedEnvironmentVariables:
- """Test deprecated environment variable prefixes."""
-
- def test_fastmcp_server_env_var_deprecation_warning(self, caplog):
- """Test that FASTMCP_SERVER_ environment variables emit deprecation warnings."""
- env_var_name = "FASTMCP_SERVER_HOST"
- original_value = os.environ.get(env_var_name)
-
- try:
- os.environ[env_var_name] = "192.168.1.1"
-
- with caplog_for_fastmcp(caplog):
- settings = Settings()
-
- # Check that a warning was logged
- assert any(
- "Using `FASTMCP_SERVER_` environment variables is deprecated. Use `FASTMCP_` instead."
- in record.message
- for record in caplog.records
- if record.levelname == "WARNING"
- )
-
- # Verify the setting is still applied
- assert settings.host == "192.168.1.1"
-
- finally:
- # Clean up environment variable
- if original_value is not None:
- os.environ[env_var_name] = original_value
- else:
- os.environ.pop(env_var_name, None)
-
-
class TestDeprecatedSettingsProperty:
"""Test deprecated settings property access."""
From deb2491f4543738d17372346e9b00d64587ac581 Mon Sep 17 00:00:00 2001
From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Date: Sat, 1 Nov 2025 12:09:36 -0700
Subject: [PATCH 03/17] Remove deprecated Context.get_http_request method
(#2332)
---
src/fastmcp/server/context.py | 18 -----------
tests/server/test_context.py | 56 +----------------------------------
2 files changed, 1 insertion(+), 73 deletions(-)
diff --git a/src/fastmcp/server/context.py b/src/fastmcp/server/context.py
index 94d983db3..152a7a88d 100644
--- a/src/fastmcp/server/context.py
+++ b/src/fastmcp/server/context.py
@@ -3,7 +3,6 @@ from __future__ import annotations
import copy
import inspect
import logging
-import warnings
import weakref
from collections.abc import Generator, Mapping, Sequence
from contextlib import contextmanager
@@ -39,8 +38,6 @@ from pydantic.networks import AnyUrl
from starlette.requests import Request
from typing_extensions import TypeVar
-import fastmcp.server.dependencies
-from fastmcp import settings
from fastmcp.server.elicitation import (
AcceptedElicitation,
CancelledElicitation,
@@ -638,21 +635,6 @@ class Context:
# This should never happen, but handle it just in case
raise ValueError(f"Unexpected elicitation action: {result.action}")
- def get_http_request(self) -> Request:
- """Get the active starlette request."""
-
- # Deprecated in 2.2.11
- if settings.deprecation_warnings:
- warnings.warn(
- "Context.get_http_request() is deprecated and will be removed in a future version. "
- "Use get_http_request() from fastmcp.server.dependencies instead. "
- "See https://gofastmcp.com/servers/context#http-requests for more details.",
- DeprecationWarning,
- stacklevel=2,
- )
-
- return fastmcp.server.dependencies.get_http_request()
-
def set_state(self, key: str, value: Any) -> None:
"""Set a value in the context state."""
self._state[key] = value
diff --git a/tests/server/test_context.py b/tests/server/test_context.py
index 2a2e38e74..915c6d9dc 100644
--- a/tests/server/test_context.py
+++ b/tests/server/test_context.py
@@ -1,9 +1,7 @@
-import warnings
-from unittest.mock import MagicMock, patch
+from unittest.mock import MagicMock
import pytest
from mcp.types import ModelPreferences
-from starlette.requests import Request
from fastmcp.server.context import (
Context,
@@ -12,58 +10,6 @@ from fastmcp.server.context import (
from fastmcp.server.server import FastMCP
-class TestContextDeprecations:
- def test_get_http_request_deprecation_warning(self):
- """Test that using Context.get_http_request() raises a deprecation warning."""
- # Create a mock FastMCP instance
- mock_fastmcp = MagicMock()
- context = Context(fastmcp=mock_fastmcp)
-
- # Patch the dependency function to return a mock request
- mock_request = MagicMock(spec=Request)
- with patch(
- "fastmcp.server.dependencies.get_http_request", return_value=mock_request
- ):
- # Check that the deprecation warning is raised
- with pytest.warns(
- DeprecationWarning, match="Context.get_http_request\\(\\) is deprecated"
- ):
- request = context.get_http_request()
-
- # Verify the function still works and returns the request
- assert request is mock_request
-
- def test_get_http_request_deprecation_message(self):
- """Test that the deprecation warning has the correct message with guidance."""
- # Create a mock FastMCP instance
- mock_fastmcp = MagicMock()
- context = Context(fastmcp=mock_fastmcp)
-
- # Patch the dependency function to return a mock request
- mock_request = MagicMock(spec=Request)
- with patch(
- "fastmcp.server.dependencies.get_http_request", return_value=mock_request
- ):
- # Capture and check the specific warning message
- with warnings.catch_warnings(record=True) as w:
- warnings.simplefilter("always")
- context.get_http_request()
-
- assert len(w) == 1
- warning = w[0]
- assert issubclass(warning.category, DeprecationWarning)
- assert "Context.get_http_request() is deprecated" in str(
- warning.message
- )
- assert (
- "Use get_http_request() from fastmcp.server.dependencies instead"
- in str(warning.message)
- )
- assert "https://gofastmcp.com/servers/context#http-requests" in str(
- warning.message
- )
-
-
@pytest.fixture
def context():
return Context(fastmcp=FastMCP())
From d845d541b8de3cb4dcaf8b3483c38e2f0c6d158f Mon Sep 17 00:00:00 2001
From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Date: Sat, 1 Nov 2025 12:11:20 -0700
Subject: [PATCH 04/17] Remove fastmcp.Image top-level import (deprecated
2.8.1) (#2334)
* Remove test warnings (#2331)
* Create new branch and fix issue
---
src/fastmcp/__init__.py | 20 --------------------
1 file changed, 20 deletions(-)
diff --git a/src/fastmcp/__init__.py b/src/fastmcp/__init__.py
index d47bf88c8..99cce018d 100644
--- a/src/fastmcp/__init__.py
+++ b/src/fastmcp/__init__.py
@@ -27,26 +27,6 @@ if settings.deprecation_warnings:
warnings.simplefilter("default", DeprecationWarning)
-def __getattr__(name: str):
- """
- Used to deprecate the module-level Image class; can be removed once it is no longer imported to root.
- """
- if name == "Image":
- # Deprecated in 2.8.1
- if settings.deprecation_warnings:
- warnings.warn(
- "The top-level `fastmcp.Image` import is deprecated "
- "and will be removed in a future version. "
- "Please use `fastmcp.utilities.types.Image` instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- from fastmcp.utilities.types import Image
-
- return Image
- raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
-
-
__all__ = [
"Client",
"Context",
From 440ea6130d4d7771cd481d8a489c328af93740e8 Mon Sep 17 00:00:00 2001
From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Date: Sat, 1 Nov 2025 12:13:09 -0700
Subject: [PATCH 05/17] Remove deprecated client parameter from FastMCPProxy
(#2333)
---
docs/servers/proxy.mdx | 1 -
src/fastmcp/server/proxy.py | 32 +-------
tests/deprecated/test_proxy_client.py | 107 --------------------------
3 files changed, 2 insertions(+), 138 deletions(-)
delete mode 100644 tests/deprecated/test_proxy_client.py
diff --git a/docs/servers/proxy.mdx b/docs/servers/proxy.mdx
index 7cbb6d90c..354db4883 100644
--- a/docs/servers/proxy.mdx
+++ b/docs/servers/proxy.mdx
@@ -317,7 +317,6 @@ proxy = FastMCPProxy(client_factory=create_client)
### Parameters
-- **`client`**: **[DEPRECATED]** A `Client` instance. Use `client_factory` instead for explicit session management.
- **`client_factory`**: A callable that returns a `Client` instance when called. This gives you full control over session creation and reuse strategies.
### Explicit Session Management
diff --git a/src/fastmcp/server/proxy.py b/src/fastmcp/server/proxy.py
index dc87e98b7..0632f3774 100644
--- a/src/fastmcp/server/proxy.py
+++ b/src/fastmcp/server/proxy.py
@@ -1,7 +1,6 @@
from __future__ import annotations
import inspect
-import warnings
from collections.abc import Awaitable, Callable
from pathlib import Path
from typing import TYPE_CHECKING, Any, cast
@@ -20,7 +19,6 @@ from mcp.types import (
)
from pydantic.networks import AnyUrl
-import fastmcp
from fastmcp.client.client import Client, FastMCP1Server
from fastmcp.client.elicitation import ElicitResult
from fastmcp.client.logging import LogMessage
@@ -461,9 +459,8 @@ class FastMCPProxy(FastMCP):
def __init__(
self,
- client: Client | None = None,
*,
- client_factory: ClientFactoryT | None = None,
+ client_factory: ClientFactoryT,
**kwargs,
):
"""
@@ -473,9 +470,6 @@ class FastMCPProxy(FastMCP):
Use FastMCP.as_proxy() for convenience with automatic session strategy.
Args:
- client: [DEPRECATED] A Client instance. Use client_factory instead for explicit
- session management. When provided, a client_factory will be automatically
- created that provides session isolation for backwards compatibility.
client_factory: A callable that returns a Client instance when called.
This gives you full control over session creation and reuse.
Can be either a synchronous or asynchronous function.
@@ -484,29 +478,7 @@ class FastMCPProxy(FastMCP):
super().__init__(**kwargs)
- # Handle client and client_factory parameters
- if client is not None and client_factory is not None:
- raise ValueError("Cannot specify both 'client' and 'client_factory'")
-
- if client is not None:
- # Deprecated in 2.10.3
- if fastmcp.settings.deprecation_warnings:
- warnings.warn(
- "Passing 'client' to FastMCPProxy is deprecated. Use 'client_factory' instead for explicit session management. "
- "For automatic session strategy, use FastMCP.as_proxy().",
- DeprecationWarning,
- stacklevel=2,
- )
-
- # Create a factory that provides session isolation for backwards compatibility
- def deprecated_client_factory():
- return client.new()
-
- self.client_factory = deprecated_client_factory
- elif client_factory is not None:
- self.client_factory = client_factory
- else:
- raise ValueError("Must specify 'client_factory'")
+ self.client_factory = client_factory
# Replace the default managers with our specialized proxy managers.
self._tool_manager = ProxyToolManager(
diff --git a/tests/deprecated/test_proxy_client.py b/tests/deprecated/test_proxy_client.py
deleted file mode 100644
index f278c812c..000000000
--- a/tests/deprecated/test_proxy_client.py
+++ /dev/null
@@ -1,107 +0,0 @@
-"""Tests for deprecated FastMCPProxy client parameter."""
-
-import warnings
-
-import pytest
-
-from fastmcp import Client, FastMCP
-from fastmcp.server.proxy import FastMCPProxy, ProxyClient
-
-
-@pytest.fixture
-def simple_server():
- """Create a simple FastMCP server for testing."""
- server = FastMCP("TestServer")
-
- @server.tool
- def simple_tool() -> str:
- return "test_result"
-
- return server
-
-
-class TestDeprecatedClientParameter:
- """Test the deprecated client parameter in FastMCPProxy."""
-
- def test_client_parameter_deprecation_warning(self, simple_server):
- """Test that using the client parameter raises a deprecation warning."""
- client = Client(simple_server)
-
- with warnings.catch_warnings(record=True) as w:
- warnings.simplefilter("always") # Ensure all warnings are captured
-
- FastMCPProxy(client=client)
-
- # Verify a deprecation warning was raised
- assert len(w) == 1
- assert issubclass(w[0].category, DeprecationWarning)
- assert "client' to FastMCPProxy is deprecated" in str(w[0].message)
- assert "client_factory" in str(w[0].message)
-
- def test_client_parameter_still_works(self, simple_server):
- """Test that the deprecated client parameter still functions."""
- client = ProxyClient(simple_server)
-
- with warnings.catch_warnings():
- warnings.simplefilter("ignore") # Suppress warnings for functionality test
-
- proxy = FastMCPProxy(client=client)
-
- # Verify the proxy was created successfully
- assert proxy is not None
- assert hasattr(proxy, "client_factory")
- assert callable(proxy.client_factory)
-
- # Verify the factory returns a new client instance (session isolation for backwards compatibility)
- returned_client = proxy.client_factory()
- assert returned_client is not client
- assert isinstance(returned_client, type(client))
-
- def test_cannot_specify_both_client_and_factory(self, simple_server):
- """Test that specifying both client and client_factory raises an error."""
- client = Client(simple_server)
-
- def factory():
- return Client(simple_server)
-
- with pytest.raises(
- ValueError, match="Cannot specify both 'client' and 'client_factory'"
- ):
- FastMCPProxy(client=client, client_factory=factory)
-
- def test_must_specify_client_factory_when_no_client(self):
- """Test that client_factory is required when client is not provided."""
- with pytest.raises(ValueError, match="Must specify 'client_factory'"):
- FastMCPProxy()
-
- def test_client_factory_preferred_over_deprecated_client(self, simple_server):
- """Test that the recommended client_factory approach works without warnings."""
-
- def factory():
- return ProxyClient(simple_server)
-
- with warnings.catch_warnings(record=True) as w:
- warnings.simplefilter("always")
-
- proxy = FastMCPProxy(client_factory=factory)
-
- # Verify no warnings were raised
- assert len(w) == 0
-
- # Verify the proxy works correctly
- assert proxy is not None
- assert proxy.client_factory is factory
-
- async def test_deprecated_client_functional_test(self, simple_server):
- """End-to-end test that deprecated client parameter still works functionally."""
- client = ProxyClient(simple_server)
-
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
-
- proxy = FastMCPProxy(client=client)
-
- # Test that the proxy can actually handle requests
- async with Client(proxy) as proxy_client:
- result = await proxy_client.call_tool("simple_tool", {})
- assert result.data == "test_result"
From 85ceda1d89fae3113ab1c4835d46076ac8d573e2 Mon Sep 17 00:00:00 2001
From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Date: Sat, 1 Nov 2025 12:19:59 -0700
Subject: [PATCH 06/17] Remove deprecated run_streamable_http_async method
(#2338)
---
docs/python-sdk/fastmcp-server-server.mdx | 6 ------
src/fastmcp/server/server.py | 25 -----------------------
tests/deprecated/test_deprecated.py | 18 ----------------
3 files changed, 49 deletions(-)
diff --git a/docs/python-sdk/fastmcp-server-server.mdx b/docs/python-sdk/fastmcp-server-server.mdx
index 41284afc7..cb877c355 100644
--- a/docs/python-sdk/fastmcp-server-server.mdx
+++ b/docs/python-sdk/fastmcp-server-server.mdx
@@ -723,12 +723,6 @@ Create a Starlette app using the specified HTTP transport.
- A Starlette application configured with the specified transport
-#### `run_streamable_http_async`
-
-```python
-run_streamable_http_async(self, host: str | None = None, port: int | None = None, log_level: str | None = None, path: str | None = None, uvicorn_config: dict[str, Any] | None = None) -> None
-```
-
#### `mount`
```python
diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py
index 1ce5fbf38..a85e9d6ad 100644
--- a/src/fastmcp/server/server.py
+++ b/src/fastmcp/server/server.py
@@ -2182,31 +2182,6 @@ class FastMCP(Generic[LifespanResultT]):
middleware=middleware,
)
- async def run_streamable_http_async(
- self,
- host: str | None = None,
- port: int | None = None,
- log_level: str | None = None,
- path: str | None = None,
- uvicorn_config: dict[str, Any] | None = None,
- ) -> None:
- # Deprecated since 2.3.2
- if fastmcp.settings.deprecation_warnings:
- warnings.warn(
- "The run_streamable_http_async method is deprecated (as of 2.3.2). "
- "Use run_http_async instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- await self.run_http_async(
- transport="http",
- host=host,
- port=port,
- log_level=log_level,
- path=path,
- uvicorn_config=uvicorn_config,
- )
-
def mount(
self,
server: FastMCP[LifespanResultT],
diff --git a/tests/deprecated/test_deprecated.py b/tests/deprecated/test_deprecated.py
index 92b28cda8..f172fd324 100644
--- a/tests/deprecated/test_deprecated.py
+++ b/tests/deprecated/test_deprecated.py
@@ -70,24 +70,6 @@ async def test_run_sse_async_deprecation_warning():
assert call_kwargs.get("transport") == "sse"
-async def test_run_streamable_http_async_deprecation_warning():
- """Test that run_streamable_http_async raises a deprecation warning."""
- server = FastMCP("TestServer")
-
- # Use patch to avoid actually running the server
- with patch.object(server, "run_http_async", new_callable=AsyncMock) as mock_run:
- with pytest.warns(
- DeprecationWarning,
- match="The run_streamable_http_async method is deprecated",
- ):
- await server.run_streamable_http_async()
-
- # Verify the mock was called with the right transport
- mock_run.assert_called_once()
- call_kwargs = mock_run.call_args.kwargs
- assert call_kwargs.get("transport") == "http"
-
-
def test_http_app_with_sse_transport():
"""Test that http_app with SSE transport works (no warning)."""
server = FastMCP("TestServer")
From 6ea606a98d8d670270457a02e6a84fe0340dc3f6 Mon Sep 17 00:00:00 2001
From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Date: Sat, 1 Nov 2025 12:28:35 -0700
Subject: [PATCH 07/17] Remove deprecated sse_app method (#2337)
---
src/fastmcp/server/server.py | 31 -----------------------------
tests/client/test_sse.py | 2 +-
tests/deprecated/test_deprecated.py | 9 ---------
3 files changed, 1 insertion(+), 41 deletions(-)
diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py
index a85e9d6ad..70d00b7bd 100644
--- a/src/fastmcp/server/server.py
+++ b/src/fastmcp/server/server.py
@@ -2081,37 +2081,6 @@ class FastMCP(Generic[LifespanResultT]):
uvicorn_config=uvicorn_config,
)
- def sse_app(
- self,
- path: str | None = None,
- message_path: str | None = None,
- middleware: list[ASGIMiddleware] | None = None,
- ) -> StarletteWithLifespan:
- """
- Create a Starlette app for the SSE server.
-
- Args:
- path: The path to the SSE endpoint
- message_path: The path to the message endpoint
- middleware: A list of middleware to apply to the app
- """
- # Deprecated since 2.3.2
- if fastmcp.settings.deprecation_warnings:
- warnings.warn(
- "The sse_app method is deprecated (as of 2.3.2). Use http_app as a modern (non-SSE) "
- "alternative, or call `fastmcp.server.http.create_sse_app` directly.",
- DeprecationWarning,
- stacklevel=2,
- )
- return create_sse_app(
- server=self,
- message_path=message_path or self._deprecated_settings.message_path,
- sse_path=path or self._deprecated_settings.sse_path,
- auth=self.auth,
- debug=self._deprecated_settings.debug,
- middleware=middleware,
- )
-
def streamable_http_app(
self,
path: str | None = None,
diff --git a/tests/client/test_sse.py b/tests/client/test_sse.py
index 7110d5cfd..b0c9199b2 100644
--- a/tests/client/test_sse.py
+++ b/tests/client/test_sse.py
@@ -8,6 +8,7 @@ from mcp import McpError
from fastmcp.client import Client
from fastmcp.client.transports import SSETransport
from fastmcp.server.dependencies import get_http_request
+from fastmcp.server.http import create_sse_app
from fastmcp.server.server import FastMCP
from fastmcp.utilities.tests import run_server_async
@@ -94,7 +95,6 @@ async def nested_sse_server():
from starlette.applications import Starlette
from starlette.routing import Mount
- from fastmcp.server.http import create_sse_app
from fastmcp.utilities.http import find_available_port
server = create_test_server()
diff --git a/tests/deprecated/test_deprecated.py b/tests/deprecated/test_deprecated.py
index f172fd324..4f60579ec 100644
--- a/tests/deprecated/test_deprecated.py
+++ b/tests/deprecated/test_deprecated.py
@@ -33,15 +33,6 @@ class TestDeprecationWarningsSetting:
mcp.settings
-def test_sse_app_deprecation_warning():
- """Test that sse_app raises a deprecation warning."""
- server = FastMCP("TestServer")
-
- with pytest.warns(DeprecationWarning, match="The sse_app method is deprecated"):
- app = server.sse_app()
- assert isinstance(app, Starlette)
-
-
def test_streamable_http_app_deprecation_warning():
"""Test that streamable_http_app raises a deprecation warning."""
server = FastMCP("TestServer")
From b78ed5a9a8f3012d9e060b156e9c86236d27193c Mon Sep 17 00:00:00 2001
From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Date: Sat, 1 Nov 2025 12:40:06 -0700
Subject: [PATCH 08/17] Remove deprecated run_sse_async method (#2335)
* Remove deprecated run_sse_async method
* Update CLI and tests to use run_http_async(transport="sse")
- Change CLI to call run_http_async with transport="sse" instead of run_sse_async
- Update test to mock run_http_async with create=True for v1 servers
* Revert CLI changes - v1 servers do have run_sse_async
- Keep CLI calling run_sse_async() for v1 compatibility
- Update test to mock run_sse_async (which exists on v1)
* Remove unnecessary type ignore for run_sse_async
Method exists on v1 FastMCP class, no type error
* Remove unused imports after test deletion
---
src/fastmcp/server/server.py | 28 ----------------------------
tests/deprecated/test_deprecated.py | 18 ------------------
2 files changed, 46 deletions(-)
diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py
index 70d00b7bd..eda3d62df 100644
--- a/src/fastmcp/server/server.py
+++ b/src/fastmcp/server/server.py
@@ -2053,34 +2053,6 @@ class FastMCP(Generic[LifespanResultT]):
await server.serve()
- async def run_sse_async(
- self,
- host: str | None = None,
- port: int | None = None,
- log_level: str | None = None,
- path: str | None = None,
- uvicorn_config: dict[str, Any] | None = None,
- ) -> None:
- """Run the server using SSE transport."""
-
- # Deprecated since 2.3.2
- if fastmcp.settings.deprecation_warnings:
- warnings.warn(
- "The run_sse_async method is deprecated (as of 2.3.2). Use run_http_async for a "
- "modern (non-SSE) alternative, or create an SSE app with "
- "`fastmcp.server.http.create_sse_app` and run it directly.",
- DeprecationWarning,
- stacklevel=2,
- )
- await self.run_http_async(
- transport="sse",
- host=host,
- port=port,
- log_level=log_level,
- path=path,
- uvicorn_config=uvicorn_config,
- )
-
def streamable_http_app(
self,
path: str | None = None,
diff --git a/tests/deprecated/test_deprecated.py b/tests/deprecated/test_deprecated.py
index 4f60579ec..ec9ce4e23 100644
--- a/tests/deprecated/test_deprecated.py
+++ b/tests/deprecated/test_deprecated.py
@@ -1,5 +1,4 @@
import warnings
-from unittest.mock import AsyncMock, patch
import pytest
from starlette.applications import Starlette
@@ -44,23 +43,6 @@ def test_streamable_http_app_deprecation_warning():
assert isinstance(app, Starlette)
-async def test_run_sse_async_deprecation_warning():
- """Test that run_sse_async raises a deprecation warning."""
- server = FastMCP("TestServer")
-
- # Use patch to avoid actually running the server
- with patch.object(server, "run_http_async", new_callable=AsyncMock) as mock_run:
- with pytest.warns(
- DeprecationWarning, match="The run_sse_async method is deprecated"
- ):
- await server.run_sse_async()
-
- # Verify the mock was called with the right transport
- mock_run.assert_called_once()
- call_kwargs = mock_run.call_args.kwargs
- assert call_kwargs.get("transport") == "sse"
-
-
def test_http_app_with_sse_transport():
"""Test that http_app with SSE transport works (no warning)."""
server = FastMCP("TestServer")
From cdbe4d9a1d485effce0a13e94e41bd87febe84d9 Mon Sep 17 00:00:00 2001
From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Date: Sat, 1 Nov 2025 12:46:21 -0700
Subject: [PATCH 09/17] Remove deprecated streamable_http_app method (#2336)
---
src/fastmcp/server/server.py | 21 ---------------------
tests/deprecated/test_deprecated.py | 11 -----------
2 files changed, 32 deletions(-)
diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py
index eda3d62df..224fd34cc 100644
--- a/src/fastmcp/server/server.py
+++ b/src/fastmcp/server/server.py
@@ -2053,27 +2053,6 @@ class FastMCP(Generic[LifespanResultT]):
await server.serve()
- def streamable_http_app(
- self,
- path: str | None = None,
- middleware: list[ASGIMiddleware] | None = None,
- ) -> StarletteWithLifespan:
- """
- Create a Starlette app for the StreamableHTTP server.
-
- Args:
- path: The path to the StreamableHTTP endpoint
- middleware: A list of middleware to apply to the app
- """
- # Deprecated since 2.3.2
- if fastmcp.settings.deprecation_warnings:
- warnings.warn(
- "The streamable_http_app method is deprecated (as of 2.3.2). Use http_app() instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- return self.http_app(path=path, middleware=middleware)
-
def http_app(
self,
path: str | None = None,
diff --git a/tests/deprecated/test_deprecated.py b/tests/deprecated/test_deprecated.py
index ec9ce4e23..78ea5a957 100644
--- a/tests/deprecated/test_deprecated.py
+++ b/tests/deprecated/test_deprecated.py
@@ -32,17 +32,6 @@ class TestDeprecationWarningsSetting:
mcp.settings
-def test_streamable_http_app_deprecation_warning():
- """Test that streamable_http_app raises a deprecation warning."""
- server = FastMCP("TestServer")
-
- with pytest.warns(
- DeprecationWarning, match="The streamable_http_app method is deprecated"
- ):
- app = server.streamable_http_app()
- assert isinstance(app, Starlette)
-
-
def test_http_app_with_sse_transport():
"""Test that http_app with SSE transport works (no warning)."""
server = FastMCP("TestServer")
From 1c885c48e7a4ff3d12ec92d965ded766242f40b7 Mon Sep 17 00:00:00 2001
From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Date: Sat, 1 Nov 2025 12:48:08 -0700
Subject: [PATCH 10/17] Remove deprecated dependencies parameter from FastMCP
constructor (#2340)
---
examples/memory.py | 11 ++-----
examples/screenshot.py | 7 ++++-
.../src/smart_home/lights/server.py | 15 ++++++----
src/fastmcp/cli/cli.py | 22 +-------------
src/fastmcp/cli/install/shared.py | 15 ----------
src/fastmcp/server/server.py | 20 -------------
tests/deprecated/test_dependencies.py | 30 -------------------
7 files changed, 18 insertions(+), 102 deletions(-)
delete mode 100644 tests/deprecated/test_dependencies.py
diff --git a/examples/memory.py b/examples/memory.py
index c5a3488a4..792bd48c6 100644
--- a/examples/memory.py
+++ b/examples/memory.py
@@ -34,15 +34,8 @@ REINFORCEMENT_FACTOR = 1.1
DEFAULT_LLM_MODEL = "openai:gpt-4o"
DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small"
-mcp = FastMCP(
- "memory",
- dependencies=[
- "pydantic-ai-slim[openai]",
- "asyncpg",
- "numpy",
- "pgvector",
- ],
-)
+# Dependencies are configured in memory.fastmcp.json
+mcp = FastMCP("memory")
DB_DSN = "postgresql://postgres:postgres@localhost:54320/memory_db"
# reset memory by deleting the profile directory
diff --git a/examples/screenshot.py b/examples/screenshot.py
index 3322c0325..866133733 100644
--- a/examples/screenshot.py
+++ b/examples/screenshot.py
@@ -1,3 +1,7 @@
+# /// script
+# dependencies = ["pyautogui", "Pillow", "fastmcp"]
+# ///
+
"""
FastMCP Screenshot Example
@@ -10,7 +14,8 @@ from fastmcp import FastMCP
from fastmcp.utilities.types import Image
# Create server
-mcp = FastMCP("Screenshot Demo", dependencies=["pyautogui", "Pillow"])
+# Dependencies are configured in screenshot.fastmcp.json
+mcp = FastMCP("Screenshot Demo")
@mcp.tool
diff --git a/examples/smart_home/src/smart_home/lights/server.py b/examples/smart_home/src/smart_home/lights/server.py
index 87e8d4455..aa559b68f 100644
--- a/examples/smart_home/src/smart_home/lights/server.py
+++ b/examples/smart_home/src/smart_home/lights/server.py
@@ -1,3 +1,10 @@
+# /// script
+# dependencies = [
+# "smart_home@git+https://github.com/jlowin/fastmcp.git#subdirectory=examples/smart_home",
+# "fastmcp",
+# ]
+# ///
+
from typing import Annotated, Any, Literal, TypedDict
from phue2.exceptions import PhueException
@@ -35,12 +42,8 @@ class HueAttributes(TypedDict, total=False):
transitiontime: NotRequired[Annotated[int, Field(description="deciseconds")]]
-lights_mcp = FastMCP(
- "Hue Lights Service (phue2)",
- dependencies=[
- "smart_home@git+https://github.com/jlowin/fastmcp.git#subdirectory=examples/smart_home",
- ],
-)
+# Dependencies are configured in lights.fastmcp.json
+lights_mcp = FastMCP("Hue Lights Service (phue2)")
@lights_mcp.tool
diff --git a/src/fastmcp/cli/cli.py b/src/fastmcp/cli/cli.py
index 17d7e1c99..3ed5a3c01 100644
--- a/src/fastmcp/cli/cli.py
+++ b/src/fastmcp/cli/cli.py
@@ -19,7 +19,6 @@ from rich.table import Table
import fastmcp
from fastmcp.cli import run as run_module
from fastmcp.cli.install import install_app
-from fastmcp.server.server import FastMCP
from fastmcp.utilities.cli import is_already_in_uv_subprocess, load_and_merge_config
from fastmcp.utilities.inspect import (
InspectFormat,
@@ -28,7 +27,6 @@ from fastmcp.utilities.inspect import (
)
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.mcp_server_config import MCPServerConfig
-from fastmcp.utilities.mcp_server_config.v1.environments.uv import UVEnvironment
logger = get_logger("cli")
console = Console()
@@ -224,29 +222,11 @@ async def dev(
)
try:
- # Load server to check for deprecated dependencies
if not config:
logger.error("No configuration available")
sys.exit(1)
assert config is not None # For type checker
- server: FastMCP = await config.source.load_server()
- if server.dependencies:
- import warnings
-
- warnings.warn(
- f"Server '{server.name}' uses deprecated 'dependencies' parameter (deprecated in FastMCP 2.11.4). "
- "Please migrate to fastmcp.json configuration file. "
- "See https://gofastmcp.com/docs/deployment/server-configuration for details.",
- DeprecationWarning,
- stacklevel=2,
- )
- # Merge server dependencies with environment dependencies
- env_deps = config.environment.dependencies or []
- all_deps = list(set(env_deps + server.dependencies))
- if not config.environment:
- config.environment = UVEnvironment(dependencies=all_deps)
- else:
- config.environment.dependencies = all_deps
+ await config.source.load_server()
env_vars = {}
if ui_port:
diff --git a/src/fastmcp/cli/install/shared.py b/src/fastmcp/cli/install/shared.py
index 3544a2b6b..8e183562a 100644
--- a/src/fastmcp/cli/install/shared.py
+++ b/src/fastmcp/cli/install/shared.py
@@ -105,21 +105,6 @@ async def process_common_args(
)
name = file.stem
- # Get server dependencies if available
- # TODO: Remove dependencies handling (deprecated in v2.11.4)
- server_dependencies = getattr(server, "dependencies", []) if server else []
- if server_dependencies:
- import warnings
-
- warnings.warn(
- "Server uses deprecated 'dependencies' parameter (deprecated in FastMCP 2.11.4). "
- "Please migrate to fastmcp.json configuration file. "
- "See https://gofastmcp.com/docs/deployment/server-configuration for details.",
- DeprecationWarning,
- stacklevel=2,
- )
- with_packages = list(set(with_packages + server_dependencies))
-
# Process environment variables if provided
env_dict: dict[str, str] | None = None
if env_file or env_vars:
diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py
index 224fd34cc..17da944c9 100644
--- a/src/fastmcp/server/server.py
+++ b/src/fastmcp/server/server.py
@@ -3,7 +3,6 @@
from __future__ import annotations
import inspect
-import json
import re
import secrets
import warnings
@@ -157,7 +156,6 @@ class FastMCP(Generic[LifespanResultT]):
auth: AuthProvider | NotSetT | None = NotSet,
middleware: Sequence[Middleware] | None = None,
lifespan: LifespanCallable | None = None,
- dependencies: list[str] | None = None,
resource_prefix_format: Literal["protocol", "path"] | None = None,
mask_error_details: bool | None = None,
tools: Sequence[Tool | Callable[..., Any]] | None = None,
@@ -258,24 +256,6 @@ class FastMCP(Generic[LifespanResultT]):
# Set up MCP protocol handlers
self._setup_handlers()
- # Handle dependencies with deprecation warning
- # TODO: Remove dependencies parameter (deprecated in v2.11.4)
- if dependencies is not None:
- import warnings
-
- warnings.warn(
- "The 'dependencies' parameter is deprecated as of FastMCP 2.11.4 and will be removed in a future version. "
- "Please specify dependencies in a fastmcp.json configuration file instead:\n"
- '{\n "entrypoint": "your_server.py",\n "environment": {\n "dependencies": '
- f"{json.dumps(dependencies)}\n }}\n}}\n"
- "See https://gofastmcp.com/docs/deployment/server-configuration for more information.",
- DeprecationWarning,
- stacklevel=2,
- )
- self.dependencies: list[str] = (
- dependencies or fastmcp.settings.server_dependencies
- ) # TODO: Remove (deprecated in v2.11.4)
-
self.sampling_handler: ServerSamplingHandler[LifespanResultT] | None = (
sampling_handler
)
diff --git a/tests/deprecated/test_dependencies.py b/tests/deprecated/test_dependencies.py
deleted file mode 100644
index b8dca71d8..000000000
--- a/tests/deprecated/test_dependencies.py
+++ /dev/null
@@ -1,30 +0,0 @@
-"""Tests for deprecated dependencies parameter.
-
-This entire file can be deleted when the dependencies parameter is removed (deprecated in v2.11.4).
-"""
-
-import warnings
-
-import pytest
-
-from fastmcp import FastMCP
-
-
-def test_dependencies_parameter_deprecated():
- """Test that using the dependencies parameter raises a deprecation warning."""
-
- with pytest.warns(DeprecationWarning, match="deprecated as of FastMCP 2.11.4"):
- server = FastMCP("Test Server", dependencies=["pandas", "numpy"])
-
- # Should still work for backward compatibility
- assert server.dependencies == ["pandas", "numpy"]
-
-
-def test_no_warning_without_dependencies():
- """Test that no warning is raised when dependencies are not used."""
-
- with warnings.catch_warnings():
- warnings.simplefilter("error") # Turn warnings into errors
- server = FastMCP("Test Server") # Should not raise
-
- assert server.dependencies == [] # Should use default empty list
From ae9fe6f4be65403001f201aa08221b8487f0f480 Mon Sep 17 00:00:00 2001
From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Date: Sat, 1 Nov 2025 12:58:04 -0700
Subject: [PATCH 11/17] Remove output_schema=False support (deprecated 2.11.4)
(#2339)
* Remove deprecated client parameter from FastMCPProxy (#2333)
* Delete deprecated test_output_schema_false.py
Tests functionality that has been removed
---
src/fastmcp/tools/tool.py | 20 +--
src/fastmcp/tools/tool_transform.py | 13 +-
tests/deprecated/test_output_schema_false.py | 139 -------------------
3 files changed, 5 insertions(+), 167 deletions(-)
delete mode 100644 tests/deprecated/test_output_schema_false.py
diff --git a/src/fastmcp/tools/tool.py b/src/fastmcp/tools/tool.py
index b58645579..2a6b1a04b 100644
--- a/src/fastmcp/tools/tool.py
+++ b/src/fastmcp/tools/tool.py
@@ -1,7 +1,6 @@
from __future__ import annotations
import inspect
-import warnings
from collections.abc import Callable
from dataclasses import dataclass
from typing import (
@@ -9,7 +8,6 @@ from typing import (
Annotated,
Any,
Generic,
- Literal,
TypeAlias,
get_type_hints,
)
@@ -21,7 +19,6 @@ from mcp.types import Tool as MCPTool
from pydantic import Field, PydanticSchemaGenerationError
from typing_extensions import TypeVar
-import fastmcp
from fastmcp.server.dependencies import get_context
from fastmcp.utilities.components import FastMCPComponent
from fastmcp.utilities.json_schema import compress_schema
@@ -173,7 +170,7 @@ class Tool(FastMCPComponent):
tags: set[str] | None = None,
annotations: ToolAnnotations | None = None,
exclude_args: list[str] | None = None,
- output_schema: dict[str, Any] | Literal[False] | NotSetT | None = NotSet,
+ output_schema: dict[str, Any] | NotSetT | None = NotSet,
serializer: ToolResultSerializerType | None = None,
meta: dict[str, Any] | None = None,
enabled: bool | None = None,
@@ -216,7 +213,7 @@ class Tool(FastMCPComponent):
description: str | NotSetT | None = NotSet,
tags: set[str] | None = None,
annotations: ToolAnnotations | NotSetT | None = NotSet,
- output_schema: dict[str, Any] | Literal[False] | NotSetT | None = NotSet,
+ output_schema: dict[str, Any] | NotSetT | None = NotSet,
serializer: ToolResultSerializerType | None = None,
meta: dict[str, Any] | NotSetT | None = NotSet,
transform_args: dict[str, ArgTransform] | None = None,
@@ -255,7 +252,7 @@ class FunctionTool(Tool):
tags: set[str] | None = None,
annotations: ToolAnnotations | None = None,
exclude_args: list[str] | None = None,
- output_schema: dict[str, Any] | Literal[False] | NotSetT | None = NotSet,
+ output_schema: dict[str, Any] | NotSetT | None = NotSet,
serializer: ToolResultSerializerType | None = None,
meta: dict[str, Any] | None = None,
enabled: bool | None = None,
@@ -269,17 +266,8 @@ class FunctionTool(Tool):
if isinstance(output_schema, NotSetT):
final_output_schema = parsed_fn.output_schema
- elif output_schema is False:
- # Handle False as deprecated synonym for None (deprecated in 2.11.4)
- if fastmcp.settings.deprecation_warnings:
- warnings.warn(
- "Passing output_schema=False is deprecated. Use output_schema=None instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- final_output_schema = None
else:
- # At this point output_schema is not NotSetT and not False, so it must be dict | None
+ # At this point output_schema is not NotSetT, so it must be dict | None
final_output_schema = output_schema
# Note: explicit schemas (dict) are used as-is without auto-wrapping
diff --git a/src/fastmcp/tools/tool_transform.py b/src/fastmcp/tools/tool_transform.py
index efbd1fb8c..afa3d30d9 100644
--- a/src/fastmcp/tools/tool_transform.py
+++ b/src/fastmcp/tools/tool_transform.py
@@ -1,7 +1,6 @@
from __future__ import annotations
import inspect
-import warnings
from collections.abc import Callable
from contextvars import ContextVar
from dataclasses import dataclass
@@ -13,7 +12,6 @@ from pydantic import ConfigDict
from pydantic.fields import Field
from pydantic.functional_validators import BeforeValidator
-import fastmcp
from fastmcp.tools.tool import ParsedFunction, Tool, ToolResult, _convert_to_content
from fastmcp.utilities.components import _convert_set_default_none
from fastmcp.utilities.json_schema import compress_schema
@@ -371,7 +369,7 @@ class TransformedTool(Tool):
transform_fn: Callable[..., Any] | None = None,
transform_args: dict[str, ArgTransform] | None = None,
annotations: ToolAnnotations | NotSetT | None = NotSet,
- output_schema: dict[str, Any] | Literal[False] | NotSetT | None = NotSet,
+ output_schema: dict[str, Any] | NotSetT | None = NotSet,
serializer: Callable[[Any], str] | NotSetT | None = NotSet,
meta: dict[str, Any] | NotSetT | None = NotSet,
enabled: bool | None = None,
@@ -486,15 +484,6 @@ class TransformedTool(Tool):
final_output_schema = tool.output_schema
else:
final_output_schema = tool.output_schema
- elif output_schema is False:
- # Handle False as deprecated synonym for None (deprecated in 2.11.4)
- if fastmcp.settings.deprecation_warnings:
- warnings.warn(
- "Passing output_schema=False is deprecated. Use output_schema=None instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- final_output_schema = None
else:
final_output_schema = cast(dict | None, output_schema)
diff --git a/tests/deprecated/test_output_schema_false.py b/tests/deprecated/test_output_schema_false.py
deleted file mode 100644
index 0dcc20d06..000000000
--- a/tests/deprecated/test_output_schema_false.py
+++ /dev/null
@@ -1,139 +0,0 @@
-"""Test deprecated output_schema=False behavior (deprecated in 2.11.4)."""
-
-import warnings
-
-import pytest
-
-from fastmcp import FastMCP
-from fastmcp.tools import Tool
-
-
-class TestDeprecatedOutputSchemaFalse:
- """Test that output_schema=False is deprecated but still works."""
-
- async def test_tool_decorator_output_schema_false_deprecated(self):
- """Test that @mcp.tool(output_schema=False) shows deprecation warning."""
- mcp = FastMCP()
-
- with pytest.warns(
- DeprecationWarning, match="output_schema=False is deprecated"
- ):
-
- @mcp.tool(output_schema=False) # type: ignore[arg-type]
- def simple_tool() -> int:
- """A simple tool."""
- return 42
-
- # Verify the tool was created with None as output_schema
- tool = mcp._tool_manager._tools["simple_tool"]
- assert tool.output_schema is None
-
- async def test_tool_from_function_output_schema_false_deprecated(self):
- """Test that Tool.from_function(output_schema=False) shows deprecation warning."""
-
- def my_function() -> str:
- """A simple function."""
- return "hello"
-
- with pytest.warns(
- DeprecationWarning, match="output_schema=False is deprecated"
- ):
- tool = Tool.from_function(my_function, output_schema=False) # type: ignore[arg-type]
-
- # Verify the tool was created with None as output_schema
- assert tool.output_schema is None
-
- async def test_tool_from_tool_output_schema_false_deprecated(self):
- """Test that Tool.from_tool(output_schema=False) shows deprecation warning."""
-
- # Create a parent tool
- def parent_function() -> dict[str, str]:
- """A parent function."""
- return {"status": "ok"}
-
- parent_tool = Tool.from_function(parent_function)
-
- with pytest.warns(
- DeprecationWarning, match="output_schema=False is deprecated"
- ):
- transformed_tool = Tool.from_tool(parent_tool, output_schema=False) # type: ignore[arg-type]
-
- # Verify the tool was created with None as output_schema
- assert transformed_tool.output_schema is None
-
- async def test_output_schema_false_functionality_preserved(self):
- """Test that output_schema=False still works functionally like output_schema=None."""
- mcp = FastMCP()
-
- # Create two tools - one with False, one with None
- with warnings.catch_warnings():
- warnings.simplefilter("ignore", DeprecationWarning)
-
- @mcp.tool(output_schema=False) # type: ignore[arg-type]
- def tool_with_false() -> dict[str, str]:
- """Tool with output_schema=False."""
- return {"result": "false"}
-
- @mcp.tool(output_schema=None)
- def tool_with_none() -> dict[str, str]:
- """Tool with output_schema=None."""
- return {"result": "none"}
-
- # Both should have None as output_schema
- assert mcp._tool_manager._tools["tool_with_false"].output_schema is None
- assert mcp._tool_manager._tools["tool_with_none"].output_schema is None
-
- # Both should work the same way
- result_false = await mcp._tool_manager._tools["tool_with_false"].run({})
- result_none = await mcp._tool_manager._tools["tool_with_none"].run({})
-
- # Both should return structured content for dict-like objects
- assert result_false.structured_content == {"result": "false"}
- assert result_none.structured_content == {"result": "none"}
-
- async def test_output_schema_false_with_scalar_return(self):
- """Test that output_schema=False works with scalar returns (no structured content)."""
- mcp = FastMCP()
-
- with warnings.catch_warnings():
- warnings.simplefilter("ignore", DeprecationWarning)
-
- @mcp.tool(output_schema=False) # type: ignore[arg-type]
- def scalar_tool() -> int:
- """Tool returning a scalar."""
- return 42
-
- tool = mcp._tool_manager._tools["scalar_tool"]
- assert tool.output_schema is None
-
- result = await tool.run({})
- # Scalar values don't produce structured content
- assert result.structured_content is None
- assert len(result.content) == 1
- assert result.content[0].text == "42" # type: ignore[attr-defined]
-
- async def test_transform_with_output_schema_false(self):
- """Test that transformation with output_schema=False still works."""
-
- # Create a parent tool
- def parent_function(x: int) -> dict[str, int]:
- """A parent function."""
- return {"value": x * 2}
-
- parent_tool = Tool.from_function(parent_function)
-
- with warnings.catch_warnings():
- warnings.simplefilter("ignore", DeprecationWarning)
-
- # Transform with output_schema=False
- transformed = Tool.from_tool(
- parent_tool,
- name="doubled",
- output_schema=False, # type: ignore[arg-type]
- )
-
- assert transformed.output_schema is None
-
- # Tool should still work
- result = await transformed.run({"x": 5})
- assert result.structured_content == {"value": 10}
From 79b36f66b114ab844199de5e55f22338c37fe0dd Mon Sep 17 00:00:00 2001
From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Date: Sat, 1 Nov 2025 13:01:00 -0700
Subject: [PATCH 12/17] Remove deprecated BearerAuthProvider module (#2341)
---
docs/docs.json | 1 -
.../fastmcp-server-auth-providers-bearer.mdx | 13 ----------
src/fastmcp/server/auth/__init__.py | 9 -------
src/fastmcp/server/auth/providers/bearer.py | 25 -------------------
tests/deprecated/test_bearer_auth_provider.py | 13 ----------
5 files changed, 61 deletions(-)
delete mode 100644 docs/python-sdk/fastmcp-server-auth-providers-bearer.mdx
delete mode 100644 src/fastmcp/server/auth/providers/bearer.py
delete mode 100644 tests/deprecated/test_bearer_auth_provider.py
diff --git a/docs/docs.json b/docs/docs.json
index 0308ae2db..cac05f32e 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -355,7 +355,6 @@
"python-sdk/fastmcp-server-auth-providers-auth0",
"python-sdk/fastmcp-server-auth-providers-aws",
"python-sdk/fastmcp-server-auth-providers-azure",
- "python-sdk/fastmcp-server-auth-providers-bearer",
"python-sdk/fastmcp-server-auth-providers-descope",
"python-sdk/fastmcp-server-auth-providers-github",
"python-sdk/fastmcp-server-auth-providers-google",
diff --git a/docs/python-sdk/fastmcp-server-auth-providers-bearer.mdx b/docs/python-sdk/fastmcp-server-auth-providers-bearer.mdx
deleted file mode 100644
index 3cfbf0239..000000000
--- a/docs/python-sdk/fastmcp-server-auth-providers-bearer.mdx
+++ /dev/null
@@ -1,13 +0,0 @@
----
-title: bearer
-sidebarTitle: bearer
----
-
-# `fastmcp.server.auth.providers.bearer`
-
-
-Backwards compatibility shim for BearerAuthProvider.
-
-The BearerAuthProvider class has been moved to fastmcp.server.auth.providers.jwt.JWTVerifier
-for better organization. This module provides a backwards-compatible import.
-
diff --git a/src/fastmcp/server/auth/__init__.py b/src/fastmcp/server/auth/__init__.py
index e33ad022a..43f2f3f2e 100644
--- a/src/fastmcp/server/auth/__init__.py
+++ b/src/fastmcp/server/auth/__init__.py
@@ -23,12 +23,3 @@ __all__ = [
"StaticTokenVerifier",
"TokenVerifier",
]
-
-
-def __getattr__(name: str):
- # Defer import because it raises a deprecation warning
- if name == "BearerAuthProvider":
- from .providers.bearer import BearerAuthProvider
-
- return BearerAuthProvider
- raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
diff --git a/src/fastmcp/server/auth/providers/bearer.py b/src/fastmcp/server/auth/providers/bearer.py
deleted file mode 100644
index 1482ab0f3..000000000
--- a/src/fastmcp/server/auth/providers/bearer.py
+++ /dev/null
@@ -1,25 +0,0 @@
-"""Backwards compatibility shim for BearerAuthProvider.
-
-The BearerAuthProvider class has been moved to fastmcp.server.auth.providers.jwt.JWTVerifier
-for better organization. This module provides a backwards-compatible import.
-"""
-
-import warnings
-
-import fastmcp
-from fastmcp.server.auth.providers.jwt import JWKData, JWKSData, RSAKeyPair
-from fastmcp.server.auth.providers.jwt import JWTVerifier as BearerAuthProvider
-
-# Re-export for backwards compatibility
-__all__ = ["BearerAuthProvider", "JWKData", "JWKSData", "RSAKeyPair"]
-
-# Deprecated in 2.11
-if fastmcp.settings.deprecation_warnings:
- warnings.warn(
- "The `fastmcp.server.auth.providers.bearer` module is deprecated "
- "and will be removed in a future version. "
- "Please use `fastmcp.server.auth.providers.jwt.JWTVerifier` "
- "instead of this module's BearerAuthProvider.",
- DeprecationWarning,
- stacklevel=2,
- )
diff --git a/tests/deprecated/test_bearer_auth_provider.py b/tests/deprecated/test_bearer_auth_provider.py
deleted file mode 100644
index 5e02677d1..000000000
--- a/tests/deprecated/test_bearer_auth_provider.py
+++ /dev/null
@@ -1,13 +0,0 @@
-import pytest
-
-# reset deprecation warnings for this module
-pytestmark = pytest.mark.filterwarnings("default::DeprecationWarning")
-
-
-def test_bearer_auth_provider_deprecated():
- """Test that BearerAuthProvider import shows deprecation warning."""
- with pytest.warns(
- DeprecationWarning,
- match="The `fastmcp.server.auth.providers.bearer` module is deprecated and will be removed in a future version. Please use `fastmcp.server.auth.providers.jwt.JWTVerifier` instead of this module's BearerAuthProvider.",
- ):
- from fastmcp.server.auth import BearerAuthProvider # noqa: F401
From e257b76bc6c0d56e7ceb431ec42171da0f9b2475 Mon Sep 17 00:00:00 2001
From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Date: Sat, 1 Nov 2025 13:24:56 -0700
Subject: [PATCH 13/17] Remove resource_prefix_format="protocol" support
(deprecated 2.4.0) (#2342)
* Remove resource_prefix_format="protocol" support (fixes #2195)
Removes deprecated protocol format (prefix+resource://path) and keeps only
path format (resource://prefix/path). Since only one format remains:
- Removed resource_prefix_format from settings, FastMCP.__init__, and helpers
- Simplified add_resource_prefix, remove_resource_prefix, has_resource_prefix
- Removed MountedServer.resource_prefix_format field
- Deleted tests for protocol format
All resource prefixes now use path format exclusively.
* Clean up resource_prefix_format references
- Remove from test files
- Update documentation to remove protocol format section
- Move custom HTTP routes note to mounting section
- Remove resource_prefix_format from settings docs
* Use inline version note instead of badge for prefix format
* Remove obsolete test functions and update docs
- Delete test functions that no longer assert anything
- Remove proxy.mdx reference to deleted prefix format section
* Format error messages per ruff
---
docs/changelog.mdx | 2 +-
docs/servers/composition.mdx | 65 +------
docs/servers/proxy.mdx | 2 -
docs/servers/server.mdx | 3 -
.../component_manager/component_service.py | 24 +--
src/fastmcp/server/server.py | 175 ++++--------------
src/fastmcp/settings.py | 13 --
tests/deprecated/test_resource_prefixes.py | 105 -----------
tests/deprecated/test_settings.py | 1 -
tests/server/test_import_server.py | 19 --
tests/server/test_mount.py | 16 --
tests/server/test_resource_prefix_formats.py | 65 -------
tests/server/test_server.py | 13 +-
13 files changed, 52 insertions(+), 451 deletions(-)
delete mode 100644 tests/deprecated/test_resource_prefixes.py
delete mode 100644 tests/server/test_resource_prefix_formats.py
diff --git a/docs/changelog.mdx b/docs/changelog.mdx
index 0e84bd4da..eec90a81d 100644
--- a/docs/changelog.mdx
+++ b/docs/changelog.mdx
@@ -1234,7 +1234,7 @@ This release introduces completely new tools for generating and customizing MCP
## [v2.4.0: Config and Conquer](https://github.com/jlowin/fastmcp/releases/tag/v2.4.0)
-**Note**: this release includes a backwards-incompatible change to how resources are prefixed when mounted in composed servers. However, it is only backwards-incompatible if users were running tests or manually loading resources by prefixed key; LLMs should not have any issue discovering the new route. See [Resource Prefix Formats](https://gofastmcp.com/servers/composition#resource-prefix-formats) for more.
+**Note**: this release includes a backwards-incompatible change to how resources are prefixed when mounted in composed servers. However, it is only backwards-incompatible if users were running tests or manually loading resources by prefixed key; LLMs should not have any issue discovering the new route.
### New Features 🎉
diff --git a/docs/servers/composition.mdx b/docs/servers/composition.mdx
index d90bb47c7..6c614000c 100644
--- a/docs/servers/composition.mdx
+++ b/docs/servers/composition.mdx
@@ -41,7 +41,7 @@ FastMCP supports [MCP proxying](/servers/proxy), which allows you to mirror a lo
You can also create proxies from configuration dictionaries that follow the MCPConfig schema, which is useful for quickly connecting to one or more remote servers. See the [Proxy Servers documentation](/servers/proxy#configuration-based-proxies) for details on configuration-based proxying. Note that MCPConfig follows an emerging standard and its format may evolve over time.
-Prefixing rules for tools, prompts, resources, and templates are identical across importing, mounting, and proxies.
+Prefixing rules for tools, prompts, resources, and templates are identical across importing, mounting, and proxies. When prefixes are used, resource URIs are prefixed using path format (since 2.4.0): `resource://prefix/path/to/resource`.
## Importing (Static Composition)
@@ -207,6 +207,10 @@ The same prefixing rules apply as with `import_server` for naming tools, resourc
The `prefix` parameter is optional. If omitted, components are mounted without modification.
+
+When mounting servers, custom HTTP routes defined with `@server.custom_route()` are also forwarded to the parent server, making them accessible through the parent's HTTP application.
+
+
#### Performance Considerations
Due to the "live link", operations like `list_tools()` on the parent server will be impacted by the speed of the slowest mounted server. In particular, HTTP-based mounted servers can introduce significant latency (300-400ms vs 1-2ms for local tools), and this slowdown affects the whole server, not just interactions with the HTTP-proxied tools. If performance is important, importing tools via [`import_server()`](#importing-static-composition) may be a more appropriate solution as it copies components once at startup rather than delegating requests at runtime.
@@ -320,62 +324,3 @@ This ensures that parent server tag policies act as a global policy for everythi
This filtering applies to both **listing** (e.g., `list_tools()`) and **execution** (e.g., `call_tool()`). Filtered components are neither visible nor executable through the parent server.
-
-## Resource Prefix Formats
-
-
-
-When mounting or importing servers, resource URIs are usually prefixed to avoid naming conflicts. FastMCP supports two different formats for resource prefixes:
-
-### Path Format (Default)
-
-In path format, prefixes are added to the path component of the URI:
-
-```
-resource://prefix/path/to/resource
-```
-
-This is the default format since FastMCP 2.4. This format is recommended because it avoids issues with URI protocol restrictions (like underscores not being allowed in protocol names).
-
-### Protocol Format (Legacy)
-
-In protocol format, prefixes are added as part of the protocol:
-
-```
-prefix+resource://path/to/resource
-```
-
-This was the default format in FastMCP before 2.4. While still supported, it's not recommended for new code as it can cause problems with prefix names that aren't valid in URI protocols.
-
-### Configuring the Prefix Format
-
-You can configure the prefix format globally in code:
-
-```python
-import fastmcp
-fastmcp.settings.resource_prefix_format = "protocol"
-```
-
-Or via environment variable:
-
-```bash
-FASTMCP_RESOURCE_PREFIX_FORMAT=protocol
-```
-
-Or per-server:
-
-```python
-from fastmcp import FastMCP
-
-# Create a server that uses legacy protocol format
-server = FastMCP("LegacyServer", resource_prefix_format="protocol")
-
-# Create a server that uses new path format
-server = FastMCP("NewServer", resource_prefix_format="path")
-```
-
-When mounting or importing servers, the prefix format of the parent server is used.
-
-
-When mounting servers, custom HTTP routes defined with `@server.custom_route()` are also forwarded to the parent server, making them accessible through the parent's HTTP application.
-
diff --git a/docs/servers/proxy.mdx b/docs/servers/proxy.mdx
index 354db4883..401005dea 100644
--- a/docs/servers/proxy.mdx
+++ b/docs/servers/proxy.mdx
@@ -266,8 +266,6 @@ These rules apply uniformly whether you:
- Create a multi-server proxy from an `MCPConfig`
- Use `FastMCP.as_proxy()` directly
-For resource URI prefix formats (path vs legacy protocol style) and configuration options, see Server Composition → Resource Prefix Formats.
-
## Mirrored Components
diff --git a/docs/servers/server.mdx b/docs/servers/server.mdx
index 8c46654a0..7f013f2dc 100644
--- a/docs/servers/server.mdx
+++ b/docs/servers/server.mdx
@@ -357,7 +357,6 @@ import fastmcp
# Access global settings
print(fastmcp.settings.log_level) # Default: "INFO"
print(fastmcp.settings.mask_error_details) # Default: False
-print(fastmcp.settings.resource_prefix_format) # Default: "path"
print(fastmcp.settings.strict_input_validation) # Default: False
print(fastmcp.settings.include_fastmcp_meta) # Default: True
```
@@ -365,7 +364,6 @@ print(fastmcp.settings.include_fastmcp_meta) # Default: True
Common global settings include:
- **`log_level`**: Logging level ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"), set with `FASTMCP_LOG_LEVEL`
- **`mask_error_details`**: Whether to hide detailed error information from clients, set with `FASTMCP_MASK_ERROR_DETAILS`
-- **`resource_prefix_format`**: How to format resource prefixes ("path" or "protocol"), set with `FASTMCP_RESOURCE_PREFIX_FORMAT`
- **`strict_input_validation`**: Controls tool input validation mode (default: False for flexible coercion), set with `FASTMCP_STRICT_INPUT_VALIDATION`. See [Input Validation Modes](/servers/tools#input-validation-modes)
- **`include_fastmcp_meta`**: Whether to include FastMCP metadata in component responses (default: True), set with `FASTMCP_INCLUDE_FASTMCP_META`
- **`env_file`**: Path to the environment file to load settings from (default: ".env"), set with `FASTMCP_ENV_FILE`. Useful when your project uses a `.env` file with syntax incompatible with python-dotenv
@@ -399,7 +397,6 @@ Global FastMCP settings can be configured via environment variables (prefixed wi
# Configure global FastMCP behavior
export FASTMCP_LOG_LEVEL=DEBUG
export FASTMCP_MASK_ERROR_DETAILS=True
-export FASTMCP_RESOURCE_PREFIX_FORMAT=protocol
export FASTMCP_STRICT_INPUT_VALIDATION=False
export FASTMCP_INCLUDE_FASTMCP_META=False
```
diff --git a/src/fastmcp/contrib/component_manager/component_service.py b/src/fastmcp/contrib/component_manager/component_service.py
index b23f96420..9fb2e4fba 100644
--- a/src/fastmcp/contrib/component_manager/component_service.py
+++ b/src/fastmcp/contrib/component_manager/component_service.py
@@ -105,16 +105,8 @@ class ComponentService:
# 2. Check mounted servers using the filtered protocol path.
for mounted in reversed(self._server._mounted_servers):
if mounted.prefix:
- if has_resource_prefix(
- key,
- mounted.prefix,
- mounted.resource_prefix_format,
- ):
- key = remove_resource_prefix(
- key,
- mounted.prefix,
- mounted.resource_prefix_format,
- )
+ if has_resource_prefix(key, mounted.prefix):
+ key = remove_resource_prefix(key, mounted.prefix)
mounted_service = ComponentService(mounted.server)
mounted_resource: (
Resource | ResourceTemplate
@@ -148,16 +140,8 @@ class ComponentService:
# 2. Check mounted servers using the filtered protocol path.
for mounted in reversed(self._server._mounted_servers):
if mounted.prefix:
- if has_resource_prefix(
- key,
- mounted.prefix,
- mounted.resource_prefix_format,
- ):
- key = remove_resource_prefix(
- key,
- mounted.prefix,
- mounted.resource_prefix_format,
- )
+ if has_resource_prefix(key, mounted.prefix):
+ key = remove_resource_prefix(key, mounted.prefix)
mounted_service = ComponentService(mounted.server)
mounted_resource: (
Resource | ResourceTemplate
diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py
index 17da944c9..dafe2a806 100644
--- a/src/fastmcp/server/server.py
+++ b/src/fastmcp/server/server.py
@@ -156,7 +156,6 @@ class FastMCP(Generic[LifespanResultT]):
auth: AuthProvider | NotSetT | None = NotSet,
middleware: Sequence[Middleware] | None = None,
lifespan: LifespanCallable | None = None,
- resource_prefix_format: Literal["protocol", "path"] | None = None,
mask_error_details: bool | None = None,
tools: Sequence[Tool | Callable[..., Any]] | None = None,
tool_transformations: Mapping[str, ToolTransformConfig] | None = None,
@@ -185,10 +184,6 @@ class FastMCP(Generic[LifespanResultT]):
sampling_handler: ServerSamplingHandler[LifespanResultT] | None = None,
sampling_handler_behavior: Literal["always", "fallback"] | None = None,
):
- self.resource_prefix_format: Literal["protocol", "path"] = (
- resource_prefix_format or fastmcp.settings.resource_prefix_format
- )
-
self._additional_http_routes: list[BaseRoute] = []
self._mounted_servers: list[MountedServer] = []
self._tool_manager: ToolManager = ToolManager(
@@ -495,9 +490,7 @@ class FastMCP(Generic[LifespanResultT]):
child_resources = await mounted.server.get_resources()
for key, resource in child_resources.items():
new_key = (
- add_resource_prefix(
- key, mounted.prefix, mounted.resource_prefix_format
- )
+ add_resource_prefix(key, mounted.prefix)
if mounted.prefix
else key
)
@@ -534,9 +527,7 @@ class FastMCP(Generic[LifespanResultT]):
child_templates = await mounted.server.get_resource_templates()
for key, template in child_templates.items():
new_key = (
- add_resource_prefix(
- key, mounted.prefix, mounted.resource_prefix_format
- )
+ add_resource_prefix(key, mounted.prefix)
if mounted.prefix
else key
)
@@ -814,11 +805,7 @@ class FastMCP(Generic[LifespanResultT]):
key = resource.key
if mounted.prefix:
- key = add_resource_prefix(
- resource.key,
- mounted.prefix,
- mounted.resource_prefix_format,
- )
+ key = add_resource_prefix(resource.key, mounted.prefix)
resource = resource.model_copy(
key=key,
update={"name": f"{mounted.prefix}_{resource.name}"},
@@ -910,11 +897,7 @@ class FastMCP(Generic[LifespanResultT]):
key = template.key
if mounted.prefix:
- key = add_resource_prefix(
- template.key,
- mounted.prefix,
- mounted.resource_prefix_format,
- )
+ key = add_resource_prefix(template.key, mounted.prefix)
template = template.model_copy(
key=key,
update={"name": f"{mounted.prefix}_{template.name}"},
@@ -1170,13 +1153,9 @@ class FastMCP(Generic[LifespanResultT]):
for mounted in reversed(self._mounted_servers):
key = uri_str
if mounted.prefix:
- if not has_resource_prefix(
- key, mounted.prefix, mounted.resource_prefix_format
- ):
+ if not has_resource_prefix(key, mounted.prefix):
continue
- key = remove_resource_prefix(
- key, mounted.prefix, mounted.resource_prefix_format
- )
+ key = remove_resource_prefix(key, mounted.prefix)
try:
# First, get the resource to check if parent's filter allows it
@@ -2197,7 +2176,6 @@ class FastMCP(Generic[LifespanResultT]):
mounted_server = MountedServer(
prefix=prefix,
server=server,
- resource_prefix_format=self.resource_prefix_format,
)
self._mounted_servers.append(mounted_server)
@@ -2298,9 +2276,7 @@ class FastMCP(Generic[LifespanResultT]):
# Import resources and templates from the server
for key, resource in (await server.get_resources()).items():
if prefix:
- resource_key = add_resource_prefix(
- key, prefix, self.resource_prefix_format
- )
+ resource_key = add_resource_prefix(key, prefix)
resource = resource.model_copy(
update={"name": f"{prefix}_{resource.name}"}, key=resource_key
)
@@ -2308,9 +2284,7 @@ class FastMCP(Generic[LifespanResultT]):
for key, template in (await server.get_resource_templates()).items():
if prefix:
- template_key = add_resource_prefix(
- key, prefix, self.resource_prefix_format
- )
+ template_key = add_resource_prefix(key, prefix)
template = template.model_copy(
update={"name": f"{prefix}_{template.name}"}, key=template_key
)
@@ -2572,13 +2546,10 @@ class FastMCP(Generic[LifespanResultT]):
class MountedServer:
prefix: str | None
server: FastMCP[Any]
- resource_prefix_format: Literal["protocol", "path"] | None = None
-def add_resource_prefix(
- uri: str, prefix: str, prefix_format: Literal["protocol", "path"] | None = None
-) -> str:
- """Add a prefix to a resource URI.
+def add_resource_prefix(uri: str, prefix: str) -> str:
+ """Add a prefix to a resource URI using path formatting (resource://prefix/path).
Args:
uri: The original resource URI
@@ -2588,16 +2559,10 @@ def add_resource_prefix(
The resource URI with the prefix added
Examples:
- With new style:
```python
add_resource_prefix("resource://path/to/resource", "prefix")
"resource://prefix/path/to/resource"
```
- With legacy style:
- ```python
- add_resource_prefix("resource://path/to/resource", "prefix")
- "prefix+resource://path/to/resource"
- ```
With absolute path:
```python
add_resource_prefix("resource:///absolute/path", "prefix")
@@ -2610,54 +2575,32 @@ def add_resource_prefix(
if not prefix:
return uri
- # Get the server settings to check for legacy format preference
+ # Split the URI into protocol and path
+ match = URI_PATTERN.match(uri)
+ if not match:
+ raise ValueError(f"Invalid URI format: {uri}. Expected protocol://path format.")
- if prefix_format is None:
- prefix_format = fastmcp.settings.resource_prefix_format
+ protocol, path = match.groups()
- if prefix_format == "protocol":
- # Legacy style: prefix+protocol://path
- return f"{prefix}+{uri}"
- elif prefix_format == "path":
- # New style: protocol://prefix/path
- # Split the URI into protocol and path
- match = URI_PATTERN.match(uri)
- if not match:
- raise ValueError(
- f"Invalid URI format: {uri}. Expected protocol://path format."
- )
-
- protocol, path = match.groups()
-
- # Add the prefix to the path
- return f"{protocol}{prefix}/{path}"
- else:
- raise ValueError(f"Invalid prefix format: {prefix_format}")
+ # Add the prefix to the path
+ return f"{protocol}{prefix}/{path}"
-def remove_resource_prefix(
- uri: str, prefix: str, prefix_format: Literal["protocol", "path"] | None = None
-) -> str:
+def remove_resource_prefix(uri: str, prefix: str) -> str:
"""Remove a prefix from a resource URI.
Args:
uri: The resource URI with a prefix
prefix: The prefix to remove
- prefix_format: The format of the prefix to remove
+
Returns:
The resource URI with the prefix removed
Examples:
- With new style:
```python
remove_resource_prefix("resource://prefix/path/to/resource", "prefix")
"resource://path/to/resource"
```
- With legacy style:
- ```python
- remove_resource_prefix("prefix+resource://path/to/resource", "prefix")
- "resource://path/to/resource"
- ```
With absolute path:
```python
remove_resource_prefix("resource://prefix//absolute/path", "prefix")
@@ -2670,41 +2613,24 @@ def remove_resource_prefix(
if not prefix:
return uri
- if prefix_format is None:
- prefix_format = fastmcp.settings.resource_prefix_format
+ # Split the URI into protocol and path
+ match = URI_PATTERN.match(uri)
+ if not match:
+ raise ValueError(f"Invalid URI format: {uri}. Expected protocol://path format.")
- if prefix_format == "protocol":
- # Legacy style: prefix+protocol://path
- legacy_prefix = f"{prefix}+"
- if uri.startswith(legacy_prefix):
- return uri[len(legacy_prefix) :]
+ protocol, path = match.groups()
+
+ # Check if the path starts with the prefix followed by a /
+ prefix_pattern = f"^{re.escape(prefix)}/(.*?)$"
+ path_match = re.match(prefix_pattern, path)
+ if not path_match:
return uri
- elif prefix_format == "path":
- # New style: protocol://prefix/path
- # Split the URI into protocol and path
- match = URI_PATTERN.match(uri)
- if not match:
- raise ValueError(
- f"Invalid URI format: {uri}. Expected protocol://path format."
- )
- protocol, path = match.groups()
-
- # Check if the path starts with the prefix followed by a /
- prefix_pattern = f"^{re.escape(prefix)}/(.*?)$"
- path_match = re.match(prefix_pattern, path)
- if not path_match:
- return uri
-
- # Return the URI without the prefix
- return f"{protocol}{path_match.group(1)}"
- else:
- raise ValueError(f"Invalid prefix format: {prefix_format}")
+ # Return the URI without the prefix
+ return f"{protocol}{path_match.group(1)}"
-def has_resource_prefix(
- uri: str, prefix: str, prefix_format: Literal["protocol", "path"] | None = None
-) -> bool:
+def has_resource_prefix(uri: str, prefix: str) -> bool:
"""Check if a resource URI has a specific prefix.
Args:
@@ -2715,16 +2641,10 @@ def has_resource_prefix(
True if the URI has the specified prefix, False otherwise
Examples:
- With new style:
```python
has_resource_prefix("resource://prefix/path/to/resource", "prefix")
True
```
- With legacy style:
- ```python
- has_resource_prefix("prefix+resource://path/to/resource", "prefix")
- True
- ```
With other path:
```python
has_resource_prefix("resource://other/path/to/resource", "prefix")
@@ -2737,28 +2657,13 @@ def has_resource_prefix(
if not prefix:
return False
- # Get the server settings to check for legacy format preference
+ # Split the URI into protocol and path
+ match = URI_PATTERN.match(uri)
+ if not match:
+ raise ValueError(f"Invalid URI format: {uri}. Expected protocol://path format.")
- if prefix_format is None:
- prefix_format = fastmcp.settings.resource_prefix_format
+ _, path = match.groups()
- if prefix_format == "protocol":
- # Legacy style: prefix+protocol://path
- legacy_prefix = f"{prefix}+"
- return uri.startswith(legacy_prefix)
- elif prefix_format == "path":
- # New style: protocol://prefix/path
- # Split the URI into protocol and path
- match = URI_PATTERN.match(uri)
- if not match:
- raise ValueError(
- f"Invalid URI format: {uri}. Expected protocol://path format."
- )
-
- _, path = match.groups()
-
- # Check if the path starts with the prefix followed by a /
- prefix_pattern = f"^{re.escape(prefix)}/"
- return bool(re.match(prefix_pattern, path))
- else:
- raise ValueError(f"Invalid prefix format: {prefix_format}")
+ # Check if the path starts with the prefix followed by a /
+ prefix_pattern = f"^{re.escape(prefix)}/"
+ return bool(re.match(prefix_pattern, path))
diff --git a/src/fastmcp/settings.py b/src/fastmcp/settings.py
index b8b579ab1..3babc4044 100644
--- a/src/fastmcp/settings.py
+++ b/src/fastmcp/settings.py
@@ -155,19 +155,6 @@ class Settings(BaseSettings):
),
] = True
- resource_prefix_format: Annotated[
- Literal["protocol", "path"],
- Field(
- description=inspect.cleandoc(
- """
- When perfixing a resource URI, either use path formatting (resource://prefix/path)
- or protocol formatting (prefix+resource://path). Protocol formatting was the default in FastMCP < 2.4;
- path formatting is current default.
- """
- ),
- ),
- ] = "path"
-
client_init_timeout: Annotated[
float | None,
Field(
diff --git a/tests/deprecated/test_resource_prefixes.py b/tests/deprecated/test_resource_prefixes.py
deleted file mode 100644
index 44390f80f..000000000
--- a/tests/deprecated/test_resource_prefixes.py
+++ /dev/null
@@ -1,105 +0,0 @@
-"""Tests for legacy resource prefix behavior."""
-
-import pytest
-
-from fastmcp import Client, FastMCP
-from fastmcp.server.server import (
- add_resource_prefix,
- has_resource_prefix,
- remove_resource_prefix,
-)
-from fastmcp.utilities.tests import temporary_settings
-
-# reset deprecation warnings for this module
-pytestmark = pytest.mark.filterwarnings("default::DeprecationWarning")
-
-
-class TestLegacyResourcePrefixes:
- """Test the legacy resource prefix behavior."""
-
- def test_add_resource_prefix_legacy(self):
- """Test that add_resource_prefix uses the legacy format when resource_prefix_format is 'protocol'."""
- with temporary_settings(resource_prefix_format="protocol"):
- result = add_resource_prefix("resource://path/to/resource", "prefix")
- assert result == "prefix+resource://path/to/resource"
-
- # Empty prefix should return the original URI
- result = add_resource_prefix("resource://path/to/resource", "")
- assert result == "resource://path/to/resource"
-
- def test_remove_resource_prefix_legacy(self):
- """Test that remove_resource_prefix uses the legacy format when resource_prefix_format is 'protocol'."""
- with temporary_settings(resource_prefix_format="protocol"):
- result = remove_resource_prefix(
- "prefix+resource://path/to/resource", "prefix"
- )
- assert result == "resource://path/to/resource"
-
- # URI without the prefix should be returned as is
- result = remove_resource_prefix("resource://path/to/resource", "prefix")
- assert result == "resource://path/to/resource"
-
- # Empty prefix should return the original URI
- result = remove_resource_prefix("resource://path/to/resource", "")
- assert result == "resource://path/to/resource"
-
- def test_has_resource_prefix_legacy(self):
- """Test that has_resource_prefix uses the legacy format when resource_prefix_format is 'protocol'."""
- with temporary_settings(resource_prefix_format="protocol"):
- result = has_resource_prefix("prefix+resource://path/to/resource", "prefix")
- assert result is True
-
- result = has_resource_prefix("resource://path/to/resource", "prefix")
- assert result is False
-
- # Empty prefix should always return False
- result = has_resource_prefix("resource://path/to/resource", "")
- assert result is False
-
-
-async def test_mount_with_legacy_prefixes():
- """Test mounting a server with legacy resource prefixes."""
- with temporary_settings(resource_prefix_format="protocol"):
- main_server = FastMCP("MainServer")
- sub_server = FastMCP("SubServer")
-
- @sub_server.resource("resource://test")
- def get_test():
- return "test content"
-
- # Mount the server with a prefix (using old argument order for this legacy test)
- with pytest.warns(DeprecationWarning, match="Mount prefixes are now optional"):
- main_server.mount("sub", sub_server) # type: ignore[arg-type]
-
- # Check that the resource is prefixed using the legacy format
- resources = await main_server.get_resources()
-
- # In legacy format, the key would be "sub+resource://test"
- assert "sub+resource://test" in resources
-
- # Test accessing the resource through client
- async with Client(main_server) as client:
- result = await client.read_resource("sub+resource://test")
- # Different content types might be returned, but we just want to verify we got something
- assert len(result) > 0
-
-
-async def test_import_server_with_legacy_prefixes():
- """Test importing a server with legacy resource prefixes."""
- with temporary_settings(resource_prefix_format="protocol"):
- main_server = FastMCP("MainServer")
- sub_server = FastMCP("SubServer")
-
- @sub_server.resource("resource://test")
- def get_test():
- return "test content"
-
- # Import the server with a prefix (using old argument order for this legacy test)
- with pytest.warns(DeprecationWarning, match="Import prefixes are now optional"):
- await main_server.import_server("sub", sub_server) # type: ignore[arg-type]
-
- # Check that the resource is prefixed using the legacy format
- resources = await main_server.get_resources()
-
- # In legacy format, the key would be "sub+resource://test"
- assert "sub+resource://test" in resources
diff --git a/tests/deprecated/test_settings.py b/tests/deprecated/test_settings.py
index 84e2c250e..c0692a14e 100644
--- a/tests/deprecated/test_settings.py
+++ b/tests/deprecated/test_settings.py
@@ -178,7 +178,6 @@ class TestDeprecatedServerInitKwargs:
on_duplicate_tools="warn",
on_duplicate_resources="error",
on_duplicate_prompts="replace",
- resource_prefix_format="path",
mask_error_details=True,
)
diff --git a/tests/server/test_import_server.py b/tests/server/test_import_server.py
index 0d5661e17..446dbfcc9 100644
--- a/tests/server/test_import_server.py
+++ b/tests/server/test_import_server.py
@@ -391,25 +391,6 @@ async def test_import_with_proxy_resource_templates():
assert content["email"] == "john@example.com"
-async def test_import_invalid_resource_prefix():
- main_app = FastMCP("MainApp")
- api_app = FastMCP("APIApp")
-
- # This test doesn't apply anymore with the new prefix format since we're not validating
- # the protocol://prefix/path format
- # Just import the server to maintain test coverage without deprecated parameters
- await main_app.import_server(api_app, "api")
-
-
-async def test_import_invalid_resource_separator():
- main_app = FastMCP("MainApp")
- api_app = FastMCP("APIApp")
-
- # This test is for maintaining coverage for importing with prefixes
- # We no longer pass the deprecated resource_separator parameter
- await main_app.import_server(api_app, "api")
-
-
async def test_import_with_no_prefix():
"""Test importing a server without providing a prefix."""
main_app = FastMCP("MainApp")
diff --git a/tests/server/test_mount.py b/tests/server/test_mount.py
index d02e86b6c..844fd2464 100644
--- a/tests/server/test_mount.py
+++ b/tests/server/test_mount.py
@@ -66,22 +66,6 @@ class TestBasicMount:
result = await client.call_tool("sub_greet", {"name": "World"})
assert result.data == "Hello, World!"
- async def test_mount_invalid_resource_prefix(self):
- main_app = FastMCP("MainApp")
- api_app = FastMCP("APIApp")
-
- # This test doesn't apply anymore with the new prefix format
- # just mount the server to maintain test coverage
- main_app.mount(api_app, "api:sub")
-
- async def test_mount_invalid_resource_separator(self):
- main_app = FastMCP("MainApp")
- api_app = FastMCP("APIApp")
-
- # This test doesn't apply anymore with the new prefix format
- # Mount without deprecated parameters
- main_app.mount(api_app, "api")
-
@pytest.mark.parametrize("prefix", ["", None])
async def test_mount_with_no_prefix(self, prefix):
main_app = FastMCP("MainApp")
diff --git a/tests/server/test_resource_prefix_formats.py b/tests/server/test_resource_prefix_formats.py
deleted file mode 100644
index c45731ccd..000000000
--- a/tests/server/test_resource_prefix_formats.py
+++ /dev/null
@@ -1,65 +0,0 @@
-"""Tests for different resource prefix formats in server mounting and importing."""
-
-from fastmcp import FastMCP
-
-
-async def test_resource_prefix_format_in_constructor():
- """Test that the resource_prefix_format parameter is respected in the constructor."""
- server_path = FastMCP("PathFormat", resource_prefix_format="path")
- server_protocol = FastMCP("ProtocolFormat", resource_prefix_format="protocol")
-
- # Check that the format is stored correctly
- assert server_path.resource_prefix_format == "path"
- assert server_protocol.resource_prefix_format == "protocol"
-
- # Register resources
- @server_path.resource("resource://test")
- def get_test_path():
- return "test content"
-
- @server_protocol.resource("resource://test")
- def get_test_protocol():
- return "test content"
-
- # Create mount servers
- main_server_path = FastMCP("MainPath", resource_prefix_format="path")
- main_server_protocol = FastMCP("MainProtocol", resource_prefix_format="protocol")
-
- # Mount the servers
- main_server_path.mount(server_path, "sub")
- main_server_protocol.mount(server_protocol, "sub")
-
- # Check that the resources are prefixed correctly
- path_resources = await main_server_path.get_resources()
- protocol_resources = await main_server_protocol.get_resources()
-
- # Path format should be resource://sub/test
- assert "resource://sub/test" in path_resources
- # Protocol format should be sub+resource://test
- assert "sub+resource://test" in protocol_resources
-
-
-async def test_resource_prefix_format_in_import_server():
- """Test that the resource_prefix_format parameter is respected in import_server."""
- server = FastMCP("TestServer")
-
- @server.resource("resource://test")
- def get_test():
- return "test content"
-
- # Import with path format
- main_server_path = FastMCP("MainPath", resource_prefix_format="path")
- await main_server_path.import_server(server, "sub")
-
- # Import with protocol format
- main_server_protocol = FastMCP("MainProtocol", resource_prefix_format="protocol")
- await main_server_protocol.import_server(server, "sub")
-
- # Check that the resources are prefixed correctly
- path_resources = await main_server_path._resource_manager.get_resources()
- protocol_resources = await main_server_protocol._resource_manager.get_resources()
-
- # Path format should be resource://sub/test
- assert "resource://sub/test" in path_resources
- # Protocol format should be sub+resource://test
- assert "sub+resource://test" in protocol_resources
diff --git a/tests/server/test_server.py b/tests/server/test_server.py
index f319ad438..7a21d78b2 100644
--- a/tests/server/test_server.py
+++ b/tests/server/test_server.py
@@ -1287,20 +1287,11 @@ class TestResourcePrefixMounting:
"""Test that resource prefix utility functions correctly match and strip resource prefixes."""
from fastmcp.server.server import has_resource_prefix, remove_resource_prefix
- # Create a basic server to get the default resource prefix format
- server = FastMCP()
-
# Test matching
- assert (
- has_resource_prefix(uri, prefix, server.resource_prefix_format)
- == expected_match
- )
+ assert has_resource_prefix(uri, prefix) == expected_match
# Test stripping
- assert (
- remove_resource_prefix(uri, prefix, server.resource_prefix_format)
- == expected_strip
- )
+ assert remove_resource_prefix(uri, prefix) == expected_strip
async def test_import_server_with_new_prefix_format(self):
"""Test that import_server correctly uses the new prefix format."""
From 1c27eeac5936c0301dd6a69c2b36130716fff80f Mon Sep 17 00:00:00 2001
From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Date: Sat, 1 Nov 2025 13:40:36 -0700
Subject: [PATCH 14/17] Remove from_client classmethod (deprecated 2.8.0)
(#2343)
* Remove deprecated from_client classmethod (fixes #2192)
* Remove unused Client import
---
src/fastmcp/server/server.py | 17 -----------------
tests/deprecated/test_deprecated.py | 9 +--------
2 files changed, 1 insertion(+), 25 deletions(-)
diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py
index dafe2a806..5817487c3 100644
--- a/src/fastmcp/server/server.py
+++ b/src/fastmcp/server/server.py
@@ -2483,23 +2483,6 @@ class FastMCP(Generic[LifespanResultT]):
return FastMCPProxy(client_factory=client_factory, **settings)
- @classmethod
- def from_client(
- cls, client: Client[ClientTransportT], **settings: Any
- ) -> FastMCPProxy:
- """
- Create a FastMCP proxy server from a FastMCP client.
- """
- # Deprecated since 2.3.5
- if fastmcp.settings.deprecation_warnings:
- warnings.warn(
- "FastMCP.from_client() is deprecated; use FastMCP.as_proxy() instead.",
- DeprecationWarning,
- stacklevel=2,
- )
-
- return cls.as_proxy(client, **settings)
-
def _should_enable_component(
self,
component: FastMCPComponent,
diff --git a/tests/deprecated/test_deprecated.py b/tests/deprecated/test_deprecated.py
index 78ea5a957..25a32aff6 100644
--- a/tests/deprecated/test_deprecated.py
+++ b/tests/deprecated/test_deprecated.py
@@ -3,7 +3,7 @@ import warnings
import pytest
from starlette.applications import Starlette
-from fastmcp import Client, FastMCP
+from fastmcp import FastMCP
from fastmcp.utilities.tests import temporary_settings
# reset deprecation warnings for this module
@@ -46,10 +46,3 @@ def test_http_app_with_sse_transport():
w for w in recorded_warnings if issubclass(w.category, DeprecationWarning)
]
assert len(deprecation_warnings) == 0
-
-
-def test_from_client_deprecation_warning():
- """Test that FastMCP.from_client raises a deprecation warning."""
- server = FastMCP("TestServer")
- with pytest.warns(DeprecationWarning, match="from_client"):
- FastMCP.from_client(Client(server))
From c7cb80a16088860e7b06ce322a1f35a8ac9754b5 Mon Sep 17 00:00:00 2001
From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Date: Sat, 1 Nov 2025 13:51:07 -0700
Subject: [PATCH 15/17] Remove add_resource_fn method (deprecated 2.7.0)
(#2345)
---
src/fastmcp/server/server.py | 38 ------------------------------------
1 file changed, 38 deletions(-)
diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py
index 5817487c3..88e0049a0 100644
--- a/src/fastmcp/server/server.py
+++ b/src/fastmcp/server/server.py
@@ -1531,44 +1531,6 @@ class FastMCP(Generic[LifespanResultT]):
return template
- def add_resource_fn(
- self,
- fn: AnyFunction,
- uri: str,
- name: str | None = None,
- description: str | None = None,
- mime_type: str | None = None,
- tags: set[str] | None = None,
- ) -> None:
- """Add a resource or template to the server from a function.
-
- If the URI contains parameters (e.g. "resource://{param}") or the function
- has parameters, it will be registered as a template resource.
-
- Args:
- fn: The function to register as a resource
- uri: The URI for the resource
- name: Optional name for the resource
- description: Optional description of the resource
- mime_type: Optional MIME type for the resource
- tags: Optional set of tags for categorizing the resource
- """
- # deprecated since 2.7.0
- if fastmcp.settings.deprecation_warnings:
- warnings.warn(
- "The add_resource_fn method is deprecated. Use the resource decorator instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- self._resource_manager.add_resource_or_template_from_fn(
- fn=fn,
- uri=uri,
- name=name,
- description=description,
- mime_type=mime_type,
- tags=tags,
- )
-
def resource(
self,
uri: str,
From 45479a73a9ad7c92d6824ebd6c96dc62d5d9addd Mon Sep 17 00:00:00 2001
From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Date: Mon, 1 Dec 2025 13:53:59 -0500
Subject: [PATCH 16/17] Update SDK
---
docs/python-sdk/fastmcp-server-proxy.mdx | 74 +++++++++----------
.../fastmcp-tools-tool_transform.mdx | 24 +++---
2 files changed, 49 insertions(+), 49 deletions(-)
diff --git a/docs/python-sdk/fastmcp-server-proxy.mdx b/docs/python-sdk/fastmcp-server-proxy.mdx
index 445fc1a68..da1447345 100644
--- a/docs/python-sdk/fastmcp-server-proxy.mdx
+++ b/docs/python-sdk/fastmcp-server-proxy.mdx
@@ -7,7 +7,7 @@ sidebarTitle: proxy
## Functions
-### `default_proxy_roots_handler`
+### `default_proxy_roots_handler`
```python
default_proxy_roots_handler(context: RequestContext[ClientSession, LifespanContextT]) -> RootsList
@@ -19,13 +19,13 @@ A handler that forwards the list roots request from the remote server to the pro
## Classes
-### `ProxyManagerMixin`
+### `ProxyManagerMixin`
A mixin for proxy managers to provide a unified client retrieval method.
-### `ProxyToolManager`
+### `ProxyToolManager`
A ToolManager that sources its tools from a remote client in addition to local and mounted tools.
@@ -33,7 +33,7 @@ A ToolManager that sources its tools from a remote client in addition to local a
**Methods:**
-#### `get_tools`
+#### `get_tools`
```python
get_tools(self) -> dict[str, Tool]
@@ -42,7 +42,7 @@ get_tools(self) -> dict[str, Tool]
Gets the unfiltered tool inventory including local, mounted, and proxy tools.
-#### `list_tools`
+#### `list_tools`
```python
list_tools(self) -> list[Tool]
@@ -51,7 +51,7 @@ list_tools(self) -> list[Tool]
Gets the filtered list of tools including local, mounted, and proxy tools.
-#### `call_tool`
+#### `call_tool`
```python
call_tool(self, key: str, arguments: dict[str, Any]) -> ToolResult
@@ -60,7 +60,7 @@ call_tool(self, key: str, arguments: dict[str, Any]) -> ToolResult
Calls a tool, trying local/mounted first, then proxy if not found.
-### `ProxyResourceManager`
+### `ProxyResourceManager`
A ResourceManager that sources its resources from a remote client in addition to local and mounted resources.
@@ -68,7 +68,7 @@ A ResourceManager that sources its resources from a remote client in addition to
**Methods:**
-#### `get_resources`
+#### `get_resources`
```python
get_resources(self) -> dict[str, Resource]
@@ -77,7 +77,7 @@ get_resources(self) -> dict[str, Resource]
Gets the unfiltered resource inventory including local, mounted, and proxy resources.
-#### `get_resource_templates`
+#### `get_resource_templates`
```python
get_resource_templates(self) -> dict[str, ResourceTemplate]
@@ -86,7 +86,7 @@ get_resource_templates(self) -> dict[str, ResourceTemplate]
Gets the unfiltered template inventory including local, mounted, and proxy templates.
-#### `list_resources`
+#### `list_resources`
```python
list_resources(self) -> list[Resource]
@@ -95,7 +95,7 @@ list_resources(self) -> list[Resource]
Gets the filtered list of resources including local, mounted, and proxy resources.
-#### `list_resource_templates`
+#### `list_resource_templates`
```python
list_resource_templates(self) -> list[ResourceTemplate]
@@ -104,7 +104,7 @@ list_resource_templates(self) -> list[ResourceTemplate]
Gets the filtered list of templates including local, mounted, and proxy templates.
-#### `read_resource`
+#### `read_resource`
```python
read_resource(self, uri: AnyUrl | str) -> str | bytes
@@ -113,7 +113,7 @@ read_resource(self, uri: AnyUrl | str) -> str | bytes
Reads a resource, trying local/mounted first, then proxy if not found.
-### `ProxyPromptManager`
+### `ProxyPromptManager`
A PromptManager that sources its prompts from a remote client in addition to local and mounted prompts.
@@ -121,7 +121,7 @@ A PromptManager that sources its prompts from a remote client in addition to loc
**Methods:**
-#### `get_prompts`
+#### `get_prompts`
```python
get_prompts(self) -> dict[str, Prompt]
@@ -130,7 +130,7 @@ get_prompts(self) -> dict[str, Prompt]
Gets the unfiltered prompt inventory including local, mounted, and proxy prompts.
-#### `list_prompts`
+#### `list_prompts`
```python
list_prompts(self) -> list[Prompt]
@@ -139,7 +139,7 @@ list_prompts(self) -> list[Prompt]
Gets the filtered list of prompts including local, mounted, and proxy prompts.
-#### `render_prompt`
+#### `render_prompt`
```python
render_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> GetPromptResult
@@ -148,7 +148,7 @@ render_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> GetPr
Renders a prompt, trying local/mounted first, then proxy if not found.
-### `ProxyTool`
+### `ProxyTool`
A Tool that represents and executes a tool on a remote server.
@@ -156,7 +156,7 @@ A Tool that represents and executes a tool on a remote server.
**Methods:**
-#### `from_mcp_tool`
+#### `from_mcp_tool`
```python
from_mcp_tool(cls, client: Client, mcp_tool: mcp.types.Tool) -> ProxyTool
@@ -165,7 +165,7 @@ from_mcp_tool(cls, client: Client, mcp_tool: mcp.types.Tool) -> ProxyTool
Factory method to create a ProxyTool from a raw MCP tool schema.
-#### `run`
+#### `run`
```python
run(self, arguments: dict[str, Any], context: Context | None = None) -> ToolResult
@@ -174,7 +174,7 @@ run(self, arguments: dict[str, Any], context: Context | None = None) -> ToolResu
Executes the tool by making a call through the client.
-### `ProxyResource`
+### `ProxyResource`
A Resource that represents and reads a resource from a remote server.
@@ -182,7 +182,7 @@ A Resource that represents and reads a resource from a remote server.
**Methods:**
-#### `from_mcp_resource`
+#### `from_mcp_resource`
```python
from_mcp_resource(cls, client: Client, mcp_resource: mcp.types.Resource) -> ProxyResource
@@ -191,7 +191,7 @@ from_mcp_resource(cls, client: Client, mcp_resource: mcp.types.Resource) -> Prox
Factory method to create a ProxyResource from a raw MCP resource schema.
-#### `read`
+#### `read`
```python
read(self) -> str | bytes
@@ -200,7 +200,7 @@ read(self) -> str | bytes
Read the resource content from the remote server.
-### `ProxyTemplate`
+### `ProxyTemplate`
A ResourceTemplate that represents and creates resources from a remote server template.
@@ -208,7 +208,7 @@ A ResourceTemplate that represents and creates resources from a remote server te
**Methods:**
-#### `from_mcp_template`
+#### `from_mcp_template`
```python
from_mcp_template(cls, client: Client, mcp_template: mcp.types.ResourceTemplate) -> ProxyTemplate
@@ -217,7 +217,7 @@ from_mcp_template(cls, client: Client, mcp_template: mcp.types.ResourceTemplate)
Factory method to create a ProxyTemplate from a raw MCP template schema.
-#### `create_resource`
+#### `create_resource`
```python
create_resource(self, uri: str, params: dict[str, Any], context: Context | None = None) -> ProxyResource
@@ -226,7 +226,7 @@ create_resource(self, uri: str, params: dict[str, Any], context: Context | None
Create a resource from the template by calling the remote server.
-### `ProxyPrompt`
+### `ProxyPrompt`
A Prompt that represents and renders a prompt from a remote server.
@@ -234,7 +234,7 @@ A Prompt that represents and renders a prompt from a remote server.
**Methods:**
-#### `from_mcp_prompt`
+#### `from_mcp_prompt`
```python
from_mcp_prompt(cls, client: Client, mcp_prompt: mcp.types.Prompt) -> ProxyPrompt
@@ -243,7 +243,7 @@ from_mcp_prompt(cls, client: Client, mcp_prompt: mcp.types.Prompt) -> ProxyPromp
Factory method to create a ProxyPrompt from a raw MCP prompt schema.
-#### `render`
+#### `render`
```python
render(self, arguments: dict[str, Any]) -> list[PromptMessage]
@@ -252,14 +252,14 @@ render(self, arguments: dict[str, Any]) -> list[PromptMessage]
Render the prompt by making a call through the client.
-### `FastMCPProxy`
+### `FastMCPProxy`
A FastMCP server that acts as a proxy to a remote MCP-compliant server.
It uses specialized managers that fulfill requests via a client factory.
-### `ProxyClient`
+### `ProxyClient`
A proxy client that forwards advanced interactions between a remote MCP server and the proxy's connected clients.
@@ -268,7 +268,7 @@ Supports forwarding roots, sampling, elicitation, logging, and progress.
**Methods:**
-#### `default_sampling_handler`
+#### `default_sampling_handler`
```python
default_sampling_handler(cls, messages: list[mcp.types.SamplingMessage], params: mcp.types.CreateMessageRequestParams, context: RequestContext[ClientSession, LifespanContextT]) -> mcp.types.CreateMessageResult
@@ -277,7 +277,7 @@ default_sampling_handler(cls, messages: list[mcp.types.SamplingMessage], params:
A handler that forwards the sampling request from the remote server to the proxy's connected clients and relays the response back to the remote server.
-#### `default_elicitation_handler`
+#### `default_elicitation_handler`
```python
default_elicitation_handler(cls, message: str, response_type: type, params: mcp.types.ElicitRequestParams, context: RequestContext[ClientSession, LifespanContextT]) -> ElicitResult
@@ -286,7 +286,7 @@ default_elicitation_handler(cls, message: str, response_type: type, params: mcp.
A handler that forwards the elicitation request from the remote server to the proxy's connected clients and relays the response back to the remote server.
-#### `default_log_handler`
+#### `default_log_handler`
```python
default_log_handler(cls, message: LogMessage) -> None
@@ -295,7 +295,7 @@ default_log_handler(cls, message: LogMessage) -> None
A handler that forwards the log notification from the remote server to the proxy's connected clients.
-#### `default_progress_handler`
+#### `default_progress_handler`
```python
default_progress_handler(cls, progress: float, total: float | None, message: str | None) -> None
@@ -304,7 +304,7 @@ default_progress_handler(cls, progress: float, total: float | None, message: str
A handler that forwards the progress notification from the remote server to the proxy's connected clients.
-### `StatefulProxyClient`
+### `StatefulProxyClient`
A proxy client that provides a stateful client factory for the proxy server.
@@ -318,7 +318,7 @@ Note that it is essential to ensure that the proxy server itself is also statefu
**Methods:**
-#### `clear`
+#### `clear`
```python
clear(self)
@@ -327,7 +327,7 @@ clear(self)
Clear all cached clients and force disconnect them.
-#### `new_stateful`
+#### `new_stateful`
```python
new_stateful(self) -> Client[ClientTransportT]
diff --git a/docs/python-sdk/fastmcp-tools-tool_transform.mdx b/docs/python-sdk/fastmcp-tools-tool_transform.mdx
index bccf670d4..8586fb4fc 100644
--- a/docs/python-sdk/fastmcp-tools-tool_transform.mdx
+++ b/docs/python-sdk/fastmcp-tools-tool_transform.mdx
@@ -7,7 +7,7 @@ sidebarTitle: tool_transform
## Functions
-### `forward`
+### `forward`
```python
forward(**kwargs: Any) -> ToolResult
@@ -36,7 +36,7 @@ tool has args `a` and `b`, and an `transform_args` was provided that maps `x` to
- `TypeError`: If provided arguments don't match the transformed schema.
-### `forward_raw`
+### `forward_raw`
```python
forward_raw(**kwargs: Any) -> ToolResult
@@ -62,7 +62,7 @@ y=2)` will call the parent tool with `x=1` and `y=2`.
- `RuntimeError`: If called outside a transformed tool context.
-### `apply_transformations_to_tools`
+### `apply_transformations_to_tools`
```python
apply_transformations_to_tools(tools: dict[str, Tool], transformations: dict[str, ToolTransformConfig]) -> dict[str, Tool]
@@ -75,7 +75,7 @@ are left unchanged.
## Classes
-### `ArgTransform`
+### `ArgTransform`
Configuration for transforming a parent tool's argument.
@@ -137,7 +137,7 @@ ArgTransform(name="new_name", description="New desc", default=None, type=int)
```
-### `ArgTransformConfig`
+### `ArgTransformConfig`
A model for requesting a single argument transform.
@@ -145,7 +145,7 @@ A model for requesting a single argument transform.
**Methods:**
-#### `to_arg_transform`
+#### `to_arg_transform`
```python
to_arg_transform(self) -> ArgTransform
@@ -154,7 +154,7 @@ to_arg_transform(self) -> ArgTransform
Convert the argument transform to a FastMCP argument transform.
-### `TransformedTool`
+### `TransformedTool`
A tool that is transformed from another tool.
@@ -171,7 +171,7 @@ inherited from the parent tool but can be overridden or disabled.
**Methods:**
-#### `run`
+#### `run`
```python
run(self, arguments: dict[str, Any]) -> ToolResult
@@ -190,10 +190,10 @@ functions.
- ToolResult object containing content and optional structured output.
-#### `from_tool`
+#### `from_tool`
```python
-from_tool(cls, tool: Tool, name: str | None = None, title: str | NotSetT | None = NotSet, description: str | NotSetT | None = NotSet, tags: set[str] | None = None, transform_fn: Callable[..., Any] | None = None, transform_args: dict[str, ArgTransform] | None = None, annotations: ToolAnnotations | NotSetT | None = NotSet, output_schema: dict[str, Any] | Literal[False] | NotSetT | None = NotSet, serializer: Callable[[Any], str] | NotSetT | None = NotSet, meta: dict[str, Any] | NotSetT | None = NotSet, enabled: bool | None = None) -> TransformedTool
+from_tool(cls, tool: Tool, name: str | None = None, title: str | NotSetT | None = NotSet, description: str | NotSetT | None = NotSet, tags: set[str] | None = None, transform_fn: Callable[..., Any] | None = None, transform_args: dict[str, ArgTransform] | None = None, annotations: ToolAnnotations | NotSetT | None = NotSet, output_schema: dict[str, Any] | NotSetT | None = NotSet, serializer: Callable[[Any], str] | NotSetT | None = NotSet, meta: dict[str, Any] | NotSetT | None = NotSet, enabled: bool | None = None) -> TransformedTool
```
Create a transformed tool from a parent tool.
@@ -272,7 +272,7 @@ async def custom_output(**kwargs) -> ToolResult:
```
-### `ToolTransformConfig`
+### `ToolTransformConfig`
Provides a way to transform a tool.
@@ -280,7 +280,7 @@ Provides a way to transform a tool.
**Methods:**
-#### `apply`
+#### `apply`
```python
apply(self, tool: Tool) -> TransformedTool
From 6b2a4ed396382ef7679b106ccace6b4a1017f0ae Mon Sep 17 00:00:00 2001
From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Date: Mon, 1 Dec 2025 14:07:03 -0500
Subject: [PATCH 17/17] Add missing imports for exclude_args deprecation
warning
---
src/fastmcp/tools/tool.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/fastmcp/tools/tool.py b/src/fastmcp/tools/tool.py
index b65d1d8fc..71fae733a 100644
--- a/src/fastmcp/tools/tool.py
+++ b/src/fastmcp/tools/tool.py
@@ -1,6 +1,7 @@
from __future__ import annotations
import inspect
+import warnings
from collections.abc import Callable
from dataclasses import dataclass
from typing import (
@@ -19,6 +20,7 @@ from mcp.types import Tool as MCPTool
from pydantic import Field, PydanticSchemaGenerationError
from typing_extensions import TypeVar
+import fastmcp
from fastmcp.server.dependencies import get_context
from fastmcp.utilities.components import FastMCPComponent
from fastmcp.utilities.json_schema import compress_schema