diff --git a/README.md b/README.md index 6a44dcc3d..cf647b881 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ > [!NOTE] > #### FastMCP 2.0 & The Official MCP SDK > -> Recognize the `FastMCP` name? You might have used the version integrated into the [official MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk), which was based on **FastMCP 1.0**. +> Recognize the `FastMCP` name? You might have seen the version that was contributed to the [official MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk), which was based on **FastMCP 1.0**. > > **Welcome to FastMCP 2.0!** This is the actively developed successor, and it significantly expands on 1.0 by introducing powerful client capabilities, server proxying & composition, OpenAPI/FastAPI integration, and more advanced features. > diff --git a/docs/deployment/asgi.mdx b/docs/deployment/asgi.mdx index e3a66d5ea..f676ff68a 100644 --- a/docs/deployment/asgi.mdx +++ b/docs/deployment/asgi.mdx @@ -5,6 +5,9 @@ description: Integrate FastMCP servers into existing Starlette, FastAPI, or othe icon: plug --- +import { VersionBadge } from '/snippets/version-badge.mdx' + + While FastMCP provides standalone server capabilities, you can also integrate your FastMCP server into existing web applications. This approach is useful for: - Adding MCP functionality to an existing website or API @@ -16,10 +19,13 @@ Please note that all FastMCP servers have a `run()` method that can be used to s ## ASGI Server - FastMCP servers can be created as [Starlette](https://www.starlette.io/) ASGI apps for straightforward hosting or integration into existing applications. -The first step is to obtain a Starlette application instance from your FastMCP server using either the `streamable_http_app()` (preferred) or `sse_app()` (legacy) methods: +The first step is to obtain a Starlette application instance from your FastMCP server using the `http_app()` method: + + +The `http_app()` method is new in FastMCP 2.3.2. In older versions, use `sse_app()` for SSE transport or `streamable_http_app()` for Streamable HTTP transport. + ```python from fastmcp import FastMCP @@ -30,18 +36,23 @@ mcp = FastMCP("MyServer") def hello(name: str) -> str: return f"Hello, {name}!" -# Get a Starlette app instance for the preferred transport -http_app = mcp.streamable_http_app() # For Streamable HTTP transport -sse_app = mcp.sse_app() # For SSE transport +# Get a Starlette app instance for Streamable HTTP transport (recommended) +http_app = mcp.http_app() + +# For legacy SSE transport (deprecated) +sse_app = mcp.http_app(transport="sse") ``` -Both methods return a Starlette application that can be integrated with other ASGI-compatible web frameworks. +Both approaches return a Starlette application that can be integrated with other ASGI-compatible web frameworks. -The MCP server's endpoint is mounted at the root path `/mcp` for Streamable HTTP transport, and `/sse` for SSE transport, though you can change these paths by passing a `path` argument to the `streamable_http_app()` or `sse_app()` methods: +The MCP server's endpoint is mounted at the root path `/mcp` for Streamable HTTP transport, and `/sse` for SSE transport, though you can change these paths by passing a `path` argument to the `http_app()` method: ```python -http_app = mcp.streamable_http_app(path="/custom-mcp-path") -sse_app = mcp.sse_app(path="/custom-sse-path") +# For Streamable HTTP transport +http_app = mcp.http_app(path="/custom-mcp-path") + +# For SSE transport (deprecated) +sse_app = mcp.http_app(path="/custom-sse-path", transport="sse") ``` ### Running the Server @@ -49,9 +60,12 @@ sse_app = mcp.sse_app(path="/custom-sse-path") To run the FastMCP server, you can use the `uvicorn` ASGI server: ```python +from fastmcp import FastMCP import uvicorn -# (define the app here) +mcp = FastMCP("MyServer") + +http_app = mcp.http_app() if __name__ == "__main__": uvicorn.run(http_app, host="0.0.0.0", port=8000) @@ -83,7 +97,7 @@ custom_middleware = [ ] # Create ASGI app with custom middleware -http_app = mcp.streamable_http_app(middleware=custom_middleware) +http_app = mcp.http_app(middleware=custom_middleware) ``` @@ -91,7 +105,7 @@ http_app = mcp.streamable_http_app(middleware=custom_middleware) -You can mount your FastMCP server in another Starlette application using the `Mount` class. +You can mount your FastMCP server in another Starlette application: ```python from fastmcp import FastMCP @@ -102,7 +116,7 @@ from starlette.routing import Mount mcp = FastMCP("MyServer") # Create the ASGI app -mcp_app = mcp.streamable_http_app(path='/mcp') +mcp_app = mcp.http_app(path='/mcp') # Create a Starlette app and mount the MCP server app = Starlette( @@ -134,7 +148,7 @@ from starlette.routing import Mount mcp = FastMCP("MyServer") # Create the ASGI app -mcp_app = mcp.streamable_http_app(path='/mcp') +mcp_app = mcp.http_app(path='/mcp') # Create nested application structure inner_app = Starlette(routes=[Mount("/inner", app=mcp_app)]) @@ -164,7 +178,7 @@ from starlette.routing import Mount mcp = FastMCP("MyServer") # Create the ASGI app -mcp_app = mcp.streamable_http_app(path='/mcp') +mcp_app = mcp.http_app(path='/mcp') # Create a FastAPI app and mount the MCP server app = FastAPI(lifespan=mcp_app.router.lifespan_context) diff --git a/docs/deployment/running-server.mdx b/docs/deployment/running-server.mdx index 9b67fea44..516846ae4 100644 --- a/docs/deployment/running-server.mdx +++ b/docs/deployment/running-server.mdx @@ -68,8 +68,8 @@ Below is a comparison of available transport options to help you choose the righ | Transport | Use Cases | Recommendation | | --------- | --------- | -------------- | | **STDIO** | Local tools, command-line scripts, and integrations with clients like Claude Desktop | Best for local tools and when clients manage server processes | -| **Streamable HTTP** | Web-based deployments, microservices, exposing MCP over a network | Recommended choice for new web-based deployments | -| **SSE** | Existing web-based deployments that rely on SSE | Suitable for compatibility with SSE clients; prefer Streamable HTTP for new projects | +| **Streamable HTTP** | Web-based deployments, microservices, exposing MCP over a network | Recommended choice for web-based deployments | +| **SSE** | Existing web-based deployments that rely on SSE | Deprecated - prefer Streamable HTTP for new projects | ### STDIO @@ -92,7 +92,7 @@ When using Stdio transport, you will typically *not* run the server yourself as -Streamable HTTP is a modern, efficient transport for exposing your MCP server via HTTP. It is generally recommended over SSE for new web-based deployments. +Streamable HTTP is a modern, efficient transport for exposing your MCP server via HTTP. It is the recommended transport for web-based deployments. To run a server using Streamable HTTP, you can use the `run()` method with the `transport` argument set to `"streamable-http"`. This will start a Uvicorn server on the default host (`127.0.0.1`), port (`8000`), and path (`/mcp`). @@ -150,7 +150,12 @@ if __name__ == "__main__": ### SSE -Server-Sent Events (SSE) is an HTTP-based protocol for server-to-client streaming. While FastMCP supports SSE, Streamable HTTP is preferred for new projects. + +The SSE transport is deprecated and may be removed in a future version. +New applications should use Streamable HTTP transport instead. + + +Server-Sent Events (SSE) is an HTTP-based protocol for server-to-client streaming. While FastMCP still supports SSE, it is deprecated and Streamable HTTP is preferred for new projects. To run a server using SSE, you can use the `run()` method with the `transport` argument set to `"sse"`. This will start a Uvicorn server on the default host (`127.0.0.1`), port (`8000`), and with default SSE path (`/sse`) and message path (`/messages/`). @@ -198,7 +203,6 @@ if __name__ == "__main__": port=4200, log_level="debug", path="/my-custom-sse-path", - message_path="/my-custom-message-path/", ) ``` ```python {7} client.py @@ -217,9 +221,37 @@ if __name__ == "__main__": ``` -Your client only needs to know the host, port, and "main" path; the message path will be transmitted to it as part of the connection handshake. +## Async Usage + +FastMCP provides both synchronous and asynchronous APIs for running your server. The `run()` method seen in previous examples is a synchronous method that internally uses `anyio.run()` to run the asynchronous server. For applications that are already running in an async context, FastMCP provides the `run_async()` method. + +```python {10-12} +from fastmcp import FastMCP +import asyncio + +mcp = FastMCP(name="MyServer") + +@mcp.tool() +def hello(name: str) -> str: + return f"Hello, {name}!" + +async def main(): + # Use run_async() in async contexts + await mcp.run_async(transport="streamable-http") + +if __name__ == "__main__": + asyncio.run(main()) +``` + + +The `run()` method cannot be called from inside an async function because it already creates its own async event loop internally. If you attempt to call `run()` from inside an async function, you'll get an error about the event loop already running. + +Always use `run_async()` inside async functions and `run()` in synchronous contexts. + + +Both `run()` and `run_async()` accept the same transport arguments, so all the examples above apply to both methods. ## Custom Routes diff --git a/docs/getting-started/installation.mdx b/docs/getting-started/installation.mdx index 556631cd0..46552e5c4 100644 --- a/docs/getting-started/installation.mdx +++ b/docs/getting-started/installation.mdx @@ -44,7 +44,7 @@ FastMCP root path: ~/Developer/fastmcp ``` ## Upgrading from the Official MCP SDK -Upgrading from the official MCP SDK's FastMCP 1.0 to FastMCP 2.0 is easy! The core server API is highly compatible, so after you install the `fastmcp` package, just change your import statement from `from mcp.server.fastmcp import FastMCP` to `from fastmcp import FastMCP`. +Upgrading from the official MCP SDK's FastMCP 1.0 to FastMCP 2.0 is generally straightforward. The core server API is highly compatible, and in many cases, changing your import statement from `from mcp.server.fastmcp import FastMCP` to `from fastmcp import FastMCP` will be sufficient. ```python {1-5} @@ -56,8 +56,9 @@ from fastmcp import FastMCP mcp = FastMCP("My MCP Server") ``` - -While the 1.0 server API is very stable for common use cases, FastMCP 2.0 introduces many new features (like the Client, proxying, composition) documented throughout this site. Review the documentation for details on new capabilities. + +Prior to `fastmcp==2.3.0` and `mcp==1.8.0`, the 2.x API always mirrored the 1.0 API. However, as the projects diverge, this can not be guaranteed. You may see deprecation warnings if you attempt to use 1.0 APIs in FastMCP 2.x. Please refer to this documentation for details on new capabilities. + ## Installing for Development diff --git a/docs/getting-started/welcome.mdx b/docs/getting-started/welcome.mdx index b56352131..aff5fe21d 100644 --- a/docs/getting-started/welcome.mdx +++ b/docs/getting-started/welcome.mdx @@ -27,7 +27,7 @@ if __name__ == "__main__": ## FastMCP 2.0 and the Official MCP SDK -Recognize the `FastMCP` name? You might have used the version integrated into the [official MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk), which was based on **FastMCP 1.0**. +Recognize the `FastMCP` name? You might have seen the version that was contributed to the [official MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk), which was based on **FastMCP 1.0**. **Welcome to FastMCP 2.0!** This is the [actively developed successor](https://github.com/jlowin/fastmcp), and it significantly expands on 1.0 by introducing powerful client capabilities, server proxying & composition, OpenAPI/FastAPI integration, and more advanced features. diff --git a/docs/servers/fastmcp.mdx b/docs/servers/fastmcp.mdx index 7b6995228..2e5e18ef2 100644 --- a/docs/servers/fastmcp.mdx +++ b/docs/servers/fastmcp.mdx @@ -114,11 +114,16 @@ if __name__ == "__main__": # This runs the server, defaulting to STDIO transport mcp.run() - # To use a different transport, e.g., Streamable HTTP: + # To use a different transport, e.g., HTTP: # mcp.run(transport="streamable-http", host="127.0.0.1", port=9000) ``` -FastMCP supports several transport options like STDIO (default, for local tools), Streamable HTTP (recommended for web services), and SSE (legacy web transport). The server can also be run using the FastMCP CLI. +FastMCP supports several transport options: +- STDIO (default, for local tools) +- Streamable HTTP (recommended for web services) +- SSE (legacy web transport, deprecated) + +The server can also be run using the FastMCP CLI. For detailed information on each transport, how to configure them (host, port, paths), and when to use which, please refer to the [**Running Your FastMCP Server**](/deployment/running-server) guide. diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py index 37e97d79f..143631b06 100644 --- a/src/fastmcp/server/server.py +++ b/src/fastmcp/server/server.py @@ -3,6 +3,8 @@ from __future__ import annotations import datetime +import inspect +import warnings from collections.abc import AsyncIterator, Awaitable, Callable from contextlib import ( AbstractAsyncContextManager, @@ -170,7 +172,7 @@ class FastMCP(Generic[LifespanResultT]): async def run_async( self, - transport: Literal["stdio", "sse", "streamable-http"] | None = None, + transport: Literal["stdio", "streamable-http", "sse"] | None = None, **transport_kwargs: Any, ) -> None: """Run the FastMCP server asynchronously. @@ -180,19 +182,21 @@ class FastMCP(Generic[LifespanResultT]): """ if transport is None: transport = "stdio" - if transport not in ["stdio", "sse", "streamable-http"]: + if transport not in ["stdio", "streamable-http", "sse"]: raise ValueError(f"Unknown transport: {transport}") if transport == "stdio": await self.run_stdio_async(**transport_kwargs) + elif transport == "streamable-http": + await self.run_http_async(transport="streamable-http", **transport_kwargs) elif transport == "sse": - await self.run_sse_async(**transport_kwargs) - else: # transport == "streamable-http" - await self.run_streamable_http_async(**transport_kwargs) + await self.run_http_async(transport="sse", **transport_kwargs) + else: + raise ValueError(f"Unknown transport: {transport}") def run( self, - transport: Literal["stdio", "sse", "streamable-http"] | None = None, + transport: Literal["stdio", "streamable-http", "sse"] | None = None, **transport_kwargs: Any, ) -> None: """Run the FastMCP server. Note this is a synchronous function. @@ -714,22 +718,31 @@ class FastMCP(Generic[LifespanResultT]): self._mcp_server.create_initialization_options(), ) - async def run_sse_async( + async def run_http_async( self, + transport: Literal["streamable-http", "sse"] = "streamable-http", host: str | None = None, port: int | None = None, log_level: str | None = None, path: str | None = None, - message_path: str | None = None, uvicorn_config: dict | None = None, ) -> None: - """Run the server using SSE transport.""" + """Run the server using HTTP transport. + + Args: + transport: Transport protocol to use - either "streamable-http" (default) or "sse" + host: Host address to bind to (defaults to settings.host) + port: Port to bind to (defaults to settings.port) + 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 + """ uvicorn_config = uvicorn_config or {} - # the SSE app hangs even when a signal is sent, so we disable the - # timeout to make it possible to close immediately. see - # https://github.com/jlowin/fastmcp/issues/296 uvicorn_config.setdefault("timeout_graceful_shutdown", 0) - app = self.sse_app(path=path, message_path=message_path) + # lifespan is required for streamable http + uvicorn_config["lifespan"] = "on" + + app = self.http_app(path=path, transport=transport) config = uvicorn.Config( app, @@ -741,6 +754,35 @@ class FastMCP(Generic[LifespanResultT]): server = uvicorn.Server(config) 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, + message_path: str | None = None, + uvicorn_config: dict | None = None, + ) -> None: + """Run the server using SSE transport.""" + warnings.warn( + inspect.cleandoc( + """ + The run_sse_async method is deprecated. Use run_http_async for a + modern (non-SSE) alternative, or create an SSE app with + `fastmcp.server.http.create_sse_app` and run it directly. + """ + ), + DeprecationWarning, + ) + await self.run_http_async( + transport="sse", + host=host, + port=port, + log_level=log_level, + path=path, + uvicorn_config=uvicorn_config, + ) + def sse_app( self, path: str | None = None, @@ -755,6 +797,15 @@ class FastMCP(Generic[LifespanResultT]): message_path: The path to the message endpoint middleware: A list of middleware to apply to the app """ + warnings.warn( + inspect.cleandoc( + """ + The sse_app method is deprecated. Use http_app as a modern (non-SSE) + alternative, or call `fastmcp.server.http.create_sse_app` directly. + """ + ), + DeprecationWarning, + ) return create_sse_app( server=self, message_path=message_path or self.settings.message_path, @@ -778,20 +829,54 @@ class FastMCP(Generic[LifespanResultT]): path: The path to the StreamableHTTP endpoint middleware: A list of middleware to apply to the app """ + warnings.warn( + "The streamable_http_app method is deprecated. Use http_app() instead.", + DeprecationWarning, + ) + return self.http_app(path=path, middleware=middleware) + + def http_app( + self, + path: str | None = None, + middleware: list[Middleware] | None = None, + transport: Literal["streamable-http", "sse"] = "streamable-http", + ) -> Starlette: + """Create a Starlette app using the specified HTTP transport. + + Args: + path: The path for the HTTP endpoint + middleware: A list of middleware to apply to the app + transport: Transport protocol to use - either "streamable-http" (default) or "sse" + + Returns: + A Starlette application configured with the specified transport + """ from fastmcp.server.http import create_streamable_http_app - return create_streamable_http_app( - server=self, - streamable_http_path=path or self.settings.streamable_http_path, - event_store=None, - auth_server_provider=self._auth_server_provider, - auth_settings=self.settings.auth, - json_response=self.settings.json_response, - stateless_http=self.settings.stateless_http, - debug=self.settings.debug, - routes=self._additional_http_routes, - middleware=middleware, - ) + if transport == "streamable-http": + return create_streamable_http_app( + server=self, + streamable_http_path=path or self.settings.streamable_http_path, + event_store=None, + auth_server_provider=self._auth_server_provider, + auth_settings=self.settings.auth, + json_response=self.settings.json_response, + stateless_http=self.settings.stateless_http, + debug=self.settings.debug, + routes=self._additional_http_routes, + middleware=middleware, + ) + elif transport == "sse": + return create_sse_app( + server=self, + message_path=path or self.settings.message_path, + sse_path=path or self.settings.sse_path, + auth_server_provider=self._auth_server_provider, + auth_settings=self.settings.auth, + debug=self.settings.debug, + routes=self._additional_http_routes, + middleware=middleware, + ) async def run_streamable_http_async( self, @@ -801,23 +886,18 @@ class FastMCP(Generic[LifespanResultT]): path: str | None = None, uvicorn_config: dict | None = None, ) -> None: - """Run the server using StreamableHTTP transport.""" - uvicorn_config = uvicorn_config or {} - uvicorn_config.setdefault("timeout_graceful_shutdown", 0) - - app = self.streamable_http_app(path=path) - - config = uvicorn.Config( - app, - host=host or self.settings.host, - port=port or self.settings.port, - log_level=log_level or self.settings.log_level.lower(), - # lifespan is required for streamable http - lifespan="on", - **uvicorn_config, + warnings.warn( + "The run_streamable_http_async method is deprecated. Use run_http_async instead.", + DeprecationWarning, + ) + await self.run_http_async( + transport="streamable-http", + host=host, + port=port, + log_level=log_level, + path=path, + uvicorn_config=uvicorn_config, ) - server = uvicorn.Server(config) - await server.serve() def mount( self, diff --git a/src/fastmcp/utilities/tests.py b/src/fastmcp/utilities/tests.py index fe259c0ce..8f6e3bd7d 100644 --- a/src/fastmcp/utilities/tests.py +++ b/src/fastmcp/utilities/tests.py @@ -55,7 +55,7 @@ def temporary_settings(**kwargs: Any): def _run_server(mcp_server: FastMCP, transport: Literal["sse"], port: int) -> None: # Some Starlette apps are not pickleable, so we need to create them here based on the indicated transport if transport == "sse": - app = mcp_server.sse_app() + app = mcp_server.http_app(transport="sse") else: raise ValueError(f"Invalid transport: {transport}") uvicorn_server = uvicorn.Server( diff --git a/tests/client/test_sse.py b/tests/client/test_sse.py index 9c6b87e20..1a09a38ec 100644 --- a/tests/client/test_sse.py +++ b/tests/client/test_sse.py @@ -58,7 +58,7 @@ def fastmcp_server(): def run_server(host: str, port: int) -> None: try: - app = fastmcp_server().sse_app() + app = fastmcp_server().http_app(transport="sse") server = uvicorn.Server( config=uvicorn.Config(app=app, host=host, port=port, log_level="error") ) @@ -96,7 +96,7 @@ async def test_http_headers(sse_server: str): def run_nested_server(host: str, port: int) -> None: try: - app = fastmcp_server().sse_app() + app = fastmcp_server().http_app(transport="sse") mount = Starlette(routes=[Mount("/nest-inner", app=app)]) mount2 = Starlette(routes=[Mount("/nest-outer", app=mount)]) server = uvicorn.Server( diff --git a/tests/client/test_streamable_http.py b/tests/client/test_streamable_http.py index 117f783f0..18553976f 100644 --- a/tests/client/test_streamable_http.py +++ b/tests/client/test_streamable_http.py @@ -58,7 +58,7 @@ def fastmcp_server(): def run_server(host: str, port: int) -> None: try: - app = fastmcp_server().streamable_http_app() + app = fastmcp_server().http_app() server = uvicorn.Server( config=uvicorn.Config( app=app, @@ -106,7 +106,7 @@ async def test_http_headers(streamable_http_server: str): def run_nested_server(host: str, port: int) -> None: try: - mcp_app = fastmcp_server().streamable_http_app() + mcp_app = fastmcp_server().http_app() mount = Starlette(routes=[Mount("/nest-inner", app=mcp_app)]) mount2 = Starlette( diff --git a/tests/server/test_http_dependencies.py b/tests/server/test_http_dependencies.py index b322aece4..680717adb 100644 --- a/tests/server/test_http_dependencies.py +++ b/tests/server/test_http_dependencies.py @@ -43,7 +43,7 @@ def fastmcp_server(): def run_server(host: str, port: int) -> None: try: - app = fastmcp_server().streamable_http_app() + app = fastmcp_server().http_app() server = uvicorn.Server( config=uvicorn.Config( app=app, diff --git a/tests/server/test_http_middleware.py b/tests/server/test_http_middleware.py index 2ff4127be..e48dc127b 100644 --- a/tests/server/test_http_middleware.py +++ b/tests/server/test_http_middleware.py @@ -1,4 +1,4 @@ -"""Tests for custom middleware in HTTP servers.""" +"""Tests for middleware in HTTP apps.""" from collections.abc import Callable from typing import Any @@ -68,7 +68,7 @@ async def test_sse_app_with_custom_middleware(): server._additional_http_routes = routes # Create the app with custom middleware - app = server.sse_app(middleware=custom_middleware) + app = server.http_app(transport="sse", middleware=custom_middleware) # Create a test client transport = ASGITransport(app=app) @@ -99,7 +99,7 @@ async def test_streamable_http_app_with_custom_middleware(): server._additional_http_routes = routes # Create the app with custom middleware - app = server.streamable_http_app(middleware=custom_middleware) + app = server.http_app(transport="streamable-http", middleware=custom_middleware) # Create a test client transport = ASGITransport(app=app) @@ -204,7 +204,7 @@ async def test_multiple_middleware_ordering(): server._additional_http_routes = routes # Create the app with custom middleware - app = server.sse_app(middleware=custom_middleware) + app = server.http_app(transport="sse", middleware=custom_middleware) # Create a test client transport = ASGITransport(app=app) diff --git a/tests/test_deprecated.py b/tests/test_deprecated.py new file mode 100644 index 000000000..c5b2ecdcd --- /dev/null +++ b/tests/test_deprecated.py @@ -0,0 +1,82 @@ +"""Tests for deprecated functionality.""" + +import warnings +from unittest.mock import AsyncMock, patch + +import pytest +from starlette.applications import Starlette + +from fastmcp import FastMCP + + +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") + + with pytest.warns( + DeprecationWarning, match="The streamable_http_app method is deprecated" + ): + app = server.streamable_http_app() + assert isinstance(app, Starlette) + + +@pytest.mark.asyncio +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" + + +@pytest.mark.asyncio +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") == "streamable-http" + + +def test_http_app_with_sse_transport(): + """Test that http_app with SSE transport works (no warning).""" + server = FastMCP("TestServer") + + # This should not raise a warning since we're using the new API + with warnings.catch_warnings(record=True) as recorded_warnings: + app = server.http_app(transport="sse") + assert isinstance(app, Starlette) + + # Verify no deprecation warnings were raised for using transport parameter + deprecation_warnings = [ + w for w in recorded_warnings if issubclass(w.category, DeprecationWarning) + ] + assert len(deprecation_warnings) == 0