ha-mcp-dev 6.5.0.dev181__tar.gz → 6.5.0.dev182__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ha_mcp_dev-6.5.0.dev181/src/ha_mcp_dev.egg-info → ha_mcp_dev-6.5.0.dev182}/PKG-INFO +1 -1
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/pyproject.toml +1 -1
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_service.py +56 -7
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/LICENSE +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/MANIFEST.in +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/README.md +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/setup.cfg +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/resources/card_types.json +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/resources/dashboard_guide.md +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/smart_search.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_config_info.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/tests/__init__.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/tests/test_constants.py +0 -0
- {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/tests/test_env_manager.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ha-mcp-dev"
|
|
7
|
-
version = "6.5.0.
|
|
7
|
+
version = "6.5.0.dev182"
|
|
8
8
|
description = "Home Assistant MCP Server - Complete control of Home Assistant through MCP"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13,<3.14"
|
|
@@ -6,13 +6,25 @@ This module provides service execution and WebSocket-enabled operation monitorin
|
|
|
6
6
|
|
|
7
7
|
from typing import Any, cast
|
|
8
8
|
|
|
9
|
+
import httpx
|
|
10
|
+
|
|
9
11
|
from ..errors import (
|
|
10
12
|
create_validation_error,
|
|
11
13
|
)
|
|
12
|
-
from .
|
|
14
|
+
from ..client.rest_client import HomeAssistantConnectionError
|
|
15
|
+
from .helpers import exception_to_structured_error, log_tool_usage
|
|
13
16
|
from .util_helpers import coerce_bool_param, parse_json_param
|
|
14
17
|
|
|
15
18
|
|
|
19
|
+
def _build_service_suggestions(domain: str, service: str, entity_id: str | None) -> list[str]:
|
|
20
|
+
"""Build common error suggestions for service call failures."""
|
|
21
|
+
return [
|
|
22
|
+
f"Verify {entity_id} exists using ha_get_state()" if entity_id else "Specify an entity_id for targeted service calls",
|
|
23
|
+
f"Check available services for {domain} domain using ha_get_domain_docs()",
|
|
24
|
+
"Use ha_search_entities() to find correct entity IDs",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
16
28
|
def register_service_tools(mcp, client, **kwargs):
|
|
17
29
|
"""Register service call and operation monitoring tools with the MCP server."""
|
|
18
30
|
device_tools = kwargs.get("device_tools")
|
|
@@ -20,6 +32,7 @@ def register_service_tools(mcp, client, **kwargs):
|
|
|
20
32
|
raise ValueError("device_tools is required for service tools registration")
|
|
21
33
|
|
|
22
34
|
@mcp.tool(annotations={"destructiveHint": True, "title": "Call Service"})
|
|
35
|
+
@log_tool_usage
|
|
23
36
|
async def ha_call_service(
|
|
24
37
|
domain: str,
|
|
25
38
|
service: str,
|
|
@@ -107,6 +120,44 @@ def register_service_tools(mcp, client, **kwargs):
|
|
|
107
120
|
response["service_response"] = result.get("service_response", result)
|
|
108
121
|
|
|
109
122
|
return response
|
|
123
|
+
except HomeAssistantConnectionError as error:
|
|
124
|
+
# Check if this is a timeout - for service calls, timeouts typically
|
|
125
|
+
# mean the service was dispatched but HA didn't respond in time.
|
|
126
|
+
# The operation is likely still running (e.g., update.install, long automations).
|
|
127
|
+
if isinstance(error.__cause__, httpx.TimeoutException):
|
|
128
|
+
return {
|
|
129
|
+
"success": True,
|
|
130
|
+
"partial": True,
|
|
131
|
+
"domain": domain,
|
|
132
|
+
"service": service,
|
|
133
|
+
"entity_id": entity_id,
|
|
134
|
+
"parameters": data,
|
|
135
|
+
"message": (
|
|
136
|
+
f"Service {domain}.{service} was dispatched but Home Assistant "
|
|
137
|
+
f"did not respond within the timeout period. The operation is likely "
|
|
138
|
+
f"still running in the background."
|
|
139
|
+
),
|
|
140
|
+
"warning": (
|
|
141
|
+
"Response timed out. This is normal for long-running services "
|
|
142
|
+
f"like updates or firmware installs. Use ha_get_state('{entity_id}') "
|
|
143
|
+
"to check the current status."
|
|
144
|
+
if entity_id
|
|
145
|
+
else "Response timed out. This is normal for long-running services. "
|
|
146
|
+
"The service was dispatched and may still be executing."
|
|
147
|
+
),
|
|
148
|
+
}
|
|
149
|
+
# Non-timeout connection errors are real failures
|
|
150
|
+
error_response = exception_to_structured_error(
|
|
151
|
+
error,
|
|
152
|
+
context={
|
|
153
|
+
"domain": domain,
|
|
154
|
+
"service": service,
|
|
155
|
+
"entity_id": entity_id,
|
|
156
|
+
},
|
|
157
|
+
)
|
|
158
|
+
if "error" in error_response and isinstance(error_response["error"], dict):
|
|
159
|
+
error_response["error"]["suggestions"] = _build_service_suggestions(domain, service, entity_id)
|
|
160
|
+
return error_response
|
|
110
161
|
except Exception as error:
|
|
111
162
|
# Use structured error response
|
|
112
163
|
error_response = exception_to_structured_error(
|
|
@@ -117,12 +168,7 @@ def register_service_tools(mcp, client, **kwargs):
|
|
|
117
168
|
"entity_id": entity_id,
|
|
118
169
|
},
|
|
119
170
|
)
|
|
120
|
-
|
|
121
|
-
suggestions = [
|
|
122
|
-
f"Verify {entity_id} exists using ha_get_state()" if entity_id else "Specify an entity_id for targeted service calls",
|
|
123
|
-
f"Check available services for {domain} domain using ha_get_domain_docs()",
|
|
124
|
-
"Use ha_search_entities() to find correct entity IDs",
|
|
125
|
-
]
|
|
171
|
+
suggestions = _build_service_suggestions(domain, service, entity_id)
|
|
126
172
|
if entity_id:
|
|
127
173
|
suggestions.extend([
|
|
128
174
|
f"For automation: ha_call_service('automation', 'trigger', entity_id='{entity_id}')",
|
|
@@ -134,6 +180,7 @@ def register_service_tools(mcp, client, **kwargs):
|
|
|
134
180
|
return error_response
|
|
135
181
|
|
|
136
182
|
@mcp.tool(annotations={"readOnlyHint": True, "title": "Get Operation Status"})
|
|
183
|
+
@log_tool_usage
|
|
137
184
|
async def ha_get_operation_status(
|
|
138
185
|
operation_id: str, timeout_seconds: int = 10
|
|
139
186
|
) -> dict[str, Any]:
|
|
@@ -144,6 +191,7 @@ def register_service_tools(mcp, client, **kwargs):
|
|
|
144
191
|
return cast(dict[str, Any], result)
|
|
145
192
|
|
|
146
193
|
@mcp.tool(annotations={"destructiveHint": True, "title": "Bulk Control"})
|
|
194
|
+
@log_tool_usage
|
|
147
195
|
async def ha_bulk_control(
|
|
148
196
|
operations: str | list[dict[str, Any]], parallel: bool | str = True
|
|
149
197
|
) -> dict[str, Any]:
|
|
@@ -177,6 +225,7 @@ def register_service_tools(mcp, client, **kwargs):
|
|
|
177
225
|
return cast(dict[str, Any], result)
|
|
178
226
|
|
|
179
227
|
@mcp.tool(annotations={"readOnlyHint": True, "title": "Get Bulk Operation Status"})
|
|
228
|
+
@log_tool_usage
|
|
180
229
|
async def ha_get_bulk_status(operation_ids: list[str]) -> dict[str, Any]:
|
|
181
230
|
"""
|
|
182
231
|
Check status of multiple device control operations.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_config_automations.py
RENAMED
|
File without changes
|
{ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_config_dashboards.py
RENAMED
|
File without changes
|
{ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_config_entry_flow.py
RENAMED
|
File without changes
|
{ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_config_helpers.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_config_scripts.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_voice_assistant.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp_dev.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp_dev.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|