Advertise required tool parameters in MCP Server (#182105)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Paulus Schoutsen
2026-09-13 08:22:56 -07:00
committed by GitHub
co-authored by Claude
parent eb5024a1f2
commit fdb882c737
3 changed files with 80 additions and 6 deletions
@@ -45,13 +45,17 @@ def _format_tool(
) -> types.Tool:
"""Format tool specification."""
input_schema = to_openapi(tool.parameters, custom_serializer=custom_serializer)
mcp_schema: dict[str, Any] = {
"type": "object",
"properties": input_schema["properties"],
}
# Omitted by to_openapi when the tool has no required parameters.
if required := input_schema.get("required"):
mcp_schema["required"] = required
return types.Tool(
name=tool.name,
description=tool.description or "",
inputSchema={
"type": "object",
"properties": input_schema["properties"],
},
inputSchema=mcp_schema,
)
+6 -2
View File
@@ -1,6 +1,7 @@
"""Common fixtures for the Model Context Protocol Server tests."""
from collections.abc import Generator
from dataclasses import dataclass, field
from unittest.mock import AsyncMock, patch
import pytest
@@ -16,8 +17,11 @@ from tests.common import MockConfigEntry
TEST_LLM_API_ID = "test-api"
@dataclass(slots=True, kw_only=True)
class MockLLMAPI(llm.API):
"""Test LLM API that does not expose any tools."""
"""Test LLM API that exposes the tools it is created with."""
tools: list[llm.Tool] = field(default_factory=list)
async def async_get_api_instance(
self, llm_context: llm.LLMContext
@@ -27,7 +31,7 @@ class MockLLMAPI(llm.API):
api=self,
api_prompt="Test prompt",
llm_context=llm_context,
tools=[],
tools=self.tools,
)
+66
View File
@@ -15,6 +15,7 @@ import mcp.client.sse
import mcp.client.streamable_http
from mcp.shared.exceptions import McpError
import pytest
import voluptuous as vol
from homeassistant.components.conversation import DOMAIN as CONVERSATION_DOMAIN
from homeassistant.components.homeassistant.exposed_entities import async_expose_entity
@@ -42,6 +43,7 @@ from homeassistant.helpers import (
)
from homeassistant.helpers.httpx_client import create_async_httpx_client
from homeassistant.setup import async_setup_component
from homeassistant.util.json import JsonObjectType
from .conftest import TEST_LLM_API_ID, MockLLMAPI
@@ -80,6 +82,25 @@ EXPECTED_PROMPT_ENTITY_DEFINITION = """
"""
class _StubTool(llm.Tool):
"""Minimal tool with a configurable parameter schema."""
name = "test_tool"
def __init__(self, parameters: vol.Schema) -> None:
"""Initialize the stub tool."""
self.parameters = parameters
async def async_call(
self,
hass: HomeAssistant,
tool_input: llm.ToolInput,
llm_context: llm.LLMContext,
) -> JsonObjectType:
"""Return an empty result."""
return {}
@pytest.fixture
async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
"""Set up the config entry."""
@@ -612,6 +633,51 @@ async def test_mcp_tools_list(
assert properties.get("name") == {"type": "string"}
@pytest.mark.parametrize("llm_hass_api", [TEST_LLM_API_ID])
@pytest.mark.parametrize(
("parameters", "expected_required"),
[
pytest.param(
vol.Schema({vol.Required("name"): str, vol.Optional("area"): str}),
["name"],
id="required-and-optional",
),
pytest.param(
vol.Schema({vol.Optional("area"): str}),
None,
id="optional-only",
),
],
)
async def test_mcp_tools_list_required_parameters(
hass: HomeAssistant,
setup_integration: None,
mcp_url: str,
mcp_client: MCPClientFactory,
hass_supervisor_access_token: str,
parameters: vol.Schema,
expected_required: list[str] | None,
) -> None:
"""Test the tools list advertises the required tool parameters."""
llm.async_register_api(
hass,
MockLLMAPI(
hass=hass,
id=TEST_LLM_API_ID,
name="Test API",
tools=[_StubTool(parameters)],
),
)
async with mcp_client(hass, mcp_url, hass_supervisor_access_token) as session:
result = await session.list_tools()
tool = next(iter(tool for tool in result.tools if tool.name == "test_tool"))
assert tool.inputSchema.get("type") == "object"
assert tool.inputSchema.get("required") == expected_required
@pytest.mark.parametrize("llm_hass_api", [llm.LLM_API_ASSIST, STATELESS_LLM_API])
async def test_mcp_tool_call(
hass: HomeAssistant,