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. 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 ### 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_members` -- list members of a circle (or all circles)
- `get_lists` -- list all task lists (optionally filtered by circle) - `get_lists` -- list all task lists (optionally filtered by circle)
- `get_tasks` -- list tasks in a specific list - `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) - `get_activities` -- list recent wall activities (author resolved to display name)
### Write (with confirmation prompt) ### 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") .name → Kategoriename (sprachabhängig, z.B. "Beverages")
.emoji → Emoji-Symbol der Kategorie .emoji → Emoji-Symbol der Kategorie
.systemCategoryId → numerische System-ID (sprach-unabhängig) .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 .sortingIndexByTaskList → dict: Listen-ID → Sortierposition
Keys = Listen-IDs der zugeordneten Listen **Achtung:** enthält ALLE Listen-IDs unabhängig vom Typ
.localeSprache des Namens (z.B. "de", "en", "ru") 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 .hiddenByTaskList → Liste von Listen-IDs, in denen die Kat. versteckt ist
a02.r.r.updatedCreated[] → tasksync (Tasks) a02.r.r.updatedCreated[] → tasksync (Tasks)
.metaId → eindeutige Task-ID .metaId → eindeutige Task-ID
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "mcp-familywall" 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" 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"
+29 -14
View File
@@ -337,23 +337,40 @@ def get_tasks(list_id: str, only_open: bool = True):
@mcp.tool() @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. """Return the task categories available for a list as JSON.
Categories are loaded from taskcategorysync (part of accgetallfamily) and Only shopping lists (taskListType=SHOPPING_LIST) have categories. TODO
filtered to those assigned to the given list via sortingIndexByTaskList. lists return an empty list. Categories are filtered by locale so only
Shopping lists have predefined system categories (aisles); TODO lists the language-appropriate names are returned (default: German).
typically only carry a single default system category.
Note: the parameter name for assigning a category when creating or 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. updating a task is not yet verified via FW_DEBUG=1. See SPEC.md.
Args: Args:
list_id: List ID from get_lists (e.g. ``taskList/23431854_29740942``). 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: 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: try:
data = _accgetallfamily() data = _accgetallfamily()
except RuntimeError as exc: except RuntimeError as exc:
@@ -370,18 +387,16 @@ def get_categories(list_id: str) -> str:
indent=2, 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]]] = [] matched: list[tuple[int, dict[str, Any]]] = []
seen: set[str] = set()
for cat in raw_cats: for cat in raw_cats:
sorting = cat.get("sortingIndexByTaskList") or {} if cat.get("locale") != locale:
if list_id not in sorting:
continue continue
cat_id: str = cat.get("metaId", "") if list_type is not None and cat.get("taskListType") != list_type:
if cat_id in seen:
continue continue
seen.add(cat_id) sort_val = int(cat.get("initialSortingIndex") or 0)
sort_val = int(sorting.get(list_id, cat.get("initialSortingIndex", 0)) or 0)
matched.append((sort_val, cat)) matched.append((sort_val, cat))
matched.sort(key=lambda t: t[0]) matched.sort(key=lambda t: t[0])