From 4411e6d93eff1ffa6ae9b62a5c8ab8c3425d0238 Mon Sep 17 00:00:00 2001 From: Marcus van Elst Date: Thu, 16 Apr 2026 09:01:04 +0200 Subject: [PATCH] feat: due_date, assignee_ids, list_id for create_task/update_task (v0.4.14) - create_task: new optional params due_date (ISO 8601) and assignee_ids (list[str]) - update_task: new optional params due_date, assignee_ids, and list_id (move task) - get_tasks: returns due_date and assignee_ids fields per task - API params verified via FW_DEBUG=1: dueDate, assignee, taskListId - SPEC.md, README, CLAUDE.md updated Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 6 ++--- README.md | 8 +++---- SPEC.md | 19 +++++++++++---- pyproject.toml | 2 +- src/mcp_familywall/__init__.py | 2 +- src/mcp_familywall/server.py | 42 ++++++++++++++++++++++++++++++---- 6 files changed, 62 insertions(+), 17 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index bbc5e66..3beaf3c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -23,15 +23,15 @@ eingebunden. ## Aktueller Stand -### Implementierte Tools (v0.4.11) +### Implementierte Tools (v0.4.14) | Kategorie | Tools | |---|---| | Kreise | `get_circles`, `get_members` | | Listen | `get_lists` | -| Tasks (Lesen) | `get_tasks` (inkl. `category_id`), `get_categories` (inkl. `custom`-Flag) | +| Tasks (Lesen) | `get_tasks` (inkl. `category_id`, `due_date`, `assignee_ids`), `get_categories` (inkl. `custom`-Flag) | | Wall | `get_activities`, `like_post` | -| Tasks (Schreiben) | `create_task` (inkl. `category_id`), `update_task` (inkl. `category_id`), `toggle_task`, `delete_task` | +| Tasks (Schreiben) | `create_task` (inkl. `category_id`, `due_date`, `assignee_ids`), `update_task` (inkl. `category_id`, `due_date`, `assignee_ids`, `list_id`), `toggle_task`, `delete_task` | | Kategorien (Schreiben) | `create_category` (inkl. `icon`), `delete_category` (System-Kategorien geschützt) | diff --git a/README.md b/README.md index 2c99ad9..039bbc4 100644 --- a/README.md +++ b/README.md @@ -2,21 +2,21 @@ MCP server for [Family Wall](https://www.familywall.com) -- read and manage your family's circles, lists, and tasks directly from Claude. -## Features (v0.4.13) +## Features (v0.4.14) ### Read - `get_circles` -- list all family circles - `get_members` -- list members of a circle (or all circles) - `get_lists` -- list all task lists (optionally filtered by circle) -- `get_tasks` -- list tasks in a specific list (includes `category_id` field) +- `get_tasks` -- list tasks in a specific list (includes `category_id`, `due_date`, `assignee_ids`) - `get_categories` -- list categories for a list (locale-filtered; custom categories always included; `custom` flag marks user-created ones) - `get_activities` -- list recent wall activities (author resolved to display name) ### Write (with confirmation prompt) -- `create_task` -- create a new task in a list (supports `category_id` for shopping lists) -- `update_task` -- update text, description, and/or category of an existing task +- `create_task` -- create a new task in a list (supports `category_id`, `due_date`, `assignee_ids`) +- `update_task` -- update text, description, category, due date, assignees, or move to a different list - `toggle_task` -- mark a task complete or reopen it - `delete_task` -- permanently delete a task - `create_category` -- create a custom category for a shopping list (with optional icon) diff --git a/SPEC.md b/SPEC.md index 5a77912..322992f 100644 --- a/SPEC.md +++ b/SPEC.md @@ -168,8 +168,10 @@ a02.r.r.updatedCreated[] → tasksync (Tasks) .name → Listen-Systemkategorien (z.B. "SYS-CAT-SHOPPINGLIST", "SYS-CAT-TODOS") .taskCategoryId → spezifische Task-Kategorie (verifiziert): metaId-Format (z.B. "taskCategory/23431854_200"), null wenn nicht gesetzt - .assignee / .assigneeIds → zugewiesene Mitglieder - .reminder → Erinnerungsdatum (ISO 8601, optional) + .assignee[] → zugewiesene Mitglieder: [{accountId: "..."}] + .assigneeIds[] → zugewiesene Member-IDs als String-Array (z.B. ["23431898"]) + .dueDate → Fälligkeitsdatum (ISO 8601, z.B. "2026-04-30T18:00:00.000Z"; null wenn nicht gesetzt) + .reminder → Erinnerungsregel (Objekt mit reminderUnit, reminderType, reminderValue — nicht das Fälligkeitsdatum!) .recurrency → Wiederholungsregel (optional) .sortingIndex → Anzeigereihenfolge ``` @@ -263,6 +265,8 @@ Content-Type: application/x-www-form-urlencoded | `text` | ja | Aufgabentitel | | `description` | nein | Optionale Beschreibung | | `taskCategoryId` | nein | Kategorie-MetaId aus `get_categories` (z.B. `taskCategory/23431854_200`) | +| `dueDate` | nein | Fälligkeitsdatum ISO 8601 (z.B. `"2026-04-30T18:00:00"`); gespeichert als `dueDate` im Task-Objekt | +| `assignee` | nein | Member-ID aus `get_members` (z.B. `"23431898"`); für mehrere Zuweisungen: mehrfach senden (`assignee=id1&assignee=id2`); gespeichert als `assigneeIds[]` im Task-Objekt | **Response-Struktur (verifiziert):** ``` @@ -285,8 +289,11 @@ Content-Type: application/x-www-form-urlencoded | `text` | nein | Neuer Titel (mindestens eines der optionalen Felder erforderlich) | | `description` | nein | Neue Beschreibung | | `taskCategoryId` | nein | Kategorie-MetaId aus `get_categories` (z.B. `taskCategory/23431854_200`) | +| `dueDate` | nein | Fälligkeitsdatum ISO 8601 (z.B. `"2026-04-30T18:00:00"`); gespeichert als `dueDate` im Task-Objekt | +| `assignee` | nein | Member-ID aus `get_members` (z.B. `"23431898"`); für mehrere Zuweisungen: mehrfach senden (`assignee=id1&assignee=id2`); leerer String (`""`) entfernt alle Zuweisungen (nicht verifiziert); gespeichert als `assigneeIds[]` im Task-Objekt | +| `taskListId` | nein | Ziel-Listen-ID zum Verschieben des Tasks (verifiziert – ändert `taskListId` im Task-Objekt) | -Hinweis: `taskListId` ist **nicht** erforderlich (verifiziert – Update ohne `taskListId` funktioniert). +Hinweis: `taskListId` ist optional – ohne diesen Parameter bleibt der Task in seiner aktuellen Liste. **Response-Struktur:** kein spezifischer Rückgabewert – Erfolg = kein `ex`/`un`-Key auf Top-Level. @@ -490,4 +497,8 @@ AND `moodStarShortcut: false` AND `moodMap: {}`. - `wallmood` Unlike: Mechanismus unbekannt — Service Worker verhindert Browser-Inspektion; alle getesteten Ansätze fehlgeschlagen (siehe oben) - ~~`taskcreate2` / `taskupdate2`: Kategorie-Paramter-Name~~ → **`taskCategoryId`**, Wert = vollständige metaId (verifiziert) - ~~`taskcategoryput`: Body-Parameter, Response-Struktur~~ → `name` (Pflicht), `emoji` (optional), Response = neues Kategorie-Objekt (verifiziert) -- ~~`taskcategorydelete`: Body-Parameter~~ → **`id`** (nicht `metaId`!), Response = `"true"` (verifiziert) \ No newline at end of file +- ~~`taskcategorydelete`: Body-Parameter~~ → **`id`** (nicht `metaId`!), Response = `"true"` (verifiziert) +- ~~`taskcreate2` / `taskupdate2`: Fälligkeitsdatum-Parameter~~ → **`dueDate`**, ISO 8601 String, gespeichert als `dueDate` im Task-Objekt (verifiziert) +- ~~`taskcreate2` / `taskupdate2`: Zuweisung-Parameter~~ → **`assignee`** (Member-ID String; mehrere Werte = mehrfach senden); gespeichert als `assigneeIds[]` (verifiziert) +- ~~`taskupdate2`: Task verschieben~~ → **`taskListId`** als optionaler Parameter setzt neue Liste (verifiziert) +- `taskupdate2`: Alle Zuweisungen entfernen (leere `assignee`-Liste) → noch nicht verifiziert \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 343479d..8d399b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "mcp-familywall" -version = "0.4.13" +version = "0.4.14" description = "MCP server for Family Wall — read your family's lists and tasks via Claude" readme = "README.md" requires-python = ">=3.12" diff --git a/src/mcp_familywall/__init__.py b/src/mcp_familywall/__init__.py index 4b2ce7d..f658d0a 100644 --- a/src/mcp_familywall/__init__.py +++ b/src/mcp_familywall/__init__.py @@ -1 +1 @@ -__version__ = "0.4.13" +__version__ = "0.4.14" diff --git a/src/mcp_familywall/server.py b/src/mcp_familywall/server.py index 9c91dd0..728f964 100644 --- a/src/mcp_familywall/server.py +++ b/src/mcp_familywall/server.py @@ -326,6 +326,8 @@ def get_tasks(list_id: str, only_open: bool = True): "description": task.get("description"), "completed": completed, "category_id": task.get("taskCategoryId"), + "due_date": task.get("dueDate"), + "assignee_ids": task.get("assigneeIds") or [], } ) @@ -676,6 +678,8 @@ def create_task( text: str, description: str | None = None, category_id: str | None = None, + due_date: str | None = None, + assignee_ids: list[str] | None = None, ) -> str: """Create a new task in the given list. @@ -688,6 +692,9 @@ def create_task( category_id: Optional category metaId from get_categories (e.g. ``taskCategory/23431854_200``). Only meaningful for shopping lists; ignored for TODO lists. + due_date: Optional due date in ISO 8601 format (e.g. ``"2026-04-30T18:00:00"``). + assignee_ids: Optional list of member IDs from get_members to assign the task + (e.g. ``["23431898"]``). Empty list assigns to nobody. Returns: JSON with the new task's metaId on success, or an error message. @@ -697,6 +704,10 @@ def create_task( params["description"] = description if category_id: params["taskCategoryId"] = category_id + if due_date is not None: + params["dueDate"] = due_date + if assignee_ids is not None: + params["assignee"] = assignee_ids if assignee_ids else "" try: data = _authenticated_call("taskcreate2", params) @@ -728,11 +739,14 @@ def update_task( text: str | None = None, description: str | None = None, category_id: str | None = None, + due_date: str | None = None, + assignee_ids: list[str] | None = None, + list_id: str | None = None, ) -> str: - """Update the text, description, and/or category of an existing task. + """Update an existing task's fields. IMPORTANT: Ask the user for confirmation before calling this tool. - At least one of *text*, *description*, or *category_id* must be provided. + At least one parameter besides *task_id* must be provided. Args: task_id: Task metaId from get_tasks. @@ -741,12 +755,26 @@ def update_task( category_id: New category metaId from get_categories (e.g. ``taskCategory/23431854_200``). Only meaningful for shopping lists; ignored for TODO lists. + due_date: New due date in ISO 8601 format (e.g. ``"2026-04-30T18:00:00"``). + Omit to leave unchanged. + assignee_ids: New list of member IDs from get_members (e.g. ``["23431898"]``). + Pass an empty list to remove all assignees. Omit to leave unchanged. + list_id: Move the task to a different list by providing the target list ID + from get_lists (e.g. ``"taskList/23431854_29740942"``). Omit to keep in + current list. Returns: JSON success indicator or an error message. """ - if text is None and description is None and category_id is None: - return "Error: At least one of 'text', 'description', or 'category_id' must be provided." + if ( + text is None + and description is None + and category_id is None + and due_date is None + and assignee_ids is None + and list_id is None + ): + return "Error: At least one of 'text', 'description', 'category_id', 'due_date', 'assignee_ids', or 'list_id' must be provided." params: dict[str, Any] = {"metaId": task_id} if text is not None: @@ -755,6 +783,12 @@ def update_task( params["description"] = description if category_id is not None: params["taskCategoryId"] = category_id + if due_date is not None: + params["dueDate"] = due_date + if assignee_ids is not None: + params["assignee"] = assignee_ids if assignee_ids else "" + if list_id is not None: + params["taskListId"] = list_id try: _authenticated_call("taskupdate2", params)