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 <noreply@anthropic.com>
This commit is contained in:
2026-04-16 09:47:51 +02:00
parent 4411e6d93e
commit 6c955d67c8
7 changed files with 70 additions and 7 deletions
+1 -1
View File
@@ -23,7 +23,7 @@ eingebunden.
## Aktueller Stand ## Aktueller Stand
### Implementierte Tools (v0.4.14) ### Implementierte Tools (v0.4.15)
| Kategorie | Tools | | Kategorie | Tools |
|---|---| |---|---|
+2 -2
View File
@@ -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. 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 ### Read
@@ -16,7 +16,7 @@ MCP server for [Family Wall](https://www.familywall.com) -- read and manage your
### Write (with confirmation prompt) ### Write (with confirmation prompt)
- `create_task` -- create a new task in a list (supports `category_id`, `due_date`, `assignee_ids`) - `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 - `toggle_task` -- mark a task complete or reopen it
- `delete_task` -- permanently delete a task - `delete_task` -- permanently delete a task
- `create_category` -- create a custom category for a shopping list (with optional icon) - `create_category` -- create a custom category for a shopping list (with optional icon)
+43 -1
View File
@@ -245,6 +245,24 @@ Content-Type: application/x-www-form-urlencoded
**Response-Struktur:** zu verifizieren beim ersten echten Call **Response-Struktur:** zu verifizieren beim ersten echten Call
(Endpoint noch nicht implementiert — für spätere Versionen) (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 ## Debug-Logging
Wenn die Umgebungsvariable `FW_DEBUG=1` gesetzt ist, loggt `fw_client.py` 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. **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 ### `taskmark` Task als erledigt/offen markieren
POST https://api.familywall.com/api/taskmark POST https://api.familywall.com/api/taskmark
Content-Type: application/x-www-form-urlencoded 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`: 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) - ~~`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`: Task verschieben~~ → **`taskListId`** als optionaler Parameter setzt neue Liste (verifiziert)
- `taskupdate2`: Alle Zuweisungen entfernen (leere `assignee`-Liste) → noch nicht verifiziert - `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)
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "mcp-familywall" 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" description = "MCP server for Family Wall — read your family's lists and tasks via Claude"
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
+1 -1
View File
@@ -1 +1 @@
__version__ = "0.4.14" __version__ = "0.4.15"
+8
View File
@@ -185,4 +185,12 @@ class FamilyWallClient:
msg = f"API error from {endpoint!r}: {error_data}" msg = f"API error from {endpoint!r}: {error_data}"
raise FamilyWallError(msg, response_data=body) 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 return body
+14 -1
View File
@@ -740,6 +740,7 @@ def update_task(
description: str | None = None, description: str | None = None,
category_id: str | None = None, category_id: str | None = None,
due_date: str | None = None, due_date: str | None = None,
clear_due_date: bool = False,
assignee_ids: list[str] | None = None, assignee_ids: list[str] | None = None,
list_id: str | None = None, list_id: str | None = None,
) -> str: ) -> str:
@@ -756,7 +757,11 @@ def update_task(
(e.g. ``taskCategory/23431854_200``). Only meaningful for (e.g. ``taskCategory/23431854_200``). Only meaningful for
shopping lists; ignored for TODO lists. shopping lists; ignored for TODO lists.
due_date: New due date in ISO 8601 format (e.g. ``"2026-04-30T18:00:00"``). 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"]``). 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. 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 list_id: Move the task to a different list by providing the target list ID
@@ -766,6 +771,14 @@ def update_task(
Returns: Returns:
JSON success indicator or an error message. 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 ( if (
text is None text is None
and description is None and description is None