diff --git a/CHANGELOG.md b/CHANGELOG.md index 6da5f7c..1676c03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,29 @@ 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.2] – 2026-04-17 + +### Fixed +- `update_task`: removed non-functional reminder write parameters (`reminder_unit`, + `reminder_value`, `clear_reminder`) — exhaustive FW_DEBUG testing (flat params, + JSON-string, PHP-bracket, all string values, with/without `localId`, alternative + endpoints) on both Free and Premium accounts confirmed that `taskupdate2` silently + ignores all reminder fields; historical SNOOZE reminders were set via the mobile + app's Service Worker which transforms requests in ways not reproducible via direct API +- `update_task`: docstring now explicitly states reminders are read-only +- SPEC.md: documented reminder write limitation with investigation findings +- CLAUDE.md: updated reminder row to reflect read-only status + +### Investigation notes +- Tested formats: flat int params, flat string params, JSON-encoded string value, + PHP-bracket notation (`reminder[reminderUnit]=DAY`), no-`localId` variant +- Tested endpoints: `tasksetalert`, `taskalertput`, `taskreminderset`, `taskalert`, + `tasknotification` — all return `"The call X is not registered"` +- Verified on both test account (Free) and real account (Premium) — same result +- Recurrency write via flat params is confirmed working (separate from reminder) + +--- + ## [1.1.1] – 2026-04-17 ### Fixed diff --git a/CLAUDE.md b/CLAUDE.md index 3a6c6f1..dd9c3ba 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -27,7 +27,7 @@ und wird in Claude Desktop eingebunden. ## Aktueller Stand -### Version: **v1.1.0** ← aktuell +### Version: **v1.1.2** ← aktuell ### Implementierte Tools @@ -133,8 +133,8 @@ Fehler bei falschen Parametern kommen nicht immer auf Top-Level: | `taskupdate2` | `metaId`, `text`, `description`, `taskCategoryId`, `dueDate`, `assignee`, `taskListId` | – | | `taskupdate2` | `dueDate` löschen | `$empty` | | `taskupdate2` | `recurrencyDescriptor` (flach!) | `recurrency, recurrencyInterval, rrule, byDay, byMonthDay, recurrencyEndDate, endOccurence` als Top-Level-Parameter; löschen: `recurrency="NONE"` | -| `taskupdate2` | `reminder` (flach!) | `reminderUnit: "MINUTE"\|"HOUR"\|"DAY", reminderValue: int, reminderType: "SNOOZE", localId: 0` als Top-Level-Parameter; löschen: `reminderUnit=""` | -| `taskupdate2` | **⚠️ Encoding** | FiZ `Ai()`-Encoder sendet alle Felder flach — KEINE verschachtelten Objekte für recurrencyDescriptor/reminder! | +| `taskupdate2` | **⚠️ Reminder read-only** | `reminderUnit`, `reminderValue`, `reminderType`, `localId` werden von der API ignoriert (verifiziert via FW_DEBUG auf Premium-Account). Service Worker der mobilen App transformiert Reminder-Requests — nicht reproduzierbar via direkter API. | +| `taskupdate2` | **⚠️ Encoding** | FiZ `Ai()`-Encoder sendet Recurrency-Felder flach (verifiziert). Reminder: read-only. | | `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 2a51ef7..ca43a41 100644 --- a/SPEC.md +++ b/SPEC.md @@ -208,16 +208,20 @@ POST https://api.familywall.com/api/taskupdate2 | `byMonthDay` | nein | int (z.B. `7` für "am 7. des Monats") | | `recurrencyEndDate` | nein | ISO-Datum (z.B. `"2026-12-31"`) | | `endOccurence` | nein | int (nach N Wiederholungen aufhören) | -| `reminderUnit` | nein | `"MINUTE"` \| `"HOUR"` \| `"DAY"` (löschen: `""`) | -| `reminderValue` | nein | int (`0` = zum Zeitpunkt, `15` = 15 min vorher); löschen: `0` | -| `reminderType` | nein | immer `"SNOOZE"` | -| `localId` | nein | immer `0` | - **Hinweis:** `taskListId` ist NICHT Pflicht beim Update. **⚠️ Encoding:** Der FiZ-`Ai()`-Encoder serialisiert alle Felder **flach** als Top-Level-Form-Parameter. -`recurrencyDescriptor` und `reminder` sind **keine** verschachtelten JSON-Objekte — die Felder -werden direkt auf Top-Level gesendet (verifiziert via xb-Encoder / fc-Encoder im JS-Bundle). +Recurrency-Felder werden direkt auf Top-Level gesendet (verifiziert via xb-Encoder im JS-Bundle). + +**⚠️ Reminder-Schreibzugriff nicht möglich:** +Alle Versuche, `reminderUnit`, `reminderValue`, `reminderType`, `localId` via `taskupdate2` +zu setzen, wurden ignoriert — die API antwortet mit HTTP 200 und `lastAction: UPDATED`, +aber `reminder` bleibt unverändert auf dem Default-Wert `{reminderUnit: MINUTE, reminderType: NONE, reminderValue: 0}`. +Auch alternative Encodings (JSON-String, PHP-Bracket, verschachtelt) und alternative Endpoints +(`tasksetalert`, `taskalertput` etc.) wurden erprobt — alle Endpoints sind nicht registriert. +**Ursache:** Reminder-Updates werden vom Service Worker der mobilen App abgefangen und transformiert. +Diese Transformation ist nicht reproduzierbar ohne den Service Worker. +Reminder sind daher **read-only** (wie in v0.9 dokumentiert). **Response:** ``` diff --git a/pyproject.toml b/pyproject.toml index d75f7cf..b1ef814 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "mcp-familywall" -version = "1.1.1" +version = "1.1.2" 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 188b56f..4747047 100644 --- a/src/mcp_familywall/server.py +++ b/src/mcp_familywall/server.py @@ -930,15 +930,17 @@ def update_task( 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. IMPORTANT: Ask the user for confirmation before calling this tool. At least one parameter besides *task_id* must be provided. + NOTE: Setting or clearing reminders is **not supported** via this tool. + The Family Wall API does not accept reminder writes through ``taskupdate2`` + — reminder updates are handled exclusively by the mobile app's Service + Worker and cannot be replicated via direct API calls. + Args: task_id: Task metaId from get_tasks. text: New title text (omit to leave unchanged). @@ -967,14 +969,6 @@ def update_task( 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. @@ -983,12 +977,6 @@ def update_task( 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 @@ -1000,14 +988,11 @@ def update_task( 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', 'list_id', 'recurrency'," - " 'clear_recurrency', 'reminder_unit'/'reminder_value', or" - " 'clear_reminder' must be provided." + " 'clear_due_date', 'assignee_ids', 'list_id', 'recurrency', or" + " 'clear_recurrency' must be provided." ) params: dict[str, Any] = {"metaId": task_id} @@ -1027,9 +1012,7 @@ def update_task( if list_id is not None: params["taskListId"] = list_id - # The FiZ Ai() encoder serialises all fields flat at the top level of the - # request body — nested objects are NOT supported. recurrencyDescriptor and - # reminder fields must therefore be sent as plain top-level parameters. + # Recurrency fields are sent flat at the top level (verified: FiZ Ai() encoder). if clear_recurrency: params["recurrency"] = "NONE" elif recurrency is not None: @@ -1039,17 +1022,6 @@ def update_task( if rrule is not None: params["rrule"] = rrule - if clear_reminder: - params["reminderUnit"] = "" - params["reminderValue"] = 0 - params["reminderType"] = "SNOOZE" - params["localId"] = 0 - elif reminder_unit is not None and reminder_value is not None: - params["reminderUnit"] = reminder_unit - params["reminderValue"] = reminder_value - params["reminderType"] = "SNOOZE" - params["localId"] = 0 - try: _authenticated_call("taskupdate2", params) except RuntimeError as exc: