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:
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user