fix: add tasklistsync, fix get_circles response, update SPEC (v0.2.1)
This commit is contained in:
@@ -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)
|
.taskListId → Zugehö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
@@ -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 @@
|
|||||||
__version__ = "0.2.0"
|
__version__ = "0.2.1"
|
||||||
|
|||||||
@@ -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[key]["r"]["r"]
|
items = data["a03"]["r"]["r"]["updatedCreated"]
|
||||||
if isinstance(items, list) and items:
|
if isinstance(items, list):
|
||||||
logger.debug("Lists found under %s.r.r (%d items)", key, len(items))
|
logger.debug("Lists found under a03.r.r.updatedCreated (%d items)", len(items))
|
||||||
return items # type: ignore[return-value]
|
return items # type: ignore[return-value]
|
||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
continue
|
pass
|
||||||
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)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user