fix: 204 is an acceptable response to DELETEing the session (#697)

This commit is contained in:
Nate Barbettini
2025-05-12 13:22:55 -07:00
committed by GitHub
parent 76919cfaa4
commit f745d69dcb
2 changed files with 67 additions and 1 deletions

View File

@@ -410,7 +410,7 @@ class StreamableHTTPTransport:
if response.status_code == 405:
logger.debug("Server does not allow session termination")
elif response.status_code != 200:
elif response.status_code not in (200, 204):
logger.warning(f"Session termination failed: {response.status_code}")
except Exception as exc:
logger.warning(f"Session termination failed: {exc}")

View File

@@ -960,6 +960,72 @@ async def test_streamablehttp_client_session_termination(
await session.list_tools()
@pytest.mark.anyio
async def test_streamablehttp_client_session_termination_204(
basic_server, basic_server_url, monkeypatch
):
"""Test client session termination functionality with a 204 response.
This test patches the httpx client to return a 204 response for DELETEs.
"""
# Save the original delete method to restore later
original_delete = httpx.AsyncClient.delete
# Mock the client's delete method to return a 204
async def mock_delete(self, *args, **kwargs):
# Call the original method to get the real response
response = await original_delete(self, *args, **kwargs)
# Create a new response with 204 status code but same headers
mocked_response = httpx.Response(
204,
headers=response.headers,
content=response.content,
request=response.request,
)
return mocked_response
# Apply the patch to the httpx client
monkeypatch.setattr(httpx.AsyncClient, "delete", mock_delete)
captured_session_id = None
# Create the streamablehttp_client with a custom httpx client to capture headers
async with streamablehttp_client(f"{basic_server_url}/mcp") as (
read_stream,
write_stream,
get_session_id,
):
async with ClientSession(read_stream, write_stream) as session:
# Initialize the session
result = await session.initialize()
assert isinstance(result, InitializeResult)
captured_session_id = get_session_id()
assert captured_session_id is not None
# Make a request to confirm session is working
tools = await session.list_tools()
assert len(tools.tools) == 4
headers = {}
if captured_session_id:
headers[MCP_SESSION_ID_HEADER] = captured_session_id
async with streamablehttp_client(f"{basic_server_url}/mcp", headers=headers) as (
read_stream,
write_stream,
_,
):
async with ClientSession(read_stream, write_stream) as session:
# Attempt to make a request after termination
with pytest.raises(
McpError,
match="Session terminated",
):
await session.list_tools()
@pytest.mark.anyio
async def test_streamablehttp_client_resumption(event_server):
"""Test client session to resume a long running tool."""