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:
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
.locale → Sprache 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
@@ -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"
|
||||||
|
|||||||
@@ -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])
|
||||||
|
|||||||
Reference in New Issue
Block a user