diff --git a/CHANGELOG.md b/CHANGELOG.md index 65fdd82..6da5f7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,17 @@ 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.1] – 2026-04-17 + +### Fixed +- `update_task`: recurrency and reminder fields now sent as flat top-level parameters + instead of nested objects — the FiZ `Ai()` encoder does not support nested objects; + `recurrencyDescriptor`/`reminder` keys are never sent, fields go directly to top level + (e.g. `recurrency=WEEKLY`, `reminderUnit=DAY` rather than `recurrencyDescriptor={...}`) +- SPEC.md and CLAUDE.md corrected to document the flat encoding + +--- + ## [1.1.0] – 2026-04-17 ### Added diff --git a/CLAUDE.md b/CLAUDE.md index 3e3b309..3a6c6f1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -130,10 +130,11 @@ 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`, `recurrencyDescriptor`, `reminder` | – | +| `taskupdate2` | `metaId`, `text`, `description`, `taskCategoryId`, `dueDate`, `assignee`, `taskListId` | – | | `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, ...}` | +| `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! | | `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 93979b8..2a51ef7 100644 --- a/SPEC.md +++ b/SPEC.md @@ -201,35 +201,23 @@ 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) | +| `recurrency` | nein | `"DAILY"` \| `"WEEKLY"` \| `"MONTHLY"` \| `"YEARLY"` \| `"NONE"` (entfernen) | +| `recurrencyInterval` | nein | int (z.B. `2` für "alle 2 Wochen") | +| `rrule` | nein | vollständige iCal-RRULE (z.B. `"FREQ=WEEKLY;INTERVAL=2;BYDAY=FR"`) | +| `byDay` | nein | z.B. `"FR"`, `"1SA"`, `"MO,TU,WE,TH,FR"` | +| `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. -**`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}` +**⚠️ 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). **Response:** ``` diff --git a/pyproject.toml b/pyproject.toml index 608cda7..d75f7cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "mcp-familywall" -version = "1.1.0" +version = "1.1.1" 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 e614b2a..188b56f 100644 --- a/src/mcp_familywall/server.py +++ b/src/mcp_familywall/server.py @@ -1027,32 +1027,28 @@ def update_task( if list_id is not None: params["taskListId"] = list_id - recurrency_descriptor: dict[str, Any] = {} + # 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. if clear_recurrency: - recurrency_descriptor["recurrency"] = "NONE" + params["recurrency"] = "NONE" elif recurrency is not None: - recurrency_descriptor["recurrency"] = recurrency + params["recurrency"] = recurrency if recurrency_interval is not None: - recurrency_descriptor["recurrencyInterval"] = recurrency_interval + params["recurrencyInterval"] = recurrency_interval if rrule is not None: - recurrency_descriptor["rrule"] = rrule - if recurrency_descriptor: - params["recurrencyDescriptor"] = recurrency_descriptor + params["rrule"] = rrule if clear_reminder: - params["reminder"] = { - "reminderUnit": "", - "reminderValue": 0, - "reminderType": "SNOOZE", - "localId": 0, - } + params["reminderUnit"] = "" + params["reminderValue"] = 0 + params["reminderType"] = "SNOOZE" + params["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, - } + params["reminderUnit"] = reminder_unit + params["reminderValue"] = reminder_value + params["reminderType"] = "SNOOZE" + params["localId"] = 0 try: _authenticated_call("taskupdate2", params)