Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/mcp/client/streamable_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ async def terminate_session(self, client: httpx.AsyncClient) -> None:

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}")
Expand Down
66 changes: 66 additions & 0 deletions tests/shared/test_streamable_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
Loading