From dab2084c74925719996a8623de2f546528499a67 Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Mon, 16 Feb 2026 09:08:32 -0500 Subject: [PATCH] Return 405 for GET requests on stateless HTTP servers --- src/fastmcp/server/http.py | 9 ++++++++- tests/client/test_streamable_http.py | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/fastmcp/server/http.py b/src/fastmcp/server/http.py index 530b6c0c2..c29c39ec8 100644 --- a/src/fastmcp/server/http.py +++ b/src/fastmcp/server/http.py @@ -326,6 +326,11 @@ def create_streamable_http_app( ) # Create protected HTTP endpoint route + # Stateless servers have no session tracking, so GET SSE streams + # (for server-initiated notifications) serve no purpose. + http_methods = ( + ["POST", "DELETE"] if stateless_http else ["GET", "POST", "DELETE"] + ) server_routes.append( Route( streamable_http_path, @@ -334,15 +339,17 @@ def create_streamable_http_app( auth.required_scopes, resource_metadata_url, ), - methods=["GET", "POST", "DELETE"], + methods=http_methods, ) ) else: # No auth required + http_methods = ["POST", "DELETE"] if stateless_http else None server_routes.append( Route( streamable_http_path, endpoint=streamable_http_app, + methods=http_methods, ) ) diff --git a/tests/client/test_streamable_http.py b/tests/client/test_streamable_http.py index e87f9e259..0f93ed4ef 100644 --- a/tests/client/test_streamable_http.py +++ b/tests/client/test_streamable_http.py @@ -231,6 +231,26 @@ async def test_elicitation_tool(streamable_http_server: str, request): assert result.data == "You said your name was: Alice!" +@pytest.mark.parametrize("streamable_http_server", [True], indirect=True) +async def test_stateless_http_rejects_get_sse(streamable_http_server: str): + """Stateless servers should reject GET SSE requests with 405.""" + import httpx + + async with httpx.AsyncClient() as http_client: + response = await http_client.get(streamable_http_server) + assert response.status_code == 405 + + +@pytest.mark.parametrize("streamable_http_server", [True], indirect=True) +async def test_stateless_http_still_accepts_post(streamable_http_server: str): + """Stateless servers should still handle POST requests normally.""" + async with Client( + transport=StreamableHttpTransport(streamable_http_server) + ) as client: + result = await client.call_tool("greet", {"name": "World"}) + assert result.data == "Hello, World!" + + async def test_nested_streamable_http_server_resolves_correctly(nested_server: str): """Test patch for https://github.com/modelcontextprotocol/python-sdk/pull/659""" async with Client(transport=StreamableHttpTransport(nested_server)) as client: