fix: get_categories locale filter and list-type filter (v0.4.9)

Bug 1: all 171 category locale variants were returned; now filtered by
locale parameter (default "de") → 13 German categories for shopping lists.

Bug 2: TODO lists returned 171 shopping categories because
sortingIndexByTaskList contains ALL list IDs regardless of type.
Fix: look up list's taskListType via taskgettasklists, then match against
category's taskListType. TODO lists (type=TODOS) return empty list since
all API categories are SHOPPING_LIST type.

sortingIndexByTaskList is explicitly documented as unreliable for type
filtering in both code comments and SPEC.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-15 21:22:00 +02:00
parent ffb8b062c8
commit 9bc6a54783
4 changed files with 42 additions and 20 deletions
+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.
## Features (v0.4.8)
## Features (v0.4.9)
### Read
@@ -10,7 +10,7 @@ MCP server for [Family Wall](https://www.familywall.com) -- read and manage your
- `get_members` -- list members of a circle (or all circles)
- `get_lists` -- list all task lists (optionally filtered by circle)
- `get_tasks` -- list tasks in a specific list
- `get_categories` -- list categories available for a list
- `get_categories` -- list categories available for a list (locale-filtered, default: German)
- `get_activities` -- list recent wall activities (author resolved to display name)
### Write (with confirmation prompt)
+10 -3
View File
@@ -139,10 +139,17 @@ a01.r.r.updatedCreated[] → taskcategorysync (Kategorien/Abteilung
.name → Kategoriename (sprachabhängig, z.B. "Beverages")
.emoji → Emoji-Symbol der Kategorie
.systemCategoryId → numerische System-ID (sprach-unabhängig)
.taskListType → Listentyp (z.B. "SHOPPING_LIST", "TODO")
.taskListType → Listentyp der Kategorie (z.B. "SHOPPING_LIST")
**Wichtig:** Alle 171 Kategorien sind SHOPPING_LIST —
es gibt keine TODO-Kategorien in der API
.sortingIndexByTaskList → dict: Listen-ID → Sortierposition
Keys = Listen-IDs der zugeordneten Listen
.localeSprache des Namens (z.B. "de", "en", "ru")
**Achtung:** enthält ALLE Listen-IDs unabhängig vom Typ
NICHT für Typ-Filterung verwenden!
Stattdessen: taskListType der Kategorie mit
taskListType der Liste (aus taskgettasklists) vergleichen
.locale → Sprache des Namens (z.B. "de", "en", "ru", "fr", "es",
"it", "nl", "pt", "sv", "ko", "ja")
Jede Sprache = eigener Eintrag mit eigenem metaId/systemCategoryId
.hiddenByTaskList → Liste von Listen-IDs, in denen die Kat. versteckt ist
a02.r.r.updatedCreated[] → tasksync (Tasks)
.metaId → eindeutige Task-ID
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "mcp-familywall"
version = "0.4.8"
version = "0.4.9"
description = "MCP server for Family Wall — read your family's lists and tasks via Claude"
readme = "README.md"
requires-python = ">=3.12"
+29 -14
View File
@@ -337,23 +337,40 @@ def get_tasks(list_id: str, only_open: bool = True):
@mcp.tool()
def get_categories(list_id: str) -> str:
def get_categories(list_id: str, locale: str = "de") -> str:
"""Return the task categories available for a list as JSON.
Categories are loaded from taskcategorysync (part of accgetallfamily) and
filtered to those assigned to the given list via sortingIndexByTaskList.
Shopping lists have predefined system categories (aisles); TODO lists
typically only carry a single default system category.
Only shopping lists (taskListType=SHOPPING_LIST) have categories. TODO
lists return an empty list. Categories are filtered by locale so only
the language-appropriate names are returned (default: German).
Note: the parameter name for assigning a category when creating or
updating a task is not yet verified via FW_DEBUG=1. See SPEC.md.
Args:
list_id: List ID from get_lists (e.g. ``taskList/23431854_29740942``).
locale: BCP-47 language code for category names (default ``"de"``).
Supported values seen in API: de, en, fr, es, it, nl, pt, sv,
ru, ko, ja.
Returns:
JSON list of {id, name, emoji} objects ordered by sortingIndex.
JSON list of {id, name, emoji} objects ordered by sortingIndex,
or an empty list when the list type has no categories.
"""
# Resolve the list's taskListType from taskgettasklists so we can match
# only the categories that belong to the same list type. Non-fatal: if
# the lookup fails we skip the type filter and return all locale matches.
list_type: str | None = None
try:
list_data = _authenticated_call("taskgettasklists", {})
raw_lists = list_data.get("a00", {}).get("r", {}).get("r", []) or []
for lst in raw_lists:
if lst.get("metaId") == list_id:
list_type = lst.get("taskListType")
break
except RuntimeError:
pass
try:
data = _accgetallfamily()
except RuntimeError as exc:
@@ -370,18 +387,16 @@ def get_categories(list_id: str) -> str:
indent=2,
)
# Collect categories assigned to this list, ordered by their sorting index.
# Filter by locale (exact match) and taskListType (when known).
# sortingIndexByTaskList is NOT used for filtering: all categories are
# assigned to all lists regardless of type, making it an unreliable signal.
matched: list[tuple[int, dict[str, Any]]] = []
seen: set[str] = set()
for cat in raw_cats:
sorting = cat.get("sortingIndexByTaskList") or {}
if list_id not in sorting:
if cat.get("locale") != locale:
continue
cat_id: str = cat.get("metaId", "")
if cat_id in seen:
if list_type is not None and cat.get("taskListType") != list_type:
continue
seen.add(cat_id)
sort_val = int(sorting.get(list_id, cat.get("initialSortingIndex", 0)) or 0)
sort_val = int(cat.get("initialSortingIndex") or 0)
matched.append((sort_val, cat))
matched.sort(key=lambda t: t[0])