From 6c955d67c8dc4c64af5b340b68092d2c1ffde28d Mon Sep 17 00:00:00 2001 From: Marcus van Elst Date: Thu, 16 Apr 2026 09:47:51 +0200 Subject: [PATCH] fix: surface a00.un API errors + document dueDate-clearing limitation (v0.4.15) - fw_client: detect nested a00.un errors (previously silent-failed as success) - update_task: add clear_due_date=True parameter that returns a clear error explaining the Family Wall API cannot clear dueDate once set - SPEC.md: document all tested clearing candidates and their API responses, add Fehlerstruktur-Varianten section - Verified: dueDate cannot be removed via any form-encoded value; all invalid date strings are rejected via a00.un.un (silently swallowed before this fix) Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 2 +- README.md | 4 +-- SPEC.md | 44 ++++++++++++++++++++++++++++++++- pyproject.toml | 2 +- src/mcp_familywall/__init__.py | 2 +- src/mcp_familywall/fw_client.py | 8 ++++++ src/mcp_familywall/server.py | 15 ++++++++++- 7 files changed, 70 insertions(+), 7 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 3beaf3c..eb25c72 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -23,7 +23,7 @@ eingebunden. ## Aktueller Stand -### Implementierte Tools (v0.4.14) +### Implementierte Tools (v0.4.15) | Kategorie | Tools | |---|---| diff --git a/README.md b/README.md index 039bbc4..df39deb 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ 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.14) +## Features (v0.4.15) ### Read @@ -16,7 +16,7 @@ MCP server for [Family Wall](https://www.familywall.com) -- read and manage your ### Write (with confirmation prompt) - `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 +- `update_task` -- update text, description, category, due date, assignees, or move to a different list (note: clearing a due date is not supported by the Family Wall API) - `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 322992f..8a88e0d 100644 --- a/SPEC.md +++ b/SPEC.md @@ -245,6 +245,24 @@ Content-Type: application/x-www-form-urlencoded **Response-Struktur:** zu verifizieren beim ersten echten Call (Endpoint noch nicht implementiert — für spätere Versionen) +## Fehlerstruktur-Varianten + +Die Family Wall API gibt Fehler in zwei verschiedenen Strukturen zurück: + +| Struktur | Beispiel | Erkennungsregel | +|---|---|---| +| Top-Level `ex` | `{"ex": {"ex": {...}}}` | `"ex" in body` | +| Top-Level `un` | `{"un": {"un": {...}}}` | `"un" in body` | +| Verschachtelt `a00.un` | `{"a00": {"un": {"un": {...}}}}` | `"un" in body["a00"]` | + +Ab v0.4.15 erkennt `fw_client.py` alle drei Varianten und wirft `FamilyWallError`. +Zuvor wurden `a00.un`-Fehler still ignoriert (silent fail), was zu irreführenden +`{"updated": True}`-Antworten bei fehlgeschlagenen Updates führte. + +**Bekannte Endpoints mit `a00.un`-Fehlern:** +- `taskupdate2` mit ungültigem `dueDate`-Wert +- `taskcategorydelete` mit falschem Parameter-Namen (obsolet nach v0.4.x-Fix) + ## Debug-Logging Wenn die Umgebungsvariable `FW_DEBUG=1` gesetzt ist, loggt `fw_client.py` @@ -297,6 +315,29 @@ Hinweis: `taskListId` ist optional – ohne diesen Parameter bleibt der Task in **Response-Struktur:** kein spezifischer Rückgabewert – Erfolg = kein `ex`/`un`-Key auf Top-Level. +**dueDate Clearing – nicht implementierbar (Stand: April 2026):** + +Ausgiebig getestete Werte für `dueDate`, die alle fehlschlugen: + +| Wert | API-Response | +|---|---| +| `""` (leerer String) | `a00.un.un: " is not a valid Date"` | +| `"null"` | `a00.un.un: "Cannot parse date val=null"` | +| `"0"` | API-Fehler (ungültiges Datum) | +| `"-1"` | `a00.un.un: "-1 is not a valid Date"` | +| `"remove"` / `"clear"` | `a00.un.un: "Cannot parse date val=..."` | +| `"0000-00-00T00:00:00"` | API-Fehler | +| `"1970-01-01T00:00:00"` | **Erfolgreich** – setzt dueDate auf Unix-Epoch (kein echtes Clearing!) | +| Separate Parameter (`removeDueDate`, `clearDueDate`, `clearFields`) | werden ignoriert, dueDate bleibt unverändert | +| Feld weglassen (kein `dueDate` in Request) | dueDate bleibt unverändert | + +**Wichtig:** Die Fehler werden als `a00.un.un` zurückgegeben (nicht Top-Level `un`). +Der `fw_client` prüfte ursprünglich nur Top-Level `un` → silent fail (ab v0.4.15 behoben: +`a00.un` wird ebenfalls erkannt und als `FamilyWallError` geworfen). + +Das `dueDate`-Feld kann einmal gesetzt nicht mehr entfernt werden. +`update_task(clear_due_date=True)` gibt eine klare Fehlermeldung zurück. + ### `taskmark` – Task als erledigt/offen markieren POST https://api.familywall.com/api/taskmark Content-Type: application/x-www-form-urlencoded @@ -501,4 +542,5 @@ AND `moodStarShortcut: false` AND `moodMap: {}`. - ~~`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 +- `taskupdate2`: Alle Zuweisungen entfernen (leere `assignee`-Liste) → noch nicht verifiziert +- ~~`taskupdate2`: `dueDate` entfernen (Clearing)~~ → **nicht möglich** (verifiziert, April 2026) — alle getesteten Werte werden als ungültiges Datum abgelehnt (siehe unten) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 8d399b3..bceea9f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "mcp-familywall" -version = "0.4.14" +version = "0.4.15" 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 f658d0a..5a4bb1d 100644 --- a/src/mcp_familywall/__init__.py +++ b/src/mcp_familywall/__init__.py @@ -1 +1 @@ -__version__ = "0.4.14" +__version__ = "0.4.15" diff --git a/src/mcp_familywall/fw_client.py b/src/mcp_familywall/fw_client.py index 89e791f..6b59e76 100644 --- a/src/mcp_familywall/fw_client.py +++ b/src/mcp_familywall/fw_client.py @@ -185,4 +185,12 @@ class FamilyWallClient: msg = f"API error from {endpoint!r}: {error_data}" raise FamilyWallError(msg, response_data=body) + # Some endpoints (e.g. taskupdate2) return per-call errors nested under + # a00.un.un instead of top-level — detect and surface them. + a00 = body.get("a00", {}) + if isinstance(a00, dict) and "un" in a00: + nested = a00["un"] + msg = f"API error from {endpoint!r}: {nested}" + raise FamilyWallError(msg, response_data=body) + return body diff --git a/src/mcp_familywall/server.py b/src/mcp_familywall/server.py index 728f964..b05e68d 100644 --- a/src/mcp_familywall/server.py +++ b/src/mcp_familywall/server.py @@ -740,6 +740,7 @@ def update_task( description: str | None = None, category_id: str | None = None, due_date: str | None = None, + clear_due_date: bool = False, assignee_ids: list[str] | None = None, list_id: str | None = None, ) -> str: @@ -756,7 +757,11 @@ def update_task( (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. + Omit to leave unchanged. Cannot be used together with *clear_due_date*. + clear_due_date: Set to ``True`` to request removal of the due date. + **Note:** The Family Wall API does not support clearing a due date once + set — this will return an informational error. This parameter exists to + surface the limitation explicitly rather than silently doing nothing. 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 @@ -766,6 +771,14 @@ def update_task( Returns: JSON success indicator or an error message. """ + if clear_due_date: + return ( + "Error: Removing a due date is not supported by the Family Wall API. " + "The 'dueDate' field cannot be cleared once set — this is a known " + "limitation of the API (all tested values are rejected as invalid dates). " + "You can set a different future date instead." + ) + if ( text is None and description is None