feat(lists): implement create_list + delete_list (v0.5.0)
Adds two new MCP tools: - create_list(name, list_type, shared_to_all, color, emoji) POST taskcreatelist; returns full list object incl. metaId - delete_list(list_id) – with canDelete safety guard POST taskdeletelist; param 'id' (same pattern as metadelete) Both endpoints verified via FW_DEBUG=1 on 2026-04-16. SPEC.md and CLAUDE.md updated with verified parameter names and response structures. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -24,19 +24,22 @@ und wird in Claude Desktop eingebunden.
|
||||
|
||||
## Aktueller Stand
|
||||
|
||||
### Implementierte Tools (v0.4.x)
|
||||
### Implementierte Tools (v0.5.x)
|
||||
|
||||
| Kategorie | Tools |
|
||||
|---|---|
|
||||
| Lesen | `get_circles`, `get_members`, `get_lists`, `get_tasks`, `get_categories`, `get_activities` |
|
||||
| Tasks | `create_task`, `update_task`, `toggle_task`, `delete_task` |
|
||||
| Listen | `create_list`, `delete_list` |
|
||||
| Kategorien | `create_category`, `delete_category` |
|
||||
| Aktivitäten | `like_post` |
|
||||
|
||||
## Roadmap
|
||||
|
||||
- v0.4.x: Kategorie-Management, Task-Felder (due_date, assignee, list_id) ← aktuell
|
||||
- v0.5.x: Erinnerungen + Wiederholungen (Premium-Account erforderlich)
|
||||
- v0.4.x: Kategorie-Management, Task-Felder (due_date, assignee, list_id) ✓
|
||||
- v0.5.x: Listen-Management (create_list, delete_list) ← aktuell
|
||||
- v0.5.1: update_list (Umbenennen), Sharing-Verwaltung
|
||||
- v0.6.x: Erinnerungen + Wiederholungen (Premium-Account erforderlich)
|
||||
- v2.0: Schreibzugriff auf Wall-Posts (Erstellen, Kommentieren)
|
||||
|
||||
|
||||
@@ -121,6 +124,8 @@ Fehler bei falschen Parametern kommen nicht immer auf Top-Level:
|
||||
| `wallmood` | `wall_message_id`, `moodType` | `"STAR"` für Like |
|
||||
| `taskcategoryput` | `name`, `emoji` | – |
|
||||
| `taskcategorydelete` | `id` | metaId der Kategorie |
|
||||
| `taskcreatelist` | `name`, `taskListType`, `sharedToAll`, `color`, `emoji` | `taskListType`: `"SHOPPING_LIST"`/`"TODOS"` |
|
||||
| `taskdeletelist` | `id` | metaId der Liste |
|
||||
|
||||
### Self-Like-Restriction
|
||||
Eigene Posts können nicht geliked werden. API antwortet 200, macht aber nichts.
|
||||
|
||||
@@ -280,6 +280,51 @@ POST https://api.familywall.com/api/taskcategorydelete
|
||||
gelöscht werden, sind dann aber dauerhaft weg und nicht wiederherstellbar.
|
||||
MCP-Server schützt dagegen durch Check auf `rights.canDelete`.
|
||||
|
||||
### `taskcreatelist` – Liste erstellen
|
||||
POST https://api.familywall.com/api/taskcreatelist
|
||||
|
||||
**Body-Parameter:**
|
||||
|
||||
| Parameter | Pflicht | Wert |
|
||||
|---|---|---|
|
||||
| `name` | ja | Listen-Name (max 200 Zeichen) |
|
||||
| `taskListType` | ja | `"SHOPPING_LIST"` oder `"TODOS"` |
|
||||
| `sharedToAll` | nein | `"true"` / `"false"` (default: `"true"`) |
|
||||
| `color` | nein | Hex-Farbwert z.B. `"#4784EC"` |
|
||||
| `emoji` | nein | Unicode-Emoji z.B. `"🛒"` |
|
||||
|
||||
**Response:**
|
||||
```
|
||||
a00.r.r → vollständiges Listen-Objekt
|
||||
.metaId → neue Listen-ID (z.B. "taskList/23431854_29759623")
|
||||
.name → Listen-Name
|
||||
.taskListType → SHOPPING_LIST oder TODOS
|
||||
.sharedToAll → "true" / "false"
|
||||
.rights.canDelete → "true" (user-created lists)
|
||||
```
|
||||
|
||||
**Verifiziert am:** 2026-04-16 via FW_DEBUG=1
|
||||
|
||||
### `taskdeletelist` – Liste löschen
|
||||
POST https://api.familywall.com/api/taskdeletelist
|
||||
|
||||
**Body-Parameter:**
|
||||
|
||||
| Parameter | Wert |
|
||||
|---|---|
|
||||
| `id` | Listen-metaId ⚠️ nicht `listId` oder `taskListId`! |
|
||||
|
||||
**Response:**
|
||||
```
|
||||
a00.r.r → "true" (String)
|
||||
```
|
||||
|
||||
**Hinweis:** Löscht die Liste und alle enthaltenen Tasks unwiderruflich.
|
||||
System-Listen (`rights.canDelete` fehlt oder `null`) sind nicht löschbar.
|
||||
MCP-Server prüft dies vor dem Löschen via `taskgettasklists`.
|
||||
|
||||
**Verifiziert am:** 2026-04-16 via FW_DEBUG=1
|
||||
|
||||
### `taskgettasklists` – Listen abrufen (alternativ)
|
||||
POST https://api.familywall.com/api/taskgettasklists
|
||||
|
||||
|
||||
@@ -869,6 +869,157 @@ def delete_task(task_id: str) -> str:
|
||||
return json.dumps({"deleted": True, "id": task_id}, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tool: create_list
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def create_list(
|
||||
name: str,
|
||||
list_type: str,
|
||||
shared_to_all: bool = True,
|
||||
color: str | None = None,
|
||||
emoji: str | None = None,
|
||||
) -> str:
|
||||
"""Create a new task list in Family Wall.
|
||||
|
||||
IMPORTANT: Ask the user for confirmation before calling this tool.
|
||||
|
||||
Args:
|
||||
name: Display name for the new list (max 200 characters).
|
||||
list_type: List type — either ``"SHOPPING_LIST"`` or ``"TODOS"``.
|
||||
shared_to_all: When ``True`` (default) the list is shared with all
|
||||
circle members. When ``False`` it is private to the creator.
|
||||
color: Optional background colour as a hex string (e.g. ``"#4784EC"``).
|
||||
emoji: Optional Unicode emoji to use as the list icon (e.g. ``"🛒"``).
|
||||
|
||||
Returns:
|
||||
JSON with the new list object on success, or an error message.
|
||||
"""
|
||||
if list_type not in ("SHOPPING_LIST", "TODOS"):
|
||||
return "Error: list_type must be 'SHOPPING_LIST' or 'TODOS'."
|
||||
if len(name) > 200:
|
||||
return "Error: name must not exceed 200 characters."
|
||||
|
||||
params: dict[str, Any] = {
|
||||
"name": name,
|
||||
"taskListType": list_type,
|
||||
"sharedToAll": "true" if shared_to_all else "false",
|
||||
}
|
||||
if color:
|
||||
params["color"] = color
|
||||
if emoji:
|
||||
params["emoji"] = emoji
|
||||
|
||||
try:
|
||||
data = _authenticated_call("taskcreatelist", params)
|
||||
except RuntimeError as exc:
|
||||
return f"Error: {exc}"
|
||||
|
||||
try:
|
||||
list_obj = data["a00"]["r"]["r"]
|
||||
meta_id: str = list_obj["metaId"]
|
||||
except (KeyError, TypeError):
|
||||
return json.dumps(
|
||||
{"warning": "Unexpected taskcreatelist response structure", "raw": data},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
"created": True,
|
||||
"id": meta_id,
|
||||
"name": list_obj.get("name", name),
|
||||
"type": list_obj.get("taskListType"),
|
||||
"shared_to_all": list_obj.get("sharedToAll") == "true",
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tool: delete_list
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def delete_list(list_id: str) -> str:
|
||||
"""Permanently delete a task list and all its tasks.
|
||||
|
||||
IMPORTANT: Ask the user for confirmation before calling this tool.
|
||||
|
||||
This action cannot be undone. All tasks inside the list are also deleted.
|
||||
Only lists with ``rights.canDelete="true"`` (user-created lists) can be
|
||||
deleted. System lists are protected and this tool will refuse to delete them.
|
||||
|
||||
Args:
|
||||
list_id: List metaId from get_lists
|
||||
(e.g. ``"taskList/23431854_29759623"``). Must be a custom list
|
||||
(``rights.canDelete="true"``).
|
||||
|
||||
Returns:
|
||||
JSON success indicator or an error message.
|
||||
"""
|
||||
# Verify + delete in a single session to minimise round-trips.
|
||||
try:
|
||||
email, password = get_credentials()
|
||||
except RuntimeError as exc:
|
||||
return f"Error: {exc}"
|
||||
|
||||
list_obj: dict[str, Any] | None = None
|
||||
try:
|
||||
with FamilyWallClient() as client:
|
||||
client.login(email, password)
|
||||
|
||||
# Fetch lists and verify the target can be deleted.
|
||||
raw = client.call("taskgettasklists", {})
|
||||
try:
|
||||
raw_lists: list[dict[str, Any]] = raw["a00"]["r"]["r"]
|
||||
if not isinstance(raw_lists, list):
|
||||
raw_lists = []
|
||||
except (KeyError, TypeError):
|
||||
raw_lists = []
|
||||
|
||||
list_obj = next(
|
||||
(lst for lst in raw_lists if lst.get("metaId") == list_id), None
|
||||
)
|
||||
if list_obj is None:
|
||||
client.logout()
|
||||
return f"Error: List '{list_id}' not found."
|
||||
|
||||
can_delete: str | None = (list_obj.get("rights") or {}).get("canDelete")
|
||||
if can_delete != "true":
|
||||
client.logout()
|
||||
return json.dumps(
|
||||
{
|
||||
"error": "System lists cannot be deleted.",
|
||||
"id": list_id,
|
||||
"name": list_obj.get("name"),
|
||||
"hint": "Only user-created lists (rights.canDelete=true) can be deleted.",
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
|
||||
# Verified — delete in the same session.
|
||||
# taskdeletelist uses 'id' (same pattern as metadelete / taskcategorydelete).
|
||||
client.call("taskdeletelist", {"id": list_id})
|
||||
client.logout()
|
||||
except FamilyWallError as exc:
|
||||
return f"Error: Family Wall API error: {exc}"
|
||||
except Exception as exc:
|
||||
return f"Error: Connection error: {exc}"
|
||||
|
||||
return json.dumps(
|
||||
{"deleted": True, "id": list_id, "name": list_obj.get("name")},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tool: like_post
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user