ha-mcp-dev 6.3.1.dev137__py3-none-any.whl
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/__init__.py +51 -0
- ha_mcp/__main__.py +753 -0
- ha_mcp/_pypi_marker +0 -0
- ha_mcp/auth/__init__.py +10 -0
- ha_mcp/auth/consent_form.py +501 -0
- ha_mcp/auth/provider.py +786 -0
- ha_mcp/client/__init__.py +10 -0
- ha_mcp/client/rest_client.py +924 -0
- ha_mcp/client/websocket_client.py +656 -0
- ha_mcp/client/websocket_listener.py +340 -0
- ha_mcp/config.py +175 -0
- ha_mcp/errors.py +399 -0
- ha_mcp/py.typed +0 -0
- ha_mcp/resources/card_types.json +48 -0
- ha_mcp/resources/dashboard_guide.md +573 -0
- ha_mcp/server.py +189 -0
- ha_mcp/smoke_test.py +108 -0
- ha_mcp/tools/__init__.py +11 -0
- ha_mcp/tools/backup.py +457 -0
- ha_mcp/tools/device_control.py +687 -0
- ha_mcp/tools/enhanced.py +191 -0
- ha_mcp/tools/helpers.py +184 -0
- ha_mcp/tools/registry.py +199 -0
- ha_mcp/tools/smart_search.py +797 -0
- ha_mcp/tools/tools_addons.py +343 -0
- ha_mcp/tools/tools_areas.py +478 -0
- ha_mcp/tools/tools_blueprints.py +279 -0
- ha_mcp/tools/tools_bug_report.py +570 -0
- ha_mcp/tools/tools_calendar.py +391 -0
- ha_mcp/tools/tools_camera.py +149 -0
- ha_mcp/tools/tools_config_automations.py +508 -0
- ha_mcp/tools/tools_config_dashboards.py +1502 -0
- ha_mcp/tools/tools_config_entry_flow.py +223 -0
- ha_mcp/tools/tools_config_helpers.py +925 -0
- ha_mcp/tools/tools_config_info.py +258 -0
- ha_mcp/tools/tools_config_scripts.py +267 -0
- ha_mcp/tools/tools_entities.py +76 -0
- ha_mcp/tools/tools_filesystem.py +589 -0
- ha_mcp/tools/tools_groups.py +306 -0
- ha_mcp/tools/tools_hacs.py +787 -0
- ha_mcp/tools/tools_history.py +714 -0
- ha_mcp/tools/tools_integrations.py +297 -0
- ha_mcp/tools/tools_labels.py +713 -0
- ha_mcp/tools/tools_mcp_component.py +305 -0
- ha_mcp/tools/tools_registry.py +1115 -0
- ha_mcp/tools/tools_resources.py +622 -0
- ha_mcp/tools/tools_search.py +573 -0
- ha_mcp/tools/tools_service.py +211 -0
- ha_mcp/tools/tools_services.py +352 -0
- ha_mcp/tools/tools_system.py +363 -0
- ha_mcp/tools/tools_todo.py +530 -0
- ha_mcp/tools/tools_traces.py +496 -0
- ha_mcp/tools/tools_updates.py +476 -0
- ha_mcp/tools/tools_utility.py +519 -0
- ha_mcp/tools/tools_voice_assistant.py +345 -0
- ha_mcp/tools/tools_zones.py +387 -0
- ha_mcp/tools/util_helpers.py +199 -0
- ha_mcp/utils/__init__.py +30 -0
- ha_mcp/utils/domain_handlers.py +380 -0
- ha_mcp/utils/fuzzy_search.py +339 -0
- ha_mcp/utils/operation_manager.py +442 -0
- ha_mcp/utils/usage_logger.py +290 -0
- ha_mcp_dev-6.3.1.dev137.dist-info/METADATA +229 -0
- ha_mcp_dev-6.3.1.dev137.dist-info/RECORD +71 -0
- ha_mcp_dev-6.3.1.dev137.dist-info/WHEEL +5 -0
- ha_mcp_dev-6.3.1.dev137.dist-info/entry_points.txt +7 -0
- ha_mcp_dev-6.3.1.dev137.dist-info/licenses/LICENSE +21 -0
- ha_mcp_dev-6.3.1.dev137.dist-info/top_level.txt +2 -0
- tests/__init__.py +1 -0
- tests/test_constants.py +15 -0
- tests/test_env_manager.py +336 -0
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Todo/Shopping List management tools for Home Assistant MCP server.
|
|
3
|
+
|
|
4
|
+
This module provides tools for managing Home Assistant todo lists including:
|
|
5
|
+
- Listing all todo list entities
|
|
6
|
+
- Getting items from a todo list
|
|
7
|
+
- Adding items to a todo list
|
|
8
|
+
- Updating/completing todo items
|
|
9
|
+
- Removing items from a todo list
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
from typing import Annotated, Any, Literal
|
|
14
|
+
|
|
15
|
+
from pydantic import Field
|
|
16
|
+
|
|
17
|
+
from .helpers import log_tool_usage
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def register_todo_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
23
|
+
"""Register Home Assistant todo list management tools."""
|
|
24
|
+
|
|
25
|
+
@mcp.tool(annotations={"idempotentHint": True, "readOnlyHint": True, "tags": ["todo"], "title": "Get Todo"})
|
|
26
|
+
@log_tool_usage
|
|
27
|
+
async def ha_get_todo(
|
|
28
|
+
entity_id: Annotated[
|
|
29
|
+
str | None,
|
|
30
|
+
Field(
|
|
31
|
+
description="Todo list entity ID (e.g., 'todo.shopping_list'). "
|
|
32
|
+
"If omitted, lists all todo list entities.",
|
|
33
|
+
default=None,
|
|
34
|
+
),
|
|
35
|
+
] = None,
|
|
36
|
+
status: Annotated[
|
|
37
|
+
Literal["needs_action", "completed"] | None,
|
|
38
|
+
Field(
|
|
39
|
+
description="Filter items by status: 'needs_action' for incomplete, 'completed' for done. "
|
|
40
|
+
"Only applies when entity_id is provided.",
|
|
41
|
+
default=None,
|
|
42
|
+
),
|
|
43
|
+
] = None,
|
|
44
|
+
) -> dict[str, Any]:
|
|
45
|
+
"""
|
|
46
|
+
Get todo lists or items - list all todo lists or get items from a specific list.
|
|
47
|
+
|
|
48
|
+
Without an entity_id: Lists all todo list entities in Home Assistant.
|
|
49
|
+
With an entity_id: Gets items from that specific todo list, optionally filtered by status.
|
|
50
|
+
|
|
51
|
+
**LISTING TODO LISTS (entity_id omitted):**
|
|
52
|
+
Returns all entities in the 'todo' domain, including shopping lists
|
|
53
|
+
and any other todo-type integrations.
|
|
54
|
+
|
|
55
|
+
Each todo list includes:
|
|
56
|
+
- entity_id: The unique identifier (e.g., 'todo.shopping_list')
|
|
57
|
+
- friendly_name: Human-readable name
|
|
58
|
+
- state: Number of incomplete items or current status
|
|
59
|
+
|
|
60
|
+
**GETTING TODO ITEMS (entity_id provided):**
|
|
61
|
+
Retrieves items from the specified todo list.
|
|
62
|
+
|
|
63
|
+
Status filter values:
|
|
64
|
+
- needs_action: Items that still need to be done
|
|
65
|
+
- completed: Items that have been marked as done
|
|
66
|
+
- None (default): Returns all items regardless of status
|
|
67
|
+
|
|
68
|
+
Item properties:
|
|
69
|
+
- uid: Unique identifier for the item
|
|
70
|
+
- summary: The item text/description
|
|
71
|
+
- status: Current status (needs_action or completed)
|
|
72
|
+
- description: Optional detailed description
|
|
73
|
+
- due: Optional due date (if supported)
|
|
74
|
+
|
|
75
|
+
EXAMPLES:
|
|
76
|
+
- List all todo lists: ha_get_todo()
|
|
77
|
+
- Get all items: ha_get_todo("todo.shopping_list")
|
|
78
|
+
- Get incomplete items: ha_get_todo("todo.shopping_list", status="needs_action")
|
|
79
|
+
- Get completed items: ha_get_todo("todo.shopping_list", status="completed")
|
|
80
|
+
|
|
81
|
+
USE CASES:
|
|
82
|
+
- "What todo lists do I have?"
|
|
83
|
+
- "Show me my shopping list"
|
|
84
|
+
- "What's on my todo list?"
|
|
85
|
+
- "Show completed items"
|
|
86
|
+
"""
|
|
87
|
+
try:
|
|
88
|
+
# List mode - no entity_id provided
|
|
89
|
+
if entity_id is None:
|
|
90
|
+
# Get all states and filter by todo domain
|
|
91
|
+
states = await client.get_states()
|
|
92
|
+
|
|
93
|
+
todo_lists = []
|
|
94
|
+
for state in states:
|
|
95
|
+
eid = state.get("entity_id", "")
|
|
96
|
+
if eid.startswith("todo."):
|
|
97
|
+
todo_lists.append({
|
|
98
|
+
"entity_id": eid,
|
|
99
|
+
"friendly_name": state.get("attributes", {}).get(
|
|
100
|
+
"friendly_name", eid
|
|
101
|
+
),
|
|
102
|
+
"state": state.get("state"),
|
|
103
|
+
"icon": state.get("attributes", {}).get("icon"),
|
|
104
|
+
"supported_features": state.get("attributes", {}).get(
|
|
105
|
+
"supported_features"
|
|
106
|
+
),
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
"success": True,
|
|
111
|
+
"count": len(todo_lists),
|
|
112
|
+
"todo_lists": todo_lists,
|
|
113
|
+
"message": f"Found {len(todo_lists)} todo list(s)",
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# Get items mode - entity_id provided
|
|
117
|
+
# Validate entity_id format
|
|
118
|
+
if not entity_id.startswith("todo."):
|
|
119
|
+
return {
|
|
120
|
+
"success": False,
|
|
121
|
+
"error": f"Invalid entity_id: {entity_id}. Must start with 'todo.'",
|
|
122
|
+
"suggestions": ["Use ha_get_todo() without entity_id to find valid todo list entity IDs"],
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
# Use WebSocket to get todo items
|
|
126
|
+
message: dict[str, Any] = {
|
|
127
|
+
"type": "todo/item/list",
|
|
128
|
+
"entity_id": entity_id,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
result = await client.send_websocket_message(message)
|
|
132
|
+
|
|
133
|
+
if result.get("success"):
|
|
134
|
+
items = result.get("result", {}).get("items", [])
|
|
135
|
+
|
|
136
|
+
# Filter by status if specified
|
|
137
|
+
if status:
|
|
138
|
+
items = [item for item in items if item.get("status") == status]
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
"success": True,
|
|
142
|
+
"entity_id": entity_id,
|
|
143
|
+
"status_filter": status,
|
|
144
|
+
"count": len(items),
|
|
145
|
+
"items": items,
|
|
146
|
+
"message": f"Found {len(items)} item(s) in {entity_id}",
|
|
147
|
+
}
|
|
148
|
+
else:
|
|
149
|
+
error = result.get("error", "Unknown error")
|
|
150
|
+
return {
|
|
151
|
+
"success": False,
|
|
152
|
+
"error": f"Failed to get todo items: {error}",
|
|
153
|
+
"entity_id": entity_id,
|
|
154
|
+
"suggestions": [
|
|
155
|
+
"Verify the entity_id exists using ha_get_todo()",
|
|
156
|
+
"Check Home Assistant WebSocket connection",
|
|
157
|
+
],
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
except Exception as e:
|
|
161
|
+
logger.error(f"Error in ha_get_todo: {e}")
|
|
162
|
+
if entity_id:
|
|
163
|
+
return {
|
|
164
|
+
"success": False,
|
|
165
|
+
"error": f"Failed to get todo items: {str(e)}",
|
|
166
|
+
"entity_id": entity_id,
|
|
167
|
+
"suggestions": [
|
|
168
|
+
"Check Home Assistant connection",
|
|
169
|
+
"Verify entity_id is correct",
|
|
170
|
+
"Use ha_get_todo() to find valid todo lists",
|
|
171
|
+
],
|
|
172
|
+
}
|
|
173
|
+
else:
|
|
174
|
+
return {
|
|
175
|
+
"success": False,
|
|
176
|
+
"error": f"Failed to list todo lists: {str(e)}",
|
|
177
|
+
"suggestions": [
|
|
178
|
+
"Check Home Assistant connection",
|
|
179
|
+
"Verify todo integration is enabled",
|
|
180
|
+
],
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
@mcp.tool(annotations={"destructiveHint": True, "tags": ["todo"], "title": "Add Todo Item"})
|
|
184
|
+
@log_tool_usage
|
|
185
|
+
async def ha_add_todo_item(
|
|
186
|
+
entity_id: Annotated[
|
|
187
|
+
str,
|
|
188
|
+
Field(
|
|
189
|
+
description="Todo list entity ID (e.g., 'todo.shopping_list')"
|
|
190
|
+
),
|
|
191
|
+
],
|
|
192
|
+
summary: Annotated[
|
|
193
|
+
str,
|
|
194
|
+
Field(description="Item text/name to add (e.g., 'Buy milk')"),
|
|
195
|
+
],
|
|
196
|
+
description: Annotated[
|
|
197
|
+
str | None,
|
|
198
|
+
Field(
|
|
199
|
+
description="Optional detailed description for the item",
|
|
200
|
+
default=None,
|
|
201
|
+
),
|
|
202
|
+
] = None,
|
|
203
|
+
due_date: Annotated[
|
|
204
|
+
str | None,
|
|
205
|
+
Field(
|
|
206
|
+
description="Optional due date in YYYY-MM-DD format (e.g., '2024-12-25')",
|
|
207
|
+
default=None,
|
|
208
|
+
),
|
|
209
|
+
] = None,
|
|
210
|
+
due_datetime: Annotated[
|
|
211
|
+
str | None,
|
|
212
|
+
Field(
|
|
213
|
+
description="Optional due datetime in ISO format (e.g., '2024-12-25T14:00:00')",
|
|
214
|
+
default=None,
|
|
215
|
+
),
|
|
216
|
+
] = None,
|
|
217
|
+
) -> dict[str, Any]:
|
|
218
|
+
"""
|
|
219
|
+
Add an item to a Home Assistant todo list.
|
|
220
|
+
|
|
221
|
+
Creates a new item in the specified todo list with optional description and due date.
|
|
222
|
+
|
|
223
|
+
PARAMETERS:
|
|
224
|
+
- entity_id: The todo list to add to (e.g., 'todo.shopping_list')
|
|
225
|
+
- summary: The item text (required)
|
|
226
|
+
- description: Optional detailed description
|
|
227
|
+
- due_date: Optional due date (YYYY-MM-DD format)
|
|
228
|
+
- due_datetime: Optional due datetime (ISO format, overrides due_date if both provided)
|
|
229
|
+
|
|
230
|
+
EXAMPLES:
|
|
231
|
+
- Add simple item: ha_add_todo_item("todo.shopping_list", "Buy milk")
|
|
232
|
+
- Add with description: ha_add_todo_item("todo.shopping_list", "Buy milk", description="2% organic")
|
|
233
|
+
- Add with due date: ha_add_todo_item("todo.tasks", "Pay bills", due_date="2024-12-31")
|
|
234
|
+
|
|
235
|
+
USE CASES:
|
|
236
|
+
- "Add milk to my shopping list"
|
|
237
|
+
- "Add 'call mom' to my todo list"
|
|
238
|
+
- "Remind me to pay rent by the 1st"
|
|
239
|
+
|
|
240
|
+
NOTE: Not all todo integrations support all features (description, due dates).
|
|
241
|
+
The Shopping List integration only supports summary.
|
|
242
|
+
"""
|
|
243
|
+
try:
|
|
244
|
+
# Validate entity_id format
|
|
245
|
+
if not entity_id.startswith("todo."):
|
|
246
|
+
return {
|
|
247
|
+
"success": False,
|
|
248
|
+
"error": f"Invalid entity_id: {entity_id}. Must start with 'todo.'",
|
|
249
|
+
"suggestions": ["Use ha_get_todo() to find valid todo list entity IDs"],
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
# Build service data
|
|
253
|
+
service_data: dict[str, Any] = {
|
|
254
|
+
"entity_id": entity_id,
|
|
255
|
+
"item": summary,
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
# Add optional fields if provided
|
|
259
|
+
if description:
|
|
260
|
+
service_data["description"] = description
|
|
261
|
+
if due_datetime:
|
|
262
|
+
service_data["due_datetime"] = due_datetime
|
|
263
|
+
elif due_date:
|
|
264
|
+
service_data["due_date"] = due_date
|
|
265
|
+
|
|
266
|
+
# Call the service
|
|
267
|
+
result = await client.call_service("todo", "add_item", service_data)
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
"success": True,
|
|
271
|
+
"entity_id": entity_id,
|
|
272
|
+
"item": summary,
|
|
273
|
+
"description": description,
|
|
274
|
+
"due_date": due_date,
|
|
275
|
+
"due_datetime": due_datetime,
|
|
276
|
+
"result": result,
|
|
277
|
+
"message": f"Successfully added '{summary}' to {entity_id}",
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
except Exception as e:
|
|
281
|
+
logger.error(f"Error adding todo item: {e}")
|
|
282
|
+
return {
|
|
283
|
+
"success": False,
|
|
284
|
+
"error": f"Failed to add todo item: {str(e)}",
|
|
285
|
+
"entity_id": entity_id,
|
|
286
|
+
"item": summary,
|
|
287
|
+
"suggestions": [
|
|
288
|
+
"Verify the entity_id exists using ha_get_todo()",
|
|
289
|
+
"Check if the todo list supports adding items",
|
|
290
|
+
"Some todo lists may not support description or due dates",
|
|
291
|
+
],
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
@mcp.tool(annotations={"destructiveHint": True, "tags": ["todo"], "title": "Update Todo Item"})
|
|
295
|
+
@log_tool_usage
|
|
296
|
+
async def ha_update_todo_item(
|
|
297
|
+
entity_id: Annotated[
|
|
298
|
+
str,
|
|
299
|
+
Field(
|
|
300
|
+
description="Todo list entity ID (e.g., 'todo.shopping_list')"
|
|
301
|
+
),
|
|
302
|
+
],
|
|
303
|
+
item: Annotated[
|
|
304
|
+
str,
|
|
305
|
+
Field(
|
|
306
|
+
description="Item to update - can be the item UID or the exact item summary/name"
|
|
307
|
+
),
|
|
308
|
+
],
|
|
309
|
+
rename: Annotated[
|
|
310
|
+
str | None,
|
|
311
|
+
Field(
|
|
312
|
+
description="New name/summary for the item",
|
|
313
|
+
default=None,
|
|
314
|
+
),
|
|
315
|
+
] = None,
|
|
316
|
+
status: Annotated[
|
|
317
|
+
Literal["needs_action", "completed"] | None,
|
|
318
|
+
Field(
|
|
319
|
+
description="New status: 'completed' to mark done, 'needs_action' to mark incomplete",
|
|
320
|
+
default=None,
|
|
321
|
+
),
|
|
322
|
+
] = None,
|
|
323
|
+
description: Annotated[
|
|
324
|
+
str | None,
|
|
325
|
+
Field(
|
|
326
|
+
description="New description for the item",
|
|
327
|
+
default=None,
|
|
328
|
+
),
|
|
329
|
+
] = None,
|
|
330
|
+
due_date: Annotated[
|
|
331
|
+
str | None,
|
|
332
|
+
Field(
|
|
333
|
+
description="New due date in YYYY-MM-DD format",
|
|
334
|
+
default=None,
|
|
335
|
+
),
|
|
336
|
+
] = None,
|
|
337
|
+
due_datetime: Annotated[
|
|
338
|
+
str | None,
|
|
339
|
+
Field(
|
|
340
|
+
description="New due datetime in ISO format",
|
|
341
|
+
default=None,
|
|
342
|
+
),
|
|
343
|
+
] = None,
|
|
344
|
+
) -> dict[str, Any]:
|
|
345
|
+
"""
|
|
346
|
+
Update or complete a todo item in Home Assistant.
|
|
347
|
+
|
|
348
|
+
Modifies an existing item in the specified todo list. Can be used to:
|
|
349
|
+
- Mark items as completed or incomplete
|
|
350
|
+
- Rename items
|
|
351
|
+
- Update descriptions and due dates
|
|
352
|
+
|
|
353
|
+
IDENTIFYING ITEMS:
|
|
354
|
+
- Use the item's UID (from ha_get_todo)
|
|
355
|
+
- Or use the exact item summary/name text
|
|
356
|
+
|
|
357
|
+
STATUS VALUES:
|
|
358
|
+
- completed: Mark the item as done
|
|
359
|
+
- needs_action: Mark the item as not done (reopen)
|
|
360
|
+
|
|
361
|
+
EXAMPLES:
|
|
362
|
+
- Complete item: ha_update_todo_item("todo.shopping_list", "Buy milk", status="completed")
|
|
363
|
+
- Rename item: ha_update_todo_item("todo.tasks", "Old task", rename="New task name")
|
|
364
|
+
- Update due date: ha_update_todo_item("todo.tasks", "Pay bills", due_date="2024-12-31")
|
|
365
|
+
- Reopen item: ha_update_todo_item("todo.tasks", "Task to redo", status="needs_action")
|
|
366
|
+
|
|
367
|
+
USE CASES:
|
|
368
|
+
- "Mark milk as bought"
|
|
369
|
+
- "Complete the eggs on my shopping list"
|
|
370
|
+
- "Change the due date for my task"
|
|
371
|
+
- "Rename 'call mom' to 'video call with mom'"
|
|
372
|
+
|
|
373
|
+
NOTE: At least one update field (rename, status, description, due_date, due_datetime) must be provided.
|
|
374
|
+
"""
|
|
375
|
+
try:
|
|
376
|
+
# Validate entity_id format
|
|
377
|
+
if not entity_id.startswith("todo."):
|
|
378
|
+
return {
|
|
379
|
+
"success": False,
|
|
380
|
+
"error": f"Invalid entity_id: {entity_id}. Must start with 'todo.'",
|
|
381
|
+
"suggestions": ["Use ha_get_todo() to find valid todo list entity IDs"],
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
# Validate at least one update field is provided
|
|
385
|
+
if not any([rename, status, description, due_date, due_datetime]):
|
|
386
|
+
return {
|
|
387
|
+
"success": False,
|
|
388
|
+
"error": "At least one update field must be provided (rename, status, description, due_date, or due_datetime)",
|
|
389
|
+
"suggestions": ["Specify what to update, e.g., status='completed' to mark item done"],
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
# Build service data
|
|
393
|
+
service_data: dict[str, Any] = {
|
|
394
|
+
"entity_id": entity_id,
|
|
395
|
+
"item": item,
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
# Add update fields if provided
|
|
399
|
+
if rename:
|
|
400
|
+
service_data["rename"] = rename
|
|
401
|
+
if status:
|
|
402
|
+
service_data["status"] = status
|
|
403
|
+
if description:
|
|
404
|
+
service_data["description"] = description
|
|
405
|
+
if due_datetime:
|
|
406
|
+
service_data["due_datetime"] = due_datetime
|
|
407
|
+
elif due_date:
|
|
408
|
+
service_data["due_date"] = due_date
|
|
409
|
+
|
|
410
|
+
# Call the service
|
|
411
|
+
result = await client.call_service("todo", "update_item", service_data)
|
|
412
|
+
|
|
413
|
+
# Build response message
|
|
414
|
+
updates = []
|
|
415
|
+
if rename:
|
|
416
|
+
updates.append(f"renamed to '{rename}'")
|
|
417
|
+
if status:
|
|
418
|
+
updates.append(f"status set to '{status}'")
|
|
419
|
+
if description:
|
|
420
|
+
updates.append("description updated")
|
|
421
|
+
if due_date or due_datetime:
|
|
422
|
+
updates.append("due date updated")
|
|
423
|
+
|
|
424
|
+
update_msg = ", ".join(updates) if updates else "updated"
|
|
425
|
+
|
|
426
|
+
return {
|
|
427
|
+
"success": True,
|
|
428
|
+
"entity_id": entity_id,
|
|
429
|
+
"item": item,
|
|
430
|
+
"updates": {
|
|
431
|
+
"rename": rename,
|
|
432
|
+
"status": status,
|
|
433
|
+
"description": description,
|
|
434
|
+
"due_date": due_date,
|
|
435
|
+
"due_datetime": due_datetime,
|
|
436
|
+
},
|
|
437
|
+
"result": result,
|
|
438
|
+
"message": f"Successfully updated '{item}' in {entity_id}: {update_msg}",
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
except Exception as e:
|
|
442
|
+
logger.error(f"Error updating todo item: {e}")
|
|
443
|
+
return {
|
|
444
|
+
"success": False,
|
|
445
|
+
"error": f"Failed to update todo item: {str(e)}",
|
|
446
|
+
"entity_id": entity_id,
|
|
447
|
+
"item": item,
|
|
448
|
+
"suggestions": [
|
|
449
|
+
"Verify the item exists using ha_get_todo()",
|
|
450
|
+
"Check if you're using the correct item name or UID",
|
|
451
|
+
"Some todo lists may not support all update operations",
|
|
452
|
+
],
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
@mcp.tool(annotations={"destructiveHint": True, "idempotentHint": True, "tags": ["todo"], "title": "Remove Todo Item"})
|
|
456
|
+
@log_tool_usage
|
|
457
|
+
async def ha_remove_todo_item(
|
|
458
|
+
entity_id: Annotated[
|
|
459
|
+
str,
|
|
460
|
+
Field(
|
|
461
|
+
description="Todo list entity ID (e.g., 'todo.shopping_list')"
|
|
462
|
+
),
|
|
463
|
+
],
|
|
464
|
+
item: Annotated[
|
|
465
|
+
str,
|
|
466
|
+
Field(
|
|
467
|
+
description="Item to remove - can be the item UID or the exact item summary/name"
|
|
468
|
+
),
|
|
469
|
+
],
|
|
470
|
+
) -> dict[str, Any]:
|
|
471
|
+
"""
|
|
472
|
+
Remove an item from a Home Assistant todo list.
|
|
473
|
+
|
|
474
|
+
Permanently deletes an item from the specified todo list.
|
|
475
|
+
|
|
476
|
+
IDENTIFYING ITEMS:
|
|
477
|
+
- Use the item's UID (from ha_get_todo)
|
|
478
|
+
- Or use the exact item summary/name text
|
|
479
|
+
|
|
480
|
+
EXAMPLES:
|
|
481
|
+
- Remove by name: ha_remove_todo_item("todo.shopping_list", "Buy milk")
|
|
482
|
+
- Remove by UID: ha_remove_todo_item("todo.shopping_list", "abc123-uid")
|
|
483
|
+
|
|
484
|
+
USE CASES:
|
|
485
|
+
- "Remove milk from my shopping list"
|
|
486
|
+
- "Delete the eggs item"
|
|
487
|
+
- "Clear 'call mom' from my todo"
|
|
488
|
+
|
|
489
|
+
WARNING: This permanently removes the item. To mark as completed instead,
|
|
490
|
+
use ha_update_todo_item() with status="completed".
|
|
491
|
+
"""
|
|
492
|
+
try:
|
|
493
|
+
# Validate entity_id format
|
|
494
|
+
if not entity_id.startswith("todo."):
|
|
495
|
+
return {
|
|
496
|
+
"success": False,
|
|
497
|
+
"error": f"Invalid entity_id: {entity_id}. Must start with 'todo.'",
|
|
498
|
+
"suggestions": ["Use ha_get_todo() to find valid todo list entity IDs"],
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
# Build service data
|
|
502
|
+
service_data: dict[str, Any] = {
|
|
503
|
+
"entity_id": entity_id,
|
|
504
|
+
"item": item,
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
# Call the service
|
|
508
|
+
result = await client.call_service("todo", "remove_item", service_data)
|
|
509
|
+
|
|
510
|
+
return {
|
|
511
|
+
"success": True,
|
|
512
|
+
"entity_id": entity_id,
|
|
513
|
+
"item": item,
|
|
514
|
+
"result": result,
|
|
515
|
+
"message": f"Successfully removed '{item}' from {entity_id}",
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
except Exception as e:
|
|
519
|
+
logger.error(f"Error removing todo item: {e}")
|
|
520
|
+
return {
|
|
521
|
+
"success": False,
|
|
522
|
+
"error": f"Failed to remove todo item: {str(e)}",
|
|
523
|
+
"entity_id": entity_id,
|
|
524
|
+
"item": item,
|
|
525
|
+
"suggestions": [
|
|
526
|
+
"Verify the item exists using ha_get_todo()",
|
|
527
|
+
"Check if you're using the correct item name or UID",
|
|
528
|
+
"Make sure the item hasn't already been removed",
|
|
529
|
+
],
|
|
530
|
+
}
|