1
0
mirror of https://github.com/home-assistant/core.git synced 2026-02-15 07:36:16 +00:00

Improve MCP SSE fallback error handling (#162655)

This commit is contained in:
Allen Porter
2026-02-13 10:39:34 -08:00
committed by GitHub
parent c15da19b84
commit a0af35f2dc
2 changed files with 35 additions and 14 deletions

View File

@@ -7,6 +7,7 @@ import datetime
import logging
import httpx
from mcp import McpError
from mcp.client.session import ClientSession
from mcp.client.sse import sse_client
from mcp.client.streamable_http import streamable_http_client
@@ -63,10 +64,15 @@ async def mcp_client(
# Method not Allowed likely means this is not a streamable HTTP server,
# but it may be an SSE server. This is part of the MCP Transport
# backwards compatibility specification.
# We also handle other generic McpErrors since proxies may not respond
# consistently with a 405.
if (
isinstance(main_error, httpx.HTTPStatusError)
and main_error.response.status_code == 405
):
) or isinstance(main_error, McpError):
_LOGGER.debug(
"Streamable HTTP client failed, attempting SSE client: %s", main_error
)
try:
async with (
sse_client(url=url, headers=headers) as streams,

View File

@@ -4,7 +4,8 @@ import re
from unittest.mock import AsyncMock, Mock, patch
import httpx
from mcp.types import CallToolResult, ListToolsResult, TextContent, Tool
from mcp import McpError
from mcp.types import CallToolResult, ErrorData, ListToolsResult, TextContent, Tool
import pytest
import voluptuous as vol
@@ -136,30 +137,44 @@ async def test_mcp_server_sse_transport_failure(
"Connection error", [httpx.ConnectError("Connection failed")]
)
await hass.config_entries.async_setup(config_entry.entry_id)
assert config_entry.state is ConfigEntryState.SETUP_RETRY
@pytest.mark.parametrize(
("side_effect"),
[
(
ExceptionGroup(
"Method not allowed",
[
httpx.HTTPStatusError(
"Method not allowed",
request=None,
response=httpx.Response(405),
)
],
),
),
(
ExceptionGroup(
"Some exception group",
[McpError(ErrorData(code=500, message="Session terminated"))],
)
),
],
)
async def test_mcp_client_fallback_to_sse_success(
hass: HomeAssistant,
config_entry: MockConfigEntry,
mock_http_streamable_client: AsyncMock,
mock_sse_client: AsyncMock,
mock_mcp_client: Mock,
side_effect: Exception,
) -> None:
"""Test mcp_client falls back to SSE on method not allowed error.
"""Test mcp_client falls back to SSE on some errors.
This exercises the backwards compatibility part of the MCP Transport
specification.
"""
http_405 = httpx.HTTPStatusError(
"Method not allowed",
request=None, # type: ignore[arg-type]
response=httpx.Response(405),
)
mock_http_streamable_client.side_effect = ExceptionGroup(
"Method not allowed", [http_405]
)
mock_http_streamable_client.side_effect = side_effect
# Setup mocks for SSE fallback
mock_sse_client.return_value.__aenter__.return_value = ("read", "write")