mirror of
https://github.com/home-assistant/core.git
synced 2026-10-06 14:29:21 -04:00
Return METHOD_NOT_FOUND for unsupported MCP methods in mcp_server (#183350)
This commit is contained in:
@@ -33,6 +33,7 @@ from contextlib import asynccontextmanager
|
||||
from dataclasses import dataclass
|
||||
from http import HTTPStatus
|
||||
import logging
|
||||
from typing import get_args
|
||||
|
||||
from aiohttp import web
|
||||
from aiohttp.web_exceptions import HTTPBadRequest, HTTPNotFound
|
||||
@@ -61,6 +62,12 @@ _LOGGER = logging.getLogger(__name__)
|
||||
STREAMABLE_API = "/api/mcp"
|
||||
TIMEOUT = 60 # Seconds
|
||||
|
||||
KNOWN_MCP_METHODS: frozenset[str] = frozenset(
|
||||
req_cls.model_fields["method"].default
|
||||
for req_cls in get_args(types.ClientRequestType)
|
||||
if "method" in req_cls.model_fields
|
||||
)
|
||||
|
||||
# Legacy SSE endpoint
|
||||
SSE_API = f"/{DOMAIN}/sse"
|
||||
MESSAGES_API = f"/{DOMAIN}/messages/{{session_id}}"
|
||||
@@ -286,6 +293,20 @@ async def _async_handle_streamable_message(
|
||||
_LOGGER.debug("Notification or response received, returning 202")
|
||||
return web.Response(status=HTTPStatus.ACCEPTED)
|
||||
|
||||
if message.root.method not in KNOWN_MCP_METHODS:
|
||||
error_response = types.JSONRPCError(
|
||||
jsonrpc="2.0",
|
||||
id=message.root.id,
|
||||
error=types.ErrorData(
|
||||
code=types.METHOD_NOT_FOUND,
|
||||
message="Method not found",
|
||||
data=message.root.method,
|
||||
),
|
||||
)
|
||||
return web.json_response(
|
||||
data=error_response.model_dump(by_alias=True, exclude_none=True),
|
||||
)
|
||||
|
||||
# The MCP server runs as a background task for the duration of the
|
||||
# request. We open a buffered stream pair to communicate with it. The
|
||||
# request is sent to the MCP server and we wait for a single response
|
||||
|
||||
@@ -1217,3 +1217,47 @@ async def test_require_admin_allows_admin(
|
||||
headers={"accept": CONTENT_TYPE_JSON},
|
||||
)
|
||||
assert response.status == HTTPStatus.OK
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"method",
|
||||
[
|
||||
pytest.param("server/discover", id="server_discover"),
|
||||
pytest.param("no/such-method", id="unknown_method"),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("setup_integration")
|
||||
async def test_unsupported_method_returns_method_not_found(
|
||||
hass_client: ClientSessionGenerator,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
method: str,
|
||||
) -> None:
|
||||
"""Test that unsupported methods return JSON-RPC METHOD_NOT_FOUND without log warnings."""
|
||||
client = await hass_client()
|
||||
|
||||
response = await client.post(
|
||||
STREAMABLE_API,
|
||||
json={
|
||||
"jsonrpc": "2.0",
|
||||
"id": "request-123",
|
||||
"method": method,
|
||||
"params": {
|
||||
"_meta": {
|
||||
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
|
||||
}
|
||||
},
|
||||
},
|
||||
headers={"accept": CONTENT_TYPE_JSON},
|
||||
)
|
||||
assert response.status == HTTPStatus.OK
|
||||
data = await response.json()
|
||||
assert data == {
|
||||
"jsonrpc": "2.0",
|
||||
"id": "request-123",
|
||||
"error": {
|
||||
"code": -32601,
|
||||
"message": "Method not found",
|
||||
"data": method,
|
||||
},
|
||||
}
|
||||
assert "Failed to validate request" not in caplog.text
|
||||
|
||||
Reference in New Issue
Block a user