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.
Files changed (78) hide show
  1. {ha_mcp_dev-6.5.0.dev181/src/ha_mcp_dev.egg-info → ha_mcp_dev-6.5.0.dev182}/PKG-INFO +1 -1
  2. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/pyproject.toml +1 -1
  3. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_service.py +56 -7
  4. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  5. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/LICENSE +0 -0
  6. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/MANIFEST.in +0 -0
  7. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/README.md +0 -0
  8. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/setup.cfg +0 -0
  9. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/__init__.py +0 -0
  10. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/__main__.py +0 -0
  11. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/_pypi_marker +0 -0
  12. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/auth/__init__.py +0 -0
  13. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/auth/consent_form.py +0 -0
  14. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/auth/provider.py +0 -0
  15. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/client/__init__.py +0 -0
  16. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/client/rest_client.py +0 -0
  17. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/client/websocket_client.py +0 -0
  18. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/client/websocket_listener.py +0 -0
  19. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/config.py +0 -0
  20. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/errors.py +0 -0
  21. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/py.typed +0 -0
  22. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/resources/card_types.json +0 -0
  23. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/resources/dashboard_guide.md +0 -0
  24. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/server.py +0 -0
  25. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/smoke_test.py +0 -0
  26. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/__init__.py +0 -0
  27. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/backup.py +0 -0
  28. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/device_control.py +0 -0
  29. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/enhanced.py +0 -0
  30. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/helpers.py +0 -0
  31. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/registry.py +0 -0
  32. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/smart_search.py +0 -0
  33. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_addons.py +0 -0
  34. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_areas.py +0 -0
  35. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  36. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  37. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_calendar.py +0 -0
  38. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_camera.py +0 -0
  39. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  40. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  41. {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
  42. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  43. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_config_info.py +0 -0
  44. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  45. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_entities.py +0 -0
  46. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  47. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_groups.py +0 -0
  48. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_hacs.py +0 -0
  49. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_history.py +0 -0
  50. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_integrations.py +0 -0
  51. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_labels.py +0 -0
  52. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  53. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_registry.py +0 -0
  54. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_resources.py +0 -0
  55. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_search.py +0 -0
  56. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_services.py +0 -0
  57. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_system.py +0 -0
  58. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_todo.py +0 -0
  59. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_traces.py +0 -0
  60. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_updates.py +0 -0
  61. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_utility.py +0 -0
  62. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  63. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/tools_zones.py +0 -0
  64. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/tools/util_helpers.py +0 -0
  65. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/utils/__init__.py +0 -0
  66. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/utils/domain_handlers.py +0 -0
  67. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  68. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/utils/operation_manager.py +0 -0
  69. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/utils/python_sandbox.py +0 -0
  70. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp/utils/usage_logger.py +0 -0
  71. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  72. {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
  73. {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
  74. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  75. {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
  76. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/tests/__init__.py +0 -0
  77. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/tests/test_constants.py +0 -0
  78. {ha_mcp_dev-6.5.0.dev181 → ha_mcp_dev-6.5.0.dev182}/tests/test_env_manager.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 6.5.0.dev181
3
+ Version: 6.5.0.dev182
4
4
  Summary: Home Assistant MCP Server - Complete control of Home Assistant through MCP
5
5
  Author-email: Julien <github@qc-h.net>
6
6
  License: MIT
@@ -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.dev181"
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 .helpers import exception_to_structured_error
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
- # Add service-specific suggestions
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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 6.5.0.dev181
3
+ Version: 6.5.0.dev182
4
4
  Summary: Home Assistant MCP Server - Complete control of Home Assistant through MCP
5
5
  Author-email: Julien <github@qc-h.net>
6
6
  License: MIT