fix(tasks): send recurrency/reminder as flat top-level params (v1.1.1)

FiZ Ai() encoder does not support nested objects — recurrencyDescriptor
and reminder fields must be top-level params (recurrency=WEEKLY, not
recurrencyDescriptor={recurrency:WEEKLY}). Same fix for reminder fields.
SPEC.md and CLAUDE.md updated to document the flat encoding.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-17 21:18:58 +02:00
parent f5eb0a46c8
commit 08ee5fb84a
5 changed files with 45 additions and 49 deletions
+11
View File
@@ -10,6 +10,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
This project follows Semantic Versioning (SemVer). This project follows Semantic Versioning (SemVer).
Breaking changes (removed tools, changed parameters) increment the major version. 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 ## [1.1.0] 2026-04-17
### Added ### Added
+4 -3
View File
@@ -130,10 +130,11 @@ Fehler bei falschen Parametern kommen nicht immer auf Top-Level:
| Endpoint | Parameter | Wert/Format | | Endpoint | Parameter | Wert/Format |
|---|---|---| |---|---|---|
| `taskcreate2` | `taskListId`, `text`, `description`, `taskCategoryId`, `dueDate`, `assignee` | | | `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` | `dueDate` löschen | `$empty` |
| `taskupdate2` | `recurrencyDescriptor` | `{recurrency, recurrencyInterval, rrule, byDay, byMonthDay, recurrencyEndDate, endOccurence}`; löschen: `{recurrency: "NONE"}` | | `taskupdate2` | `recurrencyDescriptor` (flach!) | `recurrency, recurrencyInterval, rrule, byDay, byMonthDay, recurrencyEndDate, endOccurence` als Top-Level-Parameter; löschen: `recurrency="NONE"` |
| `taskupdate2` | `reminder` | `{reminderUnit: "MINUTE"\|"HOUR"\|"DAY", reminderValue: int, reminderType: "SNOOZE", localId: 0}`; löschen: `{reminderUnit: "", reminderValue: 0, ...}` | | `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"` | | `taskmark` | `taskId`, `complete` | `"true"`/`"false"` |
| `metadelete` | `id` | metaId des Tasks / Rezepts | | `metadelete` | `id` | metaId des Tasks / Rezepts |
| `wallmood` | `wall_message_id`, `moodType` | `"STAR"` für Like | | `wallmood` | `wall_message_id`, `moodType` | `"STAR"` für Like |
+14 -26
View File
@@ -201,35 +201,23 @@ POST https://api.familywall.com/api/taskupdate2
| `dueDate` | nein | ISO 8601 oder `$empty` zum Löschen | | `dueDate` | nein | ISO 8601 oder `$empty` zum Löschen |
| `assignee` | nein | Member-accountId (mehrfach sendbar), `""` zum Entfernen aller | | `assignee` | nein | Member-accountId (mehrfach sendbar), `""` zum Entfernen aller |
| `taskListId` | nein | neue Listen-metaId (verschiebt Task) | | `taskListId` | nein | neue Listen-metaId (verschiebt Task) |
| `recurrencyDescriptor` | nein | Objekt mit Wiederholungs-Feldern (siehe unten) | | `recurrency` | nein | `"DAILY"` \| `"WEEKLY"` \| `"MONTHLY"` \| `"YEARLY"` \| `"NONE"` (entfernen) |
| `reminder` | nein | Objekt mit Erinnerungs-Feldern (siehe unten) | | `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. **Hinweis:** `taskListId` ist NICHT Pflicht beim Update.
**`recurrencyDescriptor`-Felder** (verifiziert via JS-Bundle xb-Encoder): **⚠️ Encoding:** Der FiZ-`Ai()`-Encoder serialisiert alle Felder **flach** als Top-Level-Form-Parameter.
`recurrencyDescriptor` und `reminder` sind **keine** verschachtelten JSON-Objekte — die Felder
| Feld | Wert | werden direkt auf Top-Level gesendet (verifiziert via xb-Encoder / fc-Encoder im JS-Bundle).
|---|---|
| `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:** **Response:**
``` ```
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "mcp-familywall" 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" description = "MCP server for Family Wall — manage your family's circles, lists, tasks, recipes, and meal plan via Claude"
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
+15 -19
View File
@@ -1027,32 +1027,28 @@ def update_task(
if list_id is not None: if list_id is not None:
params["taskListId"] = list_id 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: if clear_recurrency:
recurrency_descriptor["recurrency"] = "NONE" params["recurrency"] = "NONE"
elif recurrency is not None: elif recurrency is not None:
recurrency_descriptor["recurrency"] = recurrency params["recurrency"] = recurrency
if recurrency_interval is not None: if recurrency_interval is not None:
recurrency_descriptor["recurrencyInterval"] = recurrency_interval params["recurrencyInterval"] = recurrency_interval
if rrule is not None: if rrule is not None:
recurrency_descriptor["rrule"] = rrule params["rrule"] = rrule
if recurrency_descriptor:
params["recurrencyDescriptor"] = recurrency_descriptor
if clear_reminder: if clear_reminder:
params["reminder"] = { params["reminderUnit"] = ""
"reminderUnit": "", params["reminderValue"] = 0
"reminderValue": 0, params["reminderType"] = "SNOOZE"
"reminderType": "SNOOZE", params["localId"] = 0
"localId": 0,
}
elif reminder_unit is not None and reminder_value is not None: elif reminder_unit is not None and reminder_value is not None:
params["reminder"] = { params["reminderUnit"] = reminder_unit
"reminderUnit": reminder_unit, params["reminderValue"] = reminder_value
"reminderValue": reminder_value, params["reminderType"] = "SNOOZE"
"reminderType": "SNOOZE", params["localId"] = 0
"localId": 0,
}
try: try:
_authenticated_call("taskupdate2", params) _authenticated_call("taskupdate2", params)