fix: add tasklistsync, fix get_circles response, update SPEC (v0.2.1)

This commit is contained in:
2026-04-15 13:29:01 +02:00
parent 119a0b577e
commit 8262c8440c
4 changed files with 62 additions and 45 deletions
+30 -22
View File
@@ -78,9 +78,14 @@ für `session_id`.
POST https://api.familywall.com/api/famlistfamily POST https://api.familywall.com/api/famlistfamily
Content-Type: application/x-www-form-urlencoded Content-Type: application/x-www-form-urlencoded
**Body-Parameter:** keine (zu verifizieren beim ersten echten Call) **Body-Parameter:** keine (verifiziert)
**Response-Struktur:** zu verifizieren beim ersten echten Call **Response-Struktur (verifiziert):**
```
a00.r.r[] → Kreise
.metaId → eindeutige Kreis-ID
.name → Kreisname
```
### `accgetallfamily` Listen + Tasks abrufen ### `accgetallfamily` Listen + Tasks abrufen
POST https://api.familywall.com/api/accgetallfamily POST https://api.familywall.com/api/accgetallfamily
@@ -92,25 +97,27 @@ Content-Type: application/x-www-form-urlencoded
|---|---| |---|---|
| `a01call` | `"taskcategorysync"` | | `a01call` | `"taskcategorysync"` |
| `a02call` | `"tasksync"` | | `a02call` | `"tasksync"` |
| `a03call` | `"tasklistsync"` |
Hinweis: `partnerScope`, `a03call`, `a03id`, `withStateBean` werden Hinweis: `partnerScope`, `a03id`, `withStateBean` werden weggelassen.
weggelassen. Falls der Server sie erfordert, beim ersten echten Call
nachbessern.
**Response-Struktur (relevant für v1.0):** **Response-Struktur (verifiziert):**
a00.r.r[] → Listen (taskcategorysync) ```
.metaId → eindeutige Listen-ID a00 → famlistfamily-Daten (Kreise) Nebeneffekt, nicht verwendet
.name → Name (ggf. Systembezeichnung, s.u.) a01.r.r[] → taskcategorysync (Einkaufskategorien/Abteilungen)
.taskListType → Typ der Liste a02.r.r.updatedCreated[] → tasksync (Tasks)
.remainingTaskNumber → offene Einträge (String) .metaId → eindeutige Task-ID
.totalTaskNumber → Gesamteinträge (String) .text → Aufgabentext
.<Kreis-Feld> → zu verifizieren beim ersten echten Call .description → optionale Beschreibung
a02.r.r.updatedCreated[] Tasks (tasksync) .taskListIdZugehörigkeit zur Liste (= metaId der Liste)
.metaId → eindeutige Task-ID .complete → "true" / "false" (String, nicht Boolean!)
.text → Aufgabentext a03.r.r.updatedCreated[] → tasklistsync (Listen)
.description → optionale Beschreibung .metaId → eindeutige Listen-ID
.taskListId → Zugehörigkeit zur Liste (= metaId der Liste) .name → Name (ggf. Systembezeichnung, s.u.)
.complete → "true" / "false" (String, nicht Boolean!) .taskListType → Typ der Liste
.remainingTaskNumber → offene Einträge (String)
.totalTaskNumber → Gesamteinträge (String)
```
## Systembezeichnungen für Listen-Namen ## Systembezeichnungen für Listen-Namen
@@ -134,7 +141,8 @@ offener Punkte (z.B. `type`-Parameter beim Login, Kreis-Felder in Response).
## Noch zu verifizieren ## Noch zu verifizieren
- ~~Exakter Wert für `type`-Parameter beim Login~~ → nicht senden (verifiziert per JS-Analyse) - ~~Exakter Wert für `type`-Parameter beim Login~~ → nicht senden (verifiziert per JS-Analyse)
- Response-Struktur von `famlistfamily` (Kreise) - ~~Response-Struktur von `famlistfamily` (Kreise)~~ → a00.r.r[], metaId + name (verifiziert)
- Kreis-Zuordnung in `accgetallfamily`-Response - ~~Ob `a03call=tasklistsync` benötigt wird~~ → ja, liefert Listen unter a03.r.r.updatedCreated[] (verifiziert)
- Ob `partnerScope` / `withStateBean` benötigt werden - Kreis-Zuordnung in `accgetallfamily`-Response → noch offen (Feld in Listen-Objekten unbekannt)
- ~~Ob `partnerScope` / `withStateBean` benötigt werden~~ → nein (verifiziert)
- Session-Lebensdauer (irrelevant da kein Caching) - Session-Lebensdauer (irrelevant da kein Caching)
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "mcp-familywall" name = "mcp-familywall"
version = "0.2.0" version = "0.2.1"
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.2.0" __version__ = "0.2.1"
+30 -21
View File
@@ -37,7 +37,11 @@ def _accgetallfamily() -> dict[str, Any]:
client.login(email, password) client.login(email, password)
data = client.call( data = client.call(
"accgetallfamily", "accgetallfamily",
{"a01call": "taskcategorysync", "a02call": "tasksync"}, {
"a01call": "taskcategorysync",
"a02call": "tasksync",
"a03call": "tasklistsync",
},
) )
client.logout() client.logout()
return data return data
@@ -48,25 +52,23 @@ def _accgetallfamily() -> dict[str, Any]:
def _extract_lists(data: dict[str, Any]) -> list[dict[str, Any]]: def _extract_lists(data: dict[str, Any]) -> list[dict[str, Any]]:
"""Extract the list of task categories from an accgetallfamily response. """Extract task lists from an accgetallfamily response.
SPEC says a00.r.r[], but a02.r.r[] has also been observed. Lists live under a03.r.r.updatedCreated[] (tasklistsync).
Both paths are tried defensively; the first non-empty result wins.
Args: Args:
data: Raw response body from accgetallfamily. data: Raw response body from accgetallfamily.
Returns: Returns:
List of raw list-category dicts (may be empty). List of raw task-list dicts (may be empty).
""" """
for key in ("a00", "a02"): try:
try: items = data["a03"]["r"]["r"]["updatedCreated"]
items = data[key]["r"]["r"] if isinstance(items, list):
if isinstance(items, list) and items: logger.debug("Lists found under a03.r.r.updatedCreated (%d items)", len(items))
logger.debug("Lists found under %s.r.r (%d items)", key, len(items)) return items # type: ignore[return-value]
return items # type: ignore[return-value] except (KeyError, TypeError):
except (KeyError, TypeError): pass
continue
return [] return []
@@ -100,13 +102,10 @@ def get_circles() -> str:
"""Return all Family Wall circles (Kreise) the account belongs to. """Return all Family Wall circles (Kreise) the account belongs to.
Each circle has an id and a name. Each circle has an id and a name.
Because the response structure of the famlistfamily endpoint has not yet Response structure verified: a00.r.r[] with metaId and name fields.
been verified against a live API call, the raw response is returned as
JSON for the first verification pass.
Returns: Returns:
JSON string — either a list of {id, name} objects once the structure JSON string — list of {id, name} objects.
is confirmed, or the raw API response for verification.
""" """
try: try:
email, password = get_credentials() email, password = get_credentials()
@@ -123,9 +122,19 @@ def get_circles() -> str:
except Exception as exc: except Exception as exc:
return f"Connection error: {exc}" return f"Connection error: {exc}"
# Response structure not yet verified — return raw JSON for inspection. try:
# TODO: once confirmed, extract and return [{id, name}, ...] list. raw_circles = data["a00"]["r"]["r"]
return json.dumps(data, ensure_ascii=False, indent=2) if not isinstance(raw_circles, list):
raise TypeError("a00.r.r is not a list")
except (KeyError, TypeError):
return json.dumps(
{"warning": "Unexpected famlistfamily response structure", "raw": data},
ensure_ascii=False,
indent=2,
)
result = [{"id": c.get("metaId"), "name": c.get("name")} for c in raw_circles]
return json.dumps(result, ensure_ascii=False, indent=2)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------