feat(lists): get_lists() without scope returns all circles (v0.8.2)
Implement v0.8.2: When get_lists() is called without a scope parameter, it now fetches lists from ALL circles instead of only the primary circle. Implementation: - Login once, call famlistfamily to get all circle IDs - For each circle, call taskgettasklists(scope=<circle_id>) - Merge results and return all lists with circle_id field All tests passed: test_multi circle creation, list creation in secondary circle, get_lists() without scope returns lists from both circles. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -24,7 +24,7 @@ und wird in Claude Desktop eingebunden.
|
||||
|
||||
## Aktueller Stand
|
||||
|
||||
### Implementierte Tools (v0.8.0)
|
||||
### Implementierte Tools (v0.8.2)
|
||||
|
||||
| Kategorie | Tools |
|
||||
|---|---|
|
||||
@@ -51,8 +51,10 @@ und wird in Claude Desktop eingebunden.
|
||||
- v0.7.3: update_list (Umbenennen, emoji/color ändern) ✓
|
||||
- v0.7.4: update_circle (Kreis umbenennen) ✓
|
||||
- v0.7.5: Primärkreis-Schutz in update_circle (isFirstFamily-Check) ✓
|
||||
- v0.8.0: Rezept-Kategorien (get_recipe_categories, create_recipe + category_ids, update_recipe + category_ids) ✓ ← aktuell
|
||||
- v0.8.x: Erinnerungen + Wiederholungen (Premium-Account erforderlich)
|
||||
- v0.8.0: Rezept-Kategorien (get_recipe_categories, create_recipe + category_ids, update_recipe + category_ids) ✓
|
||||
- v0.8.1: Bugfixes (recipe categories) ✓
|
||||
- v0.8.2: get_lists() ohne scope → alle Kreise ✓ ← aktuell
|
||||
- v0.9.x: Erinnerungen + Wiederholungen (Premium-Account erforderlich)
|
||||
- v0.8.x: mpadditemtolist (Zutaten → Einkaufsliste)
|
||||
- v2.0: Schreibzugriff auf Wall-Posts (Erstellen, Kommentieren)
|
||||
|
||||
|
||||
@@ -379,12 +379,16 @@ POST https://api.familywall.com/api/taskgettasklists
|
||||
|---|---|---|
|
||||
| `scope` | nein | Kreis-metaId z.B. `"family/23447378"` (ohne scope → primärer Kreis) |
|
||||
|
||||
**Scope-Verhalten:**
|
||||
**Scope-Verhalten (Server):**
|
||||
- Ohne `scope`: Nur Listen des primären Kreises werden zurückgegeben
|
||||
- Mit `scope=family/XXXX`: Nur Listen des angegebenen Kreises
|
||||
- Es gibt keinen servereitigen Filter für mehrere Kreise gleichzeitig
|
||||
- Andere Parameter (`familyId`, `circleId`, etc.) werden ignoriert
|
||||
|
||||
**Scope-Verhalten (MCP-Tool get_lists, v0.8.2+):**
|
||||
- Ohne `scope`: Der MCP-Server ruft für **jeden Kreis** separate `taskgettasklists(scope=<circle_id>)`-Calls ab und merged die Ergebnisse
|
||||
- Mit `scope=family/XXXX` oder `scope="Kreis Name"`: Nur dieser Kreis (wie bisher)
|
||||
|
||||
**Response-Struktur:**
|
||||
```
|
||||
a00.r.r[] → Liste aller Task-Listen des Kreises
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "mcp-familywall"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
description = "MCP server for Family Wall — read your family's lists and tasks via Claude"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "0.8.1"
|
||||
__version__ = "0.8.2"
|
||||
|
||||
@@ -286,7 +286,7 @@ def get_lists(scope: str | None = None) -> str:
|
||||
- A circle display name (e.g. ``"Test Kreis 2"``) — resolved to
|
||||
the matching circle metaId via ``get_circles`` first.
|
||||
|
||||
When ``None`` (default) the primary circle's lists are returned.
|
||||
When ``None`` (default) all lists from all circles are returned.
|
||||
|
||||
Returns:
|
||||
JSON list of list objects with keys id, name, type, open, total,
|
||||
@@ -300,10 +300,10 @@ def get_lists(scope: str | None = None) -> str:
|
||||
# Build the scope param for taskgettasklists.
|
||||
# When scope is provided as a circle name (not a metaId), we need to
|
||||
# resolve it via famlistfamily first — done in the same session.
|
||||
api_scope: str | None = None
|
||||
api_scopes: list[str] = []
|
||||
if scope:
|
||||
if scope.startswith("family/"):
|
||||
api_scope = scope
|
||||
api_scopes = [scope]
|
||||
else:
|
||||
# Treat as circle name — look up the metaId.
|
||||
try:
|
||||
@@ -321,22 +321,22 @@ def get_lists(scope: str | None = None) -> str:
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
api_scope = matched["metaId"]
|
||||
api_scopes = [matched["metaId"]]
|
||||
else:
|
||||
# No scope filter: fetch all circles and iterate over them.
|
||||
try:
|
||||
circles = _famlistfamily()
|
||||
api_scopes = [c["metaId"] for c in circles if "metaId" in c]
|
||||
except RuntimeError as exc:
|
||||
return f"Error: {exc}"
|
||||
|
||||
try:
|
||||
with FamilyWallClient() as client:
|
||||
client.login(email, password)
|
||||
params: dict[str, Any] = {}
|
||||
if api_scope:
|
||||
params["scope"] = api_scope
|
||||
all_lists: list[dict[str, Any]] = []
|
||||
for circle_scope in api_scopes:
|
||||
params: dict[str, Any] = {"scope": circle_scope}
|
||||
data = client.call("taskgettasklists", params)
|
||||
client.logout()
|
||||
except FamilyWallError as exc:
|
||||
return f"Error: {exc}"
|
||||
except Exception as exc:
|
||||
return f"Connection error: {exc}"
|
||||
|
||||
# Try known response patterns; fall back to raw JSON for verification.
|
||||
raw_lists: list[dict[str, Any]] | None = None
|
||||
try:
|
||||
candidate = data["a00"]["r"]["r"]
|
||||
@@ -347,15 +347,17 @@ def get_lists(scope: str | None = None) -> str:
|
||||
except (KeyError, TypeError):
|
||||
pass
|
||||
|
||||
if raw_lists is None:
|
||||
return json.dumps(
|
||||
{"warning": "Unexpected taskgettasklists response structure", "raw": data},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
if raw_lists is not None:
|
||||
all_lists.extend(raw_lists)
|
||||
|
||||
client.logout()
|
||||
except FamilyWallError as exc:
|
||||
return f"Error: {exc}"
|
||||
except Exception as exc:
|
||||
return f"Connection error: {exc}"
|
||||
|
||||
result = []
|
||||
for item in raw_lists:
|
||||
for item in all_lists:
|
||||
# emoji: API returns "" when unset — normalise to None for a clean JSON null.
|
||||
# color: API omits the key entirely when unset — .get() returns None directly.
|
||||
raw_emoji: str = item.get("emoji", "")
|
||||
|
||||
Reference in New Issue
Block a user