ha-mcp-dev 7.2.0.dev362__tar.gz → 7.2.0.dev363__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-7.2.0.dev362/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.2.0.dev363}/PKG-INFO +1 -1
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/pyproject.toml +1 -1
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_history.py +119 -13
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/LICENSE +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/README.md +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/setup.cfg +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/smart_search.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/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 = "7.2.0.
|
|
7
|
+
version = "7.2.0.dev363"
|
|
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"
|
|
@@ -28,6 +28,7 @@ from .helpers import (
|
|
|
28
28
|
)
|
|
29
29
|
from .util_helpers import (
|
|
30
30
|
add_timezone_metadata,
|
|
31
|
+
build_pagination_metadata,
|
|
31
32
|
coerce_int_param,
|
|
32
33
|
parse_string_list_param,
|
|
33
34
|
)
|
|
@@ -178,7 +179,14 @@ class HistoryTools:
|
|
|
178
179
|
limit: Annotated[
|
|
179
180
|
int | str | None,
|
|
180
181
|
Field(
|
|
181
|
-
description='Max
|
|
182
|
+
description='Max entries per entity. Default: 100, Max: 1000. For source="history": state changes. For source="statistics": aggregated rows. With multiple entity_ids, offset must be 0 and total rows returned can reach limit × len(entity_ids).',
|
|
183
|
+
default=None,
|
|
184
|
+
),
|
|
185
|
+
] = None,
|
|
186
|
+
offset: Annotated[
|
|
187
|
+
int | str | None,
|
|
188
|
+
Field(
|
|
189
|
+
description="Number of entries to skip per entity for pagination. Default: 0. Offset > 0 requires a single entity_id. Use with limit and has_more/next_offset in the response.",
|
|
182
190
|
default=None,
|
|
183
191
|
),
|
|
184
192
|
] = None,
|
|
@@ -205,9 +213,9 @@ class HistoryTools:
|
|
|
205
213
|
- "history" (default): Raw state changes, ~10 day retention, full resolution
|
|
206
214
|
- "statistics": Pre-aggregated data, permanent retention, requires state_class
|
|
207
215
|
|
|
208
|
-
**Shared params:** entity_ids, start_time, end_time
|
|
209
|
-
**History params:** minimal_response, significant_changes_only
|
|
210
|
-
**Statistics params:** period, statistic_types
|
|
216
|
+
**Shared params:** entity_ids, start_time, end_time, limit, offset
|
|
217
|
+
**History params:** minimal_response, significant_changes_only
|
|
218
|
+
**Statistics params:** period, statistic_types
|
|
211
219
|
|
|
212
220
|
**Default time range:** 24h for history, 30 days for statistics
|
|
213
221
|
|
|
@@ -221,10 +229,16 @@ class HistoryTools:
|
|
|
221
229
|
- Computing period averages ("Average living room temperature over 6 months?")
|
|
222
230
|
- Entities must have state_class (measurement, total, total_increasing)
|
|
223
231
|
|
|
232
|
+
**WARNING:** limit and offset apply per entity (not globally across all entities).
|
|
233
|
+
All data is fetched from HA before slicing; limit/offset are client-side.
|
|
234
|
+
With multiple entity_ids, offset must be 0 — use a single entity_id for offset > 0.
|
|
235
|
+
Use has_more and next_offset from the response to paginate.
|
|
236
|
+
|
|
224
237
|
**Example -- history (default):**
|
|
225
238
|
```python
|
|
226
239
|
ha_get_history(entity_ids="sensor.bedroom_temperature", start_time="24h")
|
|
227
240
|
ha_get_history(entity_ids=["sensor.temperature", "sensor.humidity"], start_time="7d", limit=500)
|
|
241
|
+
ha_get_history(entity_ids="sensor.temperature", start_time="7d", limit=100, offset=100)
|
|
228
242
|
```
|
|
229
243
|
|
|
230
244
|
**Example -- statistics:**
|
|
@@ -232,12 +246,42 @@ class HistoryTools:
|
|
|
232
246
|
ha_get_history(source="statistics", entity_ids="sensor.total_energy_kwh", start_time="30d", period="day")
|
|
233
247
|
ha_get_history(source="statistics", entity_ids="sensor.living_room_temperature",
|
|
234
248
|
start_time="6m", period="month", statistic_types=["mean", "min", "max"])
|
|
249
|
+
ha_get_history(source="statistics", entity_ids="sensor.energy_kwh",
|
|
250
|
+
start_time="30d", period="5minute", limit=100, offset=200)
|
|
235
251
|
```
|
|
236
252
|
"""
|
|
237
253
|
try:
|
|
238
254
|
# Parse entity_ids
|
|
239
255
|
entity_id_list = _parse_entity_ids(entity_ids)
|
|
240
256
|
|
|
257
|
+
# Offset > 0 is only supported for single-entity requests.
|
|
258
|
+
# build_pagination_metadata applies per entity — limit=100 across
|
|
259
|
+
# 5 entities returns up to 500 rows with no top-level has_more signal.
|
|
260
|
+
# Coerce and validate offset before the multi-entity guard so that
|
|
261
|
+
# invalid strings (e.g. "garbage") produce VALIDATION_INVALID_PARAMETER
|
|
262
|
+
# instead of a bare ValueError swallowed by the outer except.
|
|
263
|
+
try:
|
|
264
|
+
_effective_offset_check = coerce_int_param(
|
|
265
|
+
offset,
|
|
266
|
+
param_name="offset",
|
|
267
|
+
default=0,
|
|
268
|
+
min_value=0,
|
|
269
|
+
)
|
|
270
|
+
except ValueError as e:
|
|
271
|
+
raise_tool_error(create_error_response(
|
|
272
|
+
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
273
|
+
str(e),
|
|
274
|
+
context={"parameter": "offset"},
|
|
275
|
+
suggestions=["Provide offset as a non-negative integer (e.g., 0)"],
|
|
276
|
+
))
|
|
277
|
+
if _effective_offset_check > 0 and len(entity_id_list) > 1:
|
|
278
|
+
raise_tool_error(create_error_response(
|
|
279
|
+
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
280
|
+
"offset > 0 requires a single entity_id",
|
|
281
|
+
context={"offset": offset, "entity_count": len(entity_id_list)},
|
|
282
|
+
suggestions=["Use a single entity_id when offset > 0, or use offset=0 for multi-entity requests."],
|
|
283
|
+
))
|
|
284
|
+
|
|
241
285
|
# Source-dependent default hours
|
|
242
286
|
default_hours = _DEFAULT_START_HOURS_BY_SOURCE[source]
|
|
243
287
|
|
|
@@ -259,12 +303,13 @@ class HistoryTools:
|
|
|
259
303
|
return await _fetch_statistics(
|
|
260
304
|
ws_client, self._client, entity_id_list,
|
|
261
305
|
start_dt, end_dt, period, statistic_types,
|
|
306
|
+
limit, offset,
|
|
262
307
|
)
|
|
263
308
|
else:
|
|
264
309
|
return await _fetch_history(
|
|
265
310
|
ws_client, self._client, entity_id_list,
|
|
266
311
|
start_dt, end_dt, minimal_response,
|
|
267
|
-
significant_changes_only, limit,
|
|
312
|
+
significant_changes_only, limit, offset,
|
|
268
313
|
_DEFAULT_HISTORY_LIMIT, _MAX_HISTORY_LIMIT,
|
|
269
314
|
)
|
|
270
315
|
finally:
|
|
@@ -371,6 +416,7 @@ async def _fetch_history(
|
|
|
371
416
|
minimal_response: bool,
|
|
372
417
|
significant_changes_only: bool,
|
|
373
418
|
limit: int | str | None,
|
|
419
|
+
offset: int | str | None,
|
|
374
420
|
default_limit: int,
|
|
375
421
|
max_limit: int,
|
|
376
422
|
) -> dict[str, Any]:
|
|
@@ -382,7 +428,7 @@ async def _fetch_history(
|
|
|
382
428
|
default=default_limit,
|
|
383
429
|
min_value=1,
|
|
384
430
|
max_value=max_limit,
|
|
385
|
-
)
|
|
431
|
+
)
|
|
386
432
|
except ValueError as e:
|
|
387
433
|
raise_tool_error(create_error_response(
|
|
388
434
|
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
@@ -391,6 +437,21 @@ async def _fetch_history(
|
|
|
391
437
|
suggestions=["Provide limit as an integer (e.g., 100)"],
|
|
392
438
|
))
|
|
393
439
|
|
|
440
|
+
try:
|
|
441
|
+
effective_offset = coerce_int_param(
|
|
442
|
+
offset,
|
|
443
|
+
param_name="offset",
|
|
444
|
+
default=0,
|
|
445
|
+
min_value=0,
|
|
446
|
+
)
|
|
447
|
+
except ValueError as e:
|
|
448
|
+
raise_tool_error(create_error_response(
|
|
449
|
+
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
450
|
+
str(e),
|
|
451
|
+
context={"parameter": "offset"},
|
|
452
|
+
suggestions=["Provide offset as a non-negative integer (e.g., 0)"],
|
|
453
|
+
))
|
|
454
|
+
|
|
394
455
|
command_params = {
|
|
395
456
|
"start_time": start_dt.isoformat(),
|
|
396
457
|
"end_time": end_dt.isoformat(),
|
|
@@ -422,10 +483,10 @@ async def _fetch_history(
|
|
|
422
483
|
|
|
423
484
|
for entity_id in entity_id_list:
|
|
424
485
|
entity_states = result_data.get(entity_id, [])
|
|
425
|
-
|
|
486
|
+
paged_states = entity_states[effective_offset : effective_offset + effective_limit]
|
|
426
487
|
|
|
427
488
|
formatted_states = []
|
|
428
|
-
for state in
|
|
489
|
+
for state in paged_states:
|
|
429
490
|
last_updated_raw = state.get("lu", state.get("last_updated"))
|
|
430
491
|
last_changed_raw = state.get("lc", state.get("last_changed"))
|
|
431
492
|
if last_changed_raw is None and last_updated_raw is not None:
|
|
@@ -440,6 +501,12 @@ async def _fetch_history(
|
|
|
440
501
|
state_entry["attributes"] = state.get("a", state.get("attributes", {}))
|
|
441
502
|
formatted_states.append(state_entry)
|
|
442
503
|
|
|
504
|
+
pagination = build_pagination_metadata(
|
|
505
|
+
total_count=len(entity_states),
|
|
506
|
+
offset=effective_offset,
|
|
507
|
+
limit=effective_limit,
|
|
508
|
+
count=len(formatted_states),
|
|
509
|
+
)
|
|
443
510
|
entities_history.append({
|
|
444
511
|
"entity_id": entity_id,
|
|
445
512
|
"period": {
|
|
@@ -447,9 +514,7 @@ async def _fetch_history(
|
|
|
447
514
|
"end": end_dt.isoformat(),
|
|
448
515
|
},
|
|
449
516
|
"states": formatted_states,
|
|
450
|
-
|
|
451
|
-
"total_available": len(entity_states),
|
|
452
|
-
"truncated": len(entity_states) > effective_limit,
|
|
517
|
+
**pagination,
|
|
453
518
|
})
|
|
454
519
|
|
|
455
520
|
history_data = {
|
|
@@ -464,6 +529,7 @@ async def _fetch_history(
|
|
|
464
529
|
"minimal_response": minimal_response,
|
|
465
530
|
"significant_changes_only": significant_changes_only,
|
|
466
531
|
"limit": effective_limit,
|
|
532
|
+
"offset": effective_offset,
|
|
467
533
|
},
|
|
468
534
|
}
|
|
469
535
|
|
|
@@ -478,8 +544,41 @@ async def _fetch_statistics(
|
|
|
478
544
|
end_dt: datetime,
|
|
479
545
|
period: str,
|
|
480
546
|
statistic_types: str | list[str] | None,
|
|
547
|
+
limit: int | str | None,
|
|
548
|
+
offset: int | str | None,
|
|
481
549
|
) -> dict[str, Any]:
|
|
482
550
|
"""Execute the recorder/statistics_during_period WebSocket call."""
|
|
551
|
+
try:
|
|
552
|
+
effective_limit = coerce_int_param(
|
|
553
|
+
limit,
|
|
554
|
+
param_name="limit",
|
|
555
|
+
default=_DEFAULT_HISTORY_LIMIT,
|
|
556
|
+
min_value=1,
|
|
557
|
+
max_value=_MAX_HISTORY_LIMIT,
|
|
558
|
+
)
|
|
559
|
+
except ValueError as e:
|
|
560
|
+
raise_tool_error(create_error_response(
|
|
561
|
+
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
562
|
+
str(e),
|
|
563
|
+
context={"parameter": "limit"},
|
|
564
|
+
suggestions=["Provide limit as an integer (e.g., 100)"],
|
|
565
|
+
))
|
|
566
|
+
|
|
567
|
+
try:
|
|
568
|
+
effective_offset = coerce_int_param(
|
|
569
|
+
offset,
|
|
570
|
+
param_name="offset",
|
|
571
|
+
default=0,
|
|
572
|
+
min_value=0,
|
|
573
|
+
)
|
|
574
|
+
except ValueError as e:
|
|
575
|
+
raise_tool_error(create_error_response(
|
|
576
|
+
ErrorCode.VALIDATION_INVALID_PARAMETER,
|
|
577
|
+
str(e),
|
|
578
|
+
context={"parameter": "offset"},
|
|
579
|
+
suggestions=["Provide offset as a non-negative integer (e.g., 0)"],
|
|
580
|
+
))
|
|
581
|
+
|
|
483
582
|
# Validate period
|
|
484
583
|
valid_periods = ["5minute", "hour", "day", "week", "month"]
|
|
485
584
|
if period not in valid_periods:
|
|
@@ -547,10 +646,11 @@ async def _fetch_statistics(
|
|
|
547
646
|
|
|
548
647
|
for entity_id in entity_id_list:
|
|
549
648
|
entity_stats = result_data.get(entity_id, [])
|
|
649
|
+
paged_stats = entity_stats[effective_offset : effective_offset + effective_limit]
|
|
550
650
|
formatted_stats = []
|
|
551
651
|
unit = None
|
|
552
652
|
|
|
553
|
-
for stat in
|
|
653
|
+
for stat in paged_stats:
|
|
554
654
|
stat_entry: dict[str, Any] = {"start": stat.get("start")}
|
|
555
655
|
for stat_type in all_stat_types:
|
|
556
656
|
if stat_type in stat:
|
|
@@ -559,12 +659,18 @@ async def _fetch_statistics(
|
|
|
559
659
|
unit = stat["unit_of_measurement"]
|
|
560
660
|
formatted_stats.append(stat_entry)
|
|
561
661
|
|
|
662
|
+
pagination = build_pagination_metadata(
|
|
663
|
+
total_count=len(entity_stats),
|
|
664
|
+
offset=effective_offset,
|
|
665
|
+
limit=effective_limit,
|
|
666
|
+
count=len(formatted_stats),
|
|
667
|
+
)
|
|
562
668
|
entities_statistics.append({
|
|
563
669
|
"entity_id": entity_id,
|
|
564
670
|
"period": period,
|
|
565
671
|
"statistics": formatted_stats,
|
|
566
|
-
"count": len(formatted_stats),
|
|
567
672
|
"unit_of_measurement": unit,
|
|
673
|
+
**pagination,
|
|
568
674
|
})
|
|
569
675
|
|
|
570
676
|
empty_entities: list[str] = [
|
|
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-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/resources/skills-vendor/AGENTS.md
RENAMED
|
File without changes
|
{ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/resources/skills-vendor/CLAUDE.md
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/resources/skills-vendor/LICENSE
RENAMED
|
File without changes
|
{ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/resources/skills-vendor/README.md
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
|
{ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/best_practice_checker.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
|
{ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_config_automations.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_config_dashboards.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_config_entry_flow.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_config_helpers.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/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-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/tools/tools_voice_assistant.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp/transforms/categorized_search.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
|
{ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/src/ha_mcp_dev.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{ha_mcp_dev-7.2.0.dev362 → ha_mcp_dev-7.2.0.dev363}/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
|