diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py index cb5446ed9..f61c5cb64 100644 --- a/src/fastmcp/server/server.py +++ b/src/fastmcp/server/server.py @@ -1370,6 +1370,7 @@ class FastMCP(Generic[LifespanResultT]): path: str | None = None, uvicorn_config: dict[str, Any] | None = None, middleware: list[ASGIMiddleware] | None = None, + stateless_http: bool | None = None, ) -> None: """Run the server using HTTP transport. @@ -1380,6 +1381,8 @@ class FastMCP(Generic[LifespanResultT]): log_level: Log level for the server (defaults to settings.log_level) path: Path for the endpoint (defaults to settings.streamable_http_path or settings.sse_path) uvicorn_config: Additional configuration for the Uvicorn server + middleware: A list of middleware to apply to the app + stateless_http: Whether to use stateless HTTP (defaults to settings.stateless_http) """ host = host or self._deprecated_settings.host @@ -1388,7 +1391,12 @@ class FastMCP(Generic[LifespanResultT]): log_level or self._deprecated_settings.log_level ).lower() - app = self.http_app(path=path, transport=transport, middleware=middleware) + app = self.http_app( + path=path, + transport=transport, + middleware=middleware, + stateless_http=stateless_http, + ) # Get the path for the server URL server_path = ( diff --git a/src/fastmcp/settings.py b/src/fastmcp/settings.py index 6219c1dc1..11b0a5f0f 100644 --- a/src/fastmcp/settings.py +++ b/src/fastmcp/settings.py @@ -1,6 +1,7 @@ from __future__ import annotations as _annotations import inspect +import warnings from pathlib import Path from typing import Annotated, Any, Literal @@ -258,4 +259,21 @@ class Settings(BaseSettings): ] = None -settings = Settings() +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 == "settings": + import fastmcp + + settings = fastmcp.settings + # Deprecated in 2.10.2 + if settings.deprecation_warnings: + warnings.warn( + "`from fastmcp.settings import settings` is deprecated. use `fasmtpc.settings` instead.", + DeprecationWarning, + stacklevel=2, + ) + return settings + + raise AttributeError(f"module '{__name__}' has no attribute '{name}'") diff --git a/tests/client/test_streamable_http.py b/tests/client/test_streamable_http.py index f27f99399..4d5eaffef 100644 --- a/tests/client/test_streamable_http.py +++ b/tests/client/test_streamable_http.py @@ -2,7 +2,7 @@ import asyncio import json import sys from collections.abc import AsyncGenerator -from unittest.mock import AsyncMock +from unittest.mock import AsyncMock, call import pytest import uvicorn @@ -53,6 +53,7 @@ def fastmcp_server(): async def greet_with_progress(name: str, ctx: Context) -> str: """Report progress for a greeting.""" await ctx.report_progress(0.5, 1.0, "Greeting in progress") + await ctx.report_progress(0.75, 1.0, "Almost there!") return f"Hello, {name}!" # Add a resource @@ -108,8 +109,9 @@ def run_nested_server(host: str, port: int) -> None: @pytest.fixture() async def streamable_http_server( - stateless_http: bool = False, + request, ) -> AsyncGenerator[str, None]: + stateless_http = getattr(request, "param", False) with run_server_in_process( run_server, stateless_http=stateless_http, transport="http" ) as url: @@ -176,16 +178,25 @@ async def test_greet_with_progress_tool(streamable_http_server: str): result = await client.call_tool("greet_with_progress", {"name": "Alice"}) assert result.data == "Hello, Alice!" - progress_handler.assert_called_once_with(0.5, 1.0, "Greeting in progress") + progress_handler.assert_has_calls( + [ + call(0.5, 1.0, "Greeting in progress"), + call(0.75, 1.0, "Almost there!"), + ] + ) @pytest.mark.parametrize("streamable_http_server", [True, False], indirect=True) -async def test_elicitation_tool(streamable_http_server: str): +async def test_elicitation_tool(streamable_http_server: str, request): """Test calling the elicitation tool in both stateless and stateful modes.""" async def elicitation_handler(message, response_type, params, ctx): return {"value": "Alice"} + stateless_http = request.node.callspec.params.get("streamable_http_server", False) + if stateless_http: + pytest.xfail("Elicitation is not supported in stateless HTTP mode") + async with Client( transport=StreamableHttpTransport(streamable_http_server), elicitation_handler=elicitation_handler,