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
|
## Aktueller Stand
|
||||||
|
|
||||||
### Implementierte Tools (v0.8.0)
|
### Implementierte Tools (v0.8.2)
|
||||||
|
|
||||||
| Kategorie | Tools |
|
| Kategorie | Tools |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -51,8 +51,10 @@ und wird in Claude Desktop eingebunden.
|
|||||||
- v0.7.3: update_list (Umbenennen, emoji/color ändern) ✓
|
- v0.7.3: update_list (Umbenennen, emoji/color ändern) ✓
|
||||||
- v0.7.4: update_circle (Kreis umbenennen) ✓
|
- v0.7.4: update_circle (Kreis umbenennen) ✓
|
||||||
- v0.7.5: Primärkreis-Schutz in update_circle (isFirstFamily-Check) ✓
|
- 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.0: Rezept-Kategorien (get_recipe_categories, create_recipe + category_ids, update_recipe + category_ids) ✓
|
||||||
- v0.8.x: Erinnerungen + Wiederholungen (Premium-Account erforderlich)
|
- 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)
|
- v0.8.x: mpadditemtolist (Zutaten → Einkaufsliste)
|
||||||
- v2.0: Schreibzugriff auf Wall-Posts (Erstellen, Kommentieren)
|
- 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` | 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
|
- Ohne `scope`: Nur Listen des primären Kreises werden zurückgegeben
|
||||||
- Mit `scope=family/XXXX`: Nur Listen des angegebenen Kreises
|
- Mit `scope=family/XXXX`: Nur Listen des angegebenen Kreises
|
||||||
- Es gibt keinen servereitigen Filter für mehrere Kreise gleichzeitig
|
- Es gibt keinen servereitigen Filter für mehrere Kreise gleichzeitig
|
||||||
- Andere Parameter (`familyId`, `circleId`, etc.) werden ignoriert
|
- 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:**
|
**Response-Struktur:**
|
||||||
```
|
```
|
||||||
a00.r.r[] → Liste aller Task-Listen des Kreises
|
a00.r.r[] → Liste aller Task-Listen des Kreises
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "mcp-familywall"
|
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"
|
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.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
|
- A circle display name (e.g. ``"Test Kreis 2"``) — resolved to
|
||||||
the matching circle metaId via ``get_circles`` first.
|
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:
|
Returns:
|
||||||
JSON list of list objects with keys id, name, type, open, total,
|
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.
|
# Build the scope param for taskgettasklists.
|
||||||
# When scope is provided as a circle name (not a metaId), we need to
|
# When scope is provided as a circle name (not a metaId), we need to
|
||||||
# resolve it via famlistfamily first — done in the same session.
|
# resolve it via famlistfamily first — done in the same session.
|
||||||
api_scope: str | None = None
|
api_scopes: list[str] = []
|
||||||
if scope:
|
if scope:
|
||||||
if scope.startswith("family/"):
|
if scope.startswith("family/"):
|
||||||
api_scope = scope
|
api_scopes = [scope]
|
||||||
else:
|
else:
|
||||||
# Treat as circle name — look up the metaId.
|
# Treat as circle name — look up the metaId.
|
||||||
try:
|
try:
|
||||||
@@ -321,22 +321,22 @@ def get_lists(scope: str | None = None) -> str:
|
|||||||
ensure_ascii=False,
|
ensure_ascii=False,
|
||||||
indent=2,
|
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:
|
try:
|
||||||
with FamilyWallClient() as client:
|
with FamilyWallClient() as client:
|
||||||
client.login(email, password)
|
client.login(email, password)
|
||||||
params: dict[str, Any] = {}
|
all_lists: list[dict[str, Any]] = []
|
||||||
if api_scope:
|
for circle_scope in api_scopes:
|
||||||
params["scope"] = api_scope
|
params: dict[str, Any] = {"scope": circle_scope}
|
||||||
data = client.call("taskgettasklists", params)
|
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
|
raw_lists: list[dict[str, Any]] | None = None
|
||||||
try:
|
try:
|
||||||
candidate = data["a00"]["r"]["r"]
|
candidate = data["a00"]["r"]["r"]
|
||||||
@@ -347,15 +347,17 @@ def get_lists(scope: str | None = None) -> str:
|
|||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if raw_lists is None:
|
if raw_lists is not None:
|
||||||
return json.dumps(
|
all_lists.extend(raw_lists)
|
||||||
{"warning": "Unexpected taskgettasklists response structure", "raw": data},
|
|
||||||
ensure_ascii=False,
|
client.logout()
|
||||||
indent=2,
|
except FamilyWallError as exc:
|
||||||
)
|
return f"Error: {exc}"
|
||||||
|
except Exception as exc:
|
||||||
|
return f"Connection error: {exc}"
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
for item in raw_lists:
|
for item in all_lists:
|
||||||
# emoji: API returns "" when unset — normalise to None for a clean JSON null.
|
# 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.
|
# color: API omits the key entirely when unset — .get() returns None directly.
|
||||||
raw_emoji: str = item.get("emoji", "")
|
raw_emoji: str = item.get("emoji", "")
|
||||||
|
|||||||
Reference in New Issue
Block a user