From f5eb0a46c8cae1a404bbb25d817e456259708107 Mon Sep 17 00:00:00 2001 From: Marcus van Elst Date: Fri, 17 Apr 2026 21:15:05 +0200 Subject: [PATCH] feat(tasks): add recurrency and reminder write support to update_task (v1.1.0) Verified parameters from JS-Bundle xb-Encoder/fc-Encoder now wired up: recurrencyDescriptor (recurrency, recurrencyInterval, rrule) and reminder (reminderUnit, reminderValue). Adds clear_recurrency and clear_reminder flags. SPEC.md, CHANGELOG.md, CLAUDE.md updated accordingly. Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 12 +++++++ CLAUDE.md | 6 ++-- SPEC.md | 27 ++++++++++++++ pyproject.toml | 2 +- src/mcp_familywall/server.py | 70 +++++++++++++++++++++++++++++++++++- 5 files changed, 113 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 844672e..65fdd82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 This project follows Semantic Versioning (SemVer). Breaking changes (removed tools, changed parameters) increment the major version. +## [1.1.0] – 2026-04-17 + +### Added +- `update_task`: recurrency parameters (`recurrency`, `recurrency_interval`, `rrule`, + `clear_recurrency`) — set or remove task recurrence rules via `recurrencyDescriptor` +- `update_task`: reminder parameters (`reminder_unit`, `reminder_value`, `clear_reminder`) + — set or remove task reminders via `reminder` +- SPEC.md: documented `recurrencyDescriptor` and `reminder` fields for `taskupdate2` + (verified via JS-Bundle xb-Encoder / fc-Encoder) + +--- + ## [1.0.1] – 2026-04-17 ### Added diff --git a/CLAUDE.md b/CLAUDE.md index 3c7c296..3e3b309 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -27,7 +27,7 @@ und wird in Claude Desktop eingebunden. ## Aktueller Stand -### Version: **v1.0.0** ← aktuell +### Version: **v1.1.0** ← aktuell ### Implementierte Tools @@ -130,8 +130,10 @@ Fehler bei falschen Parametern kommen nicht immer auf Top-Level: | Endpoint | Parameter | Wert/Format | |---|---|---| | `taskcreate2` | `taskListId`, `text`, `description`, `taskCategoryId`, `dueDate`, `assignee` | – | -| `taskupdate2` | `metaId`, `text`, `description`, `taskCategoryId`, `dueDate`, `assignee`, `taskListId` | – | +| `taskupdate2` | `metaId`, `text`, `description`, `taskCategoryId`, `dueDate`, `assignee`, `taskListId`, `recurrencyDescriptor`, `reminder` | – | | `taskupdate2` | `dueDate` löschen | `$empty` | +| `taskupdate2` | `recurrencyDescriptor` | `{recurrency, recurrencyInterval, rrule, byDay, byMonthDay, recurrencyEndDate, endOccurence}`; löschen: `{recurrency: "NONE"}` | +| `taskupdate2` | `reminder` | `{reminderUnit: "MINUTE"\|"HOUR"\|"DAY", reminderValue: int, reminderType: "SNOOZE", localId: 0}`; löschen: `{reminderUnit: "", reminderValue: 0, ...}` | | `taskmark` | `taskId`, `complete` | `"true"`/`"false"` | | `metadelete` | `id` | metaId des Tasks / Rezepts | | `wallmood` | `wall_message_id`, `moodType` | `"STAR"` für Like | diff --git a/SPEC.md b/SPEC.md index 5e7c509..93979b8 100644 --- a/SPEC.md +++ b/SPEC.md @@ -201,9 +201,36 @@ POST https://api.familywall.com/api/taskupdate2 | `dueDate` | nein | ISO 8601 oder `$empty` zum Löschen | | `assignee` | nein | Member-accountId (mehrfach sendbar), `""` zum Entfernen aller | | `taskListId` | nein | neue Listen-metaId (verschiebt Task) | +| `recurrencyDescriptor` | nein | Objekt mit Wiederholungs-Feldern (siehe unten) | +| `reminder` | nein | Objekt mit Erinnerungs-Feldern (siehe unten) | **Hinweis:** `taskListId` ist NICHT Pflicht beim Update. +**`recurrencyDescriptor`-Felder** (verifiziert via JS-Bundle xb-Encoder): + +| Feld | Wert | +|---|---| +| `recurrency` | `"DAILY"` \| `"WEEKLY"` \| `"MONTHLY"` \| `"YEARLY"` \| `"NONE"` | +| `recurrencyInterval` | int (z.B. `2` für "alle 2 Wochen") | +| `rrule` | vollständige iCal-RRULE (z.B. `"FREQ=WEEKLY;INTERVAL=2;BYDAY=FR"`) | +| `byDay` | z.B. `"FR"`, `"1SA"`, `"MO,TU,WE,TH,FR"` | +| `byMonthDay` | int (z.B. `7` für "am 7. des Monats") | +| `recurrencyEndDate` | ISO-Datum (z.B. `"2026-12-31"`) | +| `endOccurence` | int (nach N Wiederholungen aufhören) | + +Wiederholung entfernen: `recurrencyDescriptor = {"recurrency": "NONE"}` + +**`reminder`-Felder** (verifiziert via JS-Bundle fc-Encoder): + +| Feld | Wert | +|---|---| +| `reminderUnit` | `"MINUTE"` \| `"HOUR"` \| `"DAY"` | +| `reminderValue` | int (`0` = zum Zeitpunkt, `15` = 15 min vorher, `1` = 1 Einheit vorher) | +| `reminderType` | immer `"SNOOZE"` | +| `localId` | immer `0` | + +Erinnerung entfernen: `reminder = {"reminderUnit": "", "reminderValue": 0, "reminderType": "SNOOZE", "localId": 0}` + **Response:** ``` a00.r.r → vollständiges Task-Objekt diff --git a/pyproject.toml b/pyproject.toml index e84b909..608cda7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "mcp-familywall" -version = "1.0.1" +version = "1.1.0" description = "MCP server for Family Wall — manage your family's circles, lists, tasks, recipes, and meal plan via Claude" readme = "README.md" requires-python = ">=3.12" diff --git a/src/mcp_familywall/server.py b/src/mcp_familywall/server.py index c6903aa..e614b2a 100644 --- a/src/mcp_familywall/server.py +++ b/src/mcp_familywall/server.py @@ -926,6 +926,13 @@ def update_task( clear_due_date: bool = False, assignee_ids: list[str] | None = None, list_id: str | None = None, + recurrency: str | None = None, + recurrency_interval: int | None = None, + rrule: str | None = None, + clear_recurrency: bool = False, + reminder_unit: str | None = None, + reminder_value: int | None = None, + clear_reminder: bool = False, ) -> str: """Update an existing task's fields. @@ -948,12 +955,40 @@ def update_task( 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. + recurrency: Recurrence frequency — ``"DAILY"``, ``"WEEKLY"``, ``"MONTHLY"``, + ``"YEARLY"``, or ``"NONE"``. Examples: daily → ``recurrency="DAILY", + recurrency_interval=1``; every 2 weeks on Friday → + ``recurrency="WEEKLY", recurrency_interval=2, + rrule="FREQ=WEEKLY;INTERVAL=2;BYDAY=FR"``. + Cannot be used together with *clear_recurrency*. + recurrency_interval: Repeat every N units (e.g. ``2`` for "every 2 weeks"). + Only meaningful when *recurrency* is set. + rrule: Full iCalendar RRULE string (e.g. ``"FREQ=WEEKLY;INTERVAL=2;BYDAY=FR"``). + Overrides simple recurrency fields when set. + clear_recurrency: Set to ``True`` to remove the recurrence rule from the task. + Cannot be used together with *recurrency*. + reminder_unit: Unit for the reminder offset — ``"MINUTE"``, ``"HOUR"``, or + ``"DAY"``. Must be provided together with *reminder_value*. Requires a + *due_date* to be set on the task. + reminder_value: Offset before the due date/time (e.g. ``0`` = at the time, + ``15`` = 15 min before, ``1`` with unit ``"DAY"`` = 1 day before). + Must be provided together with *reminder_unit*. + clear_reminder: Set to ``True`` to remove the reminder from the task. + Cannot be used together with *reminder_unit*/*reminder_value*. Returns: JSON success indicator or an error message. """ if clear_due_date and due_date is not None: return _err("'clear_due_date' and 'due_date' cannot be used together.") + if clear_recurrency and recurrency is not None: + return _err("'clear_recurrency' and 'recurrency' cannot be used together.") + if clear_reminder and (reminder_unit is not None or reminder_value is not None): + return _err( + "'clear_reminder' cannot be used together with 'reminder_unit'/'reminder_value'." + ) + if (reminder_unit is None) != (reminder_value is None): + return _err("'reminder_unit' and 'reminder_value' must be provided together.") if ( text is None @@ -963,10 +998,16 @@ def update_task( and not clear_due_date and assignee_ids is None and list_id is None + and recurrency is None + and not clear_recurrency + and reminder_unit is None + and not clear_reminder ): return _err( "At least one of 'text', 'description', 'category_id', 'due_date'," - " 'clear_due_date', 'assignee_ids', or 'list_id' must be provided." + " 'clear_due_date', 'assignee_ids', 'list_id', 'recurrency'," + " 'clear_recurrency', 'reminder_unit'/'reminder_value', or" + " 'clear_reminder' must be provided." ) params: dict[str, Any] = {"metaId": task_id} @@ -986,6 +1027,33 @@ def update_task( if list_id is not None: params["taskListId"] = list_id + recurrency_descriptor: dict[str, Any] = {} + if clear_recurrency: + recurrency_descriptor["recurrency"] = "NONE" + elif recurrency is not None: + recurrency_descriptor["recurrency"] = recurrency + if recurrency_interval is not None: + recurrency_descriptor["recurrencyInterval"] = recurrency_interval + if rrule is not None: + recurrency_descriptor["rrule"] = rrule + if recurrency_descriptor: + params["recurrencyDescriptor"] = recurrency_descriptor + + if clear_reminder: + params["reminder"] = { + "reminderUnit": "", + "reminderValue": 0, + "reminderType": "SNOOZE", + "localId": 0, + } + elif reminder_unit is not None and reminder_value is not None: + params["reminder"] = { + "reminderUnit": reminder_unit, + "reminderValue": reminder_value, + "reminderType": "SNOOZE", + "localId": 0, + } + try: _authenticated_call("taskupdate2", params) except RuntimeError as exc: