diff --git a/src/fastmcp/server/context.py b/src/fastmcp/server/context.py index 74af8be83..145b127fe 100644 --- a/src/fastmcp/server/context.py +++ b/src/fastmcp/server/context.py @@ -122,6 +122,7 @@ class Context: progress=progress, total=total, message=message, + related_request_id=self.request_id, ) async def read_resource(self, uri: str | AnyUrl) -> list[ReadResourceContents]: diff --git a/tests/client/test_streamable_http.py b/tests/client/test_streamable_http.py index 11f4b4e33..7e95e27b2 100644 --- a/tests/client/test_streamable_http.py +++ b/tests/client/test_streamable_http.py @@ -2,13 +2,16 @@ import asyncio import json import sys from collections.abc import AsyncGenerator +from unittest.mock import AsyncMock import pytest import uvicorn from mcp import McpError +from mcp.types import TextContent from starlette.applications import Starlette from starlette.routing import Mount +from fastmcp import Context from fastmcp.client import Client from fastmcp.client.transports import StreamableHttpTransport from fastmcp.server.dependencies import get_http_request @@ -38,6 +41,12 @@ def fastmcp_server(): await asyncio.sleep(seconds) return f"Slept for {seconds} seconds" + @server.tool + 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") + return f"Hello, {name}!" + # Add a resource @server.resource(uri="data://users") async def get_users(): @@ -63,8 +72,10 @@ def fastmcp_server(): return server -def run_server(host: str, port: int, **kwargs) -> None: - fastmcp_server().run(host=host, port=port, **kwargs) +def run_server(host: str, port: int, stateless_http: bool = False, **kwargs) -> None: + server = fastmcp_server() + server.settings.stateless_http = stateless_http + server.run(host=host, port=port, **kwargs) def run_nested_server(host: str, port: int) -> None: @@ -88,8 +99,12 @@ def run_nested_server(host: str, port: int) -> None: @pytest.fixture() -async def streamable_http_server() -> AsyncGenerator[str, None]: - with run_server_in_process(run_server, transport="streamable-http") as url: +async def streamable_http_server( + stateless_http: bool = False, +) -> AsyncGenerator[str, None]: + with run_server_in_process( + run_server, stateless_http=stateless_http, transport="streamable-http" + ) as url: async with Client(transport=StreamableHttpTransport(f"{url}/mcp")) as client: assert await client.ping() yield f"{url}/mcp" @@ -117,6 +132,24 @@ async def test_http_headers(streamable_http_server: str): assert json_result["x-demo-header"] == "ABC" +@pytest.mark.parametrize("streamable_http_server", [True, False], indirect=True) +async def test_greet_with_progress_tool(streamable_http_server: str): + """Test calling the greet tool.""" + progress_handler = AsyncMock(return_value=None) + + async with Client( + transport=StreamableHttpTransport(streamable_http_server), + progress_handler=progress_handler, + ) as client: + result = await client.call_tool("greet_with_progress", {"name": "Alice"}) + + assert isinstance(result, list) + assert isinstance(result[0], TextContent) + assert result[0].text == "Hello, Alice!" + + progress_handler.assert_called_once_with(0.5, 1.0, "Greeting in progress") + + async def test_nested_streamable_http_server_resolves_correctly(): # tests patch for # https://github.com/modelcontextprotocol/python-sdk/pull/659